mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-02 07:06:41 +00:00
Compare commits
14 Commits
poc-token-
...
dn-reverse
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76fb153d76 | ||
|
|
eee4d75932 | ||
|
|
62b8875f67 | ||
|
|
47a5478964 | ||
|
|
9922d6f953 | ||
|
|
f9bab22f61 | ||
|
|
3d8fdb7a89 | ||
|
|
fb10153ab8 | ||
|
|
b00babb8b1 | ||
|
|
3bc8cbb13f | ||
|
|
bf7bdf6c4f | ||
|
|
0a895ffc22 | ||
|
|
b81837a364 | ||
|
|
b782ac6f56 |
@@ -91,16 +91,17 @@ read_reverse_proxy_type() {
|
|||||||
echo " [3] Nginx Proxy Manager (generates config + instructions)" > /dev/stderr
|
echo " [3] Nginx Proxy Manager (generates config + instructions)" > /dev/stderr
|
||||||
echo " [4] External Caddy (generates Caddyfile snippet)" > /dev/stderr
|
echo " [4] External Caddy (generates Caddyfile snippet)" > /dev/stderr
|
||||||
echo " [5] Other/Manual (displays setup documentation)" > /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 "" > /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
|
read -r CHOICE < /dev/tty
|
||||||
|
|
||||||
if [[ -z "$CHOICE" ]]; then
|
if [[ -z "$CHOICE" ]]; then
|
||||||
CHOICE="0"
|
CHOICE="0"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! "$CHOICE" =~ ^[0-5]$ ]]; then
|
if [[ ! "$CHOICE" =~ ^[0-6]$ ]]; then
|
||||||
echo "Invalid choice. Please enter a number between 0 and 5." > /dev/stderr
|
echo "Invalid choice. Please enter a number between 0 and 6." > /dev/stderr
|
||||||
read_reverse_proxy_type
|
read_reverse_proxy_type
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
@@ -140,6 +141,35 @@ read_traefik_certresolver() {
|
|||||||
return 0
|
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() {
|
read_port_binding_preference() {
|
||||||
echo "" > /dev/stderr
|
echo "" > /dev/stderr
|
||||||
echo "Should container ports be bound to localhost only (127.0.0.1)?" > /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
|
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() {
|
wait_management_direct() {
|
||||||
set +e
|
set +e
|
||||||
local upstream_host=$(get_upstream_host)
|
local upstream_host=$(get_upstream_host)
|
||||||
@@ -246,10 +300,12 @@ initialize_default_values() {
|
|||||||
|
|
||||||
# Docker images
|
# Docker images
|
||||||
CADDY_IMAGE="caddy"
|
CADDY_IMAGE="caddy"
|
||||||
DASHBOARD_IMAGE="netbirdio/dashboard:latest"
|
#DASHBOARD_IMAGE="netbirdio/dashboard:latest"
|
||||||
|
DASHBOARD_IMAGE="netbirdio/dashboard:pr-552"
|
||||||
SIGNAL_IMAGE="netbirdio/signal:latest"
|
SIGNAL_IMAGE="netbirdio/signal:latest"
|
||||||
RELAY_IMAGE="netbirdio/relay:latest"
|
RELAY_IMAGE="netbirdio/relay:latest"
|
||||||
MANAGEMENT_IMAGE="netbirdio/management:latest"
|
MANAGEMENT_IMAGE="netbirdio/management:latest"
|
||||||
|
PROXY_IMAGE=""
|
||||||
|
|
||||||
# Reverse proxy configuration
|
# Reverse proxy configuration
|
||||||
REVERSE_PROXY_TYPE="0"
|
REVERSE_PROXY_TYPE="0"
|
||||||
@@ -263,6 +319,14 @@ initialize_default_values() {
|
|||||||
RELAY_HOST_PORT="8084"
|
RELAY_HOST_PORT="8084"
|
||||||
BIND_LOCALHOST_ONLY="true"
|
BIND_LOCALHOST_ONLY="true"
|
||||||
EXTERNAL_PROXY_NETWORK=""
|
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
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,8 +357,17 @@ configure_reverse_proxy() {
|
|||||||
TRAEFIK_CERTRESOLVER=$(read_traefik_certresolver)
|
TRAEFIK_CERTRESOLVER=$(read_traefik_certresolver)
|
||||||
fi
|
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)
|
# 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)
|
BIND_LOCALHOST_ONLY=$(read_port_binding_preference)
|
||||||
fi
|
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 "Generated files already exist, if you want to reinitialize the environment, please remove them first."
|
||||||
echo "You can use the following commands:"
|
echo "You can use the following commands:"
|
||||||
echo " $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes"
|
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."
|
echo "Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -347,6 +420,15 @@ generate_configuration_files() {
|
|||||||
5)
|
5)
|
||||||
render_docker_compose_exposed_ports > docker-compose.yml
|
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
|
echo "Invalid reverse proxy type: $REVERSE_PROXY_TYPE" > /dev/stderr
|
||||||
exit 1
|
exit 1
|
||||||
@@ -402,6 +484,50 @@ start_services_and_show_instructions() {
|
|||||||
echo ""
|
echo ""
|
||||||
echo "NetBird containers are running. Configure NPM as shown above, then access:"
|
echo "NetBird containers are running. Configure NPM as shown above, then access:"
|
||||||
echo " $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN"
|
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
|
else
|
||||||
# External proxies (nginx, external Caddy, other) - need manual config first
|
# External proxies (nginx, external Caddy, other) - need manual config first
|
||||||
print_post_setup_instructions
|
print_post_setup_instructions
|
||||||
@@ -547,6 +673,29 @@ EOF
|
|||||||
return 0
|
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() {
|
render_docker_compose() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
services:
|
services:
|
||||||
@@ -736,7 +885,18 @@ $(if [[ -n "$tls_labels" ]]; then echo " - traefik.http.routers.netbird-rel
|
|||||||
|
|
||||||
# Management (includes embedded IdP)
|
# Management (includes embedded IdP)
|
||||||
management:
|
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
|
image: $MANAGEMENT_IMAGE
|
||||||
|
MGMT_IMAGE
|
||||||
|
fi)
|
||||||
container_name: netbird-management
|
container_name: netbird-management
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks: [$network_name]
|
networks: [$network_name]
|
||||||
@@ -1115,6 +1275,258 @@ EOF
|
|||||||
return 0
|
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() {
|
render_npm_advanced_config() {
|
||||||
local upstream_host=$(get_upstream_host)
|
local upstream_host=$(get_upstream_host)
|
||||||
local relay_addr="${upstream_host}:${RELAY_HOST_PORT}"
|
local relay_addr="${upstream_host}:${RELAY_HOST_PORT}"
|
||||||
@@ -1424,6 +1836,36 @@ print_manual_instructions() {
|
|||||||
return 0
|
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() {
|
print_post_setup_instructions() {
|
||||||
case "$REVERSE_PROXY_TYPE" in
|
case "$REVERSE_PROXY_TYPE" in
|
||||||
0)
|
0)
|
||||||
@@ -1444,6 +1886,9 @@ print_post_setup_instructions() {
|
|||||||
5)
|
5)
|
||||||
print_manual_instructions
|
print_manual_instructions
|
||||||
;;
|
;;
|
||||||
|
6)
|
||||||
|
print_traefik_tcp_instructions
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown reverse proxy type: $REVERSE_PROXY_TYPE" > /dev/stderr
|
echo "Unknown reverse proxy type: $REVERSE_PROXY_TYPE" > /dev/stderr
|
||||||
;;
|
;;
|
||||||
|
|||||||
17
management/Dockerfile.multistage
Normal file
17
management/Dockerfile.multistage
Normal 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
|
||||||
@@ -39,9 +39,10 @@ var (
|
|||||||
addr string
|
addr string
|
||||||
proxyDomain string
|
proxyDomain string
|
||||||
certDir string
|
certDir string
|
||||||
acmeCerts bool
|
acmeCerts bool
|
||||||
acmeAddr string
|
acmeAddr string
|
||||||
acmeDir string
|
acmeDir string
|
||||||
|
acmeChallengeType string
|
||||||
debugEndpoint bool
|
debugEndpoint bool
|
||||||
debugEndpointAddr string
|
debugEndpointAddr string
|
||||||
healthAddr 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(&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(&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().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().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")
|
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(&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().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(&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)")
|
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,
|
GenerateACMECertificates: acmeCerts,
|
||||||
ACMEChallengeAddress: acmeAddr,
|
ACMEChallengeAddress: acmeAddr,
|
||||||
ACMEDirectory: acmeDir,
|
ACMEDirectory: acmeDir,
|
||||||
|
ACMEChallengeType: acmeChallengeType,
|
||||||
DebugEndpointEnabled: debugEndpoint,
|
DebugEndpointEnabled: debugEndpoint,
|
||||||
DebugEndpointAddress: debugEndpointAddr,
|
DebugEndpointAddress: debugEndpointAddr,
|
||||||
HealthAddress: healthAddr,
|
HealthAddress: healthAddr,
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ type Server struct {
|
|||||||
GenerateACMECertificates bool
|
GenerateACMECertificates bool
|
||||||
ACMEChallengeAddress string
|
ACMEChallengeAddress string
|
||||||
ACMEDirectory 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
|
// CertLockMethod controls how ACME certificate locks are coordinated
|
||||||
// across replicas. Default: CertLockAuto (detect environment).
|
// across replicas. Default: CertLockAuto (detect environment).
|
||||||
CertLockMethod acme.CertLockMethod
|
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.
|
// When generating ACME certificates, start a challenge server.
|
||||||
tlsConfig := &tls.Config{}
|
tlsConfig := &tls.Config{}
|
||||||
if s.GenerateACMECertificates {
|
if s.GenerateACMECertificates {
|
||||||
s.Logger.WithField("acme_server", s.ACMEDirectory).Debug("ACME certificates enabled, configuring certificate manager")
|
// Default to TLS-ALPN-01 challenge if not specified
|
||||||
s.acme = acme.NewManager(s.CertificateDirectory, s.ACMEDirectory, s, s.Logger, s.CertLockMethod)
|
if s.ACMEChallengeType == "" {
|
||||||
s.http = &http.Server{
|
s.ACMEChallengeType = "tls-alpn-01"
|
||||||
Addr: s.ACMEChallengeAddress,
|
|
||||||
Handler: s.acme.HTTPHandler(nil),
|
|
||||||
}
|
}
|
||||||
go func() {
|
s.Logger.WithFields(log.Fields{
|
||||||
if err := s.http.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
"acme_server": s.ACMEDirectory,
|
||||||
s.Logger.WithError(err).Error("ACME HTTP-01 challenge server failed")
|
"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()
|
tlsConfig = s.acme.TLSConfig()
|
||||||
|
|
||||||
// ServerName needs to be set to allow for ACME to work correctly
|
// 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
|
tlsConfig.ServerName = s.ProxyURL
|
||||||
|
|
||||||
s.Logger.WithFields(log.Fields{
|
s.Logger.WithFields(log.Fields{
|
||||||
"ServerName": s.ProxyURL,
|
"ServerName": s.ProxyURL,
|
||||||
}).Debug("started ACME challenge server")
|
"challenge_type": s.ACMEChallengeType,
|
||||||
|
}).Debug("ACME certificate manager configured")
|
||||||
} else {
|
} else {
|
||||||
s.Logger.Debug("ACME certificates disabled, using static certificates with file watching")
|
s.Logger.Debug("ACME certificates disabled, using static certificates with file watching")
|
||||||
certPath := filepath.Join(s.CertificateDirectory, s.CertificateFile)
|
certPath := filepath.Join(s.CertificateDirectory, s.CertificateFile)
|
||||||
|
|||||||
Reference in New Issue
Block a user