[infrastructure] Fix Traefik Versioning and Update Crowdsec (#8152)

This commit is contained in:
Brandon Hopkins
2026-10-13 19:46:27 +03:00
committed by GitHub
parent dfa7bd7892
commit 9efe59b9b2
7 changed files with 357 additions and 13 deletions
@@ -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
@@ -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 "<prompt>" [<default y|n>]
@@ -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
+30 -4
View File
@@ -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
+7 -3
View File
@@ -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
@@ -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-<unset>}"
# 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:-<empty>}')" >&2
exit 1
fi
cases=$((cases + 1))
done
echo "Passed $cases Traefik tag validation cases."
@@ -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."
@@ -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."