[PATCH 1/6] schema: add support for isar in rootless mode

70 views
Skip to first unread message

Felix Moessbauer

unread,
May 29, 2026, 8:25:37 AMMay 29
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
To prepare a migration path from isar to isar-rootless, we also
introduce the isar-privileged entry.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
docs/format-changelog.rst | 2 ++
kas/schema-kas.json | 4 +++-
2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/docs/format-changelog.rst b/docs/format-changelog.rst
index b43b23066..4c7a33fcc 100644
--- a/docs/format-changelog.rst
+++ b/docs/format-changelog.rst
@@ -219,3 +219,5 @@ Added

- Switch to nodistro which is the default distro setting in
openembedded-core.
+- Extend the allowed values of ``build_system`` by adding ``isar-privileged``
+ and ``isar-rootless``.
diff --git a/kas/schema-kas.json b/kas/schema-kas.json
index f3594a247..7e3be5d34 100644
--- a/kas/schema-kas.json
+++ b/kas/schema-kas.json
@@ -83,7 +83,9 @@
"enum": [
"openembedded",
"oe",
- "isar"
+ "isar",
+ "isar-privileged",
+ "isar-rootless"
]
},
"defaults": {
--
2.53.0

Felix Moessbauer

unread,
May 29, 2026, 8:25:37 AMMay 29
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
Dear kas isar users,

as a preparation for our activities to implement / try out rootless isar
builds we need a test environment to execute the build (both locally and
in CI). This test environment is provided in this patch series.

Changes since RFC v3:

- rebased onto next
- improved commit message of "clean: add support for clean..."

Changes since v2:

- rebased onto next
- add support for docker and docker-rootless
- complete overhaul of the user ns subid mapping:
now supports both direct (docker) and indirect (podman)
mappings
- prohibit usage of sudo in isar-rootless mode. On rootless executors
like podman or docker-rootless, this is not strictly needed. But
on docker system this avoids accidental container breakout.
- introduce isar-privileged and map isar to isar-privileged

Note, that the various execution modes still lack CI test coverage.

Changes since v1:

- rebased onto next
- add acl tool (isar rootless host dependency)

Note, that the interfaces still have to be discussed with isar upstream.
I'm planning to send the corresponding isar series (v3) by today. With
this kas series people already have an environment for testing.

Best regards,
Felix Moessbauer
Siemens AG

Felix Moessbauer (6):
schema: add support for isar in rootless mode
kas: add support for isar-rootless build system
extend buildsystem test to check isar-rootless configuration
kas-container: configure container for nested namespaces
kas-container: block usage of sudo in isar-rootless mode
clean: add support for cleaning isar-rootless generated data

Dockerfile | 3 +-
container-entrypoint | 55 +++++++++++++++++++
docs/format-changelog.rst | 2 +
docs/userguide/kas-container-description.inc | 5 +-
examples/isar.yml | 4 +-
kas-container | 44 ++++++++++++++-
kas/config.py | 9 ++-
kas/libcmds.py | 2 +
kas/libkas.py | 2 +-
kas/plugins/clean.py | 31 ++++++++++-
kas/plugins/menu.py | 2 +-
kas/schema-kas.json | 4 +-
tests/test_build_system.py | 12 ++++
.../test-isar-privileged.yml | 7 +++
.../test_build_system/test-isar-rootless.yml | 7 +++
15 files changed, 176 insertions(+), 13 deletions(-)
create mode 100644 tests/test_build_system/test-isar-privileged.yml
create mode 100644 tests/test_build_system/test-isar-rootless.yml

--
2.53.0

Felix Moessbauer

unread,
May 29, 2026, 8:25:38 AMMay 29
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
For rootless nesting of the uid_ns, we need to provide a list of sub
uids/gids which can be used in the sub namespace. As each id in the
sub namespace must be mappable into the parent namespace, we need to
compute the mapping based on the layout of the parent. The following
mapping works for rootless podman with --userns=keep-id.

Note, that the default range (65k) does NOT allow to map nobody/nogroup,
in the sub-namespace, as this usually is id 65k-1 and we loose at least
one id per nesting. If we get a larger range, we just map as much as
we can and by that make nobody/nogroup usable.

With these changes we also add the uidmap and acl binaries to the
container which are needed by isar to setup the namespaces and
permissions.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
Dockerfile | 3 +-
container-entrypoint | 44 ++++++++++++++++++++
docs/userguide/kas-container-description.inc | 5 ++-
kas-container | 43 ++++++++++++++++++-
4 files changed, 90 insertions(+), 5 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index 2cc62c04a..91a520bf1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -113,7 +113,8 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=${CACHE_SHARING} \
umoci skopeo \
python3-botocore \
bubblewrap \
- debootstrap && \
+ debootstrap \
+ uidmap acl && \
rm -f /etc/apt/apt.conf.d/use-snapshot.conf /etc/apt/apt.conf.d/keep-packages.conf && \
if [ -f "/etc/apt/sources.list.d/debian.sources~" ]; then \
mv -f /etc/apt/sources.list.d/debian.sources~ /etc/apt/sources.list.d/debian.sources; \
diff --git a/container-entrypoint b/container-entrypoint
index dee7275e8..da0c36d3a 100755
--- a/container-entrypoint
+++ b/container-entrypoint
@@ -34,6 +34,47 @@ enable_qemu_binfmts()
done
}

+# For rootless nesting of the uid_ns, we need to provide a list of sub
+# uids/gids which can be used in the sub namespace. As each id in the
+# sub namespace must be mappable into the parent namespace, we need to
+# compute the mapping based on the layout of the parent: Podman maps
+# to an intermediate namespace, hence giving us a user-id map we
+# actually can use for further splitting. Docker maps directly, hence
+# we need to compute the number of sub ids we can use (which have a
+# mapping in the parent ns)
+#
+# Note, that the default range (65k) does NOT allow to map nobody/nogroup,
+# in the sub-namespace, as this usually is id 65k-1 and we loose at least
+# one id per nesting. If we get a larger range, we just map as much as
+# we can and by that make nobody/nogroup usable.
+# See man user_namespaces for details.
+setup_userns_mappings()
+{
+ UID_ROW=$(sort -r /proc/self/uid_map | head -1)
+ UID_OUTER=$(printf '%s' "$UID_ROW" | awk '{print $1}')
+ UID_INNER=$(printf '%s' "$UID_ROW" | awk '{print $2}')
+ UID_COUNT=$(printf '%s' "$UID_ROW" | awk '{print $3}')
+ GID_ROW=$(sort -r /proc/self/gid_map | head -1)
+ GID_OUTER=$(printf '%s' "$GID_ROW" | awk '{print $1}')
+ GID_INNER=$(printf '%s' "$GID_ROW" | awk '{print $2}')
+ GID_COUNT=$(printf '%s' "$GID_ROW" | awk '{print $3}')
+
+ # docker (direct mapping)
+ if [ "$UID_OUTER" = "1" ]; then
+ UID_INNER="$(($(id -u builder) + 1))"
+ [ "$UID_INNER" -lt "$UID_COUNT" ] && \
+ UID_COUNT="$((UID_COUNT - UID_INNER))"
+ fi
+ if [ "$GID_OUTER" = "1" ]; then
+ GID_INNER="$(($(id -g builder) + 1))"
+ [ "$GID_INNER" -lt "$GID_COUNT" ] && \
+ GID_COUNT="$((GID_COUNT - GID_INNER))"
+ fi
+
+ echo "builder:${UID_INNER}:${UID_COUNT}" | sudo tee /etc/subuid > /dev/null
+ echo "builder:${GID_INNER}:${GID_COUNT}" | sudo tee /etc/subgid > /dev/null
+}
+
# kas-isar: enable_qemu_binfmts

chown_managed_dirs()
@@ -103,6 +144,9 @@ else

GOSU="gosu builder"
fi
+# after all uid / gid changes are done, setup the namespace mappings
+# kas-isar: setup_userns_mappings
+
# kas-container on rootless docker workaround
if [ -n "$USER_ID" ] && [ "$USER_ID" -ne 0 ] && \
[ "$KAS_DOCKER_ROOTLESS" = "1" ] && [ "$(stat -c %u /repo)" -eq 0 ]; then
diff --git a/docs/userguide/kas-container-description.inc b/docs/userguide/kas-container-description.inc
index 7f350138a..776251124 100644
--- a/docs/userguide/kas-container-description.inc
+++ b/docs/userguide/kas-container-description.inc
@@ -39,5 +39,6 @@ written to from the host. To completely remove all data managed by kas, use
so they can be removed from the host.

.. note::
- The ISAR build system is not compatible with rootless execution. By that,
- we fall back to the system docker or podman instance.
+ The ISAR build system is compatible with rootless execution in ``isar-rootless``
+ mode only. The ``isar`` and ``isar-privileged`` modes fall back to the system docker
+ or podman instance.
diff --git a/kas-container b/kas-container
index 8e4937d9c..33f2fcd84 100755
--- a/kas-container
+++ b/kas-container
@@ -68,6 +68,8 @@ usage()
printf "%b" "\nOptional arguments:\n"
printf "%b" "--isar\t\t\tUse kas-isar container to build Isar image. To force\n"
printf "%b" " \t\t\tthe use of run0 over sudo, set KAS_SUDO_CMD=run0.\n"
+ printf "%b" "--isar-privileged\tRun the isar build in privileged mode\n"
+ printf "%b" "--isar-rootless\t\tRun the isar build in rootless mode\n"
printf "%b" "--with-loop-dev Pass a loop device to the " \
"container. Only required if\n"
printf "%b" "\t\t\tloop-mounting is used by recipes.\n"
@@ -171,6 +173,33 @@ enable_isar_mode()
fi
}

+enable_isar_rootless_mode()
+{
+ if [ -n "${ISAR_ROOTLESS_MODE}" ]; then
+ return
+ fi
+ ISAR_ROOTLESS_MODE=1
+ KAS_CONTAINER_IMAGE_NAME_DEFAULT="kas-isar"
+
+ # Use --privileged to pass the ambient capabilities into the container.
+ # When calling from the user session (podman or docker-rootless), this
+ # is fundamentally different from the system docker run --privileged
+ if [ "${KAS_CONTAINER_ENGINE}" = "podman" ]; then
+ KAS_RUNTIME_ARGS="${KAS_RUNTIME_ARGS} --userns=keep-id --privileged"
+ elif [ "${KAS_DOCKER_ROOTLESS}" = "1" ]; then
+ KAS_ISAR_ARGS="--privileged"
+ else
+ # we don't need --privileged, but we need to run with SYS_ADMIN
+ # to be able to unshare.
+ KAS_ISAR_ARGS=" \
+ --security-opt seccomp=unconfined \
+ --security-opt apparmor=unconfined \
+ --security-opt systempaths=unconfined \
+ --cap-add=SYS_ADMIN \
+ "
+ fi
+}
+
enable_oe_mode()
{
if [ "${KAS_CONTAINER_ENGINE}" = "podman" ]; then
@@ -356,10 +385,14 @@ esac
# parse kas-container options
while [ $# -gt 0 ]; do
case "$1" in
- --isar)
+ --isar|--isar-privileged)
enable_isar_mode
shift 1
;;
+ --isar-rootless)
+ enable_isar_rootless_mode
+ shift 1
+ ;;
--with-loop-dev)
if ! KAS_LOOP_DEV=$(/sbin/losetup -f 2>/dev/null); then
if [ "$(id -u)" -eq 0 ]; then
@@ -580,8 +613,10 @@ else
sed 's/build_system:[ ]\+//')
fi

-if [ "${BUILD_SYSTEM}" = "isar" ]; then
+if [ "${BUILD_SYSTEM}" = "isar" ] || [ "${BUILD_SYSTEM}" = "isar-privileged" ]; then
enable_isar_mode
+elif [ "${BUILD_SYSTEM}" = "isar-rootless" ]; then
+ enable_isar_rootless_mode
elif [ -z "${ISAR_MODE}" ]; then
enable_oe_mode
fi
@@ -787,5 +822,9 @@ while [ $KAS_EXTRA_BITBAKE_ARGS -gt 0 ]; do
KAS_EXTRA_BITBAKE_ARGS=$((KAS_EXTRA_BITBAKE_ARGS - 1))
done

+if [ "${ISAR_MODE}" = "1" ] && [ "${ISAR_ROOTLESS_MODE}" = "1" ]; then
+ fatal_error "only one of --isar and --isar-rootless can be selected."
+fi
+
# shellcheck disable=SC2086
trace ${KAS_CONTAINER_COMMAND} run "$@"
--
2.53.0

Felix Moessbauer

unread,
May 29, 2026, 8:25:39 AMMay 29
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
After switching to the builder user, we prohibit using sudo. This helps
downstream layers to find locations where sudo is incorrectly used, as
well as it prevents accidential breakout on system docker.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
container-entrypoint | 11 +++++++++++
kas-container | 1 +
2 files changed, 12 insertions(+)

diff --git a/container-entrypoint b/container-entrypoint
index da0c36d3a..9d23b6248 100755
--- a/container-entrypoint
+++ b/container-entrypoint
@@ -168,6 +168,17 @@ if [ "$PWD" = / ]; then
cd /builder || exit 1
fi

