Skip to content

Commit a0a8043

Browse files
fix(netbird): require explicit port in exposedAddress (#75) (#79)
* fix(netbird): require explicit port in exposedAddress (#75) NetBird clients build their gRPC dial target from server.config.exposedAddress using Go's net/url parser, which surfaces "missing port in address" when no port is present (e.g. "https://netbird.example.com"). The README example showed a port-less URL, so users following it hit a confusing daemon error at first connect. Add a fail-fast Helm template validation that rejects port-less exposedAddress values with an actionable error pointing at the right form (e.g. "https://netbird.example.com:443"). Update README, values comment, and seed examples to use ":443" explicitly. Update the three OIDC e2e values files that previously used port-less localhost URLs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * style(netbird): fix README table formatting Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent afafd9b commit a0a8043

12 files changed

Lines changed: 112 additions & 28 deletions

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
77

88
## Unreleased
99

10+
### Added
11+
12+
- **netbird**: Fail-fast Helm template validation that rejects
13+
`server.config.exposedAddress` values without an explicit port (e.g.
14+
`https://netbird.example.com`). NetBird clients require the port — without
15+
it the daemon fails with `missing port in address`. Use
16+
`https://netbird.example.com:443` instead. Fixes #75.
17+
18+
### Changed
19+
20+
- **netbird**: README and `values.yaml` examples now show
21+
`exposedAddress` with an explicit `:443` port and document that the
22+
port is required even when it matches the scheme default.
23+
1024
## [0.4.1] — 2026-04-14
1125

1226
### Added

charts/netbird/README.md

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ helm install netbird ./charts/netbird \
5151

5252
## Minimal Configuration Example
5353

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

5663
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
6067
```yaml
6168
server:
6269
config:
63-
exposedAddress: "https://netbird.example.com"
70+
exposedAddress: "https://netbird.example.com:443"
6471
auth:
6572
issuer: "https://netbird.example.com/oauth2"
6673
dashboardRedirectURIs:
@@ -82,7 +89,7 @@ endpoint — no Keycloak, Auth0, or other external IdP is needed.
8289
```yaml
8390
server:
8491
config:
85-
exposedAddress: "https://netbird.example.com"
92+
exposedAddress: "https://netbird.example.com:443"
8693
auth:
8794
issuer: "https://auth.example.com"
8895
dashboardRedirectURIs:
@@ -105,7 +112,7 @@ database:
105112
106113
server:
107114
config:
108-
exposedAddress: "https://netbird.example.com"
115+
exposedAddress: "https://netbird.example.com:443"
109116
auth:
110117
issuer: "https://auth.example.com"
111118
dashboardRedirectURIs:
@@ -128,7 +135,7 @@ database:
128135
129136
server:
130137
config:
131-
exposedAddress: "https://netbird.example.com"
138+
exposedAddress: "https://netbird.example.com:443"
132139
auth:
133140
issuer: "https://auth.example.com"
134141
dashboardRedirectURIs:
@@ -594,20 +601,20 @@ ADFS) can be tested manually:
594601

595602
#### Server Configuration
596603

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

612619
#### Server Secrets
613620

charts/netbird/ci/e2e-values-oidc-embedded.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ server:
1818
type: ClusterIP
1919

2020
config:
21-
exposedAddress: "https://netbird.localhost"
21+
exposedAddress: "https://netbird.localhost:443"
2222
auth:
2323
issuer: "https://netbird.localhost/oauth2"
2424
dashboardRedirectURIs:

charts/netbird/ci/e2e-values-oidc-keycloak.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ server:
1717
type: ClusterIP
1818

1919
config:
20-
exposedAddress: "https://netbird.localhost"
20+
exposedAddress: "https://netbird.localhost:443"
2121
auth:
2222
issuer: "http://keycloak.netbird-e2e.svc.cluster.local:8080/realms/netbird"
2323
dashboardRedirectURIs:

charts/netbird/ci/e2e-values-oidc-zitadel.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ server:
2323
type: ClusterIP
2424

2525
config:
26-
exposedAddress: "https://netbird.localhost"
26+
exposedAddress: "https://netbird.localhost:443"
2727
auth:
2828
issuer: "http://zitadel.netbird-e2e.svc.cluster.local:8080"
2929
dashboardRedirectURIs:

charts/netbird/templates/_helpers.tpl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,28 @@ render subcommand (envsubst mode) won't interpret user values.
105105
{{- . | replace "$" "${DOLLAR}" }}
106106
{{- end }}
107107

108+
{{/*
109+
netbird.validate.exposedAddress — fail-fast validation that
110+
server.config.exposedAddress includes an explicit port.
111+
112+
NetBird clients build their gRPC dial target from this URL using Go's
113+
net/url parser, which surfaces "missing port in address" when no port is
114+
present (e.g. "https://netbird.example.com"). The port must be set
115+
explicitly even when it matches the scheme default (443/80), because
116+
NetBird does not infer it.
117+
118+
Accepts hostnames, IPv4, and bracketed IPv6. Empty exposedAddress passes
119+
(other templates already document it as required, but we don't fail
120+
here to keep `helm template` usable for partial inspection).
121+
*/}}
122+
{{- define "netbird.validate.exposedAddress" -}}
123+
{{- with .Values.server.config.exposedAddress -}}
124+
{{- if not (regexMatch `^https?://(\[[^\]]+\]|[^/:?#]+):[0-9]+([/?#].*)?$` .) -}}
125+
{{- 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\"." .) -}}
126+
{{- end -}}
127+
{{- end -}}
128+
{{- end }}
129+
108130
{{/*
109131
netbird.server.generatedSecretName — name of the auto-generated Secret.
110132
*/}}

charts/netbird/templates/server-configmap.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
apiVersion: v1
1+
{{- include "netbird.validate.exposedAddress" . -}}
2+
apiVersion: v1
23
kind: ConfigMap
34
metadata:
45
name: {{ include "netbird.server.fullname" . }}

charts/netbird/tests/__snapshot__/server-configmap_test.yaml.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ should render exposed address from values:
176176
config.yaml.tpl: |
177177
server:
178178
listenAddress: ":80"
179-
exposedAddress: "https://netbird.example.com"
179+
exposedAddress: "https://netbird.example.com:443"
180180
stunPorts:
181181
- 3478
182182
metricsPort: 9090

charts/netbird/tests/server-configmap_test.yaml

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,46 @@ tests:
2323

2424
- it: should render exposed address from values
2525
set:
26-
server.config.exposedAddress: "https://netbird.example.com"
26+
server.config.exposedAddress: "https://netbird.example.com:443"
2727
asserts:
2828
- matchSnapshot:
2929
path: data
3030

31+
- it: should fail when exposedAddress is missing a port
32+
set:
33+
server.config.exposedAddress: "https://netbird.example.com"
34+
asserts:
35+
- failedTemplate:
36+
errorPattern: "must include an explicit port"
37+
38+
- it: should fail when exposedAddress is missing port (http scheme)
39+
set:
40+
server.config.exposedAddress: "http://netbird.example.com/path"
41+
asserts:
42+
- failedTemplate:
43+
errorPattern: "must include an explicit port"
44+
45+
- it: should accept exposedAddress with explicit port and trailing slash
46+
set:
47+
server.config.exposedAddress: "https://netbird.example.com:443/"
48+
asserts:
49+
- hasDocuments:
50+
count: 1
51+
52+
- it: should accept exposedAddress with bracketed IPv6 host and port
53+
set:
54+
server.config.exposedAddress: "https://[2001:db8::1]:443"
55+
asserts:
56+
- hasDocuments:
57+
count: 1
58+
59+
- it: should accept empty exposedAddress (validation only fires when set)
60+
set:
61+
server.config.exposedAddress: ""
62+
asserts:
63+
- hasDocuments:
64+
count: 1
65+
3166
- it: should render auth issuer from values
3267
set:
3368
server.config.auth.issuer: "https://auth.example.com"

charts/netbird/values.yaml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,13 @@ server:
301301
listenAddress: ":80"
302302

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

308313
# -- List of UDP ports for the embedded STUN server.

0 commit comments

Comments
 (0)