From 1d22223e9bc6546f537fa73b30c4951100f74487 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Fri, 3 Apr 2020 14:10:08 +0100 Subject: [PATCH] Support CIDR whitelist TT23300 --- copier.yml | 8 ++ prod.yaml.jinja | 8 +- tasks.py | 10 +- test.yaml.jinja | 8 +- .../v10.0/.copier-answers.yml | 1 + .../v11.0/.copier-answers.yml | 1 + .../v12.0/.copier-answers.yml | 1 + .../v13.0/.copier-answers.yml | 1 + .../default_settings/v7.0/.copier-answers.yml | 1 + .../default_settings/v8.0/.copier-answers.yml | 1 + .../default_settings/v9.0/.copier-answers.yml | 1 + tests/samples/cidr-whitelist/prod.yaml | 77 +++++++++++++ tests/samples/cidr-whitelist/test.yaml | 103 ++++++++++++++++++ tests/test_nitpicking.py | 20 +++- 14 files changed, 236 insertions(+), 5 deletions(-) create mode 100644 tests/samples/cidr-whitelist/prod.yaml create mode 100644 tests/samples/cidr-whitelist/test.yaml diff --git a/copier.yml b/copier.yml index feb32058..af381a0d 100644 --- a/copier.yml +++ b/copier.yml @@ -132,6 +132,14 @@ paths_without_crawlers: 💡 We will convert this to `Path` rules. Check valid syntax in https://docs.traefik.io/routing/routers/#rule +cidr_whitelist: + default: null + help: >- + If you need to whitelist certain CIDR to allow only them to access your Odoo + instance, set that here please. + + ⚠️ It must be a list. And this is only supported if you deploy with Traefik 2+. + odoo_version: help: On which odoo version is it based? type: float diff --git a/prod.yaml.jinja b/prod.yaml.jinja index 312c721e..1b13f727 100644 --- a/prod.yaml.jinja +++ b/prod.yaml.jinja @@ -46,8 +46,14 @@ services: ? traefik.http.middlewares.{{ _key }}-main-buffering.buffering.retryExpression : "IsNetworkError() && Attempts() < 5" traefik.http.middlewares.{{ _key }}-main-compress.compress: "true" + {%- if cidr_whitelist %} + ? traefik.http.middlewares.{{ _key }}-main-whitelist.IPWhiteList.sourceRange + : {% for cidr in cidr_whitelist -%} + {{ cidr }}{% if not loop.last %}, {% endif %} + {%- endfor %} + {%- endif %} traefik.http.routers.{{ _key }}-main.entrypoints: "web-main" - traefik.http.routers.{{ _key }}-main.middlewares: "{{ _key }}-main-compress,{{ _key }}-main-buffering" + traefik.http.routers.{{ _key }}-main.middlewares: "{{ _key }}-main-compress,{{ _key }}-main-buffering{% if cidr_whitelist %},{{ _key }}-main-whitelist{% endif %}" traefik.http.routers.{{ _key }}-main.rule: "host(`${DOMAIN_PROD}`)" traefik.http.routers.{{ _key }}-main.service: "{{ _key }}-main" traefik.http.routers.{{ _key }}-main.tls: "true" diff --git a/tasks.py b/tasks.py index 2ee7d13b..0eb71110 100644 --- a/tasks.py +++ b/tasks.py @@ -87,7 +87,7 @@ def test(c, verbose=False): @task(develop) -def update_test_scaffoldings(c): +def update_test_samples(c): """Update default scaffolding renderings.""" # Make sure git repo is clean try: @@ -108,3 +108,11 @@ def update_test_scaffoldings(c): shutil.rmtree(dst / ".git") finally: c.run("git tag --delete test") + samples = Path("tests", "samples") + c.run( + "poetry run copier -fr HEAD -x '**' -x '!prod.yaml' -x '!test.yaml' " + "-d cidr_whitelist='[123.123.123.123/24, 456.456.456.456]' " + f"copy . {samples / 'cidr-whitelist'}", + warn=True, + ) + shutil.rmtree(samples / "cidr-whitelist" / ".venv") diff --git a/test.yaml.jinja b/test.yaml.jinja index ed518b2a..5871cdd7 100644 --- a/test.yaml.jinja +++ b/test.yaml.jinja @@ -35,8 +35,14 @@ services: ? traefik.http.middlewares.{{ _key }}-main-buffering.buffering.retryExpression : "IsNetworkError() && Attempts() < 5" traefik.http.middlewares.{{ _key }}-main-compress.compress: "true" + {%- if cidr_whitelist %} + ? traefik.http.middlewares.{{ _key }}-main-whitelist.IPWhiteList.sourceRange + : {% for cidr in cidr_whitelist -%} + {{ cidr }}{% if not loop.last %}, {% endif %} + {%- endfor %} + {%- endif %} traefik.http.routers.{{ _key }}-main.entrypoints: "web-main" - traefik.http.routers.{{ _key }}-main.middlewares: "{{ _key }}-main-compress,{{ _key }}-main-buffering,{{ _key }}-forbid-crawlers" + traefik.http.routers.{{ _key }}-main.middlewares: "{{ _key }}-main-compress,{{ _key }}-main-buffering,{{ _key }}-forbid-crawlers{% if cidr_whitelist %},{{ _key }}-main-whitelist{% endif %}" traefik.http.routers.{{ _key }}-main.rule: "host(`${DOMAIN_TEST}`)" traefik.http.routers.{{ _key }}-main.service: "{{ _key }}-main" traefik.http.routers.{{ _key }}-main.tls.certresolver: "letsencrypt" diff --git a/tests/default_settings/v10.0/.copier-answers.yml b/tests/default_settings/v10.0/.copier-answers.yml index 94a27edf..a54255de 100644 --- a/tests/default_settings/v10.0/.copier-answers.yml +++ b/tests/default_settings/v10.0/.copier-answers.yml @@ -8,6 +8,7 @@ backup_dst: null backup_email_from: null backup_email_to: null backup_tz: UTC +cidr_whitelist: null domain_prod: null domain_prod_alternatives: null domain_test: null diff --git a/tests/default_settings/v11.0/.copier-answers.yml b/tests/default_settings/v11.0/.copier-answers.yml index 7cda5943..9abd241e 100644 --- a/tests/default_settings/v11.0/.copier-answers.yml +++ b/tests/default_settings/v11.0/.copier-answers.yml @@ -8,6 +8,7 @@ backup_dst: null backup_email_from: null backup_email_to: null backup_tz: UTC +cidr_whitelist: null domain_prod: null domain_prod_alternatives: null domain_test: null diff --git a/tests/default_settings/v12.0/.copier-answers.yml b/tests/default_settings/v12.0/.copier-answers.yml index 6a4e94e1..5a7f4bcb 100644 --- a/tests/default_settings/v12.0/.copier-answers.yml +++ b/tests/default_settings/v12.0/.copier-answers.yml @@ -8,6 +8,7 @@ backup_dst: null backup_email_from: null backup_email_to: null backup_tz: UTC +cidr_whitelist: null domain_prod: null domain_prod_alternatives: null domain_test: null diff --git a/tests/default_settings/v13.0/.copier-answers.yml b/tests/default_settings/v13.0/.copier-answers.yml index 965fc330..3d6e6417 100644 --- a/tests/default_settings/v13.0/.copier-answers.yml +++ b/tests/default_settings/v13.0/.copier-answers.yml @@ -8,6 +8,7 @@ backup_dst: null backup_email_from: null backup_email_to: null backup_tz: UTC +cidr_whitelist: null domain_prod: null domain_prod_alternatives: null domain_test: null diff --git a/tests/default_settings/v7.0/.copier-answers.yml b/tests/default_settings/v7.0/.copier-answers.yml index a571c6df..8fc1b0a8 100644 --- a/tests/default_settings/v7.0/.copier-answers.yml +++ b/tests/default_settings/v7.0/.copier-answers.yml @@ -8,6 +8,7 @@ backup_dst: null backup_email_from: null backup_email_to: null backup_tz: UTC +cidr_whitelist: null domain_prod: null domain_prod_alternatives: null domain_test: null diff --git a/tests/default_settings/v8.0/.copier-answers.yml b/tests/default_settings/v8.0/.copier-answers.yml index bff349af..eb41290f 100644 --- a/tests/default_settings/v8.0/.copier-answers.yml +++ b/tests/default_settings/v8.0/.copier-answers.yml @@ -8,6 +8,7 @@ backup_dst: null backup_email_from: null backup_email_to: null backup_tz: UTC +cidr_whitelist: null domain_prod: null domain_prod_alternatives: null domain_test: null diff --git a/tests/default_settings/v9.0/.copier-answers.yml b/tests/default_settings/v9.0/.copier-answers.yml index d3268671..9ccc177e 100644 --- a/tests/default_settings/v9.0/.copier-answers.yml +++ b/tests/default_settings/v9.0/.copier-answers.yml @@ -8,6 +8,7 @@ backup_dst: null backup_email_from: null backup_email_to: null backup_tz: UTC +cidr_whitelist: null domain_prod: null domain_prod_alternatives: null domain_test: null diff --git a/tests/samples/cidr-whitelist/prod.yaml b/tests/samples/cidr-whitelist/prod.yaml new file mode 100644 index 00000000..99dc24b1 --- /dev/null +++ b/tests/samples/cidr-whitelist/prod.yaml @@ -0,0 +1,77 @@ +version: "2.4" + +services: + odoo: + extends: + file: common.yaml + service: odoo + restart: unless-stopped + env_file: + - .docker/odoo.env + - .docker/db-access.env + environment: + DOODBA_ENVIRONMENT: "${DOODBA_ENVIRONMENT-prod}" + INITIAL_LANG: "$INITIAL_LANG" + depends_on: + - db + networks: + default: + inverseproxy_shared: + labels: + traefik.longpolling.frontend.rule: "Host:${DOMAIN_PROD};PathPrefix:/longpolling/" + traefik.www.frontend.rule: "Host:${DOMAIN_PROD}" + traefik.forbid-crawlers.frontend.rule: "Host:${DOMAIN_PROD};PathPrefix:/web,/web/{anything:.*},/website/info,/website/info/{anything:.*}" + # Main service + ? traefik.http.middlewares.myproject-odoo-13-0-prod-main-buffering.buffering.retryExpression + : "IsNetworkError() && Attempts() < 5" + traefik.http.middlewares.myproject-odoo-13-0-prod-main-compress.compress: "true" + ? traefik.http.middlewares.myproject-odoo-13-0-prod-main-whitelist.IPWhiteList.sourceRange + : 123.123.123.123/24, 456.456.456.456 + traefik.http.routers.myproject-odoo-13-0-prod-main.entrypoints: "web-main" + traefik.http.routers.myproject-odoo-13-0-prod-main.middlewares: "myproject-odoo-13-0-prod-main-compress,myproject-odoo-13-0-prod-main-buffering,myproject-odoo-13-0-prod-main-whitelist" + traefik.http.routers.myproject-odoo-13-0-prod-main.rule: "host(`${DOMAIN_PROD}`)" + traefik.http.routers.myproject-odoo-13-0-prod-main.service: "myproject-odoo-13-0-prod-main" + traefik.http.routers.myproject-odoo-13-0-prod-main.tls: "true" + traefik.http.routers.myproject-odoo-13-0-prod-main.tls.certresolver: "letsencrypt" + traefik.http.services.myproject-odoo-13-0-prod-main.loadbalancer.server.port: 8069 + # Longpolling service + traefik.http.routers.myproject-odoo-13-0-prod-longpolling.entrypoints: "web-main" + traefik.http.routers.myproject-odoo-13-0-prod-longpolling.rule: + "host(`${DOMAIN_PROD}`) && pathprefix(`/longpolling/`)" + traefik.http.routers.myproject-odoo-13-0-prod-longpolling.service: "myproject-odoo-13-0-prod-longpolling" + traefik.http.services.myproject-odoo-13-0-prod-longpolling.loadbalancer.server.port: 8072 + traefik.http.routers.myproject-odoo-13-0-prod-longpolling.tls: "true" + traefik.http.routers.myproject-odoo-13-0-prod-longpolling.tls.certresolver: "letsencrypt" + # Forbid crawlers + ? traefik.http.middlewares.myproject-odoo-13-0-prod-forbid-crawlers.headers.customResponseHeaders.X-Robots-Tag + : "noindex, nofollow" + traefik.http.routers.myproject-odoo-13-0-prod-forbidden-crawlers.entrypoints: "web-main" + traefik.http.routers.myproject-odoo-13-0-prod-forbidden-crawlers.middlewares: "myproject-odoo-13-0-prod-forbid-crawlers" + traefik.http.routers.myproject-odoo-13-0-prod-forbidden-crawlers.rule: | + Host(`${DOMAIN_PROD}`) && ( + Path(`/web`, `/web/{anything:.*}`) || + Path(`/website/info`, `/website/info/{anything:.*}`) + ) + traefik.http.routers.myproject-odoo-13-0-prod-forbidden-crawlers.service: "myproject-odoo-13-0-prod-main" + traefik.http.routers.myproject-odoo-13-0-prod-forbidden-crawlers.tls: "true" + traefik.http.routers.myproject-odoo-13-0-prod-forbidden-crawlers.tls.certresolver: "letsencrypt" + + db: + extends: + file: common.yaml + service: db + env_file: + - .docker/db-creation.env + restart: unless-stopped + +networks: + default: + driver_opts: + encrypted: 1 + + inverseproxy_shared: + external: true + +volumes: + filestore: + db: diff --git a/tests/samples/cidr-whitelist/test.yaml b/tests/samples/cidr-whitelist/test.yaml new file mode 100644 index 00000000..aa6da40a --- /dev/null +++ b/tests/samples/cidr-whitelist/test.yaml @@ -0,0 +1,103 @@ +version: "2.4" + +services: + odoo: + extends: + file: common.yaml + service: odoo + env_file: + - .docker/odoo.env + - .docker/db-access.env + environment: + DOODBA_ENVIRONMENT: "${DOODBA_ENVIRONMENT-test}" + # To install demo data export DOODBA_WITHOUT_DEMO=false + WITHOUT_DEMO: "${DOODBA_WITHOUT_DEMO-all}" + SMTP_PORT: "1025" + SMTP_SERVER: smtplocal + restart: unless-stopped + depends_on: + - db + - smtp + networks: + default: + globalwhitelist_shared: + inverseproxy_shared: + labels: + traefik.frontend.headers.customResponseHeaders: "X-Robots-Tag:noindex, nofollow" + traefik.longpolling.frontend.rule: "Host:${DOMAIN_TEST};PathPrefix:/longpolling/" + traefik.www.frontend.rule: "Host:${DOMAIN_TEST}" + # Forbid crawlers + ? traefik.http.middlewares.myproject-odoo-13-0-test-forbid-crawlers.headers.customResponseHeaders.X-Robots-Tag + : "noindex, nofollow" + # Main service + ? traefik.http.middlewares.myproject-odoo-13-0-test-main-buffering.buffering.retryExpression + : "IsNetworkError() && Attempts() < 5" + traefik.http.middlewares.myproject-odoo-13-0-test-main-compress.compress: "true" + ? traefik.http.middlewares.myproject-odoo-13-0-test-main-whitelist.IPWhiteList.sourceRange + : 123.123.123.123/24, 456.456.456.456 + traefik.http.routers.myproject-odoo-13-0-test-main.entrypoints: "web-main" + traefik.http.routers.myproject-odoo-13-0-test-main.middlewares: "myproject-odoo-13-0-test-main-compress,myproject-odoo-13-0-test-main-buffering,myproject-odoo-13-0-test-forbid-crawlers,myproject-odoo-13-0-test-main-whitelist" + traefik.http.routers.myproject-odoo-13-0-test-main.rule: "host(`${DOMAIN_TEST}`)" + traefik.http.routers.myproject-odoo-13-0-test-main.service: "myproject-odoo-13-0-test-main" + traefik.http.routers.myproject-odoo-13-0-test-main.tls.certresolver: "letsencrypt" + traefik.http.services.myproject-odoo-13-0-test-main.loadbalancer.server.port: 8069 + # Longpolling service + traefik.http.routers.myproject-odoo-13-0-test-longpolling.entrypoints: "web-main" + traefik.http.routers.myproject-odoo-13-0-test-longpolling.middlewares: "myproject-odoo-13-0-test-forbid-crawlers" + traefik.http.routers.myproject-odoo-13-0-test-longpolling.rule: + "host(`${DOMAIN_TEST}`) && pathprefix(`/longpolling/`)" + traefik.http.routers.myproject-odoo-13-0-test-longpolling.service: "myproject-odoo-13-0-test-longpolling" + traefik.http.routers.myproject-odoo-13-0-test-longpolling.tls: "true" + traefik.http.routers.myproject-odoo-13-0-test-longpolling.tls.certresolver: "letsencrypt" + traefik.http.services.myproject-odoo-13-0-test-longpolling.loadbalancer.server.port: 8072 + command: + - odoo + - --workers=2 + - --max-cron-threads=1 + + db: + extends: + file: common.yaml + service: db + env_file: + - .docker/db-creation.env + restart: unless-stopped + + smtp: + extends: + file: common.yaml + service: smtpfake + restart: unless-stopped + networks: + default: + aliases: + - smtplocal + inverseproxy_shared: + labels: + traefik.docker.network: "inverseproxy_shared" + traefik.enable: "true" + traefik.frontend.passHostHeader: "true" + traefik.frontend.rule: "Host:${DOMAIN_TEST};PathPrefixStrip:/smtpfake/" + traefik.port: "8025" + volumes: + - "smtpconf:/etc/mailhog:ro,z" + entrypoint: [sh, -c] + command: + - test -r /etc/mailhog/auth && export MH_AUTH_FILE=/etc/mailhog/auth; exec MailHog + +networks: + default: + internal: true + driver_opts: + encrypted: 1 + + globalwhitelist_shared: + external: true + + inverseproxy_shared: + external: true + +volumes: + filestore: + db: + smtpconf: diff --git a/tests/test_nitpicking.py b/tests/test_nitpicking.py index 1460a288..8eb10428 100644 --- a/tests/test_nitpicking.py +++ b/tests/test_nitpicking.py @@ -71,8 +71,8 @@ def test_gitlab_badges(tmp_path: Path): assert expected_badges.strip() in (tmp_path / "README.md").read_text() -def test_alt_domains_traefik2_rules(tmp_path: Path): - """Make sure alt domains redirections are good for Traefik 2.""" +def test_alt_domains_rules(tmp_path: Path): + """Make sure alt domains redirections are good for Traefik.""" src, dst = tmp_path / "src", tmp_path / "dst" clone_self_dirty(src) copy( @@ -95,6 +95,22 @@ def test_alt_domains_traefik2_rules(tmp_path: Path): assert generated == expected +def test_cidr_whitelist_rules(tmp_path: Path): + """Make sure CIDR whitelist redirections are good for Traefik.""" + src, dst = tmp_path / "src", tmp_path / "dst" + clone_self_dirty(src) + copy( + str(src), + str(dst), + vcs_ref="HEAD", + force=True, + data={"cidr_whitelist": ["123.123.123.123/24", "456.456.456.456"]}, + ) + expected = Path("tests", "samples", "cidr-whitelist") + assert (dst / "prod.yaml").read_text() == (expected / "prod.yaml").read_text() + assert (dst / "test.yaml").read_text() == (expected / "test.yaml").read_text() + + def test_template_update_badge(tmp_path: Path): """Test that the template update badge is properly formatted.""" src, dst = tmp_path / "src", tmp_path / "dst"