From d3704d26a74f7d70074601e4b96100403707ffa3 Mon Sep 17 00:00:00 2001 From: Lukas Vrabec Date: Tue, 24 Nov 2020 20:24:50 +0100 Subject: [PATCH] Add Support for --device /dev/XXX Commit adds functionality to generate allow rules when --device switch is used for podman e.g: podman run --device /dev/tty0 fedora /bin/bash . The output policy should looks like: (block devtest (blockinherit container) (allow process process ( capability ( audit_write chown dac_override fowner fsetid kill mknod net_bind_service net_raw setfcap setgid setpcap setuid sys_chroot ))) (allow process tty_device_t ( blk_file ( getattr read write append ioctl lock open ))) (allow process tty_device_t ( chr_file ( getattr read write append ioctl lock open ))) ) TODO: CRI-O functionality is missing --- tests/selinux.py | 2 + tests/test_devices.podman.cil | 7 + tests/test_devices.podman.json | 284 +++++++++++++++++++++++++++++++++ tests/test_main.py | 8 + udica/__main__.py | 2 + udica/parse.py | 13 ++ udica/perms.py | 1 + udica/policy.py | 28 +++- 8 files changed, 344 insertions(+), 1 deletion(-) create mode 100644 tests/test_devices.podman.cil create mode 100644 tests/test_devices.podman.json diff --git a/tests/selinux.py b/tests/selinux.py index 58ee8e4..f56d132 100644 --- a/tests/selinux.py +++ b/tests/selinux.py @@ -23,6 +23,8 @@ def selabel_lookup(selabel, directory, rc): if directory == "/tmp/test": return (0, None) + elif directory == "/dev/fb0": + return (0, "system_u:object_r:framebuf_device_t:s0") else: return (0, "system_u:object_r:var_spool_t:s0") diff --git a/tests/test_devices.podman.cil b/tests/test_devices.podman.cil new file mode 100644 index 0000000..c93d282 --- /dev/null +++ b/tests/test_devices.podman.cil @@ -0,0 +1,7 @@ +(block my_container + (blockinherit container) + (allow process process ( capability ( audit_write chown dac_override fowner fsetid kill mknod net_bind_service net_raw setfcap setgid setpcap setuid sys_chroot ))) + + (allow process framebuf_device_t ( blk_file ( getattr read write append ioctl lock open ))) + (allow process framebuf_device_t ( chr_file ( getattr read write append ioctl lock open ))) +) \ No newline at end of file diff --git a/tests/test_devices.podman.json b/tests/test_devices.podman.json new file mode 100644 index 0000000..9b695b9 --- /dev/null +++ b/tests/test_devices.podman.json @@ -0,0 +1,284 @@ +[ + { + "Id": "6335b78454e65cecc2c3c78c402e16d1a0691cf416468822c7f996594e991f2b", + "Created": "2020-11-24T20:06:25.852770272+01:00", + "Path": "/bin/bash", + "Args": [ + "/bin/bash" + ], + "State": { + "OciVersion": "1.0.2-dev", + "Status": "running", + "Running": true, + "Paused": false, + "Restarting": false, + "OOMKilled": false, + "Dead": false, + "Pid": 240290, + "ConmonPid": 240287, + "ExitCode": 0, + "Error": "", + "StartedAt": "2020-11-24T20:06:25.990467073+01:00", + "FinishedAt": "0001-01-01T00:00:00Z", + "Healthcheck": { + "Status": "", + "FailingStreak": 0, + "Log": null + } + }, + "Image": "79fd58dc76113dac76a120f22cadecc3b2d1794b414f90ea368cf66096700053", + "ImageName": "registry.fedoraproject.org/fedora:latest", + "Rootfs": "", + "Pod": "", + "ResolvConfPath": "/var/run/containers/storage/overlay-containers/6335b78454e65cecc2c3c78c402e16d1a0691cf416468822c7f996594e991f2b/userdata/resolv.conf", + "HostnamePath": "/var/run/containers/storage/overlay-containers/6335b78454e65cecc2c3c78c402e16d1a0691cf416468822c7f996594e991f2b/userdata/hostname", + "HostsPath": "/var/run/containers/storage/overlay-containers/6335b78454e65cecc2c3c78c402e16d1a0691cf416468822c7f996594e991f2b/userdata/hosts", + "StaticDir": "/var/lib/containers/storage/overlay-containers/6335b78454e65cecc2c3c78c402e16d1a0691cf416468822c7f996594e991f2b/userdata", + "OCIConfigPath": "/var/lib/containers/storage/overlay-containers/6335b78454e65cecc2c3c78c402e16d1a0691cf416468822c7f996594e991f2b/userdata/config.json", + "OCIRuntime": "crun", + "LogPath": "/var/lib/containers/storage/overlay-containers/6335b78454e65cecc2c3c78c402e16d1a0691cf416468822c7f996594e991f2b/userdata/ctr.log", + "LogTag": "", + "ConmonPidFile": "/var/run/containers/storage/overlay-containers/6335b78454e65cecc2c3c78c402e16d1a0691cf416468822c7f996594e991f2b/userdata/conmon.pid", + "Name": "upbeat_ellis", + "RestartCount": 0, + "Driver": "overlay", + "MountLabel": "system_u:object_r:container_file_t:s0:c543,c755", + "ProcessLabel": "system_u:system_r:container_t:s0:c543,c755", + "AppArmorProfile": "", + "EffectiveCaps": [ + "CAP_AUDIT_WRITE", + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_MKNOD", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SETFCAP", + "CAP_SETGID", + "CAP_SETPCAP", + "CAP_SETUID", + "CAP_SYS_CHROOT" + ], + "BoundingCaps": [ + "CAP_AUDIT_WRITE", + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_MKNOD", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SETFCAP", + "CAP_SETGID", + "CAP_SETPCAP", + "CAP_SETUID", + "CAP_SYS_CHROOT" + ], + "ExecIDs": [], + "GraphDriver": { + "Name": "overlay", + "Data": { + "LowerDir": "/var/lib/containers/storage/overlay/b4fa6ff1346dec95ce4454464201fdadfd816e10eb7322048829c551ce032d08/diff", + "MergedDir": "/var/lib/containers/storage/overlay/6dea692afd62add0259ae00d5c343806467c305de241df5dee32b500db315249/merged", + "UpperDir": "/var/lib/containers/storage/overlay/6dea692afd62add0259ae00d5c343806467c305de241df5dee32b500db315249/diff", + "WorkDir": "/var/lib/containers/storage/overlay/6dea692afd62add0259ae00d5c343806467c305de241df5dee32b500db315249/work" + } + }, + "Mounts": [], + "Dependencies": [], + "NetworkSettings": { + "EndpointID": "", + "Gateway": "10.88.0.1", + "IPAddress": "10.88.0.19", + "IPPrefixLen": 16, + "IPv6Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "MacAddress": "06:d8:84:ef:b9:d0", + "Bridge": "", + "SandboxID": "", + "HairpinMode": false, + "LinkLocalIPv6Address": "", + "LinkLocalIPv6PrefixLen": 0, + "Ports": {}, + "SandboxKey": "/var/run/netns/cni-d1e80ea0-dca3-ade7-34b9-34ad49135471" + }, + "ExitCommand": [ + "/usr/bin/podman", + "--root", + "/var/lib/containers/storage", + "--runroot", + "/var/run/containers/storage", + "--log-level", + "error", + "--cgroup-manager", + "systemd", + "--tmpdir", + "/var/run/libpod", + "--runtime", + "crun", + "--storage-driver", + "overlay", + "--storage-opt", + "overlay.mountopt=nodev,metacopy=on", + "--events-backend", + "journald", + "container", + "cleanup", + "6335b78454e65cecc2c3c78c402e16d1a0691cf416468822c7f996594e991f2b" + ], + "Namespace": "", + "IsInfra": false, + "Config": { + "Hostname": "6335b78454e6", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "Tty": true, + "OpenStdin": true, + "StdinOnce": false, + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "TERM=xterm", + "container=oci", + "containers=podman", + "DISTTAG=f33container", + "FGC=f33", + "HOSTNAME=6335b78454e6", + "HOME=/root" + ], + "Cmd": [ + "/bin/bash" + ], + "Image": "registry.fedoraproject.org/fedora:latest", + "Volumes": null, + "WorkingDir": "/", + "Entrypoint": "", + "OnBuild": null, + "Labels": { + "license": "MIT", + "name": "fedora", + "vendor": "Fedora Project", + "version": "33" + }, + "Annotations": { + "io.container.manager": "libpod", + "io.kubernetes.cri-o.Created": "2020-11-24T20:06:25.852770272+01:00", + "io.kubernetes.cri-o.TTY": "true", + "io.podman.annotations.autoremove": "FALSE", + "io.podman.annotations.init": "FALSE", + "io.podman.annotations.privileged": "FALSE", + "io.podman.annotations.publish-all": "FALSE", + "org.opencontainers.image.stopSignal": "15" + }, + "StopSignal": 15, + "CreateCommand": [ + "/usr/bin/podman", + "run", + "--device", + "/dev/fb0", + "-it", + "fedora", + "/bin/bash" + ], + "Umask": "0022" + }, + "HostConfig": { + "Binds": [], + "CgroupMode": "private", + "ContainerIDFile": "", + "LogConfig": { + "Type": "k8s-file", + "Config": null + }, + "NetworkMode": "bridge", + "PortBindings": {}, + "RestartPolicy": { + "Name": "", + "MaximumRetryCount": 0 + }, + "AutoRemove": false, + "VolumeDriver": "", + "VolumesFrom": null, + "CapAdd": [], + "CapDrop": [], + "Dns": [], + "DnsOptions": [], + "DnsSearch": [], + "ExtraHosts": [], + "GroupAdd": [], + "IpcMode": "private", + "Cgroup": "", + "Cgroups": "default", + "Links": null, + "OomScoreAdj": 0, + "PidMode": "private", + "Privileged": false, + "PublishAllPorts": false, + "ReadonlyRootfs": false, + "SecurityOpt": [], + "Tmpfs": {}, + "UTSMode": "private", + "UsernsMode": "", + "ShmSize": 65536000, + "Runtime": "oci", + "ConsoleSize": [ + 0, + 0 + ], + "Isolation": "", + "CpuShares": 0, + "Memory": 0, + "NanoCpus": 0, + "CgroupParent": "", + "BlkioWeight": 0, + "BlkioWeightDevice": null, + "BlkioDeviceReadBps": null, + "BlkioDeviceWriteBps": null, + "BlkioDeviceReadIOps": null, + "BlkioDeviceWriteIOps": null, + "CpuPeriod": 0, + "CpuQuota": 0, + "CpuRealtimePeriod": 0, + "CpuRealtimeRuntime": 0, + "CpusetCpus": "", + "CpusetMems": "", + "Devices": [ + { + "PathOnHost": "/dev/fb0", + "PathInContainer": "/dev/fb0", + "CgroupPermissions": "" + } + ], + "DiskQuota": 0, + "KernelMemory": 0, + "MemoryReservation": 0, + "MemorySwap": 0, + "MemorySwappiness": 0, + "OomKillDisable": false, + "PidsLimit": 2048, + "Ulimits": [ + { + "Name": "RLIMIT_NOFILE", + "Soft": 1048576, + "Hard": 1048576 + }, + { + "Name": "RLIMIT_NPROC", + "Soft": 4194304, + "Hard": 4194304 + } + ], + "CpuCount": 0, + "CpuPercent": 0, + "IOMaximumIOps": 0, + "IOMaximumBandwidth": 0, + "CgroupConf": null + } + } +] diff --git a/tests/test_main.py b/tests/test_main.py index e9bf8b1..be47947 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -318,6 +318,14 @@ def test_append_more_rules_podman(self): self.assert_templates(output, ["base_container"]) self.assert_policy(test_file("test_append_avc.podman.cil")) + def test_devices_podman(self): + """podman run --device /dev/fb0 fedora""" + output = self.run_udica( + ["udica", "-j", "tests/test_devices.podman.json", "my_container"] + ) + self.assert_templates(output, ["base_container"]) + self.assert_policy(test_file("test_devices.podman.cil")) + def run_udica(self, args): with patch("sys.argv", args): with patch("sys.stderr.write") as mock_err, patch( diff --git a/udica/__main__.py b/udica/__main__.py index 1c30a7c..37f680f 100644 --- a/udica/__main__.py +++ b/udica/__main__.py @@ -190,6 +190,7 @@ def main(): print("Couldn't parse inspect data:", e) exit(3) container_inspect = engine_helper.parse_inspect(container_inspect_raw) + container_devices = engine_helper.get_devices(container_inspect) container_mounts = engine_helper.get_mounts(container_inspect) container_ports = engine_helper.get_ports(container_inspect) @@ -218,6 +219,7 @@ def main(): create_policy( opts, container_caps, + container_devices, container_mounts, container_ports, append_rules, diff --git a/udica/parse.py b/udica/parse.py index 569832d..d73bef1 100644 --- a/udica/parse.py +++ b/udica/parse.py @@ -71,6 +71,12 @@ def __init__(self, container_engine): def parse_inspect(self, data): return json.loads(data) + @abc.abstractmethod + def get_devices(self, data): + raise NotImplementedError( + "Error getting devices from unknown format %s" % self.container_engine + ) + @abc.abstractmethod def get_mounts(self, data): raise NotImplementedError( @@ -92,6 +98,9 @@ def get_caps(self, data, opts): class PodmanDockerHelper(EngineHelper): + def get_devices(self, data): + return data[0]["HostConfig"]["Devices"] + def get_mounts(self, data): return data[0]["Mounts"] @@ -147,6 +156,10 @@ class CrioHelper(EngineHelper): def __init__(self): super().__init__(ENGINE_CRIO) + def get_devices(self, data): + # TODO We need to add support for CRIO + return [] + def get_mounts(self, data): return data["status"]["mounts"] diff --git a/udica/perms.py b/udica/perms.py index d1fc853..b8c63ff 100644 --- a/udica/perms.py +++ b/udica/perms.py @@ -55,6 +55,7 @@ } perm = { + "devrw": "getattr read write append ioctl lock open", "drw": "add_name create getattr ioctl lock open read remove_name rmdir search setattr write", "dro": "getattr ioctl lock open read search", "frw": "append create getattr ioctl lock map open read rename setattr unlink write", diff --git a/udica/policy.py b/udica/policy.py index e9e94d6..8cdb975 100644 --- a/udica/policy.py +++ b/udica/policy.py @@ -95,7 +95,9 @@ def list_ports(port_number, port_proto): return ctype -def create_policy(opts, capabilities, mounts, ports, append_rules, inspect_format): +def create_policy( + opts, capabilities, devices, mounts, ports, append_rules, inspect_format +): policy = open(opts["ContainerName"] + ".cil", "w") policy.write("(block " + opts["ContainerName"] + "\n") policy.write(" (blockinherit container)\n") @@ -157,6 +159,10 @@ def create_policy(opts, capabilities, mounts, ports, append_rules, inspect_forma + " ( name_bind ))) \n" ) + # TODO: Devices for CRI-O containers needs to be added + if inspect_format != "CRI-O": + write_policy_for_podman_devices(devices, policy) + # mounts if inspect_format == "CRI-O": write_policy_for_crio_mounts(mounts, policy) @@ -273,6 +279,26 @@ def write_policy_for_crio_mounts(mounts, policy): ) +def write_policy_for_podman_devices(devices, policy): + for item in devices: + contexts = list_contexts(item["PathInContainer"]) + for context in contexts: + policy.write( + " (allow process " + + context + + " ( blk_file ( " + + perms.perm["devrw"] + + " ))) \n" + ) + policy.write( + " (allow process " + + context + + " ( chr_file ( " + + perms.perm["devrw"] + + " ))) \n" + ) + + def write_policy_for_podman_mounts(mounts, policy): for item in mounts: if not item["Source"].find("/"):