+if [ "$KAS_BLOCK_SUDO" = "1" ]; then
+ mkdir -p /usr/local/libexec
+ cat <<'EOF' > /usr/local/libexec/kas-no-sudo
+#!/bin/sh
+printf "KAS_BLOCK_SUDO=1: sudo is prohibited\n" >&2
+exit 1
+EOF
+ chmod +x /usr/local/libexec/kas-no-sudo
+ ln -sf /usr/local/libexec/kas-no-sudo /usr/bin/sudo
+fi
+
if [ -n "$1" ]; then
case "$1" in
build|checkout|clean*|diff|dump|for-all-repos|lock|menu|purge|shell|-*)
diff --git a/kas-container b/kas-container
index 33f2fcd84..641d17714 100755
--- a/kas-container
+++ b/kas-container
@@ -180,6 +180,7 @@ enable_isar_rootless_mode()
fi
ISAR_ROOTLESS_MODE=1
KAS_CONTAINER_IMAGE_NAME_DEFAULT="kas-isar"
+ KAS_RUNTIME_ARGS="${KAS_RUNTIME_ARGS} -e KAS_BLOCK_SUDO=1"

# Use --privileged to pass the ambient capabilities into the container.
# When calling from the user session (podman or docker-rootless), this
--
2.53.0

Felix Moessbauer

unread,
May 29, 2026, 8:25:39 AMMay 29
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
During the rootless build, data is generated inside another user id
namespace. By that, the owner of the data from outside of the namespace
is within the the subuid / subgid range, hence does not technically
belong to the calling (kas) user.

While isar takes care to not leave any temporary artifacts with
non-caller ownership behind on successfull builds, this does not apply
to partial or interrupted runs.

To be able to delete this data as well, we enter the namespace similar
to how isar is doing it, clean from the inside (with correct uid/gid
mappings) and finally remove the remainder from the outside.

On rootfull builds, we can simply use sudo to remove everything from the
outside, however sudo is not available in rootless mode.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
kas/plugins/clean.py | 31 +++++++++++++++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)

diff --git a/kas/plugins/clean.py b/kas/plugins/clean.py
index 2bffea2d4..037284020 100644
--- a/kas/plugins/clean.py
+++ b/kas/plugins/clean.py
@@ -95,7 +95,7 @@ class Clean():
dirs_to_remove = []
for tmpdir in tmpdirs:
logging.info(f'Removing {tmpdir}')
- if build_system == 'isar':
+ if (build_system or '').startswith('isar'):
dirs_to_remove.append(tmpdir)
else:
if not args.dry_run:
@@ -104,12 +104,39 @@ class Clean():
if len(dirs_to_remove) == 0:
return

+ # isar only
+ if build_system == 'isar-rootless':
+ self._rmtree_unshare(dirs_to_remove, args.dry_run)
+ else:
+ self._rmtree_sudo(dirs_to_remove, args.dry_run)
+
+ @staticmethod
+ def _rmtree_unshare(dirs_to_remove, dry_run):
+ uid = os.getuid()
+ for d in dirs_to_remove:
+ # find all dir entries that are not owned by the calling user
+ # and remove them by entering the user namespace first
+ clean_args = ['find', str(d), '(', '!', '-user', str(uid), '-type',
+ 'd', '-prune', ')', '-exec']
+ clean_args += ['unshare', '--map-auto', '--map-root-user',
+ '--keep-caps', 'rm', '-rf', '{}', ';']
+ logging.debug(' '.join(clean_args))
+ if not dry_run:
+ subprocess.check_call(clean_args)
+ # clean remaining files (owned by caller)
+ clean_args = ['rm', '-rf', str(d)]
+ logging.debug(' '.join(clean_args))
+ if not dry_run:
+ subprocess.check_call(clean_args)
+
+ @staticmethod
+ def _rmtree_sudo(dirs_to_remove, dry_run):
clean_args = ['sudo', '--prompt', '[sudo] enter password for %U '
'to clean ISAR artifacts']
clean_args.extend(['rm', '-rf'])
clean_args.extend([p.as_posix() for p in dirs_to_remove])
logging.debug(' '.join(clean_args))
- if not args.dry_run:
+ if not dry_run:
subprocess.check_call(clean_args)

@staticmethod
--
2.53.0

Felix Moessbauer

unread,
May 29, 2026, 8:25:39 AMMay 29
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
For a smooth transition from isar to isar-rootless back to isar, we
further add a warning if the build system is "isar" regarding the
semantics. For now, isar maps to isar-privileged to not break existing
layers. After some grace period, we will be able to change the default
of isar to isar-rootless.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
examples/isar.yml | 4 ++--
kas/config.py | 9 ++++++++-
kas/libcmds.py | 2 ++
kas/libkas.py | 2 +-
kas/plugins/menu.py | 2 +-
5 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/examples/isar.yml b/examples/isar.yml
index 3b9d5c7f2..4fc5750d3 100644
--- a/examples/isar.yml
+++ b/examples/isar.yml
@@ -23,9 +23,9 @@
#

header:
- version: 14
+ version: 22

-build_system: isar
+build_system: isar-privileged

machine: qemuamd64
distro: debian-trixie
diff --git a/kas/config.py b/kas/config.py
index cebc7494a..e3792efa0 100644
--- a/kas/config.py
+++ b/kas/config.py
@@ -26,6 +26,7 @@
import os
import json
import copy
+import logging
from pathlib import Path
from .repos import Repo
from .includehandler import IncludeHandler
@@ -69,7 +70,13 @@ class Config:
"""
Returns the pre-selected build system
"""
- return self._config.get('build_system', '')
+ build_system = self._config.get('build_system', '')
+ if build_system == 'isar':
+ logging.warning(
+ "The semantics of build_system: isar might change in the "
+ "future. Please use 'isar-privileged' or 'isar-rootless'.")
+ build_system = 'isar-privileged'
+ return build_system

