Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions overlays/config/traefik-config.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,11 @@
# are already taken on this machine.
HTTP_PORT=80
HTTPS_PORT=443

# Behind your own reverse proxy (nginx, Nginx Proxy Manager, HAProxy, ...) every
# request reaches the panel from the proxy's address, so the panel can't tell
# visitors apart and 5Stack can't see this server's public address. List the
# address the proxy connects from (or a range, separated by commas) and the
# panel believes its X-Forwarded-For instead. Only ever your own proxy: anyone
# listed here can claim to be any visitor. Run ./update.sh after changing it.
TRUSTED_PROXIES=
17 changes: 17 additions & 0 deletions tests/gateway/fresh_install_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,21 @@ test_http_mode_also_answers_on_443_for_a_proxy_that_forwards_there() {
done
}

# Without TRUSTED_PROXIES the sender is the visitor, whatever it says.
test_an_unlisted_sender_cannot_name_the_visitor() {
wait_for 200 http "$API" /me -H 'X-Forwarded-For: 203.0.113.7'
expect_json '.headers["x-forwarded-for"]' "$CLIENT_IP"
}

test_a_trusted_proxy_names_the_visitor() {
echo "TRUSTED_PROXIES=$CLIENT_IP" >>"$PANEL_DIR/overlays/config/traefik-config.env"
install_traefik "$PANEL_DIR/overlays/config/traefik-config.env" >/dev/null || fail "install_traefik failed"
wait_for 200 http "$API" /me -H 'X-Forwarded-For: 203.0.113.7'
expect_json '.headers["x-forwarded-for"]' "203.0.113.7, $CLIENT_IP"
wait_for 200 https "$API" /me -H 'X-Forwarded-For: 203.0.113.7'
expect_json '.headers["x-forwarded-for"]' "203.0.113.7, $CLIENT_IP"
}

start_cluster || exit 1
make_panel || exit 1
run_test test_every_panel_route_names_its_listeners_host
Expand Down Expand Up @@ -704,5 +719,7 @@ run_test test_http_mode_also_answers_on_443_for_a_proxy_that_forwards_there
run_test test_http_mode_shares_443_with_the_shared_gateway
run_test test_an_upgrade_keeps_the_shared_certificate
run_test test_an_upgrade_renews_a_shared_certificate_about_to_expire
run_test test_an_unlisted_sender_cannot_name_the_visitor
run_test test_a_trusted_proxy_names_the_visitor

summary
33 changes: 33 additions & 0 deletions tests/scripts/traefik_install_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,39 @@ test_traefik_waits_behind_servicelb_while_ingress_nginx_holds_the_ports() {
assert_not_contains "$(gateway_values)" "hostPort"
}

test_no_proxy_is_trusted_until_one_is_listed() {
ports_file HTTP_PORT=80 HTTPS_PORT=443 TRUSTED_PROXIES=
run_install install_traefik
assert_not_contains "$(gateway_values)" "forwardedHeaders"
}

test_listed_proxies_are_trusted_on_both_ports() {
ports_file HTTP_PORT=80 HTTPS_PORT=443 'TRUSTED_PROXIES=10.0.2.61, 192.168.0.0/24,fd00::1'
run_install install_traefik
assert_contains "$(gateway_values)" 'web: {exposedPort: 80, hostPort: 80, forwardedHeaders: {trustedIPs: ["10.0.2.61", "192.168.0.0/24", "fd00::1"]}}'
assert_contains "$(gateway_values)" 'websecure: {exposedPort: 443, hostPort: 443, forwardedHeaders: {trustedIPs: ["10.0.2.61", "192.168.0.0/24", "fd00::1"]}}'
}

test_listed_proxies_are_trusted_behind_servicelb_too() {
nginx_ports 80 443
ports_file HTTP_PORT=80 HTTPS_PORT=443 TRUSTED_PROXIES=10.0.2.61
run_install install_traefik
assert_contains "$(gateway_values)" 'web: {exposedPort: 80, forwardedHeaders: {trustedIPs: ["10.0.2.61"]}}'
}

test_a_trusted_proxy_that_is_not_an_address_stops_the_install() {
local bad
for bad in proxy.example.com 0.0.0.0/0 ::/0 10.0.2.61/33 '10.0.2.61;true' '10.0.2.61]}'; do
ports_file HTTP_PORT=80 HTTPS_PORT=443 "TRUSTED_PROXIES=$bad"
rm -f "$WORK_DIR/server-side-applied"
if try_install install_traefik; then
fail "install_traefik took TRUSTED_PROXIES=$bad"
fi
assert_contains "$(cat "$WORK_DIR/output")" "TRUSTED_PROXIES"
assert_eq "$(gateway_values)" ""
done
}

test_traefik_left_behind_servicelb_moves_onto_the_host_ports() {
echo LoadBalancer >"$WORK_DIR/traefik-type"
ports_file HTTP_PORT=80 HTTPS_PORT=443
Expand Down
53 changes: 42 additions & 11 deletions utils/install_traefik.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ carry_over_ingress_nginx_ports() {

install_traefik() {
local ports_env="${1:-$PANEL_DIR/overlays/config/traefik-config.env}"
local minor http_port https_port crd behind nginx crds_values left_out held object deadline
local minor http_port https_port trusted crd behind nginx crds_values left_out held object deadline
local -a kc=() handed=()
[ -n "${KUBECONFIG:-}" ] && kc=(--kubeconfig="$KUBECONFIG")

Expand All @@ -61,6 +61,10 @@ install_traefik() {
err "HTTP_PORT and HTTPS_PORT in $ports_env must be port numbers"
return 1
fi
trusted="$(trusted_proxy_list "$(grep -s '^TRUSTED_PROXIES=' "$ports_env" | tail -n 1 | cut -d= -f2- | tr -d '\r" ')")" || {
err "TRUSTED_PROXIES in $ports_env must be IP addresses or CIDR ranges separated by commas, and never everyone (/0)"
return 1
}

# Where k3s installs the Gateway API itself, gateway-crds brings only
# Traefik's CRDs. One that still holds k3s's objects, from before k3s was
Expand Down Expand Up @@ -123,7 +127,7 @@ install_traefik() {

pull_traefik_image || return 1

install_helm_chart gateway "$PANEL_DIR/charts/gateway" "$(gateway_chart_values "$http_port" "$https_port" "$behind")" || return 1
install_helm_chart gateway "$PANEL_DIR/charts/gateway" "$(gateway_chart_values "$http_port" "$https_port" "$behind" "$trusted")" || return 1

if ! kubectl "${kc[@]}" -n traefik rollout status deployment/traefik --timeout=300s; then
err "Traefik never became ready"
Expand Down Expand Up @@ -163,23 +167,50 @@ pull_traefik_image() {
output_redirect kubectl "${kc[@]}" -n traefik delete pod traefik-pull --wait=false
}

# gateway_chart_values HTTP_PORT HTTPS_PORT [servicelb]: the gateway chart's
# values. Traefik binds the host's ports itself, or with servicelb sits behind
# ServiceLB while ingress-nginx still holds them. Only the node running
# trusted_proxy_list ADDRESSES: TRUSTED_PROXIES as the YAML list Traefik takes,
# or nothing when it is empty. Fails on anything that isn't an address or a
# range, and on /0: trusting everyone lets any visitor name its own address.
trusted_proxy_list() {
local entry list=""
local v4='^([0-9]{1,3}\.){3}[0-9]{1,3}(/([1-9]|[12][0-9]|3[0-2]))?$'
local v6='^[0-9a-fA-F]*:[0-9a-fA-F:]*(/([1-9]|[1-9][0-9]|1[01][0-9]|12[0-8]))?$'
local -a entries=()
IFS=',' read -r -a entries <<<"$1"
for entry in "${entries[@]}"; do
if [ -z "$entry" ]; then
continue
fi
if ! [[ "$entry" =~ $v4 || "$entry" =~ $v6 ]]; then
return 1
fi
list="${list:+$list, }\"$entry\""
done
if [ -n "$list" ]; then
printf '[%s]' "$list"
fi
}

# gateway_chart_values HTTP_PORT HTTPS_PORT [servicelb] [TRUSTED]: the gateway
# chart's values. Traefik binds the host's ports itself, or with servicelb sits
# behind ServiceLB while ingress-nginx still holds them. Only the node running
# Traefik listens on these ports, and DNS points at the server node, not at a
# game node that joined later.
# game node that joined later. TRUSTED is trusted_proxy_list's: the reverse
# proxies whose X-Forwarded-* Traefik keeps instead of writing its own.
gateway_chart_values() {
local binding
local binding trust=""
if [ -n "${4:-}" ]; then
trust=", forwardedHeaders: {trustedIPs: $4}"
fi
if [ "${3:-}" = servicelb ]; then
binding=" ports:
web: {exposedPort: $1}
websecure: {exposedPort: $2}
web: {exposedPort: $1$trust}
websecure: {exposedPort: $2$trust}
service:
spec: {type: LoadBalancer, externalTrafficPolicy: Local}"
else
binding=" ports:
web: {exposedPort: $1, hostPort: $1}
websecure: {exposedPort: $2, hostPort: $2}"
web: {exposedPort: $1, hostPort: $1$trust}
websecure: {exposedPort: $2, hostPort: $2$trust}"
fi
cat <<EOF
traefik:
Expand Down
Loading