Compare commits

...

14 Commits

Author SHA1 Message Date
Diego Noguês
76fb153d76 fix: small conflicts 2026-02-12 18:41:57 +01:00
Diego Noguês
eee4d75932 chore: switching env var for NB_PROXY_DOMAIN 2026-02-12 18:35:30 +01:00
Diego Noguês
62b8875f67 chore: build 2026-02-12 17:42:03 +01:00
Diego Noguês
47a5478964 chore: remove unnecessary comment 2026-02-12 17:18:23 +01:00
Diego Noguês
9922d6f953 feat: switch to labels for traefik instead of static conf files 2026-02-12 17:18:23 +01:00
Diego Noguês
f9bab22f61 fix: remove change to peers group all 2026-02-12 17:18:23 +01:00
Diego Noguês
3d8fdb7a89 feat: adding IPAM settings to docker compose and setting static ip to traefik 2026-02-12 17:18:23 +01:00
Diego Noguês
fb10153ab8 feat: adding traefik and proxy component to getting-started 2026-02-12 17:17:30 +01:00
Diego Noguês
b00babb8b1 feat: switch to labels for traefik instead of static conf files 2026-02-12 16:50:43 +01:00
Diego Noguês
3bc8cbb13f fix: remove change to peers group all 2026-02-12 14:06:23 +01:00
Diego Noguês
bf7bdf6c4f feat: adding IPAM settings to docker compose and setting static ip to traefik 2026-02-12 14:02:21 +01:00
Diego Noguês
0a895ffc22 Merge branch 'dn-reverse-proxy' of github.com:netbirdio/netbird into dn-reverse-proxy 2026-02-12 12:00:17 +01:00
Diego Noguês
b81837a364 feat: adding traefik and proxy component to getting-started 2026-02-12 11:16:37 +01:00
Diego Noguês
b782ac6f56 feat: adding traefik and proxy component to getting-started 2026-02-11 15:34:18 +01:00
4 changed files with 503 additions and 23 deletions

View File

