From 98ae6b82b988edcf1a1105f1eb1d2fc3855e74c1 Mon Sep 17 00:00:00 2001 From: Jeremy Desanlis Date: Tue, 13 Jul 2021 10:11:24 +0200 Subject: [PATCH] fix(docker): handle incomplete docker config --- ggshield/scan/docker.py | 6 +++--- .../docker-incomplete-manifest-example.tar.xz | Bin 0 -> 1800 bytes tests/scan/test_scan_docker.py | 16 +++++++++++----- tests/test_docker.py | 18 ++++++++++++++++-- 4 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 tests/data/docker-incomplete-manifest-example.tar.xz diff --git a/ggshield/scan/docker.py b/ggshield/scan/docker.py index 35e2253772..3e2e158c1e 100644 --- a/ggshield/scan/docker.py +++ b/ggshield/scan/docker.py @@ -102,7 +102,7 @@ def _get_layer_infos( { "filename": filename, "created": info["created"], - "created_by": info["created_by"], + "created_by": info.get("created_by"), } for info, filename in zip( (layer for layer in config["history"] if not layer.get("empty_layer")), @@ -117,7 +117,7 @@ def _should_scan_layer(layer_info: Dict) -> bool: Only COPY and ADD layers should be scanned. """ cmd = layer_info["created_by"] - return LAYER_TO_SCAN_PATTERN.search(cmd) is not None + return LAYER_TO_SCAN_PATTERN.search(cmd) is not None if cmd else True def _get_layers_files( @@ -132,7 +132,7 @@ def _get_layers_files( def _get_layer_files(archive: tarfile.TarFile, layer_info: Dict) -> Iterable[File]: """ - Extracts File objects to be scanner for given layer. + Extracts File objects to be scanned for given layer. """ layer_filename = layer_info["filename"] layer_archive = tarfile.TarFile( diff --git a/tests/data/docker-incomplete-manifest-example.tar.xz b/tests/data/docker-incomplete-manifest-example.tar.xz new file mode 100644 index 0000000000000000000000000000000000000000..708cd7e4796940f0153897fd4bd0b9f1ec11320e GIT binary patch literal 1800 zcmV+j2lx2>H+ooF000E$*0e?f03iVu0001VFXf})cmD>)T>u#w0waLxf!x}%XyFMB`oU-wlGc-;n@UcYWGPe%^emf|GILZ&j=H5z^v(|QFW@-LeJ>MtbMo3;e zd4fu&8Q}s47`XMlx%z_d-3mmzM2~l%KC0`-%(Z<-bkveNkNkYil!jEBX=m}pEc(XD z5bLXU-0;x7|17=-n4uNnG~akSQo5YRjGsFQQgF1hRbybhL9YYH+KFJx#TPjJb7wr- z)^-Z$W8hFDYyAAFv2{g)({Sqd`xww`(%^_xKu52&8?mU7nclH~%q!cl;@_=8@!*ZJ zq$zFAfbH~*6i7Xb6Mu?5_MSgohw>d?sXkxPor2lH*uya4`FG}aPYV@h> z|Ff;U7-RL~Kg+=>T6I>#tr;h>*d1pFM)->FixepYR=Y&ME?EVHFPyPDmv-mtk+I%d z&P^g4i}o+)t#9i47?KA5>*nT48k;zO+{f0D8r3NNl2J}r(X~bBg zybvlWp-zAV3^?MTx);GS@g-GA9S1Jh@R_o6D~A9}K2$hevwA2b+4wW zYmk00qBfHyo10kWXR09YIUv$7dvcWe(iG`xl6rP)_(2#U#775NiJLJK23slBic%wU|ZL-*_E(E+H9kD&GZA4+h++tJ2>k0Fdf zBMYA39{b;0>p_1M^Er#w0GBgISVS;T9*xkyb3GkNSulTW6HLjzTqe0V znLLc%ye)=5`5L;5X4`g23O_MyOn=QfWv3OB`#>KX@oWZD2EYXmQ7~EU$E8G@+#Cv* zQ$*G-Fs7*#)c;mzSDn*Bs5?$U)`%Wz817Cr-09B~)`>o$VlK=|dj$2}QF5XN@GLa~iN9<0n* ziQ6z0Rdk9QjowSHMvY>?rbZ7kt@+`orV?L2r6St&cHIpBjy6nvAVu}#tnrJk8kl7| zOn^TRz@4|iqGjWCnpQai)jIj=;KQXnmp+ZJs}1ac-E#528&( zU2o`29Zk+pWQHGMW|$7(hvRXqa3U@*^xXu6z2wooPS075fw*KFJbFSQ4i3n4J_KUU zWP3wRT*EWJMV{0|OEv+ng)=@^7dbZo~T q8j!-I0000qZu}r<9&M}u0pbmS@Bsi%Ue6)1#Ao{g000001X)^8^?srN literal 0 HcmV?d00001 diff --git a/tests/scan/test_scan_docker.py b/tests/scan/test_scan_docker.py index 88a4a49312..f32ac9d101 100644 --- a/tests/scan/test_scan_docker.py +++ b/tests/scan/test_scan_docker.py @@ -12,6 +12,9 @@ DOCKER_EXAMPLE_PATH = Path(__file__).parent.parent / "data" / "docker-example.tar.xz" +DOCKER__INCOMPLETE_MANIFEST_EXAMPLE_PATH = ( + Path(__file__).parent.parent / "data" / "docker-incomplete-manifest-example.tar.xz" +) class ManifestMock: @@ -74,16 +77,19 @@ def test_get_config(self, members, match): with pytest.raises(InvalidDockerArchiveException, match=match): _get_config(tarfile) - def test_get_files_from_docker_archive(self): - files = get_files_from_docker_archive(DOCKER_EXAMPLE_PATH) + @pytest.mark.parametrize( + "image_path", [DOCKER_EXAMPLE_PATH, DOCKER__INCOMPLETE_MANIFEST_EXAMPLE_PATH] + ) + def test_get_files_from_docker_archive(self, image_path: Path): + files = get_files_from_docker_archive(image_path) expected_files = { "Dockerfile or build-args": None, # noqa: E501 - DOCKER_EXAMPLE_PATH + image_path / "64a345482d74ea1c0699988da4b4fe6cda54a2b0ad5da49853a9739f7a7e5bbc/layer.tar/app/file_one": "Hello, I am the first file!\n", # noqa: E501 - DOCKER_EXAMPLE_PATH + image_path / "2d185b802fb3c2e6458fe1ac98e027488cd6aedff2e3d05eb030029c1f24d60f/layer.tar/app/file_three.sh": "echo Life is beautiful.\n", # noqa: E501 - DOCKER_EXAMPLE_PATH + image_path / "2d185b802fb3c2e6458fe1ac98e027488cd6aedff2e3d05eb030029c1f24d60f/layer.tar/app/file_two.py": """print("Hi! I'm the second file but I'm happy.")\n""", # noqa: E501 } diff --git a/tests/test_docker.py b/tests/test_docker.py index c92c8e2a59..fb5dd604ff 100644 --- a/tests/test_docker.py +++ b/tests/test_docker.py @@ -13,6 +13,9 @@ DOCKER_EXAMPLE_PATH = Path(__file__).parent / "data" / "docker-example.tar.xz" +DOCKER__INCOMPLETE_MANIFEST_EXAMPLE_PATH = ( + Path(__file__).parent / "data" / "docker-incomplete-manifest-example.tar.xz" +) class TestDockerPull: @@ -137,8 +140,14 @@ def test_docker_scan_failed_to_save( assert result.exit_code == 1 @patch("ggshield.docker.get_files_from_docker_archive") + @pytest.mark.parametrize( + "image_path", [DOCKER_EXAMPLE_PATH, DOCKER__INCOMPLETE_MANIFEST_EXAMPLE_PATH] + ) def test_docker_scan_archive( - self, get_files_mock: Mock, cli_fs_runner: click.testing.CliRunner + self, + get_files_mock: Mock, + cli_fs_runner: click.testing.CliRunner, + image_path: Path, ): get_files_mock.return_value = Files( files=[File(document=_SIMPLE_SECRET, filename="file_secret")] @@ -146,7 +155,12 @@ def test_docker_scan_archive( with my_vcr.use_cassette("test_scan_file_secret"): result = cli_fs_runner.invoke( cli, - ["-v", "scan", "docker-archive", str(DOCKER_EXAMPLE_PATH)], + [ + "-v", + "scan", + "docker-archive", + str(image_path), + ], ) get_files_mock.assert_called_once() assert "1 incident has been found in file file_secret" in result.output