From 0a0b6f8f124eefd4ee47cc0fee331f238924acf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marques?= Date: Thu, 29 Oct 2020 12:25:56 +0000 Subject: [PATCH] Add first version of tests From https://github.com/Tecnativa/docker-socket-proxy/pull/14 --- poetry.lock | 177 ++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 16 ++++ tests/run_tests.sh | 84 ++++++++++++++++++++ tests/test_service.py | 90 +++++++++++++++++++++ 4 files changed, 367 insertions(+) create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100755 tests/run_tests.sh create mode 100644 tests/test_service.py diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..730d910 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,177 @@ +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "20.3.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "20.4" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pyparsing = ">=2.0.2" +six = "*" + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "plumbum" +version = "1.6.9" +description = "Plumbum: shell combinators library" +category = "main" +optional = false +python-versions = ">=2.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" + +[[package]] +name = "py" +version = "1.9.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pytest" +version = "6.1.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=17.4.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.8.2" +toml = "*" + +[package.extras] +checkqa_mypy = ["mypy (==0.780)"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "aad93df5d769d433d8739fc1fb317b1d53d9e8ddd18efaaaf8a864d543734c5a" + +[metadata.files] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, + {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +packaging = [ + {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, + {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +plumbum = [ + {file = "plumbum-1.6.9-py2.py3-none-any.whl", hash = "sha256:91418dcc66b58ab9d2e3b04b3d1e0d787dc45923154fb8b4a826bd9316dba0d6"}, + {file = "plumbum-1.6.9.tar.gz", hash = "sha256:16b9e19d96c80f2e9d051ef5f04927b834a6ac0ce5d2768eb8662b5cd53e43df"}, +] +py = [ + {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, + {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-6.1.2-py3-none-any.whl", hash = "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe"}, + {file = "pytest-6.1.2.tar.gz", hash = "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"}, +] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..dae9d82 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[tool.poetry] +name = "docker-socket-proxy" +version = "0.1.0" +description = "" +authors = ["Tecnativa S.L"] + +[tool.poetry.dependencies] +python = "^3.8" +plumbum = "^1.6.9" + +[tool.poetry.dev-dependencies] +pytest = "^6.1.2" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/run_tests.sh b/tests/run_tests.sh new file mode 100755 index 0000000..60975f1 --- /dev/null +++ b/tests/run_tests.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +set -eu + +proxy_container=docksockprox_test +socket_proxy=127.0.0.1:2375 + +start_proxy() { + echo "Starting $proxy_container with args: ${*}..." + docker run -d --name "$proxy_container" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -p "${socket_proxy}:2375" \ + "$@" \ + tecnativa/docker-socket-proxy &>/dev/null +} + +delete_proxy() { + echo "Removing ${proxy_container}..." + docker rm -f "$proxy_container" &>/dev/null +} + +docker_with_proxy() { + docker --host "$socket_proxy" "$@" 2>&1 +} + +assert() { + assertion=$1 + shift 1 + if docker_with_proxy "$@" | grep -qi 'forbidden'; then + result='forbidden' + else + result='allowed' + fi + if [ "$assertion" == "$result" ]; then + printf '%s' 'PASS' + else + printf '%s' 'FAIL' + fi + echo " | assert 'docker $*' is $assertion" +} + + +trap delete_proxy EXIT + +start_proxy +assert allowed version +assert forbidden run --rm alpine +assert forbidden pull alpine +assert forbidden logs "$proxy_container" +assert forbidden wait "$proxy_container" +assert forbidden rm -f "$proxy_container" +assert forbidden restart "$proxy_container" +assert forbidden network ls +assert forbidden config ls +assert forbidden service ls +assert forbidden stack ls +assert forbidden secret ls +assert forbidden plugin ls +assert forbidden info +assert forbidden system info +assert forbidden build . +assert forbidden swarm init + +delete_proxy +start_proxy -e CONTAINERS=1 +assert allowed logs "$proxy_container" +assert allowed inspect "$proxy_container" +assert forbidden wait "$proxy_container" +assert forbidden run --rm alpine +assert forbidden rm -f "$proxy_container" +assert forbidden restart "$proxy_container" + +delete_proxy +start_proxy -e POST=1 +assert forbidden rm -f "$proxy_container" +assert forbidden pull alpine +assert forbidden run --rm alpine +assert forbidden network create foobar + +delete_proxy +start_proxy -e NETWORKS=1 -e POST=1 +assert allowed network ls +assert allowed network create foo +assert allowed network rm foo diff --git a/tests/test_service.py b/tests/test_service.py new file mode 100644 index 0000000..1886b99 --- /dev/null +++ b/tests/test_service.py @@ -0,0 +1,90 @@ + +import pytest +import logging + +from plumbum import ProcessExecutionError, local +from plumbum.cmd import docker +from plumbum.machines.local import LocalCommand + +logger = logging.getLogger() + +CONTAINER_NAME = "docksockprox_test" +SOCKET_PROXY = "127.0.0.1:2375" + + +def _start_proxy( + container_name=CONTAINER_NAME, + socket_proxy=SOCKET_PROXY, + extra_args=None +): + logger.info(f"Starting {container_name} with args: {extra_args}...") + docker( + "run", + "-d", + "--name", container_name, + "--privileged", + "-v", "/var/run/docker.sock:/var/run/docker.sock", + "-p", f"{socket_proxy}:2375", + extra_args, + "tecnativa/docker-socket-proxy", + ) + + +def _stop_and_delete_proxy( + container_name=CONTAINER_NAME, + socket_proxy=SOCKET_PROXY, +): + logger.info(f"Removing {container_name}...") + docker( + "rm", + "-f", + container_name, + ) + + +def _query_docker_with_proxy(socket_proxy=SOCKET_PROXY, extra_args=None): + try: + _ret_code, stdout, stderr = docker.run( + ( + "--host", + socket_proxy, + extra_args, + ) + ) + except ProcessExecutionError as result: + stdout = result.stdout + stderr = result.stderr + return stdout + stderr + + +def _check_permission(assertion, extra_args=None): + if "forbidden" in _query_docker_with_proxy(extra_args=extra_args): + result = "forbidden" + else: + result = "allowed" + assert result == assertion + + +def test_default_permissions(): + try: + _start_proxy() + _check_permission("allowed", extra_args="version") + _check_permission("forbidden", ["run", "--rm", "alpine"]) + _check_permission("forbidden", ["pull", "alpine"]) + _check_permission("forbidden", ["logs", CONTAINER_NAME]) + _check_permission("forbidden", ["wait", CONTAINER_NAME]) + _check_permission("forbidden", ["rm", "-f", CONTAINER_NAME]) + _check_permission("forbidden", ["restart", CONTAINER_NAME]) + _check_permission("forbidden", ["network", "ls"]) + _check_permission("forbidden", ["config", "ls"]) + _check_permission("forbidden", ["service", "ls"]) + _check_permission("forbidden", ["stack", "ls"]) + _check_permission("forbidden", ["secret", "ls"]) + _check_permission("forbidden", ["plugin", "ls"]) + _check_permission("forbidden", ["info"]) + _check_permission("forbidden", ["system", "info"]) + _check_permission("forbidden", ["build", "."]) + _check_permission("forbidden", ["swarm", "init"]) + finally: + pass + _stop_and_delete_proxy() \ No newline at end of file