def find_missing_repos(self, repo_paths={}):
"""
diff --git a/kas/libcmds.py b/kas/libcmds.py
index 70eb95cd1..e4d059b12 100644
--- a/kas/libcmds.py
+++ b/kas/libcmds.py
@@ -518,6 +518,8 @@ class WriteBBConfig(Command):
fds.write(f'DISTRO ??= "{ctx.config.get_distro()}"\n')
fds.write('BBMULTICONFIG ?= '
f'"{ctx.config.get_multiconfig()}"\n')
+ if ctx.config.get_build_system() == 'isar-rootless':
+ fds.write('ISAR_ROOTLESS ?= "1"\n')

_write_bblayers_conf(ctx)
_write_local_conf(ctx)
diff --git a/kas/libkas.py b/kas/libkas.py
index f5f5c45ed..4c848afcb 100644
--- a/kas/libkas.py
+++ b/kas/libkas.py
@@ -405,7 +405,7 @@ def get_build_environ(build_system):
init_repo = None
if build_system in ['openembedded', 'oe']:
scripts = ['oe-init-build-env']
- elif build_system == 'isar':
+ elif build_system.startswith('isar'):
scripts = ['isar-init-build-env']
else:
scripts = ['oe-init-build-env', 'isar-init-build-env']
diff --git a/kas/plugins/menu.py b/kas/plugins/menu.py
index c5bc725a7..9288ce73d 100644
--- a/kas/plugins/menu.py
+++ b/kas/plugins/menu.py
@@ -50,7 +50,7 @@

- The ``build_system`` that will used. The static Kconfig string variable
``KAS_BUILD_SYSTEM`` defines this value which must be ``openembedded``,
- ``oe`` or ``isar`` is set.
+ ``oe``, ``isar``, ``isar-privileged`` or ``isar-rootless`` is set.

- bitbake configuration variables that will be added to the
``local_conf_header`` section of the generated configuration. All other
--
2.53.0

Felix Moessbauer

unread,
May 29, 2026, 8:25:40 AMMay 29
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
With that, we also explicitly check the isar-privileged configuration.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
tests/test_build_system.py | 12 ++++++++++++
tests/test_build_system/test-isar-privileged.yml | 7 +++++++
tests/test_build_system/test-isar-rootless.yml | 7 +++++++
3 files changed, 26 insertions(+)
create mode 100644 tests/test_build_system/test-isar-privileged.yml
create mode 100644 tests/test_build_system/test-isar-rootless.yml

diff --git a/tests/test_build_system.py b/tests/test_build_system.py
index b46d559a8..abab4a351 100644
--- a/tests/test_build_system.py
+++ b/tests/test_build_system.py
@@ -22,6 +22,7 @@

import shutil
import pytest
+import re
from kas import kas


@@ -38,6 +39,17 @@ def test_build_system(monkeykas, tmpdir):
with open('build-env', 'r') as f:
assert f.readline().strip() == 'isar'

+ kas.kas(['shell', 'test-isar-privileged.yml', '-c', 'true'])
+ with open('build-env', 'r') as f:
+ assert f.readline().strip() == 'isar'
+
+ kas.kas(['shell', 'test-isar-rootless.yml', '-c', 'true'])
+ with open('build-env', 'r') as f:
+ assert f.readline().strip() == 'isar'
+ with open(monkeykas.get_kbd() / 'conf/local.conf', 'r') as f:
+ assert any(re.match(r'^ISAR_ROOTLESS.*"1"', line)
+ for line in f.readlines())
+
kas.kas(['shell', 'test-openembedded.yml', '-c', 'true'])
with open('build-env', 'r') as f:
assert f.readline().strip() == 'openembedded'
diff --git a/tests/test_build_system/test-isar-privileged.yml b/tests/test_build_system/test-isar-privileged.yml
new file mode 100644
index 000000000..4b13734f6
--- /dev/null
+++ b/tests/test_build_system/test-isar-privileged.yml
@@ -0,0 +1,7 @@
+header:
+ version: 22
+
+build_system: isar-privileged
+
+repos:
+ this:
diff --git a/tests/test_build_system/test-isar-rootless.yml b/tests/test_build_system/test-isar-rootless.yml
new file mode 100644
index 000000000..edabcb0b4
--- /dev/null
+++ b/tests/test_build_system/test-isar-rootless.yml
@@ -0,0 +1,7 @@
+header:
+ version: 22
+
+build_system: isar-rootless
+
+repos:
+ this:
--
2.53.0

Jan Kiszka

unread,
Jun 1, 2026, 2:17:38 AMJun 1
to Felix Moessbauer, kas-...@googlegroups.com, christi...@siemens.com
On 29.05.26 14:25, Felix Moessbauer wrote:
> Dear kas isar users,
>
> as a preparation for our activities to implement / try out rootless isar
> builds we need a test environment to execute the build (both locally and
> in CI). This test environment is provided in this patch series.
>
> Changes since RFC v3:
>
> - rebased onto next
> - improved commit message of "clean: add support for clean..."
>

Your schema versioning is outdated, must be 23 now.

And drop patch 5 in v2 for now as discussed in the other thread.

Jan

--
Siemens AG, Foundational Technologies
Linux Expert Center

Felix Moessbauer

unread,
Jun 1, 2026, 2:44:25 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
Dear kas isar users,

as a preparation for our activities to implement / try out rootless isar
builds we need a test environment to execute the build (both locally and
in CI). This test environment is provided in this patch series.

Changes since v1:

- rebased onto next
- drop "block sudo" patch as implications are unclear (e.g. it would
block installing further packages, is not aligned with OE)
- correct schema version (it was bumped in v5.3)

Changes since RFC v3:

- rebased onto next
- improved commit message of "clean: add support for clean..."

Changes since v2:

- rebased onto next
- add support for docker and docker-rootless
- complete overhaul of the user ns subid mapping:
now supports both direct (docker) and indirect (podman)
mappings
- prohibit usage of sudo in isar-rootless mode. On rootless executors
like podman or docker-rootless, this is not strictly needed. But
on docker system this avoids accidental container breakout.
- introduce isar-privileged and map isar to isar-privileged

Note, that the various execution modes still lack CI test coverage.

Changes since v1:

- rebased onto next
- add acl tool (isar rootless host dependency)

Note, that the interfaces still have to be discussed with isar upstream.
I'm planning to send the corresponding isar series (v3) by today. With
this kas series people already have an environment for testing.

Best regards,
Felix Moessbauer
Siemens AG

Felix Moessbauer (5):
schema: add support for isar in rootless mode
kas: add support for isar-rootless build system
extend buildsystem test to check isar-rootless configuration
kas-container: configure container for nested namespaces
clean: add support for cleaning isar-rootless generated data

Dockerfile | 3 +-
container-entrypoint | 44 +++++++++++++++++++
docs/format-changelog.rst | 9 ++++
docs/userguide/kas-container-description.inc | 5 ++-
examples/isar.yml | 4 +-
kas-container | 43 +++++++++++++++++-
kas/config.py | 9 +++-
kas/libcmds.py | 2 +
kas/libkas.py | 2 +-
kas/plugins/clean.py | 31 ++++++++++++-
kas/plugins/menu.py | 2 +-
kas/schema-kas.json | 6 ++-
tests/test_build_system.py | 12 +++++
.../test-isar-privileged.yml | 7 +++
.../test_build_system/test-isar-rootless.yml | 7 +++
15 files changed, 172 insertions(+), 14 deletions(-)
create mode 100644 tests/test_build_system/test-isar-privileged.yml
create mode 100644 tests/test_build_system/test-isar-rootless.yml

--
2.53.0

Felix Moessbauer

unread,
Jun 1, 2026, 2:44:26 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
To prepare a migration path from isar to isar-rootless, we also
introduce the isar-privileged entry.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
docs/format-changelog.rst | 9 +++++++++
kas/schema-kas.json | 6 ++++--
2 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/docs/format-changelog.rst b/docs/format-changelog.rst
index b43b23066..fb8f4f8e3 100644
--- a/docs/format-changelog.rst
+++ b/docs/format-changelog.rst
@@ -219,3 +219,12 @@ Added

- Switch to nodistro which is the default distro setting in
openembedded-core.
+
+Version 23
+----------
+
+Added
+~~~~~
+
+- Extend the allowed values of ``build_system`` by adding ``isar-privileged``
+ and ``isar-rootless``.
diff --git a/kas/schema-kas.json b/kas/schema-kas.json
index f3594a247..6022f0ca1 100644
--- a/kas/schema-kas.json
+++ b/kas/schema-kas.json
@@ -41,7 +41,7 @@
{
"type": "integer",
"minimum": 1,
- "maximum": 22
+ "maximum": 23
}
]
},

Felix Moessbauer

unread,
Jun 1, 2026, 2:44:26 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
For a smooth transition from isar to isar-rootless back to isar, we
further add a warning if the build system is "isar" regarding the
semantics. For now, isar maps to isar-privileged to not break existing
layers. After some grace period, we will be able to change the default
of isar to isar-rootless.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
examples/isar.yml | 4 ++--
kas/config.py | 9 ++++++++-
kas/libcmds.py | 2 ++
kas/libkas.py | 2 +-
kas/plugins/menu.py | 2 +-
5 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/examples/isar.yml b/examples/isar.yml
index 3b9d5c7f2..d64dd2deb 100644
--- a/examples/isar.yml
+++ b/examples/isar.yml
@@ -23,9 +23,9 @@
#

header:
- version: 14
+ version: 23

Felix Moessbauer

unread,
Jun 1, 2026, 2:44:27 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
With that, we also explicitly check the isar-privileged configuration.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
tests/test_build_system.py | 12 ++++++++++++
tests/test_build_system/test-isar-privileged.yml | 7 +++++++
tests/test_build_system/test-isar-rootless.yml | 7 +++++++
3 files changed, 26 insertions(+)
create mode 100644 tests/test_build_system/test-isar-privileged.yml
create mode 100644 tests/test_build_system/test-isar-rootless.yml

Felix Moessbauer

unread,
Jun 1, 2026, 2:44:28 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
For rootless nesting of the uid_ns, we need to provide a list of sub
uids/gids which can be used in the sub namespace. As each id in the
sub namespace must be mappable into the parent namespace, we need to
compute the mapping based on the layout of the parent. The following
mapping works for rootless podman with --userns=keep-id.

Note, that the default range (65k) does NOT allow to map nobody/nogroup,
in the sub-namespace, as this usually is id 65k-1 and we loose at least
one id per nesting. If we get a larger range, we just map as much as
we can and by that make nobody/nogroup usable.

With these changes we also add the uidmap and acl binaries to the
container which are needed by isar to setup the namespaces and
permissions.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
Dockerfile | 3 +-
container-entrypoint | 44 ++++++++++++++++++++
docs/userguide/kas-container-description.inc | 5 ++-
kas-container | 43 ++++++++++++++++++-
4 files changed, 90 insertions(+), 5 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index 2cc62c04a..91a520bf1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -113,7 +113,8 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=${CACHE_SHARING} \
umoci skopeo \
python3-botocore \
bubblewrap \
- debootstrap && \
+ debootstrap \
+ uidmap acl && \
rm -f /etc/apt/apt.conf.d/use-snapshot.conf /etc/apt/apt.conf.d/keep-packages.conf && \
if [ -f "/etc/apt/sources.list.d/debian.sources~" ]; then \
mv -f /etc/apt/sources.list.d/debian.sources~ /etc/apt/sources.list.d/debian.sources; \
diff --git a/container-entrypoint b/container-entrypoint
index dee7275e8..da0c36d3a 100755
--- a/container-entrypoint
+++ b/container-entrypoint
diff --git a/kas-container b/kas-container
index 7aa129948..9df85b5c4 100755
--- a/kas-container
+++ b/kas-container
@@ -68,6 +68,8 @@ usage()
printf "%b" "\nOptional arguments:\n"
printf "%b" "--isar\t\t\tUse kas-isar container to build Isar image. To force\n"
printf "%b" " \t\t\tthe use of run0 over sudo, set KAS_SUDO_CMD=run0.\n"
+ printf "%b" "--isar-privileged\tRun the isar build in privileged mode\n"
+ printf "%b" "--isar-rootless\t\tRun the isar build in rootless mode\n"
printf "%b" "--with-loop-dev Pass a loop device to the " \
"container. Only required if\n"
printf "%b" "\t\t\tloop-mounting is used by recipes.\n"
@@ -180,6 +182,33 @@ enable_isar_mode()
@@ -365,10 +394,14 @@ esac
# parse kas-container options
while [ $# -gt 0 ]; do
case "$1" in
- --isar)
+ --isar|--isar-privileged)
enable_isar_mode
shift 1
;;
+ --isar-rootless)
+ enable_isar_rootless_mode
+ shift 1
+ ;;
--with-loop-dev)
if ! KAS_LOOP_DEV=$(/sbin/losetup -f 2>/dev/null); then
if [ "$(id -u)" -eq 0 ]; then
@@ -586,8 +619,10 @@ else
sed 's/build_system:[ ]\+//')
fi

-if [ "${BUILD_SYSTEM}" = "isar" ]; then
+if [ "${BUILD_SYSTEM}" = "isar" ] || [ "${BUILD_SYSTEM}" = "isar-privileged" ]; then
enable_isar_mode
+elif [ "${BUILD_SYSTEM}" = "isar-rootless" ]; then
+ enable_isar_rootless_mode
elif [ -z "${ISAR_MODE}" ]; then
enable_oe_mode
fi
@@ -793,5 +828,9 @@ while [ $KAS_EXTRA_BITBAKE_ARGS -gt 0 ]; do

Felix Moessbauer

unread,
Jun 1, 2026, 2:44:29 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
During the rootless build, data is generated inside another user id
namespace. By that, the owner of the data from outside of the namespace
is within the the subuid / subgid range, hence does not technically
belong to the calling (kas) user.

While isar takes care to not leave any temporary artifacts with
non-caller ownership behind on successfull builds, this does not apply
to partial or interrupted runs.

To be able to delete this data as well, we enter the namespace similar
to how isar is doing it, clean from the inside (with correct uid/gid
mappings) and finally remove the remainder from the outside.

On rootfull builds, we can simply use sudo to remove everything from the
outside, however sudo is not available in rootless mode.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---

MOESSBAUER, Felix

unread,
Jun 1, 2026, 4:05:57 AMJun 1
to kas-...@googlegroups.com, Kiszka, Jan, Storm, Christian

This unfortunately makes testing hard, as the build system is also read
from .config.yaml, even if we just want to enter the shell. We need to
change it so that the manually passed flags take precedence.

Felix

Jan Kiszka

unread,
Jun 1, 2026, 4:57:27 AMJun 1
to Moessbauer, Felix (FT RPD CED OES-DE), kas-...@googlegroups.com, Storm, Christian (FT RPD CED)
What (testing) use cases do you have in mind?

But, right, we should probably not simply run enable_isar_* when parsing
the command line but rather set BUILD_SYSTEM.

MOESSBAUER, Felix

unread,
Jun 1, 2026, 5:04:26 AMJun 1
to Kiszka, Jan, kas-...@googlegroups.com, Storm, Christian

The new isar testsuite wrapper that runs the tests inside the container
(isar scripts/run-tests.sh). I extended that to check for -p rootless=1
and then start the kas-container with --isar-rootless. However, this
broke when having a .config.yml in the KAS_WORK_DIR at time of invoking
the script.

>
> But, right, we should probably not simply run enable_isar_* when parsing
> the command line but rather set BUILD_SYSTEM.

I'll send a v3 of this series that fixes this. Then it becomes easy to
tests and compare the rootless vs. non rootless builds (corresponding
isar patches are incoming):

./scripts/run-tests.sh -t dev -p rootless=1
./scripts/run-tests.sh -t dev -p rootless=0

Felix

Felix Moessbauer

unread,
Jun 1, 2026, 5:08:20 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
Dear kas isar users,

as a preparation for our activities to implement / try out rootless isar
builds we need a test environment to execute the build (both locally and
in CI). This test environment is provided in this patch series.

Changes since v2:

- kas-container: explicitly providing --isar / --isar-rootless takes
precedence over auto-detected mode from .config.yml
Dockerfile | 3 +-
container-entrypoint | 44 +++++++++++++++++
docs/format-changelog.rst | 9 ++++
docs/userguide/kas-container-description.inc | 5 +-
examples/isar.yml | 4 +-
kas-container | 47 +++++++++++++++++--
kas/config.py | 9 +++-
kas/libcmds.py | 2 +
kas/libkas.py | 2 +-
kas/plugins/clean.py | 31 +++++++++++-
kas/plugins/menu.py | 2 +-
kas/schema-kas.json | 6 ++-
tests/test_build_system.py | 12 +++++
.../test-isar-privileged.yml | 7 +++
.../test_build_system/test-isar-rootless.yml | 7 +++
15 files changed, 174 insertions(+), 16 deletions(-)
create mode 100644 tests/test_build_system/test-isar-privileged.yml
create mode 100644 tests/test_build_system/test-isar-rootless.yml

--
2.53.0

Felix Moessbauer

unread,
Jun 1, 2026, 5:08:20 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
To prepare a migration path from isar to isar-rootless, we also
introduce the isar-privileged entry.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---

Felix Moessbauer

unread,
Jun 1, 2026, 5:08:21 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
For a smooth transition from isar to isar-rootless back to isar, we
further add a warning if the build system is "isar" regarding the
semantics. For now, isar maps to isar-privileged to not break existing
layers. After some grace period, we will be able to change the default
of isar to isar-rootless.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---

Felix Moessbauer

unread,
Jun 1, 2026, 5:08:21 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
With that, we also explicitly check the isar-privileged configuration.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
tests/test_build_system.py | 12 ++++++++++++
tests/test_build_system/test-isar-privileged.yml | 7 +++++++
tests/test_build_system/test-isar-rootless.yml | 7 +++++++
3 files changed, 26 insertions(+)
create mode 100644 tests/test_build_system/test-isar-privileged.yml
create mode 100644 tests/test_build_system/test-isar-rootless.yml

Felix Moessbauer

unread,
Jun 1, 2026, 5:08:22 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
For rootless nesting of the uid_ns, we need to provide a list of sub
uids/gids which can be used in the sub namespace. As each id in the
sub namespace must be mappable into the parent namespace, we need to
compute the mapping based on the layout of the parent. The following
mapping works for rootless podman with --userns=keep-id.

Note, that the default range (65k) does NOT allow to map nobody/nogroup,
in the sub-namespace, as this usually is id 65k-1 and we loose at least
one id per nesting. If we get a larger range, we just map as much as
we can and by that make nobody/nogroup usable.

With these changes we also add the uidmap and acl binaries to the
container which are needed by isar to setup the namespaces and
permissions. The --isar and --isar-rootless parameters now take
precedence over the auto-detected one from .config.yml (previously
this was not required, as OE is never executed from a isar tree and
vice versa).

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
Dockerfile | 3 +-
container-entrypoint | 44 ++++++++++++++++++
docs/userguide/kas-container-description.inc | 5 ++-
kas-container | 47 ++++++++++++++++++--
4 files changed, 92 insertions(+), 7 deletions(-)
index 7aa129948..9541fd819 100755
@@ -575,7 +608,7 @@ if [ "${KAS_CMD}" = "menu" ]; then
sed -e 's/\(.*\fconfig KAS_BUILD_SYSTEM\f\(.*\)\|.*\)/\2/' \
-e 's/\f\([[:alpha:]].*\|$\)//' \
-e 's/.*default \"\(.*\)\".*/\1/')
-else
+elif [ -z "${BUILD_SYSTEM}" ]; then
if [ -z "${KAS_FIRST_FILES}" ]; then
KAS_FIRST_FILES="${KAS_WORK_DIR}/.config.yaml"
fi
@@ -586,9 +619,11 @@ else
sed 's/build_system:[ ]\+//')
fi

-if [ "${BUILD_SYSTEM}" = "isar" ]; then
+if [ "${BUILD_SYSTEM}" = "isar" ] || [ "${BUILD_SYSTEM}" = "isar-privileged" ]; then
enable_isar_mode
-elif [ -z "${ISAR_MODE}" ]; then
+elif [ "${BUILD_SYSTEM}" = "isar-rootless" ]; then
+ enable_isar_rootless_mode
+else

Felix Moessbauer

unread,
Jun 1, 2026, 5:08:23 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
During the rootless build, data is generated inside another user id
namespace. By that, the owner of the data from outside of the namespace
is within the the subuid / subgid range, hence does not technically
belong to the calling (kas) user.

While isar takes care to not leave any temporary artifacts with
non-caller ownership behind on successfull builds, this does not apply
to partial or interrupted runs.

To be able to delete this data as well, we enter the namespace similar
to how isar is doing it, clean from the inside (with correct uid/gid
mappings) and finally remove the remainder from the outside.

On rootfull builds, we can simply use sudo to remove everything from the
outside, however sudo is not available in rootless mode.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---

MOESSBAUER, Felix

unread,
Jun 1, 2026, 5:17:51 AMJun 1
to kas-...@googlegroups.com, Kiszka, Jan, Storm, Christian

Sorry, typo. Will send a new version of this commit.

Felix

Felix Moessbauer

unread,
Jun 1, 2026, 5:32:27 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, christi...@siemens.com, Felix Moessbauer
For rootless nesting of the uid_ns, we need to provide a list of sub
uids/gids which can be used in the sub namespace. As each id in the
sub namespace must be mappable into the parent namespace, we need to
compute the mapping based on the layout of the parent. The following
mapping works for rootless podman with --userns=keep-id.

Note, that the default range (65k) does NOT allow to map nobody/nogroup,
in the sub-namespace, as this usually is id 65k-1 and we loose at least
one id per nesting. If we get a larger range, we just map as much as
we can and by that make nobody/nogroup usable.

With these changes we also add the uidmap and acl binaries to the
container which are needed by isar to setup the namespaces and
permissions. The --isar and --isar-rootless parameters now take
precedence over the auto-detected one from .config.yml (previously
this was not required, as OE is never executed from a isar tree and
vice versa).

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
This supersedes "[PATCH v3 4/5] kas-container: configure container for nested
namespaces"

Changes:
- correctly handle precedence of --isar / --isar-rootless CLI flags
to set the build-system

Dockerfile | 3 +-
container-entrypoint | 44 +++++++++++++++++
docs/userguide/kas-container-description.inc | 5 +-
kas-container | 50 ++++++++++++++++++--
4 files changed, 95 insertions(+), 7 deletions(-)
index 7aa129948..d5a23e6cd 100755
--- a/kas-container
+++ b/kas-container
@@ -68,6 +68,8 @@ usage()
printf "%b" "\nOptional arguments:\n"
printf "%b" "--isar\t\t\tUse kas-isar container to build Isar image. To force\n"
printf "%b" " \t\t\tthe use of run0 over sudo, set KAS_SUDO_CMD=run0.\n"
+ printf "%b" "--isar-privileged\tRun the isar build in privileged mode\n"
+ printf "%b" "--isar-rootless\t\tRun the isar build in rootless mode\n"
printf "%b" "--with-loop-dev Pass a loop device to the " \
"container. Only required if\n"
printf "%b" "\t\t\tloop-mounting is used by recipes.\n"
@@ -159,6 +161,7 @@ enable_isar_mode()
return
fi
ISAR_MODE=1
+ BUILD_SYSTEM="isar-privileged"

KAS_CONTAINER_IMAGE_NAME_DEFAULT="kas-isar"
KAS_ISAR_ARGS="--privileged"
@@ -180,6 +183,34 @@ enable_isar_mode()
fi
}

+enable_isar_rootless_mode()
+{
+ if [ -n "${ISAR_ROOTLESS_MODE}" ]; then
+ return
+ fi
+ ISAR_ROOTLESS_MODE=1
+ BUILD_SYSTEM="isar-rootless"
+ KAS_CONTAINER_IMAGE_NAME_DEFAULT="kas-isar"
+
+ # Use --privileged to pass the ambient capabilities into the container.
+ # When calling from the user session (podman or docker-rootless), this
+ # is fundamentally different from the system docker run --privileged
+ if [ "${KAS_CONTAINER_ENGINE}" = "podman" ]; then
+ KAS_RUNTIME_ARGS="${KAS_RUNTIME_ARGS} --userns=keep-id --privileged"
+ elif [ "${KAS_DOCKER_ROOTLESS}" = "1" ]; then
+ KAS_ISAR_ARGS="--privileged"
+ else
+ # we don't need --privileged, but we need to run with SYS_ADMIN
+ # to be able to unshare.
+ KAS_ISAR_ARGS=" \
+ --security-opt seccomp=unconfined \
+ --security-opt apparmor=unconfined \
+ --security-opt systempaths=unconfined \
+ --cap-add=SYS_ADMIN \
+ "
+ fi
+}
+
enable_oe_mode()
{
if [ "${KAS_CONTAINER_ENGINE}" = "podman" ]; then
@@ -187,6 +218,7 @@ enable_oe_mode()
# calling "podman run" has a 1:1 mapping
KAS_RUNTIME_ARGS="${KAS_RUNTIME_ARGS} --userns=keep-id"
fi
+ BUILD_SYSTEM="oe"
}

enable_unpriv_userns_docker()
@@ -365,10 +397,14 @@ esac
# parse kas-container options
while [ $# -gt 0 ]; do
case "$1" in
- --isar)
+ --isar|--isar-privileged)
enable_isar_mode
shift 1
;;
+ --isar-rootless)
+ enable_isar_rootless_mode
+ shift 1
+ ;;
--with-loop-dev)
if ! KAS_LOOP_DEV=$(/sbin/losetup -f 2>/dev/null); then
if [ "$(id -u)" -eq 0 ]; then
@@ -575,7 +611,7 @@ if [ "${KAS_CMD}" = "menu" ]; then
sed -e 's/\(.*\fconfig KAS_BUILD_SYSTEM\f\(.*\)\|.*\)/\2/' \
-e 's/\f\([[:alpha:]].*\|$\)//' \
-e 's/.*default \"\(.*\)\".*/\1/')
-else
+elif [ -z "${BUILD_SYSTEM}" ]; then
if [ -z "${KAS_FIRST_FILES}" ]; then
KAS_FIRST_FILES="${KAS_WORK_DIR}/.config.yaml"
fi
@@ -586,9 +622,11 @@ else
sed 's/build_system:[ ]\+//')
fi

-if [ "${BUILD_SYSTEM}" = "isar" ]; then
+if [ "${BUILD_SYSTEM}" = "isar" ] || [ "${BUILD_SYSTEM}" = "isar-privileged" ]; then
enable_isar_mode
-elif [ -z "${ISAR_MODE}" ]; then
+elif [ "${BUILD_SYSTEM}" = "isar-rootless" ]; then
+ enable_isar_rootless_mode
+else
enable_oe_mode
fi

@@ -793,5 +831,9 @@ while [ $KAS_EXTRA_BITBAKE_ARGS -gt 0 ]; do

Jan Kiszka

unread,
Jun 1, 2026, 5:39:01 AMJun 1
to Felix Moessbauer, kas-...@googlegroups.com, christi...@siemens.com
I would still prefer to stop calling the enable_isar_*mode service here
and down there again. I think this pattern dates back to the times where
we would run clean commands directly from the parsing loop and exit
early. That is history now.

Cleaning this up would also allow to drop those re-entrance guards from
enable_isar_*mode.

Jan

MOESSBAUER, Felix

unread,
Jun 1, 2026, 5:46:37 AMJun 1
to Kiszka, Jan, kas-...@googlegroups.com, Storm, Christian

Ok, so how about just setting BUILD_SYSTEM here and later call the
enable_<buildsystem>_mode functions?

Then we could also get rid of the ISAR_MODE (and alike) variables.
I'll do this cleanup in a separate patch upfront.

Felix

Jan Kiszka

unread,
Jun 1, 2026, 5:47:29 AMJun 1
to Moessbauer, Felix (FT RPD CED OES-DE), kas-...@googlegroups.com, Storm, Christian (FT RPD CED)
Exactly what I had in mind.

Thanks,
Jan

Felix Moessbauer

unread,
Jun 1, 2026, 8:24:23 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
Dear kas isar users,

as a preparation for our activities to implement / try out rootless isar
builds we need a test environment to execute the build (both locally and
in CI). This test environment is provided in this patch series.

Changes since v3:

- rebased onto next (with new handling of build-system mode selection)
- drop "either --isar or --isar-rootless" check (the last parameter takes
precedence which is similar to how most CLIs behave)
kas-container: configure container for nested namespaces
clean: add support for cleaning isar-rootless generated data

Dockerfile | 3 +-
container-entrypoint | 44 +++++++++++++++++++
docs/format-changelog.rst | 9 ++++
docs/userguide/kas-container-description.inc | 5 ++-
examples/isar.yml | 4 +-
kas-container | 36 ++++++++++++++-
kas/config.py | 9 +++-
kas/libcmds.py | 2 +
kas/libkas.py | 2 +-
kas/plugins/clean.py | 31 ++++++++++++-
kas/plugins/menu.py | 2 +-
kas/schema-kas.json | 6 ++-
tests/test_build_system.py | 12 +++++
.../test-isar-privileged.yml | 7 +++
.../test_build_system/test-isar-rootless.yml | 7 +++
15 files changed, 165 insertions(+), 14 deletions(-)
create mode 100644 tests/test_build_system/test-isar-privileged.yml
create mode 100644 tests/test_build_system/test-isar-rootless.yml

--
2.53.0

Felix Moessbauer

unread,
Jun 1, 2026, 8:24:25 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
During the rootless build, data is generated inside another user id
namespace. By that, the owner of the data from outside of the namespace
is within the the subuid / subgid range, hence does not technically
belong to the calling (kas) user.

While isar takes care to not leave any temporary artifacts with
non-caller ownership behind on successfull builds, this does not apply
to partial or interrupted runs.

To be able to delete this data as well, we enter the namespace similar
to how isar is doing it, clean from the inside (with correct uid/gid
mappings) and finally remove the remainder from the outside.

On rootfull builds, we can simply use sudo to remove everything from the
outside, however sudo is not available in rootless mode.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---

Felix Moessbauer

unread,
Jun 1, 2026, 8:24:27 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
For a smooth transition from isar to isar-rootless back to isar, we
further add a warning if the build system is "isar" regarding the
semantics. For now, isar maps to isar-privileged to not break existing
layers. After some grace period, we will be able to change the default
of isar to isar-rootless.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---

Felix Moessbauer

unread,
Jun 1, 2026, 8:24:27 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
To prepare a migration path from isar to isar-rootless, we also
introduce the isar-privileged entry.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---

Felix Moessbauer

unread,
Jun 1, 2026, 8:24:29 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
For rootless nesting of the uid_ns, we need to provide a list of sub
uids/gids which can be used in the sub namespace. As each id in the
sub namespace must be mappable into the parent namespace, we need to
compute the mapping based on the layout of the parent. The following
mapping works for rootless podman with --userns=keep-id.

Note, that the default range (65k) does NOT allow to map nobody/nogroup,
in the sub-namespace, as this usually is id 65k-1 and we loose at least
one id per nesting. If we get a larger range, we just map as much as
we can and by that make nobody/nogroup usable.

With these changes we also add the uidmap and acl binaries to the
container which are needed by isar to setup the namespaces and
permissions.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
Dockerfile | 3 +-
container-entrypoint | 44 ++++++++++++++++++++
docs/userguide/kas-container-description.inc | 5 ++-
kas-container | 36 +++++++++++++++-
4 files changed, 83 insertions(+), 5 deletions(-)
index 1f8e3dfb5..8002b20ec 100755
--- a/kas-container
+++ b/kas-container
@@ -68,6 +68,8 @@ usage()
printf "%b" "\nOptional arguments:\n"
printf "%b" "--isar\t\t\tUse kas-isar container to build Isar image. To force\n"
printf "%b" " \t\t\tthe use of run0 over sudo, set KAS_SUDO_CMD=run0.\n"
+ printf "%b" "--isar-privileged\tRun the isar build in privileged mode\n"
+ printf "%b" "--isar-rootless\t\tRun the isar build in rootless mode\n"
printf "%b" "--with-loop-dev Pass a loop device to the " \
"container. Only required if\n"
printf "%b" "\t\t\tloop-mounting is used by recipes.\n"
@@ -175,6 +177,29 @@ enable_isar_mode()
fi
}

+enable_isar_rootless_mode()
+{
+ KAS_CONTAINER_IMAGE_NAME_DEFAULT="kas-isar"
+
+ # Use --privileged to pass the ambient capabilities into the container.
+ # When calling from the user session (podman or docker-rootless), this
+ # is fundamentally different from the system docker run --privileged
+ if [ "${KAS_CONTAINER_ENGINE}" = "podman" ]; then
+ KAS_RUNTIME_ARGS="${KAS_RUNTIME_ARGS} --userns=keep-id --privileged"
+ elif [ "${KAS_DOCKER_ROOTLESS}" = "1" ]; then
+ KAS_ISAR_ARGS="--privileged"
+ else
+ # we don't need --privileged, but we need to run with SYS_ADMIN
+ # to be able to unshare.
+ KAS_ISAR_ARGS=" \
+ --security-opt seccomp=unconfined \
+ --security-opt apparmor=unconfined \
+ --security-opt systempaths=unconfined \
+ --cap-add=SYS_ADMIN \
+ "
+ fi
+}
+
enable_oe_mode()
{
if [ "${KAS_CONTAINER_ENGINE}" = "podman" ]; then
@@ -182,6 +207,7 @@ enable_oe_mode()
# calling "podman run" has a 1:1 mapping
KAS_RUNTIME_ARGS="${KAS_RUNTIME_ARGS} --userns=keep-id"
fi
+ BUILD_SYSTEM="oe"
}

enable_unpriv_userns_docker()
@@ -362,10 +388,14 @@ esac
BUILD_SYSTEM=""
while [ $# -gt 0 ]; do
case "$1" in
- --isar)
+ --isar | --isar-privileged)
BUILD_SYSTEM="isar"
shift 1
;;
+ --isar-rootless)
+ enable_isar_rootless_mode
+ shift 1
+ ;;
--with-loop-dev)
if ! KAS_LOOP_DEV=$(/sbin/losetup -f 2>/dev/null); then
if [ "$(id -u)" -eq 0 ]; then
@@ -583,8 +613,10 @@ else
sed 's/build_system:[ ]\+//')}
fi

-if [ "${BUILD_SYSTEM}" = "isar" ]; then
+if [ "${BUILD_SYSTEM}" = "isar" ] || [ "${BUILD_SYSTEM}" = "isar-privileged" ]; then
enable_isar_mode
+elif [ "${BUILD_SYSTEM}" = "isar-rootless" ]; then
+ enable_isar_rootless_mode
else
enable_oe_mode
fi
--
2.53.0

Felix Moessbauer

unread,
Jun 1, 2026, 8:24:29 AMJun 1
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
With that, we also explicitly check the isar-privileged configuration.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
tests/test_build_system.py | 12 ++++++++++++
tests/test_build_system/test-isar-privileged.yml | 7 +++++++
tests/test_build_system/test-isar-rootless.yml | 7 +++++++
3 files changed, 26 insertions(+)
create mode 100644 tests/test_build_system/test-isar-privileged.yml
create mode 100644 tests/test_build_system/test-isar-rootless.yml

Jan Kiszka

unread,
Jun 1, 2026, 10:13:06 AMJun 1
to Felix Moessbauer, kas-...@googlegroups.com
Let's enhance the description of the property at this chance. Suggestion:

"Defines the bitbake-based build system with ``openembedded`` as
default. ``openembedded`` and ``oe`` are equivalent. ``isar`` is
currently equivalent to ``isar-privileged`` but, as this may change, the
long form should be used."

I'm not sure if the enum values could get individual descriptions,
probably not. What's still missing with the above is a brief explanation
of when to use what.

> ]
> },
> "defaults": {

Jan Kiszka

unread,
Jun 1, 2026, 10:13:13 AMJun 1
to Felix Moessbauer, kas-...@googlegroups.com
On 01.06.26 14:24, Felix Moessbauer wrote:
Incorrect version properties here, must be 23 now.

Jan Kiszka

unread,
Jun 1, 2026, 10:14:22 AMJun 1
to Felix Moessbauer, kas-...@googlegroups.com
On 01.06.26 14:24, Felix Moessbauer wrote:
Trailing whitespace.

> +# actually can use for further splitting. Docker maps directly, hence
> +# we need to compute the number of sub ids we can use (which have a
> +# mapping in the parent ns)
> +#
> +# Note, that the default range (65k) does NOT allow to map nobody/nogroup,
> +# in the sub-namespace, as this usually is id 65k-1 and we loose at least
> +# one id per nesting. If we get a larger range, we just map as much as
> +# we can and by that make nobody/nogroup usable.
> +# See man user_namespaces for details.
> +setup_userns_mappings()
> +{
> + UID_ROW=$(sort -r /proc/self/uid_map | head -1)
> + UID_OUTER=$(printf '%s' "$UID_ROW" | awk '{print $1}')
> + UID_INNER=$(printf '%s' "$UID_ROW" | awk '{print $2}')
> + UID_COUNT=$(printf '%s' "$UID_ROW" | awk '{print $3}')
> + GID_ROW=$(sort -r /proc/self/gid_map | head -1)
> + GID_OUTER=$(printf '%s' "$GID_ROW" | awk '{print $1}')
> + GID_INNER=$(printf '%s' "$GID_ROW" | awk '{print $2}')
> + GID_COUNT=$(printf '%s' "$GID_ROW" | awk '{print $3}')
> +

Same here.
Let's use "isar-privileged" as value here, just to be future-proof.

> shift 1
> ;;
> + --isar-rootless)
> + enable_isar_rootless_mode

BUILD_SYSTEM="isar-rootless"

> + shift 1
> + ;;
> --with-loop-dev)
> if ! KAS_LOOP_DEV=$(/sbin/losetup -f 2>/dev/null); then
> if [ "$(id -u)" -eq 0 ]; then
> @@ -583,8 +613,10 @@ else
> sed 's/build_system:[ ]\+//')}
> fi
>
> -if [ "${BUILD_SYSTEM}" = "isar" ]; then
> +if [ "${BUILD_SYSTEM}" = "isar" ] || [ "${BUILD_SYSTEM}" = "isar-privileged" ]; then
> enable_isar_mode
> +elif [ "${BUILD_SYSTEM}" = "isar-rootless" ]; then
> + enable_isar_rootless_mode
> else
> enable_oe_mode
> fi

This is missing to also update

# clean can be executed without config, hence manually forward the build system
if [ "${BUILD_SYSTEM}" = "isar" ] && echo "${KAS_CMD}" | grep -qe "^clean\|purge"; then

You likely want to a case match now on isar*.

MOESSBAUER, Felix

unread,
Jun 1, 2026, 10:56:28 AMJun 1
to Kiszka, Jan, kas-...@googlegroups.com

Good point, but there is actually a difference in kas clean (which we
currently only handle based on config). I'll update the kas clean
accordingly in the v5.

IoW: we need special handling for both cases.

Felix

Felix Moessbauer

unread,
Jun 2, 2026, 4:13:29 AMJun 2
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
Dear kas isar users,

as a preparation for our activities to implement / try out rootless isar
builds we need a test environment to execute the build (both locally and
in CI). This test environment is provided in this patch series.

Changes since v4:

- schema: improved description and added docs in project-configuration.rst
- test-isar-rootless.yml: bump schema version
- kas-container: whitespace fixes, consistent use of BUILD_SYSTEM="isar-rootless"
- kas clean: add CLI option to explicitly select isar-rootless / isar-privileged
- kas clean: explicitly handle build systems (replace "default" with "openembedded")
clean: add support for cleaning isar-rootless generated data
kas-container: configure container for nested namespaces

Dockerfile | 3 +-
container-entrypoint | 44 ++++++++++++
docs/format-changelog.rst | 9 +++
docs/userguide/kas-container-description.inc | 5 +-
docs/userguide/project-configuration.rst | 19 +++--
examples/isar.yml | 4 +-
kas-container | 48 +++++++++++--
kas/config.py | 9 ++-
kas/libcmds.py | 2 +
kas/libkas.py | 2 +-
kas/plugins/clean.py | 69 ++++++++++++++-----
kas/plugins/menu.py | 2 +-
kas/schema-kas.json | 9 ++-
tests/test_build_system.py | 12 ++++
.../test-isar-privileged.yml | 7 ++
.../test_build_system/test-isar-rootless.yml | 7 ++
16 files changed, 211 insertions(+), 40 deletions(-)
create mode 100644 tests/test_build_system/test-isar-privileged.yml
create mode 100644 tests/test_build_system/test-isar-rootless.yml

--
2.53.0

Felix Moessbauer

unread,
Jun 2, 2026, 4:13:29 AMJun 2
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
To prepare a migration path from isar to isar-rootless, we also
introduce the isar-privileged entry.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
docs/format-changelog.rst | 9 +++++++++
docs/userguide/project-configuration.rst | 19 ++++++++++++-------
kas/schema-kas.json | 9 ++++++---
3 files changed, 27 insertions(+), 10 deletions(-)

diff --git a/docs/format-changelog.rst b/docs/format-changelog.rst
index b43b23066..fb8f4f8e3 100644
--- a/docs/format-changelog.rst
+++ b/docs/format-changelog.rst
@@ -219,3 +219,12 @@ Added

- Switch to nodistro which is the default distro setting in
openembedded-core.
+
+Version 23
+----------
+
+Added
+~~~~~
+
+- Extend the allowed values of ``build_system`` by adding ``isar-privileged``
+ and ``isar-rootless``.
diff --git a/docs/userguide/project-configuration.rst b/docs/userguide/project-configuration.rst
index 04c887b54..f918dbd9d 100644
--- a/docs/userguide/project-configuration.rst
+++ b/docs/userguide/project-configuration.rst
@@ -259,13 +259,18 @@ Configuration reference

``build_system``: string [optional]
:kasschemadesc:`build_system`
- Known build systems are
- ``openembedded`` (or ``oe``) and ``isar``. If set, this restricts the
- search of kas for the init script in the configured repositories to
- ``oe-init-build-env`` or ``isar-init-build-env``, respectively. If
- ``kas-container`` finds this property in the top-level kas configuration
- file (includes are not evaluated), it will automatically select the
- required container image and invocation mode.
+ If set, this restricts the search of kas for the init script in the
+ configured repositories to ``oe-init-build-env`` or
+ ``isar-init-build-env``, respectively. If ``kas-container`` finds this
+ property in the top-level kas configuration file (includes are not
+ evaluated), it will automatically select the required container image
+ and invocation mode.
+
+ .. note:: Modern versions of isar support a rootless mode, whereby builds can
+ be executed as non-privileged user. To distinguish the two modes,
+ use ``isar-rootless`` or ``isar-privileged``. The plain ``isar``
+ value is deprecated. Whether the rootless mode is supported depends
+ on your layers.

``defaults``: dict [optional]
:kasschemadesc:`defaults`
diff --git a/kas/schema-kas.json b/kas/schema-kas.json
index f3594a247..923a106c1 100644
--- a/kas/schema-kas.json
+++ b/kas/schema-kas.json
@@ -41,7 +41,7 @@
{
"type": "integer",
"minimum": 1,
- "maximum": 22
+ "maximum": 23
}
]
},
@@ -79,11 +79,14 @@
}
},
"build_system": {
- "description": "Defines the bitbake-based build system.",
+ "description": "Defines the bitbake-based build system. ``openembedded`` and ``oe`` are equivalent. ``isar`` is deprecated, use ``isar-privileged`` or ``isar-rootless`` instead.",
+ "default": "openembedded",
"enum": [
"openembedded",
"oe",
- "isar"
+ "isar",
+ "isar-privileged",
+ "isar-rootless"
]
},
"defaults": {
--
2.53.0

Felix Moessbauer

unread,
Jun 2, 2026, 4:13:31 AMJun 2
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
For a smooth transition from isar to isar-rootless back to isar, we
further add a warning if the build system is "isar" regarding the
semantics. For now, isar maps to isar-privileged to not break existing
layers. After some grace period, we will be able to change the default
of isar to isar-rootless.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---

Felix Moessbauer

unread,
Jun 2, 2026, 4:13:32 AMJun 2
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
With that, we also explicitly check the isar-privileged configuration.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
tests/test_build_system.py | 12 ++++++++++++
tests/test_build_system/test-isar-privileged.yml | 7 +++++++
tests/test_build_system/test-isar-rootless.yml | 7 +++++++
3 files changed, 26 insertions(+)
create mode 100644 tests/test_build_system/test-isar-privileged.yml
create mode 100644 tests/test_build_system/test-isar-rootless.yml

index 000000000..03a09d3ed
--- /dev/null
+++ b/tests/test_build_system/test-isar-privileged.yml
@@ -0,0 +1,7 @@
+header:
+ version: 23
+
+build_system: isar-privileged
+
+repos:
+ this:
diff --git a/tests/test_build_system/test-isar-rootless.yml b/tests/test_build_system/test-isar-rootless.yml
new file mode 100644
index 000000000..336cb8383
--- /dev/null
+++ b/tests/test_build_system/test-isar-rootless.yml
@@ -0,0 +1,7 @@
+header:
+ version: 23
+
+build_system: isar-rootless
+
+repos:
+ this:
--
2.53.0

Felix Moessbauer

unread,
Jun 2, 2026, 4:13:33 AMJun 2
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
During the rootless build, data is generated inside another user id
namespace. By that, the owner of the data from outside of the namespace
is within the the subuid / subgid range, hence does not technically
belong to the calling (kas) user.

While isar takes care to not leave any temporary artifacts with
non-caller ownership behind on successfull builds, this does not apply
to partial or interrupted runs.

To be able to delete this data as well, we enter the namespace similar
to how isar is doing it, clean from the inside (with correct uid/gid
mappings) and finally remove the remainder from the outside.

On rootfull builds, we can simply use sudo to remove everything from the
outside, however sudo is not available in rootless mode.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
kas/plugins/clean.py | 69 ++++++++++++++++++++++++++++++++++----------
1 file changed, 53 insertions(+), 16 deletions(-)

diff --git a/kas/plugins/clean.py b/kas/plugins/clean.py
index 2bffea2d4..4068196ed 100644
--- a/kas/plugins/clean.py
+++ b/kas/plugins/clean.py
@@ -61,20 +61,28 @@ class Clean():
default=False,
help='Do not remove anything, just print what '
'would be removed')
- parser.add_argument('--isar',
- action='store_true',
- default=False,
- help='Use ISAR build directory layout')
- parser.add_argument('config',
- help='Config file(s), separated by colon. Using '
- '.config.yaml in KAS_WORK_DIR if existing '
- 'and none is specified.',
- nargs='?')
+ isar_group = parser.add_mutually_exclusive_group()
+ isar_group.add_argument(
+ '--isar-privileged', '--isar',
+ action='store_true',
+ default=False,
+ help='Use ISAR (privileged) build directory layout')
+ isar_group.add_argument(
+ '--isar-rootless',
+ action='store_true',
+ default=False,
+ help='Use ISAR (rootless) build directory layout')
+ parser.add_argument(
+ 'config',
+ help='Config file(s), separated by colon. Using '
+ '.config.yaml in KAS_WORK_DIR if existing '
+ 'and none is specified.',
+ nargs='?')

def run(self, args):
ctx = create_global_context(args)
default_conf_file = Path(ctx.kas_work_dir) / CONFIG_YAML_FILE
- build_system = None
+ build_system = 'openembedded'
if args.config:
self.config_files = args.config
elif default_conf_file.exists():
@@ -83,11 +91,13 @@ class Clean():
# By definition, build_system key must be present in the first
# config file to take effect.
cf = ConfigFile.load(self.config_files.split(':')[0])
- build_system = cf.config.get('build_system')
- if args.isar:
- build_system = 'isar'
+ build_system = cf.config.get('build_system', build_system)
+ if args.isar_privileged or build_system == 'isar':
+ build_system = 'isar-privileged'
+ elif args.isar_rootless:
+ build_system = 'isar-rootless'

- logging.debug('Run clean in "%s" mode' % (build_system or 'default'))
+ logging.debug('Run clean in "%s" mode' % build_system)
if args.dry_run:
logging.warning('Dry run, not removing anything')
tmpdirs = Path(ctx.build_dir).glob('tmp*')
@@ -95,7 +105,7 @@ class Clean():
dirs_to_remove = []
for tmpdir in tmpdirs:
logging.info(f'Removing {tmpdir}')
- if build_system == 'isar':
+ if build_system.startswith('isar'):
dirs_to_remove.append(tmpdir)
else:
if not args.dry_run:
@@ -104,12 +114,39 @@ class Clean():

Felix Moessbauer

unread,
Jun 2, 2026, 4:13:33 AMJun 2
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
For rootless nesting of the uid_ns, we need to provide a list of sub
uids/gids which can be used in the sub namespace. As each id in the
sub namespace must be mappable into the parent namespace, we need to
compute the mapping based on the layout of the parent. The following
mapping works for rootless podman with --userns=keep-id.

Note, that the default range (65k) does NOT allow to map nobody/nogroup,
in the sub-namespace, as this usually is id 65k-1 and we loose at least
one id per nesting. If we get a larger range, we just map as much as
we can and by that make nobody/nogroup usable.

With these changes we also add the uidmap and acl binaries to the
container which are needed by isar to setup the namespaces and
permissions. The --isar and --isar-rootless parameters now take
precedence over the auto-detected one from .config.yml (previously
this was not required, as OE is never executed from a isar tree and
vice versa).

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
Dockerfile | 3 +-
container-entrypoint | 44 ++++++++++++++++++
docs/userguide/kas-container-description.inc | 5 +-
kas-container | 48 +++++++++++++++++---
4 files changed, 91 insertions(+), 9 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index 2cc62c04a..91a520bf1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -113,7 +113,8 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=${CACHE_SHARING} \
umoci skopeo \
python3-botocore \
bubblewrap \
- debootstrap && \
+ debootstrap \
+ uidmap acl && \
rm -f /etc/apt/apt.conf.d/use-snapshot.conf /etc/apt/apt.conf.d/keep-packages.conf && \
if [ -f "/etc/apt/sources.list.d/debian.sources~" ]; then \
mv -f /etc/apt/sources.list.d/debian.sources~ /etc/apt/sources.list.d/debian.sources; \
diff --git a/container-entrypoint b/container-entrypoint
index dee7275e8..4876386e7 100755
--- a/container-entrypoint
+++ b/container-entrypoint
@@ -34,6 +34,47 @@ enable_qemu_binfmts()
done
}

+# For rootless nesting of the uid_ns, we need to provide a list of sub
+# uids/gids which can be used in the sub namespace. As each id in the
+# sub namespace must be mappable into the parent namespace, we need to
+# compute the mapping based on the layout of the parent: Podman maps
+# to an intermediate namespace, hence giving us a user-id map we
+# actually can use for further splitting. Docker maps directly, hence
+# we need to compute the number of sub ids we can use (which have a
+# mapping in the parent ns)
+#
+# Note, that the default range (65k) does NOT allow to map nobody/nogroup,
+# in the sub-namespace, as this usually is id 65k-1 and we loose at least
+# one id per nesting. If we get a larger range, we just map as much as
+# we can and by that make nobody/nogroup usable.
+# See man user_namespaces for details.
+setup_userns_mappings()
+{
+ UID_ROW=$(sort -r /proc/self/uid_map | head -1)
+ UID_OUTER=$(printf '%s' "$UID_ROW" | awk '{print $1}')
+ UID_INNER=$(printf '%s' "$UID_ROW" | awk '{print $2}')
+ UID_COUNT=$(printf '%s' "$UID_ROW" | awk '{print $3}')
+ GID_ROW=$(sort -r /proc/self/gid_map | head -1)
+ GID_OUTER=$(printf '%s' "$GID_ROW" | awk '{print $1}')
+ GID_INNER=$(printf '%s' "$GID_ROW" | awk '{print $2}')
+ GID_COUNT=$(printf '%s' "$GID_ROW" | awk '{print $3}')
+
index 1f8e3dfb5..f98dec421 100755
+ BUILD_SYSTEM="openembedded"
}

enable_unpriv_userns_docker()
@@ -362,8 +388,12 @@ esac
BUILD_SYSTEM=""
while [ $# -gt 0 ]; do
case "$1" in
- --isar)
- BUILD_SYSTEM="isar"
+ --isar | --isar-privileged)
+ BUILD_SYSTEM="isar-privileged"
+ shift 1
+ ;;
+ --isar-rootless)
+ BUILD_SYSTEM="isar-rootless"
shift 1
;;
--with-loop-dev)
@@ -583,16 +613,22 @@ else
sed 's/build_system:[ ]\+//')}
fi

-if [ "${BUILD_SYSTEM}" = "isar" ]; then
+if [ "${BUILD_SYSTEM}" = "isar" ] || [ "${BUILD_SYSTEM}" = "isar-privileged" ]; then
enable_isar_mode
+elif [ "${BUILD_SYSTEM}" = "isar-rootless" ]; then
+ enable_isar_rootless_mode
else
enable_oe_mode
fi

# clean can be executed without config, hence manually forward the build system
-if [ "${BUILD_SYSTEM}" = "isar" ] && echo "${KAS_CMD}" | grep -qe "^clean\|purge"; then
- KAS_OPTIONS="${KAS_OPTIONS} --isar"
-fi
+case "${BUILD_SYSTEM}" in
+isar*)
+ if echo "${KAS_CMD}" | grep -qe "^clean\|purge"; then
+ KAS_OPTIONS="${KAS_OPTIONS} --${BUILD_SYSTEM}"
+ fi
+ ;;
+esac

set_container_image_var

--
2.53.0

MOESSBAUER, Felix

unread,
Jun 2, 2026, 6:27:10 AMJun 2
to kas-...@googlegroups.com, Kiszka, Jan

This is not a good place to issue the warning, as the API is called
multiple times during checkout (I guess once per missing-repos step).
By that, the warning is issued multiple times as well.

We either keep it here and track if we already warned (similar to the
logic in repos.py, or we check somewhere else). Ideas?

Felix

Jan Kiszka

unread,
Jun 2, 2026, 9:31:31 AMJun 2
to Moessbauer, Felix (FT RPD CED OES-DE), kas-...@googlegroups.com
How about moving to get_build_environ()? That should only be invoked once.

Felix Moessbauer

unread,
Jun 2, 2026, 12:06:59 PMJun 2
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
Dear kas isar users,

as a preparation for our activities to implement / try out rootless isar
builds we need a test environment to execute the build (both locally and
in CI). This test environment is provided in this patch series.

Changes since v5:

- initialize build_system with default from schema
- issue build_system==isar deprecation warning only once
(and when reading from config instead of when processing)
Felix Moessbauer (6):
schema: add support for isar in rootless mode
initialize build_system with default from schema
kas: add support for isar-rootless build system
extend buildsystem test to check isar-rootless configuration
clean: add support for cleaning isar-rootless generated data
kas-container: configure container for nested namespaces

Dockerfile | 3 +-
container-entrypoint | 44 ++++++++++++
docs/format-changelog.rst | 9 +++
docs/userguide/kas-container-description.inc | 5 +-
docs/userguide/project-configuration.rst | 19 +++--
examples/isar.yml | 4 +-
kas-container | 48 +++++++++++--
kas/config.py | 6 +-
kas/includehandler.py | 9 +++
kas/libcmds.py | 2 +
kas/libkas.py | 2 +-
kas/plugins/clean.py | 69 ++++++++++++++-----
kas/plugins/menu.py | 2 +-
kas/schema-kas.json | 9 ++-
tests/test_build_system.py | 12 ++++
.../test-isar-privileged.yml | 7 ++
.../test_build_system/test-isar-rootless.yml | 7 ++
17 files changed, 217 insertions(+), 40 deletions(-)
create mode 100644 tests/test_build_system/test-isar-privileged.yml
create mode 100644 tests/test_build_system/test-isar-rootless.yml

--
2.53.0

Felix Moessbauer

unread,
Jun 2, 2026, 12:07:01 PMJun 2
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
For a smooth transition from isar to isar-rootless back to isar, we
further add a warning if the build system is "isar" regarding the
semantics. For now, isar maps to isar-privileged to not break existing
layers. After some grace period, we will be able to change the default
of isar to isar-rootless.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
examples/isar.yml | 4 ++--
kas/includehandler.py | 9 +++++++++
kas/libcmds.py | 2 ++
kas/libkas.py | 2 +-
kas/plugins/menu.py | 2 +-
5 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/examples/isar.yml b/examples/isar.yml
index 3b9d5c7f2..d64dd2deb 100644
--- a/examples/isar.yml
+++ b/examples/isar.yml
@@ -23,9 +23,9 @@
#

header:
- version: 14
+ version: 23

-build_system: isar
+build_system: isar-privileged

machine: qemuamd64
distro: debian-trixie
diff --git a/kas/includehandler.py b/kas/includehandler.py
index a8e683b56..2e8d9f39f 100644
--- a/kas/includehandler.py
+++ b/kas/includehandler.py
@@ -67,6 +67,8 @@ class IncludeException(KasUserError):


class ConfigFile():
+ __build_system_warned__ = False
+
def __init__(self, filename, is_external, is_lockfile):
self.filename = Path(filename)
self.config = {}
@@ -136,6 +138,13 @@ class ConfigFile():
logging.warning('Obsolete ''proxy_config'' detected. '
'This has no effect and will be rejected soon.')

+ if cf.config.get('build_system') == 'isar' and is_main_file \
+ and not ConfigFile.__build_system_warned__:
+ ConfigFile.__build_system_warned__ = True
+ logging.warning(
+ "The semantics of build_system: isar might change in the "
+ "future. Please use 'isar-privileged' or 'isar-rootless'.")
+
cf.src_dir = cf.config.get(SOURCE_DIR_OVERRIDE_KEY, None)
if cf.src_dir is not None and not is_main_file:
raise LoadConfigException(

Felix Moessbauer

unread,
Jun 2, 2026, 12:07:01 PMJun 2
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
To prepare a migration path from isar to isar-rootless, we also
introduce the isar-privileged entry.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---

Felix Moessbauer

unread,
Jun 2, 2026, 12:07:03 PMJun 2
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
Instead of letting callees handle missing build_system defintions, we
directly load the default value from the schema and provide this to the
callers if the build_system is not explicitly set.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
kas/config.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/kas/config.py b/kas/config.py
index cebc7494a..92ed39f77 100644
--- a/kas/config.py
+++ b/kas/config.py
@@ -69,7 +69,11 @@ class Config:
"""
Returns the pre-selected build system
"""
- return self._config.get('build_system', '')
+ def_bs = CONFIGSCHEMA['properties']['build_system']['default']
+ build_system = self._config.get('build_system', def_bs)
+ if build_system == 'isar':
+ build_system = 'isar-privileged'
+ return build_system

def find_missing_repos(self, repo_paths={}):
"""
--
2.53.0

