From 5f5ce51ce7ab932874fd35a4c32fdd11a30ed4e3 Mon Sep 17 00:00:00 2001 From: Kevin Hanselman Date: Sat, 30 Mar 2019 14:56:12 -0400 Subject: [PATCH] add tests and a dynamic config section generator --- .gitignore | 1 + gen_cfg_section.py | 122 +++++++++++++++++++++++++++++++++++++++++++++ run_tests.sh | 84 +++++++++++++++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 .gitignore create mode 100644 gen_cfg_section.py create mode 100755 run_tests.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1e82fc7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.yaml diff --git a/gen_cfg_section.py b/gen_cfg_section.py new file mode 100644 index 0000000..f16b146 --- /dev/null +++ b/gen_cfg_section.py @@ -0,0 +1,122 @@ +import argparse +import os +import re +from urllib.request import urlopen + +import yaml + + +def parse_cli_args(): + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument( + 'api_versions', + nargs='+', + help=( + 'Docker API version(s) to support. ' + + 'If exactly two versions are passed, ' + + 'it will act as a range.' + ) + ) + parser.add_argument( + '--yaml-dir', + help='Save and load Swagger YAML files from this directory' + ) + parser.add_argument( + '--add-comments', + action='store_true', + help='Add comments to the haproxy.cfg output' + ) + return parser.parse_args() + + +def parse_api_versions(api_versions): + if len(api_versions) == 2: + min_ver = int(float(api_versions[0]) * 100) + max_ver = int(float(api_versions[1]) * 100) + assert min_ver < max_ver + return [f'{ver / 100.0:.2f}' for ver in range(min_ver, max_ver + 1)] + return api_versions + + +def create_cfg_line(endpoint): + return r' http-request allow if {{ path,url_dec -m reg -i ^(/v[\d\.]+)?/{} }} {{ env({}) -m bool }}'.format( + endpoint, re.sub(r'[^A-Z]', '', endpoint.upper()) + ) + + +def create_cfg_comment(endpoint, api_versions): + version_nums = [float(ver) for ver in api_versions] + return ' # /{} first seen: v{:.2f}, last seen: v{:.2f}'.format( + endpoint, + min(version_nums), + max(version_nums), + ) + + +def get_base_endpoints(swagger): + return set( + path_name.split('/')[1] + for path_name in swagger['paths'].keys() + ) + + +def load_swagger_yaml(api_version, yaml_dir=None): + + if yaml_dir: + yaml_path = os.path.join( + cli_args.yaml_dir, + f"docker_v{api_version.replace('.', '_')}.yaml" + ) + + if os.path.isfile(yaml_path): + print(f'Reading file {yaml_path!r}...') + with open(yaml_path) as fd: + return yaml.safe_load(fd) + + swagger_url = 'https://docs.docker.com/engine/api/v{}/swagger.yaml'.format( + api_version + ) + + print(f'Downloading Docker API version {api_version}...') + with urlopen(swagger_url) as fd: + swagger = yaml.safe_load(fd) + + if yaml_dir: + print(f'Writing file {yaml_path!r}...') + with open(yaml_path, 'w') as fd: + yaml.dump(swagger, fd) + + return swagger + + +if __name__ == '__main__': + + cli_args = parse_cli_args() + + api_versions = parse_api_versions(cli_args.api_versions) + + if cli_args.yaml_dir: + os.makedirs(cli_args.yaml_dir, exist_ok=True) + + swaggers = {} + for api_version in api_versions: + swaggers[api_version] = load_swagger_yaml( + api_version, + yaml_dir=cli_args.yaml_dir + ) + + all_base_endpoints = {} + for api_version, swagger in swaggers.items(): + for endpoint in get_base_endpoints(swagger): + if endpoint in all_base_endpoints: + all_base_endpoints[endpoint].append(api_version) + else: + all_base_endpoints[endpoint] = [api_version] + + for base_endpoint, api_versions in sorted(all_base_endpoints.items()): + if cli_args.add_comments: + print() + print(create_cfg_comment(base_endpoint, api_versions)) + print(create_cfg_line(base_endpoint)) diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..60975f1 --- /dev/null +++ b/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