From cbd3c43176c72b21ef564ea1268a94548cc64c19 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 11 Feb 2023 06:22:02 -0500 Subject: [PATCH] feat: add letsencrypt support to haproxy plugin --- docs/networking/proxies/haproxy.md | 24 +++++++ plugins/haproxy-vhosts/command-functions | 2 + plugins/haproxy-vhosts/internal-functions | 12 +++- plugins/haproxy-vhosts/subcommands/set | 6 +- .../templates/compose.yml.sigil | 10 +++ tests/unit/haproxy.bats | 62 ++++++++++++++++++- 6 files changed, 111 insertions(+), 5 deletions(-) diff --git a/docs/networking/proxies/haproxy.md b/docs/networking/proxies/haproxy.md index 9b7dfbc01..bf2291d1a 100644 --- a/docs/networking/proxies/haproxy.md +++ b/docs/networking/proxies/haproxy.md @@ -111,6 +111,30 @@ dokku haproxy:set --global log-level DEBUG After modifying, the Haproxy container will need to be restarted. +### SSL Configuration + +The haproxt plugin only supports automatic ssl certificates from it's letsencrypt integration. Managed certificates provided by the `certs` plugin are ignored. + +#### Enabling letsencrypt integration + +By default, letsencrypt is disabled and https port mappings are ignored. To enable, set the `letsencrypt-email` property with the `--global` flag: + +```shell +dokku haproxt:set --global letsencrypt-email automated@dokku.sh +``` + +After enabling, the Haproxy container will need to be restarted and apps will need to be rebuilt. All http requests will then be redirected to https. + +#### Customizing the letsencrypt server + +The letsencrypt integration is set to the production letsencrypt server by default. To change this, set the `letsencrypt-server` property with the `--global` flag: + +```shell +dokku haproxt:set --global letsencrypt-server https://acme-staging-v02.api.letsencrypt.org/directory +``` + +After enabling, the Haproxy container will need to be restarted and apps will need to be rebuilt to retrieve certificates from the new server. + ## Displaying Haproxy reports for an app You can get a report about the app's Haproxy config using the `haproxy:report` command: diff --git a/plugins/haproxy-vhosts/command-functions b/plugins/haproxy-vhosts/command-functions index 286a3bec0..1854569b7 100755 --- a/plugins/haproxy-vhosts/command-functions +++ b/plugins/haproxy-vhosts/command-functions @@ -36,6 +36,8 @@ cmd-haproxy-report-single() { verify_app_name "$APP" local flag_map=( "--haproxy-image: $(fn-haproxy-image)" + "--haproxy-letsencrypt-email: $(fn-haproxy-letsencrypt-email)" + "--haproxy-letsencrypt-server: $(fn-haproxy-letsencrypt-server)" "--haproxy-log-level: $(fn-haproxy-log-level)" ) diff --git a/plugins/haproxy-vhosts/internal-functions b/plugins/haproxy-vhosts/internal-functions index 71bbcf114..508869054 100755 --- a/plugins/haproxy-vhosts/internal-functions +++ b/plugins/haproxy-vhosts/internal-functions @@ -36,7 +36,9 @@ fn-haproxy-template-compose-file() { fi local SIGIL_PARAMS=(HAPROXY_IMAGE="$(fn-haproxy-image)" - HAPROXY_LOG_LEVEL="$(fn-haproxy-log-level)") + HAPROXY_LOG_LEVEL="$(fn-haproxy-log-level)" + HAPROXY_LETSENCRYPT_EMAIL="$(fn-haproxy-letsencrypt-email)" + HAPROXY_LETSENCRYPT_SERVER="$(fn-haproxy-letsencrypt-server)") sigil -f "$COMPOSE_TEMPLATE" "${SIGIL_PARAMS[@]}" | cat -s >"$OUTPUT_PATH" } @@ -50,3 +52,11 @@ fn-haproxy-log-level() { log_level="$(fn-plugin-property-get-default "haproxy" "--global" "log-level" "ERROR")" echo "${log_level^^}" } + +fn-haproxy-letsencrypt-email() { + fn-plugin-property-get-default "haproxy" "--global" "letsencrypt-email" "" +} + +fn-haproxy-letsencrypt-server() { + fn-plugin-property-get-default "haproxy" "--global" "letsencrypt-server" "https://acme-v02.api.letsencrypt.org/directory" +} diff --git a/plugins/haproxy-vhosts/subcommands/set b/plugins/haproxy-vhosts/subcommands/set index d893c6c6e..6406ea025 100755 --- a/plugins/haproxy-vhosts/subcommands/set +++ b/plugins/haproxy-vhosts/subcommands/set @@ -9,13 +9,13 @@ cmd-haproxy-set() { declare cmd="haproxy:set" [[ "$1" == "$cmd" ]] && shift 1 declare APP="$1" KEY="$2" VALUE="$3" - local VALID_KEYS=("image" "log-level") - local GLOBAL_KEYS=("image" "log-level") + local VALID_KEYS=("image" "log-level" "letsencrypt-email" "letsencrypt-server") + local GLOBAL_KEYS=("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: image log-level" + dokku_log_fail "Invalid key specified, valid keys include: image log-level letsencrypt-email letsencrypt-server" fi if ! fn-in-array "$KEY" "${GLOBAL_KEYS[@]}"; then diff --git a/plugins/haproxy-vhosts/templates/compose.yml.sigil b/plugins/haproxy-vhosts/templates/compose.yml.sigil index 49e714123..6b9890a32 100644 --- a/plugins/haproxy-vhosts/templates/compose.yml.sigil +++ b/plugins/haproxy-vhosts/templates/compose.yml.sigil @@ -9,11 +9,21 @@ services: - EASYHAPROXY_DISCOVER=docker - EASYHAPROXY_LABEL_PREFIX=haproxy - EASYHAPROXY_LOG_LEVEL={{ $.HAPROXY_LOG_LEVEL }} + - CERTBOT_LOG_LEVEL={{ $.HAPROXY_LOG_LEVEL }} + - HAPROXY_LOG_LEVEL={{ $.HAPROXY_LOG_LEVEL }} + {{ if $.HAPROXY_LETSENCRYPT_EMAIL }} + - EASYHAPROXY_LETSENCRYPT_EMAIL={{ $.HAPROXY_LETSENCRYPT_EMAIL }}" + - EASYHAPROXY_LETSENCRYPT_SERVER={{ $.HAPROXY_LETSENCRYPT_SERVER }} + {{ end }} + - HAPROXY_STATS_PORT=false network_mode: bridge ports: - "80:80" + {{ if $.HAPROXY_LETSENCRYPT_EMAIL }} + - "443:443" + {{ end }} restart: unless-stopped diff --git a/tests/unit/haproxy.bats b/tests/unit/haproxy.bats index ef0df21a0..e63526f59 100644 --- a/tests/unit/haproxy.bats +++ b/tests/unit/haproxy.bats @@ -5,6 +5,8 @@ load test_helper setup() { global_setup dokku nginx:stop + dokku haproxy:set --global letsencrypt-server https://acme-staging-v02.api.letsencrypt.org/directory + dokku haproxy:set --global letsencrypt-email dokku haproxy:start create_app } @@ -47,7 +49,6 @@ teardown() { assert_success } - @test "(haproxy) single domain" { run /bin/bash -c "dokku proxy:set $TEST_APP haproxy" echo "output: $output" @@ -99,3 +100,62 @@ teardown() { assert_success assert_output "python/http.server" } + +@test "(haproxy) ssl" { + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku proxy:set $TEST_APP haproxy" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"haproxy\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$TEST_APP.dokku.me:80" + + run /bin/bash -c "dokku haproxy:set --global letsencrypt-email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku haproxy:stop" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku haproxy:start" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:inspect $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"haproxy\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$TEST_APP.dokku.me" + + run /bin/bash -c "dokku proxy:report $TEST_APP --proxy-port-map" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000" +}