diff --git a/CHANGELOG.md b/CHANGELOG.md index ffa9a74..b024423 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/charts/netbird/README.md b/charts/netbird/README.md index dc94f97..018011c 100644 --- a/charts/netbird/README.md +++ b/charts/netbird/README.md @@ -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 @@ -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: @@ -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: @@ -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: @@ -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: @@ -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 diff --git a/charts/netbird/ci/e2e-values-oidc-embedded.yaml b/charts/netbird/ci/e2e-values-oidc-embedded.yaml index b512ec5..2faaeb2 100644 --- a/charts/netbird/ci/e2e-values-oidc-embedded.yaml +++ b/charts/netbird/ci/e2e-values-oidc-embedded.yaml @@ -18,7 +18,7 @@ server: type: ClusterIP config: - exposedAddress: "https://netbird.localhost" + exposedAddress: "https://netbird.localhost:443" auth: issuer: "https://netbird.localhost/oauth2" dashboardRedirectURIs: diff --git a/charts/netbird/ci/e2e-values-oidc-keycloak.yaml b/charts/netbird/ci/e2e-values-oidc-keycloak.yaml index e447c22..ed31598 100644 --- a/charts/netbird/ci/e2e-values-oidc-keycloak.yaml +++ b/charts/netbird/ci/e2e-values-oidc-keycloak.yaml @@ -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: diff --git a/charts/netbird/ci/e2e-values-oidc-zitadel.yaml b/charts/netbird/ci/e2e-values-oidc-zitadel.yaml index 8fcf9d1..0a177f9 100644 --- a/charts/netbird/ci/e2e-values-oidc-zitadel.yaml +++ b/charts/netbird/ci/e2e-values-oidc-zitadel.yaml @@ -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: diff --git a/charts/netbird/templates/_helpers.tpl b/charts/netbird/templates/_helpers.tpl index 932152b..f0e21c8 100644 --- a/charts/netbird/templates/_helpers.tpl +++ b/charts/netbird/templates/_helpers.tpl @@ -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. */}} diff --git a/charts/netbird/templates/server-configmap.yaml b/charts/netbird/templates/server-configmap.yaml index 1035a52..907dfc1 100644 --- a/charts/netbird/templates/server-configmap.yaml +++ b/charts/netbird/templates/server-configmap.yaml @@ -1,4 +1,5 @@ -apiVersion: v1 +{{- include "netbird.validate.exposedAddress" . -}} +apiVersion: v1 kind: ConfigMap metadata: name: {{ include "netbird.server.fullname" . }} diff --git a/charts/netbird/tests/__snapshot__/server-configmap_test.yaml.snap b/charts/netbird/tests/__snapshot__/server-configmap_test.yaml.snap index eb92208..7dbeaba 100644 --- a/charts/netbird/tests/__snapshot__/server-configmap_test.yaml.snap +++ b/charts/netbird/tests/__snapshot__/server-configmap_test.yaml.snap @@ -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 diff --git a/charts/netbird/tests/server-configmap_test.yaml b/charts/netbird/tests/server-configmap_test.yaml index 55dede4..c972234 100644 --- a/charts/netbird/tests/server-configmap_test.yaml +++ b/charts/netbird/tests/server-configmap_test.yaml @@ -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" diff --git a/charts/netbird/values.yaml b/charts/netbird/values.yaml index d6b1e14..24cdd14 100644 --- a/charts/netbird/values.yaml +++ b/charts/netbird/values.yaml @@ -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. diff --git a/examples/seed/mysql-values.yaml b/examples/seed/mysql-values.yaml index 119d401..67222d6 100644 --- a/examples/seed/mysql-values.yaml +++ b/examples/seed/mysql-values.yaml @@ -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: diff --git a/examples/seed/postgresql-values.yaml b/examples/seed/postgresql-values.yaml index 101444e..09d41de 100644 --- a/examples/seed/postgresql-values.yaml +++ b/examples/seed/postgresql-values.yaml @@ -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: