From b8c67329ff35481fa94148999438105b8155b033 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Wed, 6 Mar 2024 03:31:51 -0500 Subject: [PATCH] feat: allow limiting letsencrypt to certain domains when using openresty as a proxy --- docs/networking/proxies/openresty.md | 44 ++++++++++++++ plugins/openresty-vhosts/Dockerfile | 2 +- plugins/openresty-vhosts/command-functions | 1 + plugins/openresty-vhosts/internal-functions | 11 ++++ plugins/openresty-vhosts/subcommands/set | 6 +- tests/unit/openresty.bats | 67 +++++++++++++++++++++ 6 files changed, 127 insertions(+), 4 deletions(-) diff --git a/docs/networking/proxies/openresty.md b/docs/networking/proxies/openresty.md index 3c0c38295..1a275de05 100644 --- a/docs/networking/proxies/openresty.md +++ b/docs/networking/proxies/openresty.md @@ -148,6 +148,50 @@ dokku openresty:set --global letsencrypt-server https://acme-staging-v02.api.let After enabling, the OpenResty container will need to be restarted and apps will need to be rebuilt to retrieve certificates from the new server. +#### Limiting letsencrypt to certain domains + +> [!WARNING] +> Changing this value may cause OpenResty to fail to start if the value is not valid. Caution should be exercised when changing this value from the defaults. + +In cases where your server's IP may have invalid domains pointing at it, limiting letsencrypt to certain allowed domains may be desirable to reduce spam requests on the Letsencrypt servers. The default is to allow all domains to have certificates retrieved, but this can be limited by specifying the `allowed-letsencrypt-domains-func-base64` global property. + +The default internal value for `allowed-letsencrypt-domains-func-base64` is the base64 representation of `return true`, and is meant to be the body of a lua function that return a boolean value. + +```shell +value="$(echo 'return true' | base64 -w 0)" +dokku openresty:set --global allowed-letsencrypt-domains-func-base64 $value +``` + +As this is a global value, once changed, OpenResty should be stopped and started again for the value to take effect: + +```shell +dokku openresty:stop +dokku openresty:start +``` + +A more complex example would be to limit provisioning of certificates to domains in a specific list. The body of the lua function has access to a variable `domain`, and we can use it like so: + +```shell +body='allowed_domains = {"domain.com", "extra-domain.com"} + +for index, value in ipairs(allowed_domains) do + if value == domain then + return true + end +end + +return false +' +value="$(echo "$body" | base64 -w 0)" +dokku openresty:set --global allowed-letsencrypt-domains-func-base64 $value +``` + +To reset the value to the default, simply specify a blank value prior to restarting OpenResty: + +```shell +dokku openresty:set --global allowed-letsencrypt-domains-func-base64 +``` + ## Displaying OpenResty reports for an app You can get a report about the app's OpenResty config using the `openresty:report` command: diff --git a/plugins/openresty-vhosts/Dockerfile b/plugins/openresty-vhosts/Dockerfile index c33d9ac44..26ea308c7 100644 --- a/plugins/openresty-vhosts/Dockerfile +++ b/plugins/openresty-vhosts/Dockerfile @@ -1 +1 @@ -FROM dokku/openresty-docker-proxy:0.6.0 +FROM dokku/openresty-docker-proxy:0.7.0 diff --git a/plugins/openresty-vhosts/command-functions b/plugins/openresty-vhosts/command-functions index 33c2150e8..3b4b703db 100755 --- a/plugins/openresty-vhosts/command-functions +++ b/plugins/openresty-vhosts/command-functions @@ -37,6 +37,7 @@ cmd-openresty-report-single() { local flag_map=( "--openresty-access-log-format: $(fn-openresty-access-log-format "$APP")" "--openresty-access-log-path: $(fn-openresty-access-log-path "$APP")" + "--openresty-allowed-letsencrypt-domains-func-base64: $(fn-openresty-allowed-letsencrypt-domains-func-base64)" "--openresty-bind-address-ipv4: $(fn-openresty-bind-address-ipv4 "$APP")" "--openresty-bind-address-ipv6: $(fn-openresty-bind-address-ipv6 "$APP")" "--openresty-client-max-body-size: $(fn-openresty-client-max-body-size "$APP")" diff --git a/plugins/openresty-vhosts/internal-functions b/plugins/openresty-vhosts/internal-functions index 7018923d9..672ed6352 100755 --- a/plugins/openresty-vhosts/internal-functions +++ b/plugins/openresty-vhosts/internal-functions @@ -26,6 +26,12 @@ fn-openresty-access-log-path() { fn-plugin-property-get-default "openresty" "$APP" "access-log-path" "${OPENRESTY_LOG_ROOT}/${APP}-access.log" } +fn-fn-openresty-allowed-letsencrypt-domains-func-base64() { + declare desc="get the configured allowed domains func base64" + + fn-plugin-property-get-default "openresty" "--global" "allowed-letsencrypt-domains-func-base64" "return true" | base64 -w 0 +} + fn-openresty-bind-address-ipv4() { declare desc="get the configured ipv4 bind address" declare APP="$1" @@ -221,6 +227,11 @@ fn-openresty-template-compose-file() { OPENRESTY_LETSENCRYPT_EMAIL="$(fn-openresty-letsencrypt-email)" OPENRESTY_LETSENCRYPT_SERVER="$(fn-openresty-letsencrypt-server)") + local ALLOWED_DOMAINS_FUNC_BASE64="$(fn-fn-openresty-allowed-letsencrypt-domains-func-base64)" + if [[ -n "$ALLOWED_DOMAINS_FUNC_BASE64" ]]; then + SIGIL_PARAMS+=(ALLOWED_DOMAINS_FUNC_BASE64="$ALLOWED_DOMAINS_FUNC_BASE64") + fi + sigil -f "$COMPOSE_TEMPLATE" "${SIGIL_PARAMS[@]}" | cat -s >"$OUTPUT_PATH" } diff --git a/plugins/openresty-vhosts/subcommands/set b/plugins/openresty-vhosts/subcommands/set index 8aed28371..0304616db 100755 --- a/plugins/openresty-vhosts/subcommands/set +++ b/plugins/openresty-vhosts/subcommands/set @@ -9,13 +9,13 @@ cmd-openresty-set() { declare cmd="openresty:set" [[ "$1" == "$cmd" ]] && shift 1 declare APP="$1" KEY="$2" VALUE="$3" - local VALID_KEYS=("access-log-format" "access-log-path" "bind-address-ipv4" "bind-address-ipv6" "client-max-body-size" "error-log-path" "hsts" "hsts-include-subdomains" "hsts-preload" "hsts-max-age" "image" "log-level" "letsencrypt-email" "letsencrypt-server" "proxy-read-timeout" "proxy-buffer-size" "proxy-buffering" "proxy-buffers" "proxy-busy-buffers-size" "underscore-in-headers" "x-forwarded-for-value" "x-forwarded-port-value" "x-forwarded-proto-value" "x-forwarded-ssl") - local GLOBAL_KEYS=("image" "log-level" "letsencrypt-email" "letsencrypt-server") + local VALID_KEYS=("access-log-format" "access-log-path" "allowed-letsencrypt-domains-func-base64" "bind-address-ipv4" "bind-address-ipv6" "client-max-body-size" "error-log-path" "hsts" "hsts-include-subdomains" "hsts-preload" "hsts-max-age" "image" "log-level" "letsencrypt-email" "letsencrypt-server" "proxy-read-timeout" "proxy-buffer-size" "proxy-buffering" "proxy-buffers" "proxy-busy-buffers-size" "underscore-in-headers" "x-forwarded-for-value" "x-forwarded-port-value" "x-forwarded-proto-value" "x-forwarded-ssl") + local GLOBAL_KEYS=("allowed-letsencrypt-domains-func-base64" "image" "log-level" "letsencrypt-email" "letsencrypt-server") [[ -z "$KEY" ]] && dokku_log_fail "No key specified" if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then - dokku_log_fail "Invalid key specified, valid keys include: access-log-format, access-log-path, bind-address-ipv4, bind-address-ipv6, client-max-body-size, error-log-path, hsts, hsts-include-subdomains, hsts-preload, hsts-max-age, image, log-level, letsencrypt-email, letsencrypt-server, proxy-read-timeout, proxy-buffer-size, proxy-buffering, proxy-buffers, proxy-busy-buffers-size, underscore-in-headers, x-forwarded-for-value, x-forwarded-port-value, x-forwarded-proto-value, x-forwarded-ssl" + dokku_log_fail "Invalid key specified, valid keys include: access-log-format, access-log-path, allowed-letsencrypt-domains-func-base64, bind-address-ipv4, bind-address-ipv6, client-max-body-size, error-log-path, hsts, hsts-include-subdomains, hsts-preload, hsts-max-age, image, log-level, letsencrypt-email, letsencrypt-server, proxy-read-timeout, proxy-buffer-size, proxy-buffering, proxy-buffers, proxy-busy-buffers-size, underscore-in-headers, x-forwarded-for-value, x-forwarded-port-value, x-forwarded-proto-value, x-forwarded-ssl" fi if ! fn-in-array "$KEY" "${GLOBAL_KEYS[@]}"; then diff --git a/tests/unit/openresty.bats b/tests/unit/openresty.bats index d949d0ae1..e1b13aa66 100644 --- a/tests/unit/openresty.bats +++ b/tests/unit/openresty.bats @@ -138,6 +138,73 @@ teardown() { assert_output "http:80:5000 https:443:5000" } +@test "(openresty) allowed-domains" { + run /bin/bash -c "dokku proxy:set $TEST_APP openresty" + echo "output: $output" + echo "status: $status" + assert_success + + value="$(echo 'return true' | base64 -w 0)" + run /bin/bash -c "dokku openresty:set --global allowed-letsencrypt-domains-func-base64 $value" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:start" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker exec openresty-openresty-1 /usr/local/openresty/nginx/sbin/nginx -t" + echo "output: $output" + echo "status: $status" + assert_success + + body='allowed_domains = {"domain.com", "extra-domain.com"} + for index, value in ipairs(allowed_domains) do + if value == domain then + return true + end + end + return false + ' + value="$(echo "$body" | base64 -w 0)" + run /bin/bash -c "dokku openresty:set --global allowed-letsencrypt-domains-func-base64 $value" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:stop" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:start" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker exec openresty-openresty-1 /usr/local/openresty/nginx/sbin/nginx -t" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:set --global allowed-letsencrypt-domains-func-base64" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:stop" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:start" + echo "output: $output" + echo "status: $status" + assert_success +} + @test "(openresty) includes" { run /bin/bash -c "dokku proxy:set $TEST_APP openresty" echo "output: $output"