Felix Moessbauer

unread,
Jun 2, 2026, 12:07:03 PMJun 2
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
With that, we also explicitly check the isar-privileged configuration.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
tests/test_build_system.py | 12 ++++++++++++
tests/test_build_system/test-isar-privileged.yml | 7 +++++++
tests/test_build_system/test-isar-rootless.yml | 7 +++++++
3 files changed, 26 insertions(+)
create mode 100644 tests/test_build_system/test-isar-privileged.yml
create mode 100644 tests/test_build_system/test-isar-rootless.yml

Felix Moessbauer

unread,
Jun 2, 2026, 12:07:04 PMJun 2
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
For rootless nesting of the uid_ns, we need to provide a list of sub
uids/gids which can be used in the sub namespace. As each id in the
sub namespace must be mappable into the parent namespace, we need to
compute the mapping based on the layout of the parent. The following
mapping works for rootless podman with --userns=keep-id.

Note, that the default range (65k) does NOT allow to map nobody/nogroup,
in the sub-namespace, as this usually is id 65k-1 and we loose at least
one id per nesting. If we get a larger range, we just map as much as
we can and by that make nobody/nogroup usable.

With these changes we also add the uidmap and acl binaries to the
container which are needed by isar to setup the namespaces and
permissions. The --isar and --isar-rootless parameters now take
precedence over the auto-detected one from .config.yml (previously
this was not required, as OE is never executed from a isar tree and
vice versa).

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
Dockerfile | 3 +-

