From b5fd92d03fde9582eb7431ef1ce3e3c544828f76 Mon Sep 17 00:00:00 2001 From: Lukas Vrabec Date: Fri, 25 Oct 2019 19:21:01 +0200 Subject: [PATCH] New feature: parameter "--container-engine" Following commit adds new parameter for to specify which container engine is used for inspecting container. Example: # udica --container-engine podman -j my_container.json my_container ... # udica -e docker -j my_container.json my_container ... In some situations udica fails to identify which engine is used, therefore this parameter has to be used. Commit includes also test for the feature. --- README.md | 1 + tests/test_main.py | 48 +++++++++++++++++++++++++++++++++++++++ udica/__main__.py | 15 +++++++++++-- udica/man/man8/udica.8 | 6 +++++ udica/parse.py | 51 +++++++++++++++++++++++++++++++----------- 5 files changed, 106 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7deae5d..f06862f 100644 --- a/README.md +++ b/README.md @@ -215,3 +215,4 @@ Real example is demonstrated in following demo. * It's not possible to detect capabilities used by container in docker engine, therefore you *have to* use '-c' to specify capabilities for docker container manually. * It's not possible to generate custom local policy using "audit2allow -M" tool from AVCs where source context was generated by udica. For this purpose please use '--append-rules' option. + * In some situations udica fails to identify which container engine is used, therefore "--container-engine" parameter has to be used to inform udica how JSON inspection file should be parsed. diff --git a/tests/test_main.py b/tests/test_main.py index 0836dd8..e22a433 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -106,6 +106,24 @@ def test_basic_cri(self): ) self.assert_policy(test_file("test_basic.cri.cil")) + def test_basic_specified_engine_cri(self): + """Start CRI-O mounting /var/spool with read/write perms and /home with readonly perms""" + output = self.run_udica( + [ + "udica", + "--container-engine", + "CRI-O", + "-j", + "tests/test_basic.cri.json", + "--full-network-access", + "my_container", + ] + ) + self.assert_templates( + output, ["base_container", "net_container", "home_container"] + ) + self.assert_policy(test_file("test_basic.cri.cil")) + def test_default_podman(self): """podman run fedora""" output = self.run_udica( @@ -114,6 +132,21 @@ def test_default_podman(self): self.assert_templates(output, ["base_container"]) self.assert_policy(test_file("test_default.podman.cil")) + def test_default_specified_engine_podman(self): + """podman run fedora""" + output = self.run_udica( + [ + "udica", + "-e", + "podman", + "-j", + "tests/test_default.podman.json", + "my_container", + ] + ) + self.assert_templates(output, ["base_container"]) + self.assert_policy(test_file("test_default.podman.cil")) + def test_default_docker(self): """docker run fedora""" output = self.run_udica( @@ -122,6 +155,21 @@ def test_default_docker(self): self.assert_templates(output, ["base_container"]) self.assert_policy(test_file("test_default.docker.cil")) + def test_default_specified_engine_docker(self): + """docker run fedora""" + output = self.run_udica( + [ + "udica", + "-e", + "docker", + "-j", + "tests/test_default.docker.json", + "my_container", + ] + ) + self.assert_templates(output, ["base_container"]) + self.assert_policy(test_file("test_default.docker.cil")) + def test_default_oci(self): """podman run ubi8""" output = self.run_udica( diff --git a/udica/__main__.py b/udica/__main__.py index 21462cd..7f434a1 100644 --- a/udica/__main__.py +++ b/udica/__main__.py @@ -113,6 +113,15 @@ def get_args(): required=False, default=None, ) + parser.add_argument( + "-e", + "--container-engine", + type=str, + help="Specify which container engine is used for the inspected container (supports: CRI-O, docker, podman)", + dest="ContainerEngine", + required=False, + default="-", + ) args = parser.parse_args() return vars(args) @@ -169,11 +178,13 @@ def main(): exit(3) try: - inspect_format = parse.get_inspect_format(container_inspect_raw) + inspect_format = parse.get_inspect_format( + container_inspect_raw, opts["ContainerEngine"] + ) except Exception as e: print("Couldn't parse inspect data:", e) exit(3) - container_inspect = parse_inspect(container_inspect_raw) + container_inspect = parse_inspect(container_inspect_raw, opts["ContainerEngine"]) container_mounts = parse.get_mounts(container_inspect, inspect_format) container_ports = parse.get_ports(container_inspect, inspect_format) diff --git a/udica/man/man8/udica.8 b/udica/man/man8/udica.8 index 1e3a471..ad6fc5f 100644 --- a/udica/man/man8/udica.8 +++ b/udica/man/man8/udica.8 @@ -61,6 +61,10 @@ Append more SELinux allow rules generated from SELinux denials in audit daemon. .I \-s, \-\-stream-connect DOMAIN Allow container to stream connect with given SELinux domain. +.TP +.I \-e, \-\-container-engine ENGINE +Specify which container engine is used for the inspected container (supports: CRI-O, docker, podman) + .TP .I \-\-full\-network\-access Allow a container full network access @@ -110,6 +114,8 @@ you have to use '-c' to specify capabilities for docker container manually. It is not possible to generate a custom local policy using the "audit2allow -M" command from AVCs where source context was generated by udica. For this purpose please use '--append-rules' option. + In some situations udica fails to identify which container engine is used, therefore "--container-engine" parameter has to be used to inform udica how JSON inspection file should be parsed. + .SH REPORTING BUGS Report bugs to diff --git a/udica/parse.py b/udica/parse.py index 853d62b..60b95a4 100644 --- a/udica/parse.py +++ b/udica/parse.py @@ -35,6 +35,12 @@ def json_is_podman_format(json_rep): def adjust_json_from_docker(json_rep): """If the json comes from a docker call, we need to adjust it to make use of it. """ + + if not isinstance(json_rep[0]["NetworkSettings"]["Ports"], dict): + raise Exception( + "Error parsing docker engine inspection JSON structure, try to specify container engine using '--container-engine' parameter" + ) + for item in json_rep[0]["Mounts"]: item["source"] = item["Source"] if item["Mode"] == "rw": @@ -55,28 +61,37 @@ def adjust_json_from_docker(json_rep): json_rep[0]["NetworkSettings"]["Ports"] = temp_ports -def parse_inspect(data): +def parse_inspect(data, ContainerEngine): json_rep = json.loads(data) - if json_is_podman_or_docker_format(json_rep): - if not json_is_podman_format(json_rep): - adjust_json_from_docker(json_rep) + engine = validate_container_engine(ContainerEngine) + if engine == "-": + if json_is_podman_or_docker_format(json_rep): + if not json_is_podman_format(json_rep): + adjust_json_from_docker(json_rep) + + if engine == "docker": + adjust_json_from_docker(json_rep) return json_rep -def get_inspect_format(data): - json_rep = json.loads(data) - if json_is_podman_or_docker_format(json_rep): - if json_is_podman_format(json_rep): - return "podman" - return "docker" - return "CRI-O" +def get_inspect_format(data, ContainerEngine): + engine = validate_container_engine(ContainerEngine) + if engine == "-": + json_rep = json.loads(data) + if json_is_podman_or_docker_format(json_rep): + if json_is_podman_format(json_rep): + return "podman" + return "docker" + return "CRI-O" + else: + return engine def get_mounts(data, inspect_format): if inspect_format in ["podman", "docker"]: return data[0]["Mounts"] - if inspect_format == "CRI-O": + if inspect_format == "CRI-O" and not json_is_podman_or_docker_format(data): return data["status"]["mounts"] raise Exception("Error getting mounts from unknown format %s" % inspect_format) @@ -88,7 +103,7 @@ def get_ports(data, inspect_format): # Not applicable in the CRI-O case, since this is handled by the # kube-proxy/CNI. return [] - raise Exception("Error getting mounts from unknown format %s" % inspect_format) + raise Exception("Error getting ports from unknown format %s" % inspect_format) def get_caps(data, opts, inspect_format): @@ -149,3 +164,13 @@ def parse_avc_file(data): append_rules.append(new_rule) return append_rules + + +def validate_container_engine(ContainerEngine): + supported_engines = ["docker", "podman", "CRI-O", "CRIO", "-"] + if ContainerEngine in supported_engines: + if ContainerEngine == "CRIO": + return "CRI-O" + return ContainerEngine + else: + raise Exception("Container Engine %s is not supported." % ContainerEngine)