diff --git a/docs/networking/proxies/traefik.md b/docs/networking/proxies/traefik.md index a6718f0fa..5e13daa1a 100644 --- a/docs/networking/proxies/traefik.md +++ b/docs/networking/proxies/traefik.md @@ -199,6 +199,41 @@ dokku traefik:set --global letsencrypt-server https://acme-staging-v02.api.letse After enabling, the Traefik container will need to be restarted and apps will need to be rebuilt to retrieve certificates from the new server. +#### Switching to DNS-01 challenge mode + +By default, Traefik uses TLS-ALPN-01 challenge for obtaining certificates. To switch to DNS-01 challenge mode (useful for wildcard certificates or when port 443 is not accessible), you need to: + +1. Set the challenge mode to `dns`: + +```shell +dokku traefik:set --global challenge-mode dns +``` + +2. Set your DNS provider: + +```shell +dokku traefik:set --global dns-provider cloudflare +``` + +3. Configure the required environment variables for your DNS provider. Each DNS provider requires specific environment variables. The variable names should be prefixed with `dns-provider-`: + +```shell +dokku traefik:set --global dns-provider-cf_api_email user@example.com +dokku traefik:set --global dns-provider-cf_api_key your-api-key +``` + +The `dns-provider-` prefix will be stripped and the variable name will be uppercased when passed to the Traefik container. For example, `dns-provider-cf_api_email` becomes `CF_API_EMAIL`. + +After configuring, the Traefik container will need to be restarted and apps will need to be rebuilt. + +Refer to the [Traefik DNS Challenge documentation](https://doc.traefik.io/traefik/https/acme/#dnschallenge) for the list of supported DNS providers and their required environment variables. + +To switch back to TLS challenge mode: + +```shell +dokku traefik:set --global challenge-mode tls +``` + ### API Access Traefik exposes an API and Dashboard, which Dokku disables by default for security reasons. It can be exposed and customized as described below. @@ -264,7 +299,9 @@ dokku traefik:report Traefik api vhost: traefik.dokku.me Traefik basic auth password: password Traefik basic auth username: user + Traefik challenge mode: tls Traefik dashboard enabled: false + Traefik dns provider: Traefik image: traefik:v2.8 Traefik letsencrypt email: Traefik letsencrypt server: @@ -274,7 +311,9 @@ dokku traefik:report Traefik api vhost: traefik.dokku.me Traefik basic auth password: password Traefik basic auth username: user + Traefik challenge mode: tls Traefik dashboard enabled: false + Traefik dns provider: Traefik image: traefik:v2.8 Traefik letsencrypt email: Traefik letsencrypt server: @@ -284,7 +323,9 @@ dokku traefik:report Traefik api vhost: traefik.dokku.me Traefik basic auth password: password Traefik basic auth username: user + Traefik challenge mode: tls Traefik dashboard enabled: false + Traefik dns provider: Traefik image: traefik:v2.8 Traefik letsencrypt email: Traefik letsencrypt server: @@ -303,7 +344,9 @@ dokku traefik:report node-js-app Traefik api vhost: traefik.dokku.me Traefik basic auth password: password Traefik basic auth username: user + Traefik challenge mode: tls Traefik dashboard enabled: false + Traefik dns provider: Traefik image: traefik:v2.8 Traefik letsencrypt email: Traefik letsencrypt server: diff --git a/plugins/common/property-functions b/plugins/common/property-functions index 00d8a6a11..b273ddfa4 100755 --- a/plugins/common/property-functions +++ b/plugins/common/property-functions @@ -39,6 +39,12 @@ fn-plugin-property-get-all() { "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "get-all" "$PLUGIN" "$APP" } +fn-plugin-property-get-all-by-prefix() { + declare desc="returns a map of all properties for a given app with a specified prefix" + declare PLUGIN="$1" APP="$2" PREFIX="$3" + "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "get-all-by-prefix" "$PLUGIN" "$APP" "$PREFIX" +} + fn-plugin-property-get-default() { declare desc="returns the value for a given property with a specified default value" declare PLUGIN="$1" APP="$2" KEY="$3" DEFAULT="$4" diff --git a/plugins/common/src/prop/prop.go b/plugins/common/src/prop/prop.go index 5c2a87c78..f5b788dc3 100644 --- a/plugins/common/src/prop/prop.go +++ b/plugins/common/src/prop/prop.go @@ -78,6 +78,19 @@ func main() { os.Exit(1) } + for key, value := range values { + fmt.Println(fmt.Sprintf("%s %s", key, strings.TrimSuffix(value, "\n"))) + } + + case "get-all-by-prefix": + appName := flag.Arg(2) + prefix := flag.Arg(3) + values, err := common.PropertyGetAllByPrefix(pluginName, appName, prefix) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + for key, value := range values { fmt.Println(fmt.Sprintf("%s %s", key, strings.TrimSuffix(value, "\n"))) } diff --git a/plugins/traefik-vhosts/command-functions b/plugins/traefik-vhosts/command-functions index f468a0fc9..d2d28da0c 100755 --- a/plugins/traefik-vhosts/command-functions +++ b/plugins/traefik-vhosts/command-functions @@ -34,8 +34,16 @@ cmd-traefik-report() { declare cmd="traefik:report" [[ "$1" == "$cmd" ]] && shift 1 declare APP="$1" INFO_FLAG="$2" + local FORMAT="stdout" - if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then + if [[ -n "$APP" ]] && [[ "$APP" == "--format" ]]; then + FORMAT="$INFO_FLAG" + APP="" + INFO_FLAG="" + elif [[ -n "$INFO_FLAG" ]] && [[ "$INFO_FLAG" == "--format" ]]; then + FORMAT="$3" + INFO_FLAG="" + elif [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then INFO_FLAG="$APP" APP="" fi @@ -46,15 +54,15 @@ cmd-traefik-report() { if [[ -z "$APP" ]]; then for app in $(dokku_apps); do - cmd-traefik-report-single "$app" "$INFO_FLAG" | tee || true + cmd-traefik-report-single "$app" "$INFO_FLAG" "$FORMAT" | tee || true done else - cmd-traefik-report-single "$APP" "$INFO_FLAG" + cmd-traefik-report-single "$APP" "$INFO_FLAG" "$FORMAT" fi } cmd-traefik-report-single() { - declare APP="$1" INFO_FLAG="$2" + declare APP="$1" INFO_FLAG="$2" FORMAT="${3:-stdout}" if [[ "$INFO_FLAG" == "true" ]]; then INFO_FLAG="" fi @@ -64,7 +72,9 @@ cmd-traefik-report-single() { "--traefik-api-vhost: $(fn-traefik-api-vhost)" "--traefik-basic-auth-password: $(fn-traefik-basic-auth-password)" "--traefik-basic-auth-username: $(fn-traefik-basic-auth-username)" + "--traefik-challenge-mode: $(fn-traefik-challenge-mode)" "--traefik-dashboard-enabled: $(fn-traefik-dashboard-enabled)" + "--traefik-dns-provider: $(fn-traefik-dns-provider)" "--traefik-image: $(fn-traefik-image)" "--traefik-letsencrypt-email: $(fn-traefik-letsencrypt-email)" "--traefik-letsencrypt-server: $(fn-traefik-letsencrypt-server)" @@ -73,7 +83,34 @@ cmd-traefik-report-single() { "--traefik-https-entry-point: $(fn-traefik-https-entry-point)" ) - if [[ -z "$INFO_FLAG" ]]; then + # Get dns-provider-* env vars and add them (masked for stdout, unmasked for json) + local dns_provider_env_vars + dns_provider_env_vars="$(fn-traefik-dns-provider-env-vars)" + if [[ -n "$dns_provider_env_vars" ]]; then + while IFS= read -r line; do + local key value + key="$(echo "$line" | cut -d' ' -f1)" + value="$(echo "$line" | cut -d' ' -f2-)" + if [[ -n "$key" ]]; then + if [[ "$FORMAT" == "json" ]]; then + flag_map+=("--traefik-${key}: ${value}") + else + flag_map+=("--traefik-${key}: *******") + fi + fi + done <<<"$dns_provider_env_vars" + fi + + if [[ "$FORMAT" == "json" ]]; then + local json_output="{}" + for flag in "${flag_map[@]}"; do + local key value + key="$(echo "$flag" | cut -d':' -f1 | sed 's/^--traefik-//')" + value="$(echo "$flag" | cut -d':' -f2- | sed 's/^ //')" + json_output="$(echo "$json_output" | jq -c --arg key "$key" --arg value "$value" '. + {($key): $value}')" + done + echo "$json_output" + elif [[ -z "$INFO_FLAG" ]]; then dokku_log_info2_quiet "${APP} traefik information" for flag in "${flag_map[@]}"; do key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')" diff --git a/plugins/traefik-vhosts/internal-functions b/plugins/traefik-vhosts/internal-functions index e36e5338b..c12dd9981 100755 --- a/plugins/traefik-vhosts/internal-functions +++ b/plugins/traefik-vhosts/internal-functions @@ -42,11 +42,21 @@ fn-traefik-template-compose-file() { basic_auth="$(htpasswd -nb "$basic_auth_username" "$basic_auth_password" | sed -e s/\\$/\\$\\$/g)" fi + local dns_provider_env_vars="" + local dns_provider + dns_provider="$(fn-traefik-dns-provider)" + if [[ -n "$dns_provider" ]]; then + dns_provider_env_vars="$(fn-traefik-dns-provider-env-vars)" + fi + local SIGIL_PARAMS=(TRAEFIK_API_ENABLED="$(fn-traefik-api-enabled)" TRAEFIK_API_VHOST="$(fn-traefik-api-vhost)" TRAEFIK_BASIC_AUTH="$basic_auth" + TRAEFIK_CHALLENGE_MODE="$(fn-traefik-challenge-mode)" TRAEFIK_DASHBOARD_ENABLED="$(fn-traefik-dashboard-enabled)" TRAEFIK_DATA_DIR="${DOKKU_LIB_ROOT}/data/traefik" + TRAEFIK_DNS_PROVIDER="$dns_provider" + TRAEFIK_DNS_PROVIDER_ENV_VARS="$dns_provider_env_vars" TRAEFIK_IMAGE="$(fn-traefik-image)" TRAEFIK_LETSENCRYPT_EMAIL="$(fn-traefik-letsencrypt-email)" TRAEFIK_LETSENCRYPT_SERVER="$(fn-traefik-letsencrypt-server)" @@ -100,3 +110,16 @@ fn-traefik-http-entry-point() { fn-traefik-https-entry-point() { fn-plugin-property-get-default "traefik" "--global" "https-entry-point" "https" } + +fn-traefik-challenge-mode() { + fn-plugin-property-get-default "traefik" "--global" "challenge-mode" "tls" +} + +fn-traefik-dns-provider() { + fn-plugin-property-get-default "traefik" "--global" "dns-provider" "" +} + +fn-traefik-dns-provider-env-vars() { + declare desc="returns all dns-provider-* environment variables" + fn-plugin-property-get-all-by-prefix "traefik" "--global" "dns-provider-" +} diff --git a/plugins/traefik-vhosts/subcommands/set b/plugins/traefik-vhosts/subcommands/set index 867386618..1e8baa5fc 100755 --- a/plugins/traefik-vhosts/subcommands/set +++ b/plugins/traefik-vhosts/subcommands/set @@ -9,22 +9,38 @@ cmd-traefik-set() { declare cmd="traefik:set" [[ "$1" == "$cmd" ]] && shift 1 declare APP="$1" KEY="$2" VALUE="$3" - local VALID_KEYS=("api-enabled" "api-vhost" "dashboard-enabled" "basic-auth-username" "basic-auth-password" "image" "letsencrypt-email" "letsencrypt-server" "log-level" "http-entry-point" "https-entry-point") - local GLOBAL_KEYS=("api-enabled" "api-vhost" "dashboard-enabled" "basic-auth-username" "basic-auth-password" "image" "letsencrypt-email" "letsencrypt-server" "log-level" "http-entry-point" "https-entry-point") + local VALID_KEYS=("api-enabled" "api-vhost" "challenge-mode" "dashboard-enabled" "basic-auth-username" "basic-auth-password" "dns-provider" "image" "letsencrypt-email" "letsencrypt-server" "log-level" "http-entry-point" "https-entry-point") + local GLOBAL_KEYS=("api-enabled" "api-vhost" "challenge-mode" "dashboard-enabled" "basic-auth-username" "basic-auth-password" "dns-provider" "image" "letsencrypt-email" "letsencrypt-server" "log-level" "http-entry-point" "https-entry-point") + local GLOBAL_ONLY_KEYS=("challenge-mode" "dns-provider") [[ -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: api-enabled api-vhost dashboard-enabled basic-auth-username basic-auth-password image letsencrypt-email letsencrypt-server log-level" + # Allow dns-provider-* keys for setting DNS provider environment variables + local is_dns_provider_env_var=false + if [[ "$KEY" == dns-provider-* ]]; then + is_dns_provider_env_var=true fi - if ! fn-in-array "$KEY" "${GLOBAL_KEYS[@]}"; then + if ! fn-in-array "$KEY" "${VALID_KEYS[@]}" && [[ "$is_dns_provider_env_var" != "true" ]]; then + dokku_log_fail "Invalid key specified, valid keys include: api-enabled api-vhost challenge-mode dashboard-enabled basic-auth-username basic-auth-password dns-provider dns-provider- image letsencrypt-email letsencrypt-server log-level http-entry-point https-entry-point" + fi + + if ! fn-in-array "$KEY" "${GLOBAL_KEYS[@]}" && [[ "$is_dns_provider_env_var" != "true" ]]; then if [[ "$APP" == "--global" ]]; then dokku_log_fail "The key '$KEY' cannot be set globally" fi verify_app_name "$APP" fi + # dns-provider-* keys and GLOBAL_ONLY_KEYS can only be set globally + if [[ "$is_dns_provider_env_var" == "true" ]] && [[ "$APP" != "--global" ]]; then + dokku_log_fail "The key '$KEY' can only be set globally" + fi + + if fn-in-array "$KEY" "${GLOBAL_ONLY_KEYS[@]}" && [[ "$APP" != "--global" ]]; then + dokku_log_fail "The key '$KEY' can only be set globally" + fi + if [[ -n "$VALUE" ]]; then dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}" fn-plugin-property-write "traefik" "$APP" "$KEY" "$VALUE" diff --git a/plugins/traefik-vhosts/templates/compose.yml.sigil b/plugins/traefik-vhosts/templates/compose.yml.sigil index 6a3e07f57..2b277b966 100644 --- a/plugins/traefik-vhosts/templates/compose.yml.sigil +++ b/plugins/traefik-vhosts/templates/compose.yml.sigil @@ -21,8 +21,25 @@ services: - --certificatesresolvers.leresolver.acme.caserver={{ $.TRAEFIK_LETSENCRYPT_SERVER }} - --certificatesresolvers.leresolver.acme.email={{ $.TRAEFIK_LETSENCRYPT_EMAIL }} - --certificatesresolvers.leresolver.acme.storage=/data/acme.json + {{ if and (eq $.TRAEFIK_CHALLENGE_MODE "dns") ($.TRAEFIK_DNS_PROVIDER) }} + - --certificatesresolvers.leresolver.acme.dnschallenge=true + - --certificatesresolvers.leresolver.acme.dnschallenge.provider={{ $.TRAEFIK_DNS_PROVIDER }} + {{ else }} - --certificatesresolvers.leresolver.acme.tlschallenge=true {{ end }} + {{ end }} + + {{ if $.TRAEFIK_DNS_PROVIDER_ENV_VARS }} + environment: + {{ range $env_var := $.TRAEFIK_DNS_PROVIDER_ENV_VARS | split "\n" }} + {{ $env_parts := $env_var | split " " }} + {{ $env_key := index $env_parts 0 }} + {{ $env_value := index $env_parts 1 }} + {{ if $env_key }} + - {{ $env_key | replace "dns-provider-" "" | upper }}={{ $env_value }} + {{ end }} + {{ end }} + {{ end }} {{ if or (eq $.TRAEFIK_API_ENABLED "true") ($.TRAEFIK_BASIC_AUTH) ($.TRAEFIK_LETSENCRYPT_EMAIL) }} labels: @@ -37,7 +54,7 @@ services: - "traefik.http.routers.api.service=api@internal" - "traefik.http.routers.api.entrypoints={{ if $.TRAEFIK_LETSENCRYPT_EMAIL }}https{{ else }}http{{ end }}" {{ end }} - + {{ if $.TRAEFIK_BASIC_AUTH }} - "traefik.http.routers.api.middlewares=auth" - "traefik.http.middlewares.auth.basicauth.users={{ $.TRAEFIK_BASIC_AUTH }}" diff --git a/tests/unit/traefik.bats b/tests/unit/traefik.bats index 4c2aee542..c9a69a251 100644 --- a/tests/unit/traefik.bats +++ b/tests/unit/traefik.bats @@ -8,6 +8,8 @@ setup() { dokku traefik:set --global letsencrypt-server https://acme-staging-v02.api.letsencrypt.org/directory dokku traefik:set --global letsencrypt-email dokku traefik:set --global api-enabled + dokku traefik:set --global challenge-mode + dokku traefik:set --global dns-provider dokku traefik:start create_app } @@ -368,3 +370,251 @@ teardown() { assert_success assert_output_not_exists } + +@test "(traefik) [dns-01] challenge-mode property" { + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-challenge-mode" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "tls" + + run /bin/bash -c "dokku traefik:set --global challenge-mode dns" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-challenge-mode" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dns" + + run /bin/bash -c "dokku traefik:set --global challenge-mode" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-challenge-mode" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "tls" +} + +@test "(traefik) [dns-01] dns-provider property" { + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-dns-provider" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku traefik:set --global dns-provider cloudflare" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-dns-provider" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "cloudflare" + + run /bin/bash -c "dokku traefik:set --global dns-provider" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-dns-provider" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists +} + +@test "(traefik) [dns-01] dns-provider-* environment variables" { + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_key secret-key" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "dns provider cf_api_email" + assert_output_contains "dns provider cf_api_key" + assert_output_contains "*******" + assert_output_not_contains "test@example.com" + assert_output_not_contains "secret-key" + + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-dns-provider-cf_api_email" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "*******" + + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_email" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_key" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "dns provider cf_api_email" + assert_output_not_contains "dns provider cf_api_key" +} + +@test "(traefik) [dns-01] dns-provider-* can only be set globally" { + run /bin/bash -c "dokku traefik:set $TEST_APP dns-provider-cf_api_email test@example.com" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "can only be set globally" +} + +@test "(traefik) [dns-01] challenge-mode can only be set globally" { + run /bin/bash -c "dokku traefik:set $TEST_APP challenge-mode dns" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "can only be set globally" +} + +@test "(traefik) [dns-01] dns-provider can only be set globally" { + run /bin/bash -c "dokku traefik:set $TEST_APP dns-provider cloudflare" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "can only be set globally" +} + +@test "(traefik) [dns-01] report json format shows unmasked dns-provider-* values" { + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_key secret-key" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP --format json | jq -r '.\"dns-provider-cf_api_email\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "test@example.com" + + run /bin/bash -c "dokku traefik:report $TEST_APP --format json | jq -r '.\"dns-provider-cf_api_key\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "secret-key" + + # Verify stdout format still shows masked values + run /bin/bash -c "dokku traefik:report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "*******" + assert_output_not_contains "test@example.com" + assert_output_not_contains "secret-key" + + # Cleanup + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_email" + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_key" +} + +@test "(traefik) [dns-01] show-config with tls challenge" { + run /bin/bash -c "dokku traefik:set --global letsencrypt-email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.command[] | select(. == \"--certificatesresolvers.leresolver.acme.tlschallenge=true\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "--certificatesresolvers.leresolver.acme.tlschallenge=true" + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.command[] | select(contains(\"dnschallenge\"))'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists +} + +@test "(traefik) [dns-01] show-config with dns challenge" { + run /bin/bash -c "dokku traefik:set --global letsencrypt-email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global challenge-mode dns" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global dns-provider cloudflare" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_key secret-key" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.command[] | select(. == \"--certificatesresolvers.leresolver.acme.dnschallenge=true\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "--certificatesresolvers.leresolver.acme.dnschallenge=true" + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.command[] | select(. == \"--certificatesresolvers.leresolver.acme.dnschallenge.provider=cloudflare\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "--certificatesresolvers.leresolver.acme.dnschallenge.provider=cloudflare" + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.environment[] | select(. == \"CF_API_EMAIL=test@example.com\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "CF_API_EMAIL=test@example.com" + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.environment[] | select(. == \"CF_API_KEY=secret-key\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "CF_API_KEY=secret-key" + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.command[] | select(contains(\"tlschallenge\"))'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + # Cleanup + run /bin/bash -c "dokku traefik:set --global challenge-mode" + run /bin/bash -c "dokku traefik:set --global dns-provider" + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_email" + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_key" +}