Felix Moessbauer

unread,
Jun 2, 2026, 12:07:04 PMJun 2
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
During the rootless build, data is generated inside another user id
namespace. By that, the owner of the data from outside of the namespace
is within the the subuid / subgid range, hence does not technically
belong to the calling (kas) user.

While isar takes care to not leave any temporary artifacts with
non-caller ownership behind on successfull builds, this does not apply
to partial or interrupted runs.

To be able to delete this data as well, we enter the namespace similar
to how isar is doing it, clean from the inside (with correct uid/gid
mappings) and finally remove the remainder from the outside.

On rootfull builds, we can simply use sudo to remove everything from the
outside, however sudo is not available in rootless mode.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
+ if args.isar_privileged or build_system == 'isar':
+ build_system = 'isar-privileged'

Jan Kiszka

unread,
Jun 2, 2026, 12:55:53 PMJun 2
to Felix Moessbauer, kas-...@googlegroups.com
Wait! I misled you and myself here: So far, we were probing for the
build system inside kas. Thus, there is no default for kas at this point.

When using kas-container, though, we implicitly have a default because
there is no universal build container for both classes (or even 3 now
with this commit). And as we are defaulting in kas-container to the OE
container, that makes OE the de facto default for this path. A bit of a
mess.

I don't mind if we consolidate all this over an official default OE.
However, we should do that upfront and explicitly in a patch that
explains all this. We should then also drop that build-system probing
from get_build_environ() because it comes dead code with your patch 3.

