From 9efe59b9b2b2d4678e55ec1161be37479eb39314 Mon Sep 17 00:00:00 2001 From: Brandon Hopkins <76761586+TechHutTV@users.noreply.github.com> Date: Tue, 13 Oct 2026 09:46:27 -0700 Subject: [PATCH] [infrastructure] Fix Traefik Versioning and Update Crowdsec (#8152) --- .../workflows/test-infrastructure-files.yml | 12 +++ .../getting-started-enterprise.sh | 82 ++++++++++++++-- infrastructure_files/getting-started.sh | 34 ++++++- infrastructure_files/migrate.sh | 10 +- .../tests/test-getting-started-enterprise.sh | 96 +++++++++++++++++++ .../tests/test-getting-started-networks.sh | 54 +++++++++++ .../tests/test-getting-started.sh | 82 ++++++++++++++++ 7 files changed, 357 insertions(+), 13 deletions(-) create mode 100644 infrastructure_files/tests/test-getting-started-enterprise.sh create mode 100644 infrastructure_files/tests/test-getting-started-networks.sh create mode 100644 infrastructure_files/tests/test-getting-started.sh diff --git a/.github/workflows/test-infrastructure-files.yml b/.github/workflows/test-infrastructure-files.yml index 1313379ee..4875adc31 100644 --- a/.github/workflows/test-infrastructure-files.yml +++ b/.github/workflows/test-infrastructure-files.yml @@ -258,6 +258,18 @@ jobs: with: persist-credentials: false + - name: Verify enterprise Traefik tag validation + run: bash infrastructure_files/tests/test-getting-started-enterprise.sh + + - name: Verify community Traefik image validation + run: bash infrastructure_files/tests/test-getting-started.sh + + - name: Verify community Compose network scoping + run: bash infrastructure_files/tests/test-getting-started-networks.sh + + - name: Verify migration Compose network scoping + run: bash infrastructure_files/tests/test-getting-started-networks.sh --migration + - name: Verify fresh-install session cookie key hardening run: | grep -Fxq ' SESSION_COOKIE_ENCRYPTION_KEY=$(openssl rand -base64 32)' infrastructure_files/getting-started.sh diff --git a/infrastructure_files/getting-started-enterprise.sh b/infrastructure_files/getting-started-enterprise.sh index 344bd2fed..b6565bff1 100755 --- a/infrastructure_files/getting-started-enterprise.sh +++ b/infrastructure_files/getting-started-enterprise.sh @@ -1,8 +1,5 @@ #!/bin/bash -set -e -set -o pipefail - # NetBird Enterprise — Getting Started # Single-node bootstrap for a self-hosted NetBird Enterprise stack with the # embedded identity provider. Owner is created via first-login flow. @@ -23,6 +20,9 @@ PROXY_TOKEN_ID="" # server trusts X-Forwarded-* headers from this address only. TRAEFIK_IP="172.30.0.10" +TRAEFIK_HEADER_CONFIG=' - "--entrypoints.web.http.aliasHeadersStrategy=delete" + - "--entrypoints.websecure.http.aliasHeadersStrategy=delete"' + LICENSE_VERDICT="unknown" LICENSE_LOG_LINES="" @@ -50,6 +50,25 @@ check_openssl() { echo "openssl is not installed or not in PATH." > /dev/stderr exit 1 fi + return +} + +# Only parseable versions can guarantee that aliasHeadersStrategy and its +# CVE-2026-88004 fix (v3.7.13) are available in the fresh-install compose. +check_traefik_tag() { + local tag="${NETBIRD_TRAEFIK_TAG:-}" ver major minor patch + [[ -n "$tag" ]] || return 0 + ver="${tag#v}" + if [[ ! "$ver" =~ ^([0-9]+)\.([0-9]+)(\.([0-9]+))?$ ]]; then + die "Cannot verify NETBIRD_TRAEFIK_TAG=$tag. Use a numeric version tag such as v3.7.13 or newer, or unset NETBIRD_TRAEFIK_TAG for the default (v3.7.14)." + fi + major=$((10#${BASH_REMATCH[1]})) + minor=$((10#${BASH_REMATCH[2]})) + patch=$((10#${BASH_REMATCH[4]:-0})) + if (( major < 3 || (major == 3 && minor < 7) || (major == 3 && minor == 7 && patch < 13) )); then + die "NETBIRD_TRAEFIK_TAG=$tag predates aliasHeadersStrategy and its CVE-2026-88004 fix (added in v3.7.12, fixed in v3.7.13), which this installer always enables. Use v3.7.13 or newer, or unset NETBIRD_TRAEFIK_TAG for the default (v3.7.14)." + fi + return } die() { @@ -62,6 +81,7 @@ env_get() { local value value=$(sed -n "s/^$1=//p" .env | tail -n 1) echo "${value:-$2}" + return } # merge_env upserts the KEY=VALUE lines from stdin into .env, in place. @@ -72,14 +92,17 @@ merge_env() { { print } END { for (i = 1; i <= n; i++) if (o[i] in v) print v[o[i]] }' - .env) printf '%s\n' "$merged" > .env + return } rand_secret() { openssl rand -base64 32 | sed "$SED_STRIP_PADDING" + return } rand_b64_key() { openssl rand -base64 32 + return } check_nb_domain() { @@ -133,6 +156,7 @@ read_nb_domain() { fi fi echo "$value" + return } read_letsencrypt_email() { @@ -150,6 +174,7 @@ read_letsencrypt_email() { return fi echo "$value" + return } read_required() { @@ -163,6 +188,7 @@ read_required() { fi done echo "$value" + return } read_secret() { @@ -177,6 +203,7 @@ read_secret() { fi done echo "$value" + return } # read_yes_no "" [] @@ -199,6 +226,7 @@ read_yes_no() { [Yy] | [Yy][Ee][Ss]) echo "yes" ;; *) echo "no" ;; esac + return } read_crowdsec_option() { @@ -208,6 +236,7 @@ read_crowdsec_option() { echo " blocks known malicious sources before they reach services exposed through" echo " the proxy. Adds a CrowdSec container to the stack." NETBIRD_CROWDSEC=$(read_yes_no "Enable CrowdSec" "n") + return } # Gate the install on explicit acceptance of the NetBird On-Premise EULA. @@ -245,6 +274,7 @@ EOF exit 1 fi echo "" > /dev/stderr + return } wait_postgres() { @@ -267,6 +297,7 @@ wait_postgres() { done echo " done" set -e + return } wait_for_license_verdict() { @@ -359,6 +390,7 @@ up_all_but_proxy() { services=$($DOCKER_COMPOSE_COMMAND config --services | grep -vx proxy) # shellcheck disable=SC2086 $DOCKER_COMPOSE_COMMAND up -d $services + return } wait_crowdsec() { @@ -372,6 +404,7 @@ wait_crowdsec() { admin_token() { $DOCKER_COMPOSE_COMMAND run --rm --no-deps -T netbird-server admin token "$@" --config /etc/netbird/config.yaml + return } # revoke_proxy_token revokes the token this run minted if the proxy never started. @@ -384,6 +417,7 @@ revoke_proxy_token() { fi echo "Could not revoke the unused proxy token ${PROXY_TOKEN_ID}. Revoke it with:" > /dev/stderr echo " $DOCKER_COMPOSE_COMMAND run --rm netbird-server admin token revoke ${PROXY_TOKEN_ID} --config /etc/netbird/config.yaml" > /dev/stderr + return } # start_proxy mints the proxy token and CrowdSec bouncer key, then starts the proxy. @@ -419,6 +453,7 @@ start_proxy() { die "Could not start the proxy. Check the proxy logs, then re-run with --enable-proxy." fi PROXY_TOKEN_ID="" + return } print_proxy_notes() { @@ -433,10 +468,12 @@ print_proxy_notes() { if [[ "$NETBIRD_CROWDSEC" == "yes" ]]; then echo " CrowdSec is running. Enable it per service in the dashboard under Access Control." fi + return } init_environment() { check_openssl + check_traefik_tag DOCKER_COMPOSE_COMMAND=$(check_docker_compose) if [[ -f .env ]] || [[ -f docker-compose.yml ]] || [[ -f config.yaml ]]; then @@ -555,12 +592,14 @@ init_environment() { if [[ "$LICENSE_VERDICT" == "rejected" ]]; then exit 1 fi + return } # service_block NAME prints a service's definition from the compose file on stdin. service_block() { local name="$1" awk -v s=" ${name}:" '$0 == s { p = 1; print; next } p && (/^[^ ]/ || /^ [^ ]/) { exit } p' + return } # enable_features adds the proxy and/or traffic events to the install in the @@ -585,6 +624,9 @@ enable_features() { # so the re-render never removes a working Let's Encrypt setup. local traefik_block traefik_block=$(service_block traefik < docker-compose.yml) + # Enabling features does not upgrade Traefik. Preserve the existing header policy, + # including its absence on older releases that do not support these options. + TRAEFIK_HEADER_CONFIG=$(printf '%s\n' "$traefik_block" | grep -Ei -- '--entrypoints\.[^.]+\.http\.(alias|underscore)headersstrategy=' || true) if ! grep -q certificatesresolvers <<< "$traefik_block"; then CUSTOM_TLS_CERTS=$(awk '/:\/certs:ro$/ { sub(/^ *- /, ""); sub(/:\/certs:ro$/, ""); print; exit }' <<< "$traefik_block") fi @@ -684,6 +726,7 @@ enable_features() { if [[ "$want_proxy" == "yes" ]]; then print_proxy_notes fi + return } rollback() { @@ -697,6 +740,7 @@ rollback() { done $DOCKER_COMPOSE_COMMAND up -d --remove-orphans $DOCKER_COMPOSE_COMMAND restart netbird-server + return } # ------------------------------------------------------------------ @@ -723,7 +767,7 @@ NETBIRD_DOMAIN=${NETBIRD_DOMAIN} # Reverse proxy (Traefik) NETBIRD_LETSENCRYPT_EMAIL=${NETBIRD_LETSENCRYPT_EMAIL} -NETBIRD_TRAEFIK_TAG=${NETBIRD_TRAEFIK_TAG:-v3.6} +NETBIRD_TRAEFIK_TAG=${NETBIRD_TRAEFIK_TAG:-v3.7.14} NETBIRD_TRAEFIK_IP=${TRAEFIK_IP} # Image tags. Default to "latest" @@ -770,20 +814,23 @@ NETBIRD_ENCRYPTION_KEY=${NETBIRD_ENCRYPTION_KEY} # Dashboard OIDC scopes NETBIRD_AUTH_SUPPORTED_SCOPES=${NETBIRD_AUTH_SUPPORTED_SCOPES:-openid profile email groups} EOF + return } render_env_flow() { echo "NETBIRD_ENRICHER_TAG=${NETBIRD_ENRICHER_TAG:-latest}" echo "NETBIRD_RECEIVER_TAG=${NETBIRD_RECEIVER_TAG:-latest}" + return } render_env_proxy() { echo "NETBIRD_PROXY_TAG=${NETBIRD_PROXY_TAG:-latest}" echo "NETBIRD_PROXY_TOKEN=" if [[ "$NETBIRD_CROWDSEC" == "yes" ]]; then - echo "NETBIRD_CROWDSEC_TAG=${NETBIRD_CROWDSEC_TAG:-v1.7.7}" + echo "NETBIRD_CROWDSEC_TAG=${NETBIRD_CROWDSEC_TAG:-v1.8.1}" echo "NETBIRD_CROWDSEC_BOUNCER_KEY=" fi + return } render_docker_compose() { @@ -801,6 +848,7 @@ render_docker_compose() { render_compose_footer } | if [[ -n "${NETBIRD_LICENSE_SERVER_BASE_URL:-}" ]]; then cat; else sed '/NETBIRD_LICENSE_SERVER_BASE_URL/d'; fi \ | if [[ -n "$CUSTOM_TLS_CERTS" ]]; then sed -e '/certificatesresolvers/d' -e '/certresolver/d'; else cat; fi + return } render_compose_header() { @@ -815,6 +863,7 @@ x-default: &default services: EOF + return } render_compose_common() { @@ -840,6 +889,12 @@ render_compose_common() { - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--entrypoints.websecure.allowACMEByPass=true" +EOF + # Installs that predate these options have no header config; don't leave a blank line. + if [[ -n "$TRAEFIK_HEADER_CONFIG" ]]; then + printf '%s\n' "$TRAEFIK_HEADER_CONFIG" + fi + cat <<'EOF' # readTimeout bounds the whole request, and gRPC streams / relay WebSockets # never end one; idleTimeout would close the keep-alive connection they # are reused over. Entrypoint-wide is the only scope Traefik offers here. @@ -916,6 +971,7 @@ EOF - LETSENCRYPT_EMAIL= EOF + return } render_compose_server() { @@ -963,6 +1019,7 @@ render_compose_server() { - NETBIRD_LICENSE_SERVER_BASE_URL=${NETBIRD_LICENSE_SERVER_BASE_URL} EOF + return } render_compose_flow() { @@ -1034,6 +1091,7 @@ render_compose_flow() { - traefik.http.services.netbird-flow-h2c.loadbalancer.server.scheme=h2c EOF + return } render_compose_proxy() { @@ -1097,6 +1155,7 @@ EOF EOF fi echo "" + return } render_compose_postgres() { @@ -1119,6 +1178,7 @@ render_compose_postgres() { - netbird_postgres:/var/lib/postgresql/data EOF + return } render_compose_footer() { @@ -1151,6 +1211,7 @@ networks: - subnet: 172.30.0.0/24 gateway: 172.30.0.1 EOF + return } render_config_yaml() { @@ -1215,6 +1276,7 @@ EOF if [[ "$NETBIRD_TRAFFIC_FLOW" == "yes" ]]; then render_config_flow fi + return } render_config_flow() { @@ -1225,6 +1287,7 @@ render_config_flow() { address: "https://${NETBIRD_DOMAIN}:443" interval: "60s" EOF + return } render_traefik_proxy() { @@ -1235,6 +1298,7 @@ tcp: proxyProtocol: version: 2 EOF + return } usage() { @@ -1249,6 +1313,7 @@ the current directory. --enable-traffic-events add traffic events logging (NATS, receiver, enricher) -h, --help show this help EOF + return } main() { @@ -1268,6 +1333,11 @@ main() { else enable_features "$enable_proxy" "$enable_flow" fi + return } -main "$@" +if [[ "${BASH_SOURCE[0]:-$0}" == "$0" ]]; then + set -e + set -o pipefail + main "$@" +fi diff --git a/infrastructure_files/getting-started.sh b/infrastructure_files/getting-started.sh index d5c6d9dc9..902956199 100755 --- a/infrastructure_files/getting-started.sh +++ b/infrastructure_files/getting-started.sh @@ -87,6 +87,27 @@ check_jq() { return 0 } +check_traefik_image() { + local image="${TRAEFIK_IMAGE##*/}" major minor patch + + # A version tag cannot establish which release a digest points to. + if [[ "$TRAEFIK_IMAGE" == *@* || + ! "$image" =~ ^[^:@]+:v?([0-9]+)(\.([0-9]+)(\.([0-9]+))?)?$ ]]; then + echo "Cannot verify TRAEFIK_IMAGE=$TRAEFIK_IMAGE." >&2 + echo "Use a numeric Traefik version tag (v3.7.13 or newer) without a digest." >&2 + return 1 + fi + major=$((10#${BASH_REMATCH[1]})) + minor=$((10#${BASH_REMATCH[3]:-0})) + patch=$((10#${BASH_REMATCH[5]:-0})) + if (( major < 3 || (major == 3 && minor < 7) || (major == 3 && minor == 7 && patch < 13) )); then + echo "TRAEFIK_IMAGE=$TRAEFIK_IMAGE is incompatible with this configuration." >&2 + echo "Use Traefik v3.7.13 or newer for aliasHeadersStrategy and its header-sanitization fix." >&2 + return 1 + fi + return 0 +} + get_main_ip_address() { if [[ "$OSTYPE" == "darwin"* ]]; then interface=$(route -n get default | grep 'interface:' | awk '{print $2}') @@ -448,8 +469,8 @@ initialize_default_values() { # Combined server replaces separate signal, relay, and management containers NETBIRD_SERVER_IMAGE=${NETBIRD_SERVER_IMAGE:-"netbirdio/netbird-server:latest"} NETBIRD_PROXY_IMAGE=${NETBIRD_PROXY_IMAGE:-"netbirdio/reverse-proxy:latest"} - TRAEFIK_IMAGE=${TRAEFIK_IMAGE:-"traefik:v3.6"} - CROWDSEC_IMAGE=${CROWDSEC_IMAGE:-"crowdsecurity/crowdsec:v1.7.7"} + TRAEFIK_IMAGE=${TRAEFIK_IMAGE:-"traefik:v3.7.14"} + CROWDSEC_IMAGE=${CROWDSEC_IMAGE:-"crowdsecurity/crowdsec:v1.8.1"} # Reverse proxy configuration REVERSE_PROXY_TYPE="0" TRAEFIK_EXTERNAL_NETWORK="" @@ -613,6 +634,7 @@ generate_configuration_files() { # Render docker-compose and proxy config based on selection case "$REVERSE_PROXY_TYPE" in 0) + check_traefik_image || return 1 render_docker_compose_traefik_builtin > docker-compose.yml if [[ "$ENABLE_PROXY" == "true" ]]; then # Create placeholder proxy.env so docker-compose can validate @@ -912,11 +934,13 @@ services: # Docker provider - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - - "--providers.docker.network=netbird" + - "--providers.docker.network=\${COMPOSE_PROJECT_NAME:-}_netbird" # Entrypoints - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--entrypoints.websecure.allowACMEByPass=true" + - "--entrypoints.web.http.aliasHeadersStrategy=delete" + - "--entrypoints.websecure.http.aliasHeadersStrategy=delete" # Disable timeouts for long-lived gRPC streams - "--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0" - "--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0" @@ -1779,4 +1803,6 @@ print_post_setup_instructions() { return 0 } -init_environment +if [[ "${BASH_SOURCE[0]:-$0}" == "$0" ]]; then + init_environment +fi diff --git a/infrastructure_files/migrate.sh b/infrastructure_files/migrate.sh index 67895fab6..46971a57a 100755 --- a/infrastructure_files/migrate.sh +++ b/infrastructure_files/migrate.sh @@ -838,7 +838,7 @@ generate_docker_compose_traefik() { services: # Traefik reverse proxy (automatic TLS via Let's Encrypt) traefik: - image: traefik:v3.6 + image: traefik:v3.7.14 container_name: netbird-traefik restart: unless-stopped networks: @@ -851,11 +851,13 @@ services: # Docker provider - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - - "--providers.docker.network=netbird" + - "--providers.docker.network=\${COMPOSE_PROJECT_NAME:-}_netbird" # Entrypoints - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--entrypoints.websecure.allowACMEByPass=true" + - "--entrypoints.web.http.aliasHeadersStrategy=delete" + - "--entrypoints.websecure.http.aliasHeadersStrategy=delete" # Disable timeouts for long-lived gRPC streams - "--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0" - "--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0" @@ -1283,4 +1285,6 @@ main() { return 0 } -main "$@" +if [[ "${BASH_SOURCE[0]:-$0}" == "$0" ]]; then + main "$@" +fi diff --git a/infrastructure_files/tests/test-getting-started-enterprise.sh b/infrastructure_files/tests/test-getting-started-enterprise.sh new file mode 100644 index 000000000..9335caf2a --- /dev/null +++ b/infrastructure_files/tests/test-getting-started-enterprise.sh @@ -0,0 +1,96 @@ +#!/bin/bash + +set -euo pipefail + +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +source "$script_dir/../getting-started-enterprise.sh" + +test_dir=$(mktemp -d) +trap 'rm -rf "$test_dir"' EXIT +cases=0 + +for errexit in +o -o; do + for pipefail in +o -o; do + bash -c ' + set "$1" errexit + set "$2" pipefail + before_options=$SHELLOPTS + source "$3" + if [[ "$SHELLOPTS" != "$before_options" ]]; then + echo "Sourcing changed shell options: $before_options -> $SHELLOPTS" >&2 + exit 1 + fi + ' bash "$errexit" "$pipefail" "$script_dir/../getting-started-enterprise.sh" + done +done + +assert_tag() { + local expected_status="$1" expected_message="$2" actual_status=0 + shift 2 + local label="${1-}" + + # Each call needs its own process because die exits rather than returning. + ( + unset NETBIRD_TRAEFIK_TAG + if [[ $# -gt 0 ]]; then + NETBIRD_TRAEFIK_TAG="$1" + fi + check_traefik_tag + ) >"$test_dir/stdout" 2>"$test_dir/stderr" || actual_status=$? + + if [[ "$actual_status" != "$expected_status" ]]; then + echo "Tag '$label': expected exit $expected_status, got $actual_status" >&2 + cat "$test_dir/stderr" >&2 + exit 1 + fi + if [[ -s "$test_dir/stdout" ]]; then + echo "Tag '$label': unexpected output on stdout" >&2 + cat "$test_dir/stdout" >&2 + exit 1 + fi + if [[ -n "$expected_message" ]]; then + if ! grep -Fq "$expected_message" "$test_dir/stderr"; then + echo "Tag '$label': expected diagnostic '$expected_message'" >&2 + cat "$test_dir/stderr" >&2 + exit 1 + fi + elif [[ -s "$test_dir/stderr" ]]; then + echo "Tag '$label': unexpected output on stderr" >&2 + cat "$test_dir/stderr" >&2 + exit 1 + fi + cases=$((cases + 1)) +} + +# Unset and empty overrides both select the installer's default image. +assert_tag 0 "" +assert_tag 0 "" "" + +for tag in v3.7.13 3.7.13 v3.7.14 v3.8 3.8 v4.0 v4.0.0 \ + v03.07.013 v3.08.0 v3.7.019; do + assert_tag 0 "" "$tag" +done + +for tag in v2.11.0 v3.6.20 v3.7 3.7 v3.7.11 v3.7.12 3.7.12 \ + v03.07.012 v3.7.08; do + assert_tag 1 "predates aliasHeadersStrategy" "$tag" +done + +for tag in latest custom-build v2 v3 sha256:abcdef v3.7.11-alpine \ + v3.7.13-alpine v3.7.13-rc1 v3.7.14+custom v3.7.14@sha256:abcdef \ + 3..14 v3.7.14.0 " 3.7.14" $'v3.7.14\n'; do + assert_tag 1 "Cannot verify NETBIRD_TRAEFIK_TAG=" "$tag" +done + +# Re-rendering an install that has no header config must not add a blank line +# to the Traefik command list. +for header_config in "" "$TRAEFIK_HEADER_CONFIG"; do + rendered=$(TRAEFIK_HEADER_CONFIG="$header_config" render_compose_common) + if [[ "$(printf '%s\n' "$rendered" | service_block traefik)" == *$'\n\n'* ]]; then + echo "Traefik command list has a blank line (header config: '${header_config:-}')" >&2 + exit 1 + fi + cases=$((cases + 1)) +done + +echo "Passed $cases Traefik tag validation cases." diff --git a/infrastructure_files/tests/test-getting-started-networks.sh b/infrastructure_files/tests/test-getting-started-networks.sh new file mode 100644 index 000000000..9283a80bc --- /dev/null +++ b/infrastructure_files/tests/test-getting-started-networks.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +set -eo pipefail + +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +source "$script_dir/../getting-started.sh" +compose_command=$(check_docker_compose) +read -r -a compose <<< "$compose_command" + +test_dir=$(mktemp -d) +trap 'rm -rf "$test_dir"' EXIT +mkdir "$test_dir/deployment" +cd "$test_dir/deployment" + +unset COMPOSE_PROJECT_NAME COMPOSE_FILE COMPOSE_ENV_FILES +# The generator's environment must not bake a project name into the output. +if [[ "${1:-}" == "--migration" ]]; then + COMPOSE_PROJECT_NAME=render-only bash -s -- "$script_dir/../migrate.sh" >/dev/null <<'EOF' +source "$1" +INSTALL_DIR=$PWD +DOMAIN=netbird.test +generate_dashboard_env +generate_docker_compose_traefik +EOF +else + TRAEFIK_IMAGE=traefik:v3.7.14 + NETBIRD_DOMAIN=netbird.test + NETBIRD_AGENT_NETWORK=false + initialize_default_values + COMPOSE_PROJECT_NAME=render-only generate_configuration_files >/dev/null +fi + +assert_network() { + local expected="$1" actual provider + shift + "${compose[@]}" "$@" config --format json >config.json + actual=$(jq -r '.networks.netbird.name' config.json) + provider=$(jq -r '.services.traefik.command[] | select(startswith("--providers.docker.network="))' config.json) + if [[ "$actual" != "$expected" || "$provider" != "--providers.docker.network=$expected" ]]; then + echo "Expected network '$expected'; Compose chose '$actual', Traefik received '$provider'" >&2 + exit 1 + fi +} + +assert_network deployment_netbird +COMPOSE_PROJECT_NAME=env-project assert_network env-project_netbird +echo 'COMPOSE_PROJECT_NAME=file-project' >.env +assert_network file-project_netbird +COMPOSE_PROJECT_NAME=env-project assert_network cli-project_netbird -p cli-project +: >.env +echo 'name: config-project' >>docker-compose.yml +assert_network config-project_netbird + +echo "Passed 5 Compose network scoping cases." diff --git a/infrastructure_files/tests/test-getting-started.sh b/infrastructure_files/tests/test-getting-started.sh new file mode 100644 index 000000000..0826340e5 --- /dev/null +++ b/infrastructure_files/tests/test-getting-started.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +set -eo pipefail + +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +source "$script_dir/../getting-started.sh" + +test_dir=$(mktemp -d) +trap 'rm -rf "$test_dir"' EXIT +cases=0 + +assert_image() { + local expected_status="$1" expected_message="$2" image="$3" actual_status=0 + local case_dir="$test_dir/$cases" + mkdir "$case_dir" + + ( + cd "$case_dir" + TRAEFIK_IMAGE="$image" + NETBIRD_DOMAIN=netbird.test + NETBIRD_AGENT_NETWORK=false + initialize_default_values + generate_configuration_files + ) >"$case_dir/stdout" 2>"$case_dir/stderr" || actual_status=$? + + if [[ "$actual_status" != "$expected_status" ]]; then + echo "Image '$image': expected exit $expected_status, got $actual_status" >&2 + cat "$case_dir/stderr" >&2 + exit 1 + fi + if [[ "$expected_status" == 0 ]]; then + if [[ -s "$case_dir/stderr" ]]; then + cat "$case_dir/stderr" >&2 + exit 1 + fi + for entrypoint in web websecure; do + if ! grep -Fq -- "--entrypoints.$entrypoint.http.aliasHeadersStrategy=delete" "$case_dir/docker-compose.yml"; then + echo "Image '$image': missing header protection for $entrypoint" >&2 + exit 1 + fi + done + if ! grep -Fxq " image: ${image:-traefik:v3.7.14}" "$case_dir/docker-compose.yml"; then + echo "Image '$image': unexpected rendered image" >&2 + exit 1 + fi + else + if ! grep -Fq "$expected_message" "$case_dir/stderr"; then + echo "Image '$image': expected diagnostic '$expected_message'" >&2 + cat "$case_dir/stderr" >&2 + exit 1 + fi + if [[ -e "$case_dir/docker-compose.yml" || -e "$case_dir/config.yaml" || -e "$case_dir/dashboard.env" ]]; then + echo "Image '$image': generated configuration despite failed validation" >&2 + exit 1 + fi + fi + cases=$((cases + 1)) +} + +# An empty override selects the default image. +assert_image 0 "" "" +for image in traefik:v3.7.13 traefik:3.7.13 traefik:v3.7.14 traefik:v3.8 \ + traefik:v4 traefik:v4.0.0 traefik:v03.07.013 traefik:v3.08.0 \ + registry.example:5000/team/traefik:3.7.13; do + assert_image 0 "" "$image" +done + +for tag in v2 v2.11.0 v3 v3.6.20 v3.7 v3.7.11 v3.7.12 v03.07.012 v3.7.08; do + assert_image 1 "incompatible with this configuration" "traefik:$tag" +done + +digest="sha256:$(printf '%064d' 0)" +for image in traefik:latest traefik:custom-build traefik registry.example:5000/traefik \ + "traefik@$digest" "registry.example:5000/team/traefik@$digest" \ + "traefik:v3.7.13@$digest" "traefik:latest@$digest" \ + traefik:v3.7.13-rc1 traefik:v3.7.13-alpine traefik:v3.7.14+custom \ + traefik:3..14 traefik:v3.7.14.0 traefik:custom:v3.7.14 :v3.7.14 \ + "traefik: v3.7.14" $'traefik:v3.7.14\n'; do + assert_image 1 "Cannot verify TRAEFIK_IMAGE=" "$image" +done + +echo "Passed $cases Traefik image configuration cases."