From a8e8ebde4400e94036df35f72b08708f00bd4ffe Mon Sep 17 00:00:00 2001 From: Nikita Dubrovskii Date: Fri, 18 Oct 2024 17:04:07 +0200 Subject: [PATCH] org.osbuild.selinux: support for specifying where file_contexts comes from file_context now can come from - tree (current default) - mount - input Example: ``` - type: org.osbuild.selinux inputs: tree: type: org.osbuild.tree origin: org.osbuild.pipeline references: - name:tree options: file_contexts: input://tree/etc/selinux/targeted/contexts/files/file_contexts ``` --- stages/org.osbuild.selinux | 6 ++- stages/org.osbuild.selinux.meta.json | 12 ++++-- stages/test/test_selinux.py | 62 +++++++++++++++++++--------- 3 files changed, 56 insertions(+), 24 deletions(-) diff --git a/stages/org.osbuild.selinux b/stages/org.osbuild.selinux index 40487599..8e25a281 100755 --- a/stages/org.osbuild.selinux +++ b/stages/org.osbuild.selinux @@ -9,6 +9,7 @@ from osbuild.util import parsing, selinux def main(args): # Get the path where the tree is + tree = args["tree"] options = args["options"] file_contexts = options.get("file_contexts") exclude_paths = options.get("exclude_paths") @@ -16,7 +17,10 @@ def main(args): root, target = parsing.parse_location_into_parts(target, args) if file_contexts: - file_contexts = os.path.join(args["tree"], options["file_contexts"]) + if "://" not in file_contexts: + file_contexts = os.path.normpath(f"{tree}/{file_contexts}") + else: + file_contexts = parsing.parse_location(file_contexts, args) if exclude_paths: exclude_paths = [os.path.normpath(f"{root}/{target}/{p}") for p in exclude_paths] selinux.setfiles(file_contexts, os.path.normpath(root), target, exclude_paths=exclude_paths) diff --git a/stages/org.osbuild.selinux.meta.json b/stages/org.osbuild.selinux.meta.json index e536cead..9a9d7bb1 100644 --- a/stages/org.osbuild.selinux.meta.json +++ b/stages/org.osbuild.selinux.meta.json @@ -1,8 +1,8 @@ { "summary": "Set SELinux file contexts", "description": [ - "Sets correct SELinux labels for every file in the tree, according to the", - "SELinux policy installed inside the tree.", + "Sets correct SELinux labels for every file in the tree or on mount, according to", + "the SELinux policy.", "Uses the host's `setfiles` program and the tree's `file_contexts`, usually", " /etc/selinux//contexts/files/file_contexts", "where is the value set in /etc/selinux/config (usually \"targeted\"", @@ -40,8 +40,8 @@ "default": "tree:///" }, "file_contexts": { - "type": "string", - "description": "Path to the active SELinux policy's `file_contexts`" + "description": "Path to the active SELinux policy's `file_contexts`. Supports `tree://`, `mount://`, and `input://` schemes. Plain paths imply `tree://`.", + "type": "string" }, "exclude_paths": { "type": "array", @@ -70,6 +70,10 @@ }, "mounts": { "type": "array" + }, + "inputs": { + "type": "object", + "additionalProperties": true } } } diff --git a/stages/test/test_selinux.py b/stages/test/test_selinux.py index cca72f87..9a1e01f2 100644 --- a/stages/test/test_selinux.py +++ b/stages/test/test_selinux.py @@ -31,6 +31,7 @@ def get_test_input(test_data, file_contexts=False, labels=False): ({"exclude_paths": ["/sysroot"]}, ""), ({"target": "mount://disk/boot/efi"}, ""), ({"target": "tree:///boot/efi"}, ""), + ({"file_contexts": "path/to/file_contexts"}, ""), # bad ({"target": "/boot/efi"}, "'/boot/efi' does not match '^mount://[^/]+/|^tree:///'"), ({"file_contexts": 1234}, "1234 is not of type 'string'"), @@ -58,19 +59,38 @@ def test_schema_validation_selinux_required_options(stage_schema): @patch("osbuild.util.selinux.setfiles") -def test_selinux_file_contexts(mocked_setfiles, tmp_path, stage_module): - options = { - "file_contexts": "etc/selinux/thing", - } +def test_selinux_file_contexts_schemeless_relpath(mocked_setfiles, tmp_path, stage_module): + tree = tmp_path / "tree" + args = { - "tree": f"{tmp_path}", - "options": options + "tree": tree, + "options": { + "file_contexts": "etc/selinux/thing", + } } stage_module.main(args) assert len(mocked_setfiles.call_args_list) == 1 args, kwargs = mocked_setfiles.call_args_list[0] - assert args == (f"{tmp_path}/etc/selinux/thing", os.fspath(tmp_path), "/") + assert args == (f"{tree}/etc/selinux/thing", os.fspath(tree), "/") + assert kwargs == {"exclude_paths": None} + + +@patch("osbuild.util.selinux.setfiles") +def test_selinux_file_contexts_schemeless_abspath(mocked_setfiles, tmp_path, stage_module): + tree = tmp_path / "tree" + + args = { + "tree": tree, + "options": { + "file_contexts": "/etc/selinux/thing", + } + } + stage_module.main(args) + + assert len(mocked_setfiles.call_args_list) == 1 + args, kwargs = mocked_setfiles.call_args_list[0] + assert args == (f"{tree}/etc/selinux/thing", os.fspath(tree), "/") assert kwargs == {"exclude_paths": None} @@ -78,44 +98,48 @@ def test_selinux_file_contexts(mocked_setfiles, tmp_path, stage_module): def test_selinux_file_contexts_mounts(mocked_setfiles, tmp_path, stage_module): tree = tmp_path / "tree" mounts = tmp_path / "mounts" + root = mounts / "." args = { - "tree": f"{tree}", + "tree": tree, "options": { - "file_contexts": "etc/selinux/thing", + "file_contexts": "mount://root/etc/selinux/file_contexts", "target": "mount://root/" }, "paths": { "mounts": mounts, }, "mounts": { - "root": {"path": mounts} + "root": { + "path": root + } } } stage_module.main(args) assert len(mocked_setfiles.call_args_list) == 1 args, kwargs = mocked_setfiles.call_args_list[0] - assert args == (f"{tree}/etc/selinux/thing", f"{mounts}", "/") + assert args == (f"{mounts}/etc/selinux/file_contexts", f"{mounts}", "/") assert kwargs == {"exclude_paths": None} @patch("osbuild.util.selinux.setfiles") def test_selinux_file_contexts_exclude(mocked_setfiles, tmp_path, stage_module): - options = { - "file_contexts": "etc/selinux/thing", - "exclude_paths": ["/sysroot"], - } + tree = tmp_path / "tree" + args = { - "tree": f"{tmp_path}", - "options": options + "tree": tree, + "options": { + "file_contexts": "etc/selinux/thing", + "exclude_paths": ["/sysroot"], + } } stage_module.main(args) assert len(mocked_setfiles.call_args_list) == 1 args, kwargs = mocked_setfiles.call_args_list[0] - assert args == (f"{tmp_path}/etc/selinux/thing", os.fspath(tmp_path), "/") - assert kwargs == {"exclude_paths": [f"{tmp_path}/sysroot"]} + assert args == (f"{tree}/etc/selinux/thing", os.fspath(tree), "/") + assert kwargs == {"exclude_paths": [f"{tree}/sysroot"]} @patch("osbuild.util.selinux.setfilecon")