> "enum": [
> "openembedded",
> "oe",
> - "isar"
> + "isar",
> + "isar-privileged",
> + "isar-rootless"
> ]
> },
> "defaults": {

Felix Moessbauer

unread,
Jun 3, 2026, 5:24:33 AMJun 3
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
Currently, the build system is handled inconsistently across kas and
kas-container. While the default is openembedded, if not provided all
permutations of isar and oe init scripts are searched and executed
(without setting the build_system internally after finding a script).

We clean this up by directly loading the default value from the schema
and provide that to all callers across kas. This allows us to simplify
the init script probing. As the schema change is purely internal (no
user-facing changes), we don't increase the version.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
docs/userguide/project-configuration.rst | 13 ++++++-------
kas/config.py | 3 ++-
kas/libkas.py | 11 +++--------
kas/schema-kas.json | 1 +
4 files changed, 12 insertions(+), 16 deletions(-)

diff --git a/docs/userguide/project-configuration.rst b/docs/userguide/project-configuration.rst
index 04c887b54..71d17ec4f 100644
--- a/docs/userguide/project-configuration.rst
+++ b/docs/userguide/project-configuration.rst
@@ -259,13 +259,12 @@ Configuration reference

``build_system``: string [optional]
:kasschemadesc:`build_system`
- Known build systems are
- ``openembedded`` (or ``oe``) and ``isar``. If set, this restricts the
- search of kas for the init script in the configured repositories to
- ``oe-init-build-env`` or ``isar-init-build-env``, respectively. If
- ``kas-container`` finds this property in the top-level kas configuration
- file (includes are not evaluated), it will automatically select the
- required container image and invocation mode.
+ Supported build systems are ``openembedded`` (or ``oe``) and ``isar``.
+ This value determines which init script is searched for in the configured
+ repositories (``oe-init-build-env`` or ``isar-init-build-env``,
+ respectively). ``kas-container`` evaluates this property in the top-level kas
+ configuration file only (includes are not evaluated) to select the required
+ container image and invocation mode.

``defaults``: dict [optional]
:kasschemadesc:`defaults`
diff --git a/kas/config.py b/kas/config.py
index cebc7494a..3ebdda822 100644
--- a/kas/config.py
+++ b/kas/config.py
@@ -69,7 +69,8 @@ class Config:
"""
Returns the pre-selected build system
"""
- return self._config.get('build_system', '')
+ default_bs = CONFIGSCHEMA['properties']['build_system']['default']
+ return self._config.get('build_system', default_bs)

def find_missing_repos(self, repo_paths={}):
"""
diff --git a/kas/libkas.py b/kas/libkas.py
index f5f5c45ed..bdfcf1712 100644
--- a/kas/libkas.py
+++ b/kas/libkas.py
@@ -404,15 +404,10 @@ def get_build_environ(build_system):

init_repo = None
if build_system in ['openembedded', 'oe']:
- scripts = ['oe-init-build-env']
+ script = 'oe-init-build-env'
elif build_system == 'isar':
- scripts = ['isar-init-build-env']
- else:
- scripts = ['oe-init-build-env', 'isar-init-build-env']
- permutations = \
- [(repo, script) for repo in get_context().config.get_repos()
- for script in scripts]
- for (repo, script) in permutations:
+ script = 'isar-init-build-env'
+ for repo in get_context().config.get_repos():
if os.path.exists(repo.path + '/' + script):
if init_repo:
raise InitBuildEnvError(
diff --git a/kas/schema-kas.json b/kas/schema-kas.json
index f3594a247..b8dc86c6c 100644
--- a/kas/schema-kas.json
+++ b/kas/schema-kas.json
@@ -80,6 +80,7 @@
},
"build_system": {
"description": "Defines the bitbake-based build system.",
+ "default": "openembedded",
"enum": [
"openembedded",
"oe",
--
2.53.0

Felix Moessbauer

unread,
Jun 3, 2026, 5:24:34 AMJun 3
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
This function is called from a Macro with a specific context. Instead of
relying on the global context, use the specific one that is provided to
the caller.

While this does not make any difference in the current behavior, it
reduces the global state and aligns the architecture with other commands.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
kas/libcmds.py | 4 +++-
kas/libkas.py | 10 +++++-----
2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/kas/libcmds.py b/kas/libcmds.py
index 70eb95cd1..5f3b7ee3f 100644
--- a/kas/libcmds.py
+++ b/kas/libcmds.py
@@ -470,7 +470,9 @@ class SetupEnviron(Command):
return 'setup_environ'

def execute(self, ctx):
- ctx.environ.update(get_build_environ(ctx.config.get_build_system()))
+ ctx.environ.update(
+ get_build_environ(ctx.config.get_build_system(), ctx)
+ )


class WriteBBConfig(Command):
diff --git a/kas/libkas.py b/kas/libkas.py
index bdfcf1712..0331d092a 100644
--- a/kas/libkas.py
+++ b/kas/libkas.py
@@ -395,7 +395,7 @@ def get_buildtools_version():
return -1


-def get_build_environ(build_system):
+def get_build_environ(build_system, ctx):
"""
Creates the build environment variables.
"""
@@ -407,7 +407,7 @@ def get_build_environ(build_system):
script = 'oe-init-build-env'
elif build_system == 'isar':
script = 'isar-init-build-env'
- for repo in get_context().config.get_repos():
+ for repo in ctx.config.get_repos():
if os.path.exists(repo.path + '/' + script):
if init_repo:
raise InitBuildEnvError(
@@ -420,7 +420,7 @@ def get_build_environ(build_system):
if not init_repo:
raise InitBuildEnvError('Did not find any init-build-env script')

- conf_buildtools = get_context().config.get_buildtools()
+ conf_buildtools = ctx.config.get_buildtools()
buildtools_env = ""

if conf_buildtools:
@@ -475,7 +475,7 @@ def get_build_environ(build_system):
env = {}
env['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'

- (_, output) = run_cmd([str(get_bb_env_file), get_context().build_dir],
+ (_, output) = run_cmd([str(get_bb_env_file), ctx.build_dir],
cwd=init_repo.path, env=env)
if init_script_log != '/dev/null':
with open(init_script_log) as log:
@@ -492,7 +492,7 @@ def get_build_environ(build_system):
except ValueError:
pass

- conf_env = get_context().config.get_environment()
+ conf_env = ctx.config.get_environment()

env_vars = ['SSTATE_DIR', 'SSTATE_MIRRORS', 'DL_DIR', 'TMPDIR']
env_vars.extend(conf_env)
--
2.53.0

Jan Kiszka

unread,
Jun 3, 2026, 5:47:14 AMJun 3
to Felix Moessbauer, kas-...@googlegroups.com
Subject should reflect that we are now consistently using a default.

On 03.06.26 11:24, Felix Moessbauer wrote:
> Currently, the build system is handled inconsistently across kas and
> kas-container. While the default is openembedded, if not provided all

While the default in kas-container is openembedded, kas probes all
permutations if no build system is provided...

> permutations of isar and oe init scripts are searched and executed
> (without setting the build_system internally after finding a script).
>
> We clean this up by directly loading the default value from the schema
> and provide that to all callers across kas. This allows us to simplify
> the init script probing. As the schema change is purely internal (no
> user-facing changes), we don't increase the version.

This is not quite true. We had no default in the schema, we only had it
in kas-container. As most (if not all) isar users are container-based,
this has no practical impact, but it is formally still a spec change.

Jan

Felix Moessbauer

unread,
Jun 3, 2026, 9:35:15 AMJun 3
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
Dear kas isar users,

as a preparation for our activities to implement / try out rootless isar
builds we need a test environment to execute the build (both locally and
in CI). This test environment is provided in this patch series.

Changes since v6:

- rebased onto next (which brings the build_system refactoring)
Felix Moessbauer (5):
schema: add support for isar in rootless mode
kas: add support for isar-rootless build system
extend buildsystem test to check isar-rootless configuration
clean: add support for cleaning isar-rootless generated data
kas-container: configure container for nested namespaces

Dockerfile | 3 +-
container-entrypoint | 44 ++++++++++++
docs/format-changelog.rst | 9 +++
docs/userguide/kas-container-description.inc | 5 +-
docs/userguide/project-configuration.rst | 6 +-
examples/isar.yml | 4 +-
kas-container | 48 +++++++++++--
kas/config.py | 5 +-
kas/includehandler.py | 9 +++
kas/libcmds.py | 2 +
kas/libkas.py | 2 +-
kas/plugins/clean.py | 69 ++++++++++++++-----
kas/plugins/menu.py | 2 +-
kas/schema-kas.json | 8 ++-
tests/test_build_system.py | 12 ++++
.../test-isar-privileged.yml | 7 ++
.../test_build_system/test-isar-rootless.yml | 7 ++
17 files changed, 208 insertions(+), 34 deletions(-)
create mode 100644 tests/test_build_system/test-isar-privileged.yml
create mode 100644 tests/test_build_system/test-isar-rootless.yml

--
2.53.0

Felix Moessbauer

unread,
Jun 3, 2026, 9:35:16 AMJun 3
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
To prepare a migration path from isar to isar-rootless, we also
introduce the isar-privileged entry.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
docs/format-changelog.rst | 9 +++++++++
docs/userguide/project-configuration.rst | 6 +++++-
kas/schema-kas.json | 8 +++++---
3 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/docs/format-changelog.rst b/docs/format-changelog.rst
index b43b23066..fb8f4f8e3 100644
--- a/docs/format-changelog.rst
+++ b/docs/format-changelog.rst
@@ -219,3 +219,12 @@ Added

- Switch to nodistro which is the default distro setting in
openembedded-core.
+
+Version 23
+----------
+
+Added
+~~~~~
+
+- Extend the allowed values of ``build_system`` by adding ``isar-privileged``
+ and ``isar-rootless``.
diff --git a/docs/userguide/project-configuration.rst b/docs/userguide/project-configuration.rst
index 71d17ec4f..989f4a4fb 100644
--- a/docs/userguide/project-configuration.rst
+++ b/docs/userguide/project-configuration.rst
@@ -259,13 +259,17 @@ Configuration reference

``build_system``: string [optional]
:kasschemadesc:`build_system`
- Supported build systems are ``openembedded`` (or ``oe``) and ``isar``.
This value determines which init script is searched for in the configured
repositories (``oe-init-build-env`` or ``isar-init-build-env``,
respectively). ``kas-container`` evaluates this property in the top-level kas
configuration file only (includes are not evaluated) to select the required
container image and invocation mode.

+ .. note:: Modern versions of isar support a rootless mode, whereby builds can
+ be executed as non-privileged user. To distinguish the two modes,
+ use ``isar-rootless`` or ``isar-privileged``. Whether the rootless
+ mode is supported depends on your layers.
+
``defaults``: dict [optional]
:kasschemadesc:`defaults`
This may help you to avoid repeating the same property assignment in
diff --git a/kas/schema-kas.json b/kas/schema-kas.json
index b8dc86c6c..923a106c1 100644
--- a/kas/schema-kas.json
+++ b/kas/schema-kas.json
@@ -41,7 +41,7 @@
{
"type": "integer",
"minimum": 1,
- "maximum": 22
+ "maximum": 23
}
]
},
@@ -79,12 +79,14 @@
}
},
"build_system": {
- "description": "Defines the bitbake-based build system.",
+ "description": "Defines the bitbake-based build system. ``openembedded`` and ``oe`` are equivalent. ``isar`` is deprecated, use ``isar-privileged`` or ``isar-rootless`` instead.",
"default": "openembedded",
"enum": [
"openembedded",
"oe",
- "isar"
+ "isar",
+ "isar-privileged",
+ "isar-rootless"
]
},
"defaults": {
--
2.53.0

Felix Moessbauer

unread,
Jun 3, 2026, 9:35:16 AMJun 3
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
For a smooth transition from isar to isar-rootless back to isar, we
further add a warning if the build system is "isar" regarding the
semantics. For now, isar maps to isar-privileged to not break existing
layers. After some grace period, we will be able to change the default
of isar to isar-rootless.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
examples/isar.yml | 4 ++--
kas/config.py | 5 ++++-
kas/includehandler.py | 9 +++++++++
kas/libcmds.py | 2 ++
kas/libkas.py | 2 +-
kas/plugins/menu.py | 2 +-
6 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/examples/isar.yml b/examples/isar.yml
index 3b9d5c7f2..d64dd2deb 100644
--- a/examples/isar.yml
+++ b/examples/isar.yml
@@ -23,9 +23,9 @@
#

header:
- version: 14
+ version: 23

-build_system: isar
+build_system: isar-privileged

machine: qemuamd64
distro: debian-trixie
diff --git a/kas/config.py b/kas/config.py
index 3ebdda822..1267094f9 100644
--- a/kas/config.py
+++ b/kas/config.py
@@ -70,7 +70,10 @@ class Config:
Returns the pre-selected build system
"""
default_bs = CONFIGSCHEMA['properties']['build_system']['default']
- return self._config.get('build_system', default_bs)
+ build_system = self._config.get('build_system', default_bs)
+ if build_system == 'isar':
+ build_system = 'isar-privileged'
+ return build_system

def find_missing_repos(self, repo_paths={}):
"""
diff --git a/kas/includehandler.py b/kas/includehandler.py
index a8e683b56..2e8d9f39f 100644
--- a/kas/includehandler.py
+++ b/kas/includehandler.py
@@ -67,6 +67,8 @@ class IncludeException(KasUserError):


class ConfigFile():
+ __build_system_warned__ = False
+
def __init__(self, filename, is_external, is_lockfile):
self.filename = Path(filename)
self.config = {}
@@ -136,6 +138,13 @@ class ConfigFile():
logging.warning('Obsolete ''proxy_config'' detected. '
'This has no effect and will be rejected soon.')

+ if cf.config.get('build_system') == 'isar' and is_main_file \
+ and not ConfigFile.__build_system_warned__:
+ ConfigFile.__build_system_warned__ = True
+ logging.warning(
+ "The semantics of build_system: isar might change in the "
+ "future. Please use 'isar-privileged' or 'isar-rootless'.")
+
cf.src_dir = cf.config.get(SOURCE_DIR_OVERRIDE_KEY, None)
if cf.src_dir is not None and not is_main_file:
raise LoadConfigException(
diff --git a/kas/libcmds.py b/kas/libcmds.py
index 5f3b7ee3f..bdda561a0 100644
--- a/kas/libcmds.py
+++ b/kas/libcmds.py
@@ -520,6 +520,8 @@ class WriteBBConfig(Command):
fds.write(f'DISTRO ??= "{ctx.config.get_distro()}"\n')
fds.write('BBMULTICONFIG ?= '
f'"{ctx.config.get_multiconfig()}"\n')
+ if ctx.config.get_build_system() == 'isar-rootless':
+ fds.write('ISAR_ROOTLESS ?= "1"\n')

_write_bblayers_conf(ctx)
_write_local_conf(ctx)
diff --git a/kas/libkas.py b/kas/libkas.py
index 0331d092a..58321288c 100644
--- a/kas/libkas.py
+++ b/kas/libkas.py
@@ -405,7 +405,7 @@ def get_build_environ(build_system, ctx):
init_repo = None
if build_system in ['openembedded', 'oe']:
script = 'oe-init-build-env'
- elif build_system == 'isar':
+ elif build_system.startswith('isar'):
script = 'isar-init-build-env'
for repo in ctx.config.get_repos():
if os.path.exists(repo.path + '/' + script):

Felix Moessbauer

unread,
Jun 3, 2026, 9:35:17 AMJun 3
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
With that, we also explicitly check the isar-privileged configuration.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
tests/test_build_system.py | 12 ++++++++++++
tests/test_build_system/test-isar-privileged.yml | 7 +++++++
tests/test_build_system/test-isar-rootless.yml | 7 +++++++
3 files changed, 26 insertions(+)
create mode 100644 tests/test_build_system/test-isar-privileged.yml
create mode 100644 tests/test_build_system/test-isar-rootless.yml

Felix Moessbauer

unread,
Jun 3, 2026, 9:35:18 AMJun 3
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
During the rootless build, data is generated inside another user id
namespace. By that, the owner of the data from outside of the namespace
is within the the subuid / subgid range, hence does not technically
belong to the calling (kas) user.

While isar takes care to not leave any temporary artifacts with
non-caller ownership behind on successfull builds, this does not apply
to partial or interrupted runs.

To be able to delete this data as well, we enter the namespace similar
to how isar is doing it, clean from the inside (with correct uid/gid
mappings) and finally remove the remainder from the outside.

On rootfull builds, we can simply use sudo to remove everything from the
outside, however sudo is not available in rootless mode.

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
+ if args.isar_privileged or build_system == 'isar':
+ build_system = 'isar-privileged'

Felix Moessbauer

unread,
Jun 3, 2026, 9:35:20 AMJun 3
to kas-...@googlegroups.com, jan.k...@siemens.com, Felix Moessbauer
For rootless nesting of the uid_ns, we need to provide a list of sub
uids/gids which can be used in the sub namespace. As each id in the
sub namespace must be mappable into the parent namespace, we need to
compute the mapping based on the layout of the parent. The following
mapping works for rootless podman with --userns=keep-id.

Note, that the default range (65k) does NOT allow to map nobody/nogroup,
in the sub-namespace, as this usually is id 65k-1 and we loose at least
one id per nesting. If we get a larger range, we just map as much as
we can and by that make nobody/nogroup usable.

With these changes we also add the uidmap and acl binaries to the
container which are needed by isar to setup the namespaces and
permissions. The --isar and --isar-rootless parameters now take
precedence over the auto-detected one from .config.yml (previously
this was not required, as OE is never executed from a isar tree and
vice versa).

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
---
Dockerfile | 3 +-

Jan Kiszka

unread,
Jun 4, 2026, 2:17:41 AMJun 4
to Felix Moessbauer, kas-...@googlegroups.com, Cedric Hombourger
Traps and pitfalls all the way: How do we want to react on
--with-loop-dev when isar-rootless is set? It does not really make sense
and should rather be rejected.

Or we use this chance to drop that switch? Cedric, IIRC you had some
remaining use cases for it, right?

Jan

Jan Kiszka

unread,
Jun 7, 2026, 2:43:16 PMJun 7
to Felix Moessbauer, kas-...@googlegroups.com, Cedric Hombourger
As the removal is now in next, at least tentative, I rebased this series
and applied it. This patch received some modifications, though. Will
post it here soon.

Thanks,

Jan Kiszka

unread,
Jun 7, 2026, 2:45:16 PMJun 7
to Felix Moessbauer, kas-...@googlegroups.com
From: Felix Moessbauer <felix.mo...@siemens.com>

For rootless nesting of the uid_ns, we need to provide a list of sub
uids/gids which can be used in the sub namespace. As each id in the
sub namespace must be mappable into the parent namespace, we need to
compute the mapping based on the layout of the parent. The following
mapping works for rootless podman with --userns=keep-id.

Note, that the default range (65k) does NOT allow to map nobody/nogroup,
in the sub-namespace, as this usually is id 65k-1 and we loose at least
one id per nesting. If we get a larger range, we just map as much as
we can and by that make nobody/nogroup usable.

With these changes we also add the uidmap and acl binaries to the
container which are needed by isar to setup the namespaces and
permissions. The --isar and --isar-rootless parameters now take
precedence over the auto-detected one from .config.yml (previously
this was not required, as OE is never executed from a isar tree and
vice versa).

Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
[Jan: rebased, discouraged use of plain --isar]
Signed-off-by: Jan Kiszka <jan.k...@siemens.com>
---

Changes in 7.1:
- Wording adjustment for help, also dropping --isar
- Warn is plain --isar is used, analogously to build_system changes

Dockerfile | 3 +-
container-entrypoint | 44 +++++++++++++++
docs/userguide/kas-container-description.inc | 5 +-
kas-container | 56 +++++++++++++++++---
4 files changed, 97 insertions(+), 11 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index 2cc62c0..91a520b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -113,7 +113,8 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=${CACHE_SHARING} \
umoci skopeo \
python3-botocore \
bubblewrap \
- debootstrap && \
+ debootstrap \
+ uidmap acl && \
rm -f /etc/apt/apt.conf.d/use-snapshot.conf /etc/apt/apt.conf.d/keep-packages.conf && \
if [ -f "/etc/apt/sources.list.d/debian.sources~" ]; then \
mv -f /etc/apt/sources.list.d/debian.sources~ /etc/apt/sources.list.d/debian.sources; \
diff --git a/container-entrypoint b/container-entrypoint
index dee7275..4876386 100755
index 7f35013..7762511 100644
--- a/docs/userguide/kas-container-description.inc
+++ b/docs/userguide/kas-container-description.inc
@@ -39,5 +39,6 @@ written to from the host. To completely remove all data managed by kas, use
so they can be removed from the host.

.. note::
- The ISAR build system is not compatible with rootless execution. By that,
- we fall back to the system docker or podman instance.
+ The ISAR build system is compatible with rootless execution in ``isar-rootless``
+ mode only. The ``isar`` and ``isar-privileged`` modes fall back to the system docker
+ or podman instance.
diff --git a/kas-container b/kas-container
index e0c6734..55bf8bc 100755
--- a/kas-container
+++ b/kas-container
@@ -66,8 +66,10 @@ usage()
printf "%b" "menu\t\t\tProvide configuration menu and trigger " \
"configured build.\n"
printf "%b" "\nOptional arguments:\n"
- printf "%b" "--isar\t\t\tUse kas-isar container to build Isar image. To force\n"
- printf "%b" " \t\t\tthe use of run0 over sudo, set KAS_SUDO_CMD=run0.\n"
+ printf "%b" "--isar-privileged\tRun an Isar build in privileged mode. " \
+ "To force the use\n"
+ printf "%b" "\t\t\tof run0 over sudo, set KAS_SUDO_CMD=run0.\n"
+ printf "%b" "--isar-rootless\t\tRun an Isar build in rootless mode\n"
printf "%b" "--runtime-args\t\tAdditional arguments to pass to the " \
"container runtime\n"
printf "%b" "\t\t\tfor running the build.\n"
@@ -163,6 +165,29 @@ enable_isar_mode()
@@ -170,6 +195,7 @@ enable_oe_mode()
# calling "podman run" has a 1:1 mapping
KAS_RUNTIME_ARGS="${KAS_RUNTIME_ARGS} --userns=keep-id"
fi
+ BUILD_SYSTEM="openembedded"
}

enable_unpriv_userns_docker()
@@ -321,8 +347,16 @@ BUILD_SYSTEM=""
KAS_OPTIONS_DIRECT=""
while [ $# -gt 0 ]; do
case "$1" in
- --isar)
- BUILD_SYSTEM="isar"
+ --isar | --isar-privileged)
+ if [ "$1" = "--isar" ]; then
+ warning "The semantic of '--isar' might change in the" \
+ "future. Please use '--isar-privileged' instead."
+ fi
+ BUILD_SYSTEM="isar-privileged"
+ shift 1
+ ;;
+ --isar-rootless)
+ BUILD_SYSTEM="isar-rootless"
shift 1
;;
--runtime-args | --docker-args)
@@ -553,16 +587,22 @@ else
sed 's/build_system:[ ]\+//')}
fi

-if [ "${BUILD_SYSTEM}" = "isar" ]; then
+if [ "${BUILD_SYSTEM}" = "isar" ] || [ "${BUILD_SYSTEM}" = "isar-privileged" ]; then
enable_isar_mode
+elif [ "${BUILD_SYSTEM}" = "isar-rootless" ]; then
+ enable_isar_rootless_mode
else
enable_oe_mode
fi

# clean can be executed without config, hence manually forward the build system
-if [ "${BUILD_SYSTEM}" = "isar" ] && echo "${KAS_CMD}" | grep -qe "^clean\|purge"; then
- KAS_OPTIONS="${KAS_OPTIONS} --isar"
-fi
+case "${BUILD_SYSTEM}" in
+isar*)
+ if echo "${KAS_CMD}" | grep -qe "^clean\|purge"; then
+ KAS_OPTIONS="${KAS_OPTIONS} --${BUILD_SYSTEM}"
+ fi
+ ;;
+esac

set_container_image_var

--
2.47.3

MOESSBAUER, Felix

unread,
Jun 8, 2026, 5:13:35 AMJun 8
to Kiszka, Jan, kas-...@googlegroups.com
On Sun, 2026-06-07 at 20:45 +0200, Jan Kiszka wrote:
> From: Felix Moessbauer <felix.mo...@siemens.com>
>
> For rootless nesting of the uid_ns, we need to provide a list of sub
> uids/gids which can be used in the sub namespace. As each id in the
> sub namespace must be mappable into the parent namespace, we need to
> compute the mapping based on the layout of the parent. The following
> mapping works for rootless podman with --userns=keep-id.
>
> Note, that the default range (65k) does NOT allow to map nobody/nogroup,
> in the sub-namespace, as this usually is id 65k-1 and we loose at least
> one id per nesting. If we get a larger range, we just map as much as
> we can and by that make nobody/nogroup usable.
>
> With these changes we also add the uidmap and acl binaries to the
> container which are needed by isar to setup the namespaces and
> permissions. The --isar and --isar-rootless parameters now take
> precedence over the auto-detected one from .config.yml (previously
> this was not required, as OE is never executed from a isar tree and
> vice versa).
>
> Signed-off-by: Felix Moessbauer <felix.mo...@siemens.com>
> [Jan: rebased, discouraged use of plain --isar]

Thanks!

Felix

Reply all
Reply to author
Forward
0 new messages