Skip to content
Open
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
5 changes: 4 additions & 1 deletion .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ jobs:
cd e2e/environment
docker compose logs management --tail=500 --no-color > management.log 2>&1 || true
docker compose logs reverse-proxy --tail=500 --no-color > reverse-proxy.log 2>&1 || true
docker compose logs signal --tail=500 --no-color > signal.log 2>&1 || true

- uses: actions/upload-artifact@v7
if: ${{ !cancelled() }}
Expand All @@ -204,4 +205,6 @@ jobs:
if: ${{ failure() }}
with:
name: reverse-proxy-logs
path: e2e/environment/reverse-proxy.log
path: |
e2e/environment/reverse-proxy.log
e2e/environment/signal.log
112 changes: 112 additions & 0 deletions .github/workflows/reverse-proxy-target-access-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
name: Reverse Proxy Target Access E2E

on:
pull_request:
types: [opened, synchronize, reopened, edited]
workflow_dispatch:
inputs:
backend-tag:
description: 'OSS management, reverse-proxy, and Signal image tag'
required: true
type: string
default: 'main'

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
target-access-traffic:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha || github.ref }}

- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
cache: 'npm'

- name: Resolve backend image tag
id: backend
env:
INPUT_TAG: ${{ inputs.backend-tag }}
PR_BODY: ${{ github.event.pull_request.body }}
run: |
tag="$INPUT_TAG"
if [ -z "$tag" ]; then
tag=$(printf '%s\n' "$PR_BODY" | awk '
!found && tolower($0) ~ /^[[:space:]]*reverse-proxy-tag:/ {
sub(/^[^:]*:[[:space:]]*/, "");
sub(/[[:space:]]*$/, "");
print;
found = 1;
}
END { if (!found) print "main" }
')
fi
if [[ ! "$tag" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$ ]]; then
echo "::error::Invalid backend image tag"
exit 1
fi
printf 'Using backend image tag: %s\n' "$tag"
printf 'tag=%s\n' "$tag" >> "$GITHUB_OUTPUT"

- name: Install dependencies and browser
run: |
npm install
npx playwright install --with-deps chromium
sudo apt-get install -y jq
printf '{}\n' > .local-config.json

- name: Set up test environment
working-directory: e2e/environment
env:
MANAGEMENT_IMAGE: ghcr.io/netbirdio/management:${{ steps.backend.outputs.tag }}
REVERSE_PROXY_IMAGE: ghcr.io/netbirdio/reverse-proxy:${{ steps.backend.outputs.tag }}
SIGNAL_IMAGE: ghcr.io/netbirdio/signal:${{ steps.backend.outputs.tag }}
MANAGEMENT_DISABLE_GEOLOCATION: 'true'
run: bash create-test-env.sh

- name: Build dashboard
env:
APP_ENV: test
run: npm run build

- name: Run target access traffic test
run: npx playwright test --config=e2e/playwright.config.ts reverse-proxy-target-access-traffic.spec.ts --retries=0

- name: Append Playwright summary
if: always() && hashFiles('e2e/test-results/results.json') != ''
run: |
jq -r '.stats | "Passed: \(.expected // 0), failed: \(.unexpected // 0), flaky: \(.flaky // 0), skipped: \(.skipped // 0)"' \
e2e/test-results/results.json >> "$GITHUB_STEP_SUMMARY"

- name: Collect container logs
if: failure()
working-directory: e2e/environment
run: docker compose logs --tail=500 --no-color management signal reverse-proxy reverse-proxy-no-ports > target-access-containers.log 2>&1 || true

- name: Upload Playwright report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: target-access-playwright-report
path: e2e/playwright-report/

- name: Upload failure diagnostics
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: target-access-diagnostics
path: |
e2e/test-results/
e2e/environment/target-access-containers.log
89 changes: 89 additions & 0 deletions e2e/environment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Dashboard E2E environment

Run `bash create-test-env.sh` from this directory, then build and run the
dashboard tests as described in [the E2E guide](../CLAUDE.md).

The default images are `ghcr.io/netbirdio/management-cloud:main`,
`ghcr.io/netbirdio/reverse-proxy:main`, and `ghcr.io/netbirdio/signal:main`.
`MANAGEMENT_IMAGE_TAG`, `REVERSE_PROXY_IMAGE_TAG`, and `SIGNAL_IMAGE_TAG`
select other tags and refresh them during setup. Signal runs inside the Compose
network behind Caddy so embedded proxy clients can finish connecting when
the suite creates overlay targets. `SIGNAL_IMAGE` accepts a full image reference,
following the same override rules as management and proxy images.

Management also receives a generated relay signing secret for traffic flow
credentials. Relay addresses stay empty because this fixture uses direct
connections; no relay service is required. This keeps flow configuration enabled
without management exiting when connected peers request their configuration.

To run the focused target-access traffic test, check out the matching backend
and dashboard branches, then build both backend images and the dashboard:

```bash
cd /path/to/netbird
docker build -f management/Dockerfile.multistage -t local/netbird-management:target-access .
docker build -f proxy/Dockerfile.multistage -t local/netbird-proxy:target-access .

cd /path/to/dashboard/e2e/environment
MANAGEMENT_IMAGE=local/netbird-management:target-access \
REVERSE_PROXY_IMAGE=local/netbird-proxy:target-access \
MANAGEMENT_DISABLE_GEOLOCATION=true \
bash create-test-env.sh

cd ../..
APP_ENV=test npm run build
npx playwright test --config=e2e/playwright.config.ts reverse-proxy-target-access-traffic.spec.ts
```

Install the dashboard dependencies and Playwright Chromium before running these
commands, following [the E2E guide](../CLAUDE.md). The test creates and edits
targets through the dashboard, then sends real HTTPS requests through the proxy
to a local HTTP upstream. Its direct proxy-cluster target exercises management
and proxy access control without requiring an enrolled peer or an overlay
connection.

Explicit image references take precedence over tags. Setup uses an existing
local image or pulls it if missing; rebuild local images before rerunning after
backend changes. Management images must provide `/go/bin/netbird-mgmt` with
the `management` and `token` commands. The NetBird OSS management image supports
the focused reverse-proxy tests; cloud-specific tests still require the cloud
image. Target access tests require both images to include `access_action`
support and intentionally fail against an older management image that drops it.

The primary proxy's HTTPS listener is published at `https://127.0.0.1:18443`.
Set `REVERSE_PROXY_PORT` before setup to use another host port. The generated
`../playwright.env.json` provides:

| Key | Purpose |
| --- | --- |
| `REVERSE_PROXY_URL` | Local HTTPS address for proxy traffic |
| `REVERSE_PROXY_CA_CERT` | Test CA certificate path, relative to `playwright.env.json` |
| `REVERSE_PROXY_UPSTREAM_HOST` | Runner IP reachable from the proxy container |

Traffic helpers connect to `REVERSE_PROXY_URL`, set the service domain as TLS
servername and HTTP Host, and trust `REVERSE_PROXY_CA_CERT`. TLS verification
stays enabled; public DNS records are unnecessary. A test can bind its own HTTP
upstream on `0.0.0.0` with an ephemeral port and configure a proxy-cluster target
using `REVERSE_PROXY_UPSTREAM_HOST` and that port. This also lets the test count
upstream requests to verify that denied traffic never arrives.

For a focused local run without country selectors, set
`MANAGEMENT_DISABLE_GEOLOCATION=true` to avoid downloading geolocation data.
Leave the default `false` for the full suite. This setting does not relax the
proxy's access-control assertions.

The separate **Reverse Proxy Target Access E2E** Actions workflow runs this
traffic spec and its login prerequisites on pull requests. A line such as
`reverse-proxy-tag: pr-7954` in the PR description selects that same published
tag for `ghcr.io/netbirdio/management`, `ghcr.io/netbirdio/reverse-proxy`, and
`ghcr.io/netbirdio/signal`; editing the description starts another run.
Without that line, the tag defaults to `main`. Manual runs accept a
`backend-tag` input. All three images must already be published, and management
and proxy must include target access support. The job uses public images without
registry credentials and disables geolocation for this focused test. The full
**Playwright E2E Tests** workflow
continues to use its cloud management image and run every spec with geolocation
enabled.

Run `bash clean-test-env.sh` from this directory to remove this Compose stack,
its test data, and generated configuration before setting up another run.
77 changes: 67 additions & 10 deletions e2e/environment/create-test-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,36 @@ set -e
# Tag of the management-cloud image to pull. Override via env var to pin the
# tests to a specific management-cloud build (e.g., a feature branch image).
MANAGEMENT_IMAGE_TAG="${MANAGEMENT_IMAGE_TAG:-main}"
echo "Using ghcr.io/netbirdio/management-cloud:${MANAGEMENT_IMAGE_TAG}"
MANAGEMENT_IMAGE_OVERRIDE="${MANAGEMENT_IMAGE:-}"
MANAGEMENT_IMAGE="${MANAGEMENT_IMAGE_OVERRIDE:-ghcr.io/netbirdio/management-cloud:${MANAGEMENT_IMAGE_TAG}}"
echo "Using ${MANAGEMENT_IMAGE}"

# Tag of the reverse-proxy image to pull. Override via env var to pin the
# tests to a specific reverse-proxy build (e.g., a feature branch image).
REVERSE_PROXY_IMAGE_TAG="${REVERSE_PROXY_IMAGE_TAG:-main}"
echo "Using ghcr.io/netbirdio/reverse-proxy:${REVERSE_PROXY_IMAGE_TAG}"
REVERSE_PROXY_IMAGE_OVERRIDE="${REVERSE_PROXY_IMAGE:-}"
REVERSE_PROXY_IMAGE="${REVERSE_PROXY_IMAGE_OVERRIDE:-ghcr.io/netbirdio/reverse-proxy:${REVERSE_PROXY_IMAGE_TAG}}"
echo "Using ${REVERSE_PROXY_IMAGE}"

SIGNAL_IMAGE_TAG="${SIGNAL_IMAGE_TAG:-main}"
SIGNAL_IMAGE_OVERRIDE="${SIGNAL_IMAGE:-}"
SIGNAL_IMAGE="${SIGNAL_IMAGE_OVERRIDE:-ghcr.io/netbirdio/signal:${SIGNAL_IMAGE_TAG}}"
echo "Using ${SIGNAL_IMAGE}"

REVERSE_PROXY_PORT="${REVERSE_PROXY_PORT:-18443}"
# Focused local tests may not need the geolocation database download. Keep it
# enabled by default because the full suite tests country selectors.
MANAGEMENT_DISABLE_GEOLOCATION="${MANAGEMENT_DISABLE_GEOLOCATION:-false}"

prepare_test_image() {
local image="$1"
local override="$2"
# Explicit references support locally built feature images. Tag-based runs
# still refresh remote images so mutable tags such as main stay current.
if [ -z "$override" ] || ! docker image inspect "$image" > /dev/null 2>&1; then
docker pull "$image"
fi
}

handle_request_command_status() {
PARSED_RESPONSE=$1
Expand Down Expand Up @@ -513,6 +537,7 @@ initEnvironment() {
NETBIRD_HTTP_PROTOCOL="http"
TURN_USER="self"
TURN_PASSWORD=$(openssl rand -base64 32 | sed 's/=//g')
RELAY_SECRET=$(openssl rand -hex 32)
TURN_MIN_PORT=49152
TURN_MAX_PORT=65535

Expand Down Expand Up @@ -564,9 +589,10 @@ initEnvironment() {
echo -e "\nRendering Playwright environment file...\n"
renderPlaywrightEnv > "../playwright.env.json"

echo -e "\nPulling latest images...\n"
docker pull "ghcr.io/netbirdio/management-cloud:${MANAGEMENT_IMAGE_TAG}"
docker pull "ghcr.io/netbirdio/reverse-proxy:${REVERSE_PROXY_IMAGE_TAG}"
echo -e "\nPreparing NetBird images...\n"
prepare_test_image "$MANAGEMENT_IMAGE" "$MANAGEMENT_IMAGE_OVERRIDE"
prepare_test_image "$REVERSE_PROXY_IMAGE" "$REVERSE_PROXY_IMAGE_OVERRIDE"
prepare_test_image "$SIGNAL_IMAGE" "$SIGNAL_IMAGE_OVERRIDE"

# Pre-create the proxy cert directories BEFORE starting containers so that
# docker's bind-mounts (./proxy-certs and ./proxy-certs-no-ports) reuse our
Expand Down Expand Up @@ -717,6 +743,11 @@ renderManagementJson() {
],
"TimeBasedCredentials": false
},
"Relay": {
"Addresses": [],
"CredentialsTTL": "24h",
"Secret": "$RELAY_SECRET"
},
"Signal": {
"Proto": "$NETBIRD_HTTP_PROTOCOL",
"URI": "$NETBIRD_DOMAIN:$NETBIRD_PORT"
Expand Down Expand Up @@ -816,6 +847,9 @@ exportGeoDatabases() {
}

renderDockerCompose() {
# Docker may inject outbound proxy settings. Local test traffic must stay on
# the Compose network, including upstream servers running on the test runner.
local test_no_proxy="${NO_PROXY:-localhost,127.0.0.1,::1},${NETBIRD_DOMAIN},caddy,management,signal,zitadel,crdb,postgres,agentgateway-stub"
# Cached geolocation databases (restored by CI into ./geo-cache) are
# mounted into management's data dir so it skips the slow first-boot
# download from pkgs.netbird.io. With no cache the mounts are omitted
Expand All @@ -836,6 +870,9 @@ services:
image: caddy
restart: unless-stopped
networks: [ netbird ]
environment:
- NO_PROXY=${test_no_proxy}
- no_proxy=${test_no_proxy}
ports:
- '33443:443'
- '33080:80'
Expand All @@ -852,9 +889,15 @@ services:
networks: [ netbird ]
volumes:
- ./agentgateway-stub.Caddyfile:/etc/caddy/Caddyfile
# Signal is reached through Caddy by embedded clients in the reverse proxies.
signal:
image: ${SIGNAL_IMAGE}
restart: unless-stopped
networks: [netbird]
command: ["--port", "10000", "--log-file", "console"]
# Management
management:
image: ghcr.io/netbirdio/management-cloud:${MANAGEMENT_IMAGE_TAG}
image: ${MANAGEMENT_IMAGE}
restart: unless-stopped
networks: [netbird]
environment:
Expand All @@ -869,6 +912,9 @@ services:
- NETBIRD_LICENSE_SERVER_BASE_URL=${NETBIRD_LICENSE_SERVER_BASE_URL}
- NB_TRAFFIC_FLOW_INTERVAL=20s
- NB_SINGLE_INSTANCE_MODE=true
- NB_DISABLE_GEOLOCATION=${MANAGEMENT_DISABLE_GEOLOCATION}
- NO_PROXY=${test_no_proxy}
- no_proxy=${test_no_proxy}
volumes:
- netbird_management:/var/lib/netbird${geo_mounts}
- ./management.json:/etc/netbird/management.json
Expand Down Expand Up @@ -916,12 +962,16 @@ services:
- netbird_zitadel_certs:/crdb-certs:ro
# Reverse proxy (supports custom listen ports for UDP/TCP)
reverse-proxy:
image: ghcr.io/netbirdio/reverse-proxy:${REVERSE_PROXY_IMAGE_TAG}
image: ${REVERSE_PROXY_IMAGE}
restart: unless-stopped
networks: [netbird]
ports:
- '127.0.0.1:${REVERSE_PROXY_PORT}:8443'
env_file:
- ./proxy.env
environment:
- NO_PROXY=${test_no_proxy}
- no_proxy=${test_no_proxy}
# No spec exercises country-based enforcement, and skipping the
# GeoLite2 download removes a startup stall of up to 2 minutes
# when pkgs.netbird.io is slow.
Expand Down Expand Up @@ -950,14 +1000,17 @@ services:
]
depends_on:
- management
- signal
# Reverse proxy with custom ports disabled (auto-assigned listen ports only)
reverse-proxy-no-ports:
image: ghcr.io/netbirdio/reverse-proxy:${REVERSE_PROXY_IMAGE_TAG}
image: ${REVERSE_PROXY_IMAGE}
restart: unless-stopped
networks: [netbird]
env_file:
- ./proxy-no-ports.env
environment:
- NO_PROXY=${test_no_proxy}
- no_proxy=${test_no_proxy}
# See the primary proxy: no spec needs geo enforcement.
- NB_PROXY_DISABLE_GEOLOCATION=true
volumes:
Expand All @@ -979,6 +1032,7 @@ services:
]
depends_on:
- management
- signal
# CockroachDB for zitadel
crdb:
restart: 'always'
Expand Down Expand Up @@ -1013,9 +1067,12 @@ renderPlaywrightEnv() {
cat <<EOF
{
"ZITADEL_URL": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT",
"BASE_URL": "http://localhost:1337"
"BASE_URL": "http://localhost:1337",
"REVERSE_PROXY_URL": "https://127.0.0.1:$REVERSE_PROXY_PORT",
"REVERSE_PROXY_CA_CERT": "environment/proxy-certs/tls.crt",
"REVERSE_PROXY_UPSTREAM_HOST": "$NETBIRD_DOMAIN"
}
EOF
}

initEnvironment
initEnvironment
Loading
Loading