@@ -91,16 +91,17 @@ read_reverse_proxy_type() {
echo " [3] Nginx Proxy Manager (generates config + instructions)" > /dev/stderr
echo " [4] External Caddy (generates Caddyfile snippet)" > /dev/stderr
echo " [5] Other/Manual (displays setup documentation)" > /dev/stderr
echo " [6] Traefik TCP Proxy (single port 443 + STUN)" > /dev/stderr
echo "" > /dev/stderr
echo -n "Enter choice [0-5] (default: 0): " > /dev/stderr
echo -n "Enter choice [0-6] (default: 0): " > /dev/stderr
read -r CHOICE < /dev/tty
if [[ -z "$CHOICE" ]]; then
CHOICE="0"
fi
if [[ ! "$CHOICE" =~ ^[0-5]$ ]]; then
echo "Invalid choice. Please enter a number between 0 and 5." > /dev/stderr
if [[ ! "$CHOICE" =~ ^[0-6]$ ]]; then
echo "Invalid choice. Please enter a number between 0 and 6." > /dev/stderr
read_reverse_proxy_type
return
fi
@@ -140,6 +141,35 @@ read_traefik_certresolver() {
return 0
}
read_traefik_tcp_acme_email() {
echo "" > /dev/stderr
echo "Enter your email for Let's Encrypt certificate notifications." > /dev/stderr
echo -n "Email address: " > /dev/stderr
read -r EMAIL < /dev/tty
if [[ -z "$EMAIL" ]]; then
echo "Email is required for Let's Encrypt." > /dev/stderr
read_traefik_tcp_acme_email
return
fi
echo "$EMAIL"
return 0
}
read_enable_proxy() {
echo "" > /dev/stderr
echo "Do you want to enable the NetBird Proxy service?" > /dev/stderr
echo "The proxy exposes internal NetBird network resources to the internet." > /dev/stderr
echo -n "Enable proxy? [y/N]: " > /dev/stderr
read -r CHOICE < /dev/tty
if [[ "$CHOICE" =~ ^[Yy]$ ]]; then
echo "true"
else
echo "false"
fi
return 0
}
read_port_binding_preference() {
echo "" > /dev/stderr
echo "Should container ports be bound to localhost only (127.0.0.1)?" > /dev/stderr
@@ -206,6 +236,30 @@ wait_management() {
return 0
}
wait_management_traefik() {
set +e
echo -n "Waiting for Management server to become ready"
counter=1
while true; do
# Check the embedded IdP endpoint through Traefik
if curl -sk -f -o /dev/null "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2/.well-known/openid-configuration" 2>/dev/null; then
break
fi
if [[ $counter -eq 60 ]]; then
echo ""
echo "Taking too long. Checking logs..."
$DOCKER_COMPOSE_COMMAND logs --tail=20 traefik
$DOCKER_COMPOSE_COMMAND logs --tail=20 management
fi
echo -n " ."
sleep 2
counter=$((counter + 1))
done
echo " done"
set -e
return 0
}
wait_management_direct() {
set +e
local upstream_host=$(get_upstream_host)
@@ -246,10 +300,12 @@ initialize_default_values() {
# Docker images
CADDY_IMAGE="caddy"
DASHBOARD_IMAGE="netbirdio/dashboard:latest"
#DASHBOARD_IMAGE="netbirdio/dashboard:latest"
DASHBOARD_IMAGE="netbirdio/dashboard:pr-552"
SIGNAL_IMAGE="netbirdio/signal:latest"
RELAY_IMAGE="netbirdio/relay:latest"
MANAGEMENT_IMAGE="netbirdio/management:latest"
MANAGEMENT_IMAGE="netbirdio/management:latest"
PROXY_IMAGE=""
# Reverse proxy configuration
REVERSE_PROXY_TYPE="0"
@@ -263,6 +319,14 @@ initialize_default_values() {
RELAY_HOST_PORT="8084"
BIND_LOCALHOST_ONLY="true"
EXTERNAL_PROXY_NETWORK=""
# Traefik TCP proxy configuration
TRAEFIK_IMAGE="traefik:v3.6"
TRAEFIK_TCP_ACME_EMAIL=""
# NetBird Proxy configuration
ENABLE_PROXY="false"
PROXY_TOKEN=""
return 0
}
@@ -293,8 +357,17 @@ configure_reverse_proxy() {
TRAEFIK_CERTRESOLVER=$(read_traefik_certresolver)
fi
# Handle Traefik TCP proxy prompts
if [[ "$REVERSE_PROXY_TYPE" == "6" ]]; then
TRAEFIK_TCP_ACME_EMAIL=$(read_traefik_tcp_acme_email)
# Prompt for NetBird Proxy configuration
ENABLE_PROXY=$(read_enable_proxy)
# Note: PROXY_TOKEN will be auto-generated after Management starts
fi
# Handle port binding for external proxy options (2-5)
if [[ "$REVERSE_PROXY_TYPE" -ge 2 ]]; then
if [[ "$REVERSE_PROXY_TYPE" -ge 2 && "$REVERSE_PROXY_TYPE" -le 5 ]]; then
BIND_LOCALHOST_ONLY=$(read_port_binding_preference)
fi
@@ -313,7 +386,7 @@ check_existing_installation() {
echo "Generated files already exist, if you want to reinitialize the environment, please remove them first."
echo "You can use the following commands:"
echo " $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes"
echo " rm -f docker-compose.yml Caddyfile dashboard.env management.json relay.env nginx-netbird.conf caddyfile-netbird.txt npm-advanced-config.txt"
echo " rm -f docker-compose.yml Caddyfile dashboard.env management.json relay.env nginx-netbird.conf caddyfile-netbird.txt npm-advanced-config.txt proxy.env"
echo "Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard."
exit 1
fi
@@ -347,6 +420,15 @@ generate_configuration_files() {
5)
render_docker_compose_exposed_ports > docker-compose.yml
;;
6)
render_docker_compose_traefik_tcp > docker-compose.yml
if [[ "$ENABLE_PROXY" == "true" ]]; then
# Create placeholder proxy.env so docker-compose can validate
# This will be overwritten with the actual token after Management starts
echo "# Placeholder - will be updated with token after Management starts" > proxy.env
echo "NB_PROXY_TOKEN=placeholder" >> proxy.env
fi
;;
*)
echo "Invalid reverse proxy type: $REVERSE_PROXY_TYPE" > /dev/stderr
exit 1
@@ -402,6 +484,50 @@ start_services_and_show_instructions() {
echo ""
echo "NetBird containers are running. Configure NPM as shown above, then access:"
echo " $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN"
elif [[ "$REVERSE_PROXY_TYPE" == "6" ]]; then
# Traefik TCP Proxy - two-phase startup if proxy is enabled
echo -e "$MSG_STARTING_SERVICES"
if [[ "$ENABLE_PROXY" == "true" ]]; then
# Phase 1: Start core services (without proxy)
echo "Starting core services..."
$DOCKER_COMPOSE_COMMAND up -d traefik dashboard signal relay management
sleep 3
wait_management_traefik
# Phase 2: Create proxy token and start proxy
echo ""
echo "Creating proxy access token..."
# Use docker exec with bash to run the token command directly
# (bypassing the entrypoint which adds 'management' as first arg)
PROXY_TOKEN=$($DOCKER_COMPOSE_COMMAND exec -T management \
bash -c '/go/bin/netbird-mgmt token create --name "default-proxy" --config /etc/netbird/management.json' 2>/dev/null | grep "^Token:" | awk '{print $2}')
if [[ -z "$PROXY_TOKEN" ]]; then
echo "ERROR: Failed to create proxy token. Check management logs." > /dev/stderr
$DOCKER_COMPOSE_COMMAND logs --tail=20 management
exit 1
fi
echo "Proxy token created successfully."
# Generate proxy.env with the token
render_proxy_env > proxy.env
# Start proxy service
echo "Starting proxy service..."
$DOCKER_COMPOSE_COMMAND up -d proxy
else
# No proxy - start all services at once
$DOCKER_COMPOSE_COMMAND up -d
sleep 3
wait_management_traefik
fi
echo -e "$MSG_DONE"
print_post_setup_instructions
else
# External proxies (nginx, external Caddy, other) - need manual config first
print_post_setup_instructions
@@ -547,6 +673,29 @@ EOF
return 0
}
render_proxy_env() {
cat <<EOF
# NetBird Proxy Configuration
NB_PROXY_DEBUG_LOGS=false
# Use internal Docker network to connect to management (avoids hairpin NAT issues)
NB_PROXY_MANAGEMENT_ADDRESS=http://management:80
# Allow insecure gRPC connection to management (required for internal Docker network)
NB_PROXY_ALLOW_INSECURE=true
# Public URL where this proxy is reachable (used for cluster registration)
NB_PROXY_DOMAIN=$NETBIRD_DOMAIN
NB_PROXY_ADDRESS=:8443
NB_PROXY_TOKEN=$PROXY_TOKEN
NB_PROXY_CERTIFICATE_DIRECTORY=/certs
NB_PROXY_ACME_CERTIFICATES=true
NB_PROXY_ACME_CHALLENGE_TYPE=tls-alpn-01
NB_PROXY_OIDC_CLIENT_ID=netbird-proxy
NB_PROXY_OIDC_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2
NB_PROXY_OIDC_SCOPES=openid,profile,email
NB_PROXY_FORWARDED_PROTO=https
EOF
return 0
}
render_docker_compose() {
cat <<EOF
services:
@@ -736,7 +885,18 @@ $(if [[ -n "$tls_labels" ]]; then echo " - traefik.http.routers.netbird-rel
# Management (includes embedded IdP)
management:
$(if [[ "$ENABLE_PROXY" == "true" ]]; then
cat <<MGMT_BUILD
build:
context: ..
dockerfile: management/Dockerfile.multistage
pull_policy: build
MGMT_BUILD
else
cat <<MGMT_IMAGE
image: $MANAGEMENT_IMAGE
MGMT_IMAGE
fi)
container_name: netbird-management
restart: unless-stopped
networks: [$network_name]
@@ -1115,6 +1275,258 @@ EOF
return 0
}
render_docker_compose_traefik_tcp() {
# Generate proxy service section if enabled
local proxy_service=""
local proxy_volumes=""
local proxy_tcp_labels=""
if [[ "$ENABLE_PROXY" == "true" ]]; then
proxy_service="
# NetBird Proxy - exposes internal resources to the internet
proxy:
build:
context: ../
dockerfile: proxy/Dockerfile
# Always rebuild to pick up code changes during testing
pull_policy: build
#image: $PROXY_IMAGE
container_name: netbird-proxy
# Hairpin NAT fix: route domain back to traefik's static IP within Docker
extra_hosts:
- \"$NETBIRD_DOMAIN:172.30.0.10\"
restart: unless-stopped
networks: [netbird]
depends_on:
- signal
env_file:
- ./proxy.env
volumes:
- netbird_proxy_certs:/certs
labels:
# TCP passthrough for any unmatched domain (proxy handles its own TLS)
- traefik.enable=true
- traefik.tcp.routers.proxy-passthrough.entrypoints=websecure
- traefik.tcp.routers.proxy-passthrough.rule=HostSNI(\`*\`)
- traefik.tcp.routers.proxy-passthrough.tls.passthrough=true
- traefik.tcp.routers.proxy-passthrough.service=proxy-tls
- traefik.tcp.routers.proxy-passthrough.priority=1
- traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443
logging:
driver: \"json-file\"
options:
max-size: \"500m\"
max-file: \"2\"
"
proxy_volumes="
netbird_proxy_certs:"
fi
cat <<EOF
services:
# Traefik - single port 443 entry point with TLS termination
traefik:
image: $TRAEFIK_IMAGE
container_name: netbird-traefik
restart: unless-stopped
networks:
netbird:
ipv4_address: 172.30.0.10
ports:
- '443:443'
volumes:
- netbird_traefik_data:/data
- /var/run/docker.sock:/var/run/docker.sock:ro
command:
# Logging
- --log.level=INFO
- --accesslog=true
# Docker provider
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --providers.docker.network=netbird
# Entrypoints
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.allowACMEByPass=true
# Disable timeouts for long-lived gRPC streams
- --entrypoints.websecure.transport.respondingtimeouts.readtimeout=0s
- --entrypoints.websecure.transport.respondingtimeouts.writetimeout=0s
- --entrypoints.websecure.transport.respondingtimeouts.idletimeout=0s
# Let's Encrypt ACME
- --certificatesresolvers.letsencrypt.acme.email=$TRAEFIK_TCP_ACME_EMAIL
- --certificatesresolvers.letsencrypt.acme.storage=/data/acme.json
- --certificatesresolvers.letsencrypt.acme.tlschallenge=true
# gRPC transport settings (disable response timeout for long-lived streams)
- --serverstransport.forwardingtimeouts.responseheadertimeout=0s
- --serverstransport.forwardingtimeouts.idleconntimeout=0s
logging:
driver: "json-file"
options:
max-size: "500m"
max-file: "2"
# UI dashboard
dashboard:
image: $DASHBOARD_IMAGE
container_name: netbird-dashboard
restart: unless-stopped
networks: [netbird]
env_file:
- ./dashboard.env
labels:
- traefik.enable=true
- traefik.http.routers.netbird-dashboard.entrypoints=websecure
- traefik.http.routers.netbird-dashboard.rule=Host(\`$NETBIRD_DOMAIN\`)
- traefik.http.routers.netbird-dashboard.tls=true
- traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt
- traefik.http.routers.netbird-dashboard.service=dashboard
- traefik.http.routers.netbird-dashboard.priority=1
- traefik.http.services.dashboard.loadbalancer.server.port=80
logging:
driver: "json-file"
options:
max-size: "500m"
max-file: "2"
# Signal
signal:
image: $SIGNAL_IMAGE
container_name: netbird-signal
restart: unless-stopped
networks: [netbird]
labels:
- traefik.enable=true
# Signal WebSocket
- traefik.http.routers.netbird-signal-ws.entrypoints=websecure
- traefik.http.routers.netbird-signal-ws.rule=Host(\`$NETBIRD_DOMAIN\`) && PathPrefix(\`/ws-proxy/signal\`)
- traefik.http.routers.netbird-signal-ws.tls=true
- traefik.http.routers.netbird-signal-ws.tls.certresolver=letsencrypt
- traefik.http.routers.netbird-signal-ws.service=signal-ws
- traefik.http.routers.netbird-signal-ws.priority=100
- traefik.http.services.signal-ws.loadbalancer.server.port=80
# Signal gRPC
- traefik.http.routers.netbird-signal-grpc.entrypoints=websecure
- traefik.http.routers.netbird-signal-grpc.rule=Host(\`$NETBIRD_DOMAIN\`) && PathPrefix(\`/signalexchange.SignalExchange/\`)
- traefik.http.routers.netbird-signal-grpc.tls=true
- traefik.http.routers.netbird-signal-grpc.tls.certresolver=letsencrypt
- traefik.http.routers.netbird-signal-grpc.service=signal-grpc
- traefik.http.routers.netbird-signal-grpc.priority=100
- traefik.http.services.signal-grpc.loadbalancer.server.port=10000
- traefik.http.services.signal-grpc.loadbalancer.server.scheme=h2c
logging:
driver: "json-file"
options:
max-size: "500m"
max-file: "2"
# Relay (includes embedded STUN server)
relay:
image: $RELAY_IMAGE
container_name: netbird-relay
restart: unless-stopped
networks: [netbird]
ports:
- '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'
env_file:
- ./relay.env
labels:
- traefik.enable=true
- traefik.http.routers.netbird-relay.entrypoints=websecure
- traefik.http.routers.netbird-relay.rule=Host(\`$NETBIRD_DOMAIN\`) && PathPrefix(\`/relay\`)
- traefik.http.routers.netbird-relay.tls=true
- traefik.http.routers.netbird-relay.tls.certresolver=letsencrypt
- traefik.http.routers.netbird-relay.service=relay
- traefik.http.routers.netbird-relay.priority=100
- traefik.http.services.relay.loadbalancer.server.port=80
logging:
driver: "json-file"
options:
max-size: "500m"
max-file: "2"
# Management (includes embedded IdP)
management:
$(if [[ "$ENABLE_PROXY" == "true" ]]; then
cat <<MGMT_BUILD
build:
context: ..
dockerfile: management/Dockerfile.multistage
pull_policy: build
MGMT_BUILD
else
cat <<MGMT_IMAGE
image: $MANAGEMENT_IMAGE
MGMT_IMAGE
fi)
container_name: netbird-management
restart: unless-stopped
networks: [netbird]
volumes:
- netbird_management:/var/lib/netbird
- ./management.json:/etc/netbird/management.json
command: [
"--port", "80",
"--log-file", "console",
"--log-level", "info",
"--disable-anonymous-metrics=false",
"--single-account-mode-domain=netbird.selfhosted",
"--dns-domain=netbird.selfhosted",
"--idp-sign-key-refresh-enabled",
]
labels:
- traefik.enable=true
# Management API
- traefik.http.routers.netbird-api.entrypoints=websecure
- traefik.http.routers.netbird-api.rule=Host(\`$NETBIRD_DOMAIN\`) && PathPrefix(\`/api\`)
- traefik.http.routers.netbird-api.tls=true
- traefik.http.routers.netbird-api.tls.certresolver=letsencrypt
- traefik.http.routers.netbird-api.service=management
- traefik.http.routers.netbird-api.priority=100
# Management WebSocket
- traefik.http.routers.netbird-mgmt-ws.entrypoints=websecure
- traefik.http.routers.netbird-mgmt-ws.rule=Host(\`$NETBIRD_DOMAIN\`) && PathPrefix(\`/ws-proxy/management\`)
- traefik.http.routers.netbird-mgmt-ws.tls=true
- traefik.http.routers.netbird-mgmt-ws.tls.certresolver=letsencrypt
- traefik.http.routers.netbird-mgmt-ws.service=management
- traefik.http.routers.netbird-mgmt-ws.priority=100
# Management gRPC
- traefik.http.routers.netbird-mgmt-grpc.entrypoints=websecure
- traefik.http.routers.netbird-mgmt-grpc.rule=Host(\`$NETBIRD_DOMAIN\`) && PathPrefix(\`/management.ManagementService/\`)
- traefik.http.routers.netbird-mgmt-grpc.tls=true
- traefik.http.routers.netbird-mgmt-grpc.tls.certresolver=letsencrypt
- traefik.http.routers.netbird-mgmt-grpc.service=management-grpc
- traefik.http.routers.netbird-mgmt-grpc.priority=100
# OAuth2 (embedded IdP)
- traefik.http.routers.netbird-oauth2.entrypoints=websecure
- traefik.http.routers.netbird-oauth2.rule=Host(\`$NETBIRD_DOMAIN\`) && PathPrefix(\`/oauth2\`)
- traefik.http.routers.netbird-oauth2.tls=true
- traefik.http.routers.netbird-oauth2.tls.certresolver=letsencrypt
- traefik.http.routers.netbird-oauth2.service=management
- traefik.http.routers.netbird-oauth2.priority=100
# Services
- traefik.http.services.management.loadbalancer.server.port=80
- traefik.http.services.management-grpc.loadbalancer.server.port=80
- traefik.http.services.management-grpc.loadbalancer.server.scheme=h2c
logging:
driver: "json-file"
options:
max-size: "500m"
max-file: "2"
${proxy_service}
volumes:
netbird_traefik_data:
netbird_management:${proxy_volumes}
networks:
netbird:
driver: bridge
ipam:
config:
- subnet: 172.30.0.0/24
gateway: 172.30.0.1
EOF
return 0
}
render_npm_advanced_config() {
local upstream_host=$(get_upstream_host)
local relay_addr="${upstream_host}:${RELAY_HOST_PORT}"
@@ -1424,6 +1836,36 @@ print_manual_instructions() {
return 0
}
print_traefik_tcp_instructions() {
echo ""
echo "$MSG_SEPARATOR"
echo " TRAEFIK TCP PROXY SETUP"
echo "$MSG_SEPARATOR"
echo ""
echo "This configuration uses Traefik as a single entry point on port 443."
echo "Traefik handles TLS termination with Let's Encrypt and routes to services."
echo ""
echo "Open ports:"
echo " - 443/tcp (HTTPS - all NetBird services)"
echo " - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)"
echo ""
echo "Generated files:"
echo " - docker-compose.yml (container definitions with Traefik labels)"
if [[ "$ENABLE_PROXY" == "true" ]]; then
echo " - proxy.env (NetBird Proxy configuration)"
echo ""
echo "NetBird Proxy:"
echo " The proxy service is enabled and will be built from source."
echo " Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy."
echo " The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge."
echo " Point your proxy domains (CNAMEs) to this server's IP address."
fi
echo ""
echo "You can access the NetBird dashboard at $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN"
echo "Follow the onboarding steps to set up your NetBird instance."
return 0
}
print_post_setup_instructions() {
case "$REVERSE_PROXY_TYPE" in
0)
@@ -1444,6 +1886,9 @@ print_post_setup_instructions() {
5)
print_manual_instructions
;;
6)
print_traefik_tcp_instructions
;;
*)
echo "Unknown reverse proxy type: $REVERSE_PROXY_TYPE" > /dev/stderr
;;

View File

@@ -0,0 +1,17 @@
FROM golang:1.25-bookworm AS builder
WORKDIR /app
# Install build dependencies
RUN apt-get update && apt-get install -y gcc libc6-dev && rm -rf /var/lib/apt/lists/*
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=1 GOOS=linux go build -ldflags="-s -w" -o netbird-mgmt ./management
FROM ubuntu:24.04
RUN apt update && apt install -y ca-certificates && rm -fr /var/cache/apt
ENTRYPOINT [ "/go/bin/netbird-mgmt","management"]
CMD ["--log-file", "console"]
COPY --from=builder /app/netbird-mgmt /go/bin/netbird-mgmt

View File

@@ -39,9 +39,10 @@ var (
addr string
proxyDomain string
certDir string
acmeCerts bool
acmeAddr string
acmeDir string
acmeCerts bool
acmeAddr string
acmeDir string
acmeChallengeType string
debugEndpoint bool
debugEndpointAddr string
healthAddr string
@@ -72,9 +73,10 @@ func init() {
rootCmd.Flags().StringVar(&addr, "addr", envStringOrDefault("NB_PROXY_ADDRESS", ":443"), "Reverse proxy address to listen on")
rootCmd.Flags().StringVar(&proxyDomain, "domain", envStringOrDefault("NB_PROXY_DOMAIN", ""), "The Domain at which this proxy will be reached. e.g., netbird.example.com")
rootCmd.Flags().StringVar(&certDir, "cert-dir", envStringOrDefault("NB_PROXY_CERTIFICATE_DIRECTORY", "./certs"), "Directory to store certificates")
rootCmd.Flags().BoolVar(&acmeCerts, "acme-certs", envBoolOrDefault("NB_PROXY_ACME_CERTIFICATES", false), "Generate ACME certificates using HTTP-01 challenges")
rootCmd.Flags().StringVar(&acmeAddr, "acme-addr", envStringOrDefault("NB_PROXY_ACME_ADDRESS", ":80"), "HTTP address for ACME HTTP-01 challenges")
rootCmd.Flags().BoolVar(&acmeCerts, "acme-certs", envBoolOrDefault("NB_PROXY_ACME_CERTIFICATES", false), "Generate ACME certificates automatically")
rootCmd.Flags().StringVar(&acmeAddr, "acme-addr", envStringOrDefault("NB_PROXY_ACME_ADDRESS", ":80"), "HTTP address for ACME HTTP-01 challenges (only used when acme-challenge-type is http-01)")
rootCmd.Flags().StringVar(&acmeDir, "acme-dir", envStringOrDefault("NB_PROXY_ACME_DIRECTORY", acme.LetsEncryptURL), "URL of ACME challenge directory")
rootCmd.Flags().StringVar(&acmeChallengeType, "acme-challenge-type", envStringOrDefault("NB_PROXY_ACME_CHALLENGE_TYPE", "tls-alpn-01"), "ACME challenge type: tls-alpn-01 (default, port 443 only) or http-01 (requires port 80)")
rootCmd.Flags().BoolVar(&debugEndpoint, "debug-endpoint", envBoolOrDefault("NB_PROXY_DEBUG_ENDPOINT", false), "Enable debug HTTP endpoint")
rootCmd.Flags().StringVar(&debugEndpointAddr, "debug-endpoint-addr", envStringOrDefault("NB_PROXY_DEBUG_ENDPOINT_ADDRESS", "localhost:8444"), "Address for the debug HTTP endpoint")
rootCmd.Flags().StringVar(&healthAddr, "health-addr", envStringOrDefault("NB_PROXY_HEALTH_ADDRESS", "localhost:8080"), "Address for the health probe endpoint (liveness/readiness/startup)")
@@ -151,6 +153,7 @@ func runServer(cmd *cobra.Command, args []string) error {
GenerateACMECertificates: acmeCerts,
ACMEChallengeAddress: acmeAddr,
ACMEDirectory: acmeDir,
ACMEChallengeType: acmeChallengeType,
DebugEndpointEnabled: debugEndpoint,
DebugEndpointAddress: debugEndpointAddr,
HealthAddress: healthAddr,

View File

@@ -77,6 +77,9 @@ type Server struct {
GenerateACMECertificates bool
ACMEChallengeAddress string
ACMEDirectory string
// ACMEChallengeType specifies the ACME challenge type: "http-01" or "tls-alpn-01".
// Defaults to "tls-alpn-01" if not specified.
ACMEChallengeType string
// CertLockMethod controls how ACME certificate locks are coordinated
// across replicas. Default: CertLockAuto (detect environment).
CertLockMethod acme.CertLockMethod
@@ -205,17 +208,28 @@ func (s *Server) ListenAndServe(ctx context.Context, addr string) (err error) {
// When generating ACME certificates, start a challenge server.
tlsConfig := &tls.Config{}
if s.GenerateACMECertificates {
s.Logger.WithField("acme_server", s.ACMEDirectory).Debug("ACME certificates enabled, configuring certificate manager")
s.acme = acme.NewManager(s.CertificateDirectory, s.ACMEDirectory, s, s.Logger, s.CertLockMethod)
s.http = &http.Server{
Addr: s.ACMEChallengeAddress,
Handler: s.acme.HTTPHandler(nil),
// Default to TLS-ALPN-01 challenge if not specified
if s.ACMEChallengeType == "" {
s.ACMEChallengeType = "tls-alpn-01"
}
go func() {
if err := s.http.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
s.Logger.WithError(err).Error("ACME HTTP-01 challenge server failed")
s.Logger.WithFields(log.Fields{
"acme_server": s.ACMEDirectory,
"challenge_type": s.ACMEChallengeType,
}).Debug("ACME certificates enabled, configuring certificate manager")
s.acme = acme.NewManager(s.CertificateDirectory, s.ACMEDirectory, s, s.Logger, s.CertLockMethod)
// Only start HTTP server for HTTP-01 challenge type
if s.ACMEChallengeType == "http-01" {
s.http = &http.Server{
Addr: s.ACMEChallengeAddress,
Handler: s.acme.HTTPHandler(nil),
}
}()
go func() {
if err := s.http.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
s.Logger.WithError(err).Error("ACME HTTP-01 challenge server failed")
}
}()
}
tlsConfig = s.acme.TLSConfig()
// ServerName needs to be set to allow for ACME to work correctly
@@ -223,8 +237,9 @@ func (s *Server) ListenAndServe(ctx context.Context, addr string) (err error) {
tlsConfig.ServerName = s.ProxyURL
s.Logger.WithFields(log.Fields{
"ServerName": s.ProxyURL,
}).Debug("started ACME challenge server")
"ServerName": s.ProxyURL,
"challenge_type": s.ACMEChallengeType,
}).Debug("ACME certificate manager configured")
} else {
s.Logger.Debug("ACME certificates disabled, using static certificates with file watching")
certPath := filepath.Join(s.CertificateDirectory, s.CertificateFile)