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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

## Unreleased

### Added

- **netbird**: Fail-fast Helm template validation that rejects
`server.config.exposedAddress` values without an explicit port (e.g.
`https://netbird.example.com`). NetBird clients require the port — without
it the daemon fails with `missing port in address`. Use
`https://netbird.example.com:443` instead. Fixes #75.

### Changed

- **netbird**: README and `values.yaml` examples now show
`exposedAddress` with an explicit `:443` port and document that the
port is required even when it matches the scheme default.

## [0.4.1] — 2026-04-14

### Added
Expand Down
43 changes: 25 additions & 18 deletions charts/netbird/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ helm install netbird ./charts/netbird \

## Minimal Configuration Example

> **`exposedAddress` must include an explicit port** (e.g. `https://netbird.example.com:443`),
> even when the port matches the scheme default. NetBird clients build their
> gRPC dial target from this URL using Go's `net/url` parser; without an
> explicit port the daemon fails to connect with `missing port in address`.
> The chart enforces this at template time and refuses to install with a
> port-less value.

### Embedded IdP (no external provider)

NetBird includes a built-in identity provider, so an external OAuth2/OIDC
Expand All @@ -60,7 +67,7 @@ provider is **not required**. To use the embedded IdP, set the issuer to
```yaml
server:
config:
exposedAddress: "https://netbird.example.com"
exposedAddress: "https://netbird.example.com:443"
auth:
issuer: "https://netbird.example.com/oauth2"
dashboardRedirectURIs:
Expand All @@ -82,7 +89,7 @@ endpoint — no Keycloak, Auth0, or other external IdP is needed.
```yaml
server:
config:
exposedAddress: "https://netbird.example.com"
exposedAddress: "https://netbird.example.com:443"
auth:
issuer: "https://auth.example.com"
dashboardRedirectURIs:
Expand All @@ -105,7 +112,7 @@ database:

server:
config:
exposedAddress: "https://netbird.example.com"
exposedAddress: "https://netbird.example.com:443"
auth:
issuer: "https://auth.example.com"
dashboardRedirectURIs:
Expand All @@ -128,7 +135,7 @@ database:

server:
config:
exposedAddress: "https://netbird.example.com"
exposedAddress: "https://netbird.example.com:443"
auth:
issuer: "https://auth.example.com"
dashboardRedirectURIs:
Expand Down Expand Up @@ -594,20 +601,20 @@ ADFS) can be tested manually:

#### Server Configuration

| Key | Type | Default | Description |
| ------------------------------------------ | ------ | ----------------------------- | ---------------------------------------- |
| `server.config.listenAddress` | string | `":80"` | Address and port the server listens on |
| `server.config.exposedAddress` | string | `""` | Public URL for peer connections |
| `server.config.stunPorts` | list | `[3478]` | UDP ports for the embedded STUN server |
| `server.config.metricsPort` | int | `9090` | Prometheus metrics port |
| `server.config.healthcheckAddress` | string | `":9000"` | Health check endpoint address |
| `server.config.logLevel` | string | `"info"` | Log verbosity (debug, info, warn, error) |
| `server.config.logFile` | string | `"console"` | Log output destination |
| `server.config.dataDir` | string | `"/var/lib/netbird"` | Data directory for state and DB |
| `server.config.auth.issuer` | string | `""` | OAuth2/OIDC issuer URL |
| `server.config.auth.signKeyRefreshEnabled` | bool | `true` | Auto-refresh IdP signing keys |
| `server.config.auth.dashboardRedirectURIs` | list | `[]` | Dashboard OAuth2 redirect URIs |
| `server.config.auth.cliRedirectURIs` | list | `["http://localhost:53000/"]` | CLI redirect URIs |
| Key | Type | Default | Description |
| ------------------------------------------ | ------ | ----------------------------- | ------------------------------------------------------------------------------------- |
| `server.config.listenAddress` | string | `":80"` | Address and port the server listens on |
| `server.config.exposedAddress` | string | `""` | Public URL for peer connections — `https://host:port` (port required, see note below) |
| `server.config.stunPorts` | list | `[3478]` | UDP ports for the embedded STUN server |
| `server.config.metricsPort` | int | `9090` | Prometheus metrics port |
| `server.config.healthcheckAddress` | string | `":9000"` | Health check endpoint address |
| `server.config.logLevel` | string | `"info"` | Log verbosity (debug, info, warn, error) |
| `server.config.logFile` | string | `"console"` | Log output destination |
| `server.config.dataDir` | string | `"/var/lib/netbird"` | Data directory for state and DB |
| `server.config.auth.issuer` | string | `""` | OAuth2/OIDC issuer URL |
| `server.config.auth.signKeyRefreshEnabled` | bool | `true` | Auto-refresh IdP signing keys |
| `server.config.auth.dashboardRedirectURIs` | list | `[]` | Dashboard OAuth2 redirect URIs |
| `server.config.auth.cliRedirectURIs` | list | `["http://localhost:53000/"]` | CLI redirect URIs |

#### Server Secrets

Expand Down
2 changes: 1 addition & 1 deletion charts/netbird/ci/e2e-values-oidc-embedded.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ server:
type: ClusterIP

config:
exposedAddress: "https://netbird.localhost"
exposedAddress: "https://netbird.localhost:443"
auth:
issuer: "https://netbird.localhost/oauth2"
dashboardRedirectURIs:
Expand Down
2 changes: 1 addition & 1 deletion charts/netbird/ci/e2e-values-oidc-keycloak.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ server:
type: ClusterIP

config:
exposedAddress: "https://netbird.localhost"
exposedAddress: "https://netbird.localhost:443"
auth:
issuer: "http://keycloak.netbird-e2e.svc.cluster.local:8080/realms/netbird"
dashboardRedirectURIs:
Expand Down
2 changes: 1 addition & 1 deletion charts/netbird/ci/e2e-values-oidc-zitadel.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ server:
type: ClusterIP

config:
exposedAddress: "https://netbird.localhost"
exposedAddress: "https://netbird.localhost:443"
auth:
issuer: "http://zitadel.netbird-e2e.svc.cluster.local:8080"
dashboardRedirectURIs:
Expand Down
22 changes: 22 additions & 0 deletions charts/netbird/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,28 @@ render subcommand (envsubst mode) won't interpret user values.
{{- . | replace "$" "${DOLLAR}" }}
{{- end }}

{{/*
netbird.validate.exposedAddress — fail-fast validation that
server.config.exposedAddress includes an explicit port.

NetBird clients build their gRPC dial target from this URL using Go's
net/url parser, which surfaces "missing port in address" when no port is
present (e.g. "https://netbird.example.com"). The port must be set
explicitly even when it matches the scheme default (443/80), because
NetBird does not infer it.

Accepts hostnames, IPv4, and bracketed IPv6. Empty exposedAddress passes
(other templates already document it as required, but we don't fail
here to keep `helm template` usable for partial inspection).
*/}}
{{- define "netbird.validate.exposedAddress" -}}
{{- with .Values.server.config.exposedAddress -}}
{{- if not (regexMatch `^https?://(\[[^\]]+\]|[^/:?#]+):[0-9]+([/?#].*)?$` .) -}}
{{- fail (printf "server.config.exposedAddress %q must include an explicit port (e.g. \"https://netbird.example.com:443\"). NetBird clients require the port; without it the daemon fails with \"missing port in address\"." .) -}}
{{- end -}}
{{- end -}}
{{- end }}

{{/*
netbird.server.generatedSecretName — name of the auto-generated Secret.
*/}}
Expand Down
3 changes: 2 additions & 1 deletion charts/netbird/templates/server-configmap.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
apiVersion: v1
{{- include "netbird.validate.exposedAddress" . -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "netbird.server.fullname" . }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ should render exposed address from values:
config.yaml.tpl: |
server:
listenAddress: ":80"
exposedAddress: "https://netbird.example.com"
exposedAddress: "https://netbird.example.com:443"
stunPorts:
- 3478
metricsPort: 9090
Expand Down
37 changes: 36 additions & 1 deletion charts/netbird/tests/server-configmap_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,46 @@ tests:

- it: should render exposed address from values
set:
server.config.exposedAddress: "https://netbird.example.com"
server.config.exposedAddress: "https://netbird.example.com:443"
asserts:
- matchSnapshot:
path: data

- it: should fail when exposedAddress is missing a port
set:
server.config.exposedAddress: "https://netbird.example.com"
asserts:
- failedTemplate:
errorPattern: "must include an explicit port"

- it: should fail when exposedAddress is missing port (http scheme)
set:
server.config.exposedAddress: "http://netbird.example.com/path"
asserts:
- failedTemplate:
errorPattern: "must include an explicit port"

- it: should accept exposedAddress with explicit port and trailing slash
set:
server.config.exposedAddress: "https://netbird.example.com:443/"
asserts:
- hasDocuments:
count: 1

- it: should accept exposedAddress with bracketed IPv6 host and port
set:
server.config.exposedAddress: "https://[2001:db8::1]:443"
asserts:
- hasDocuments:
count: 1

- it: should accept empty exposedAddress (validation only fires when set)
set:
server.config.exposedAddress: ""
asserts:
- hasDocuments:
count: 1

- it: should render auth issuer from values
set:
server.config.auth.issuer: "https://auth.example.com"
Expand Down
9 changes: 7 additions & 2 deletions charts/netbird/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,13 @@ server:
listenAddress: ":80"

# -- The public-facing URL where peers connect to the server.
# Format: "https://hostname:port". Distributed to peers and must be
# reachable from all clients.
# Format: "https://hostname:port" — the port is REQUIRED, even when
# it matches the scheme default (443 for https, 80 for http).
# NetBird clients build their gRPC dial target from this URL using
# Go's net/url parser; without an explicit port the daemon fails
# with "missing port in address". The chart enforces this with a
# fail-fast template validation.
# Distributed to peers and must be reachable from all clients.
exposedAddress: ""

# -- List of UDP ports for the embedded STUN server.
Expand Down
2 changes: 1 addition & 1 deletion examples/seed/mysql-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ database:

server:
config:
exposedAddress: "https://netbird.example.com"
exposedAddress: "https://netbird.example.com:443"
auth:
issuer: "https://auth.example.com"
dashboardRedirectURIs:
Expand Down
2 changes: 1 addition & 1 deletion examples/seed/postgresql-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ database:

server:
config:
exposedAddress: "https://netbird.example.com"
exposedAddress: "https://netbird.example.com:443"
auth:
issuer: "https://auth.example.com"
dashboardRedirectURIs:
Expand Down
Loading