[GH-ISSUE #6056] [Feature Request] Reverse proxy: transparent auth via NetBird peer identity #11538

Open
opened 2026-08-05 01:29:58 -04:00 by saavagebueno · 0 comments
Owner

Originally created by @Seralius on GitHub (May 3, 2026).
Original GitHub issue: https://github.com/netbirdio/netbird/issues/6056

Problem

The reverse proxy treats all traffic the same regardless of how it arrived. A request from a fully enrolled NetBird peer over the mesh and a request from an anonymous browser on the public internet hit the exact same auth wall. The control plane already knows which user a given peer IP belongs to, but the proxy can't use that. So I get prompted for SSO/password/PIN a second time even though I'm already on the mesh, and per-service user-group ACLs can't operate on peer identity at all.

Issue #5556 raised the friction side and proposed an IP/CIDR-based auth bypass. That kills the double-login annoyance, but it also throws away identity: user-group filters can no longer match per user, audit logs lose the actor, and backends don't know who's calling.

This request proposes the identity-preserving version.

Proposed solution

Add a new auth method on reverse proxy services (call it "NetBird Peer Identity" or "Transparent SSO") that creates a second auth tier alongside the existing public methods:

  • Public tier (existing): SSO/password/PIN for traffic over the public internet.
  • Internal tier (new): match the request's source IP against the peer-to-user mapping the management server already maintains. If a peer matches, treat the request as authenticated as that user. No interactive flow.

How it works:

  1. Resolve source IP to owning user via the existing peer mapping.
  2. User-group filters still apply. A peer whose owner isn't in the allowed groups gets denied the same way an SSO user would. This is the difference from #5556.
  3. Optionally inject identity headers to the backend (Remote-User, Remote-Groups, Remote-Email), similar to Authelia/Authentik forward-auth.
  4. Fall through to whichever public-tier methods are configured if no peer match is found, so external users keep working.
  5. Audit events attribute the actor: auth: peer-identity (user=alice@example.com, peer=alice-laptop).

One thing to get right: verify per request. Don't issue long-lived session cookies for peer-authenticated requests. Authorization should track live mesh state. If a peer disconnects and the same browser reaches the service over a public path, public-tier auth needs to kick in on the very next request. That only works if peer identity is checked per request rather than baked into a cookie at first contact. The naive "plug it in alongside SSO" implementation would silently break this.

Most of the pieces already exist

  • The peer-to-user mapping is already maintained by the management server.
  • Source-IP/CIDR matching is already implemented as a connection-level access restriction. This proposal is basically the same lookup, but resolving the matched IP to a user and feeding it into the existing auth/ACL pipeline.
  • The user-group filter mechanism already exists on services; this just gives it another way to populate "current user."

No new identity provider, no new mapping infrastructure, no new ACL model. It's wiring three existing things together.

What this enables

I care about this beyond just skipping the second login. NetBird has no concept of an authenticated internal channel at the proxy layer right now. Adding one opens up things that are otherwise awkward to do:

  • Services only reachable to peers, never published publicly. With peer identity as the auth method, these get identity-aware auth without forcing SSO on users already on the mesh.
  • Different auth policy by channel: peer auth over the mesh, step-up SSO + MFA from the public internet. Can't express this today.
  • Backends that want per-user scoping (Nextcloud, Grafana, internal dashboards) can get Remote-User headers from the proxy without bolting on Authelia or Authentik for header injection.
  • Every request through the proxy becomes attributable to a user. Right now in homelab setups, the requests that bypass interactive auth have no actor attached, because nobody wants to SSO into their own Jellyfin from their own laptop.

Alternatives I looked at

  • IP/CIDR bypass (#5556): solves the friction, discards identity, weakens group ACLs, degrades the audit trail.
  • Sidecar header-injecting proxy: duplicates state that already lives in the control plane. I tried this briefly and it was annoying to keep in sync.
  • External forward-auth (Authelia, Authentik): this is what I'm running today. It works, but maintaining a parallel identity layer that ignores what NetBird already knows about peers and users feels like the wrong answer long term.
  • Manual split-DNS to the target service: bypasses the proxy entirely, breaks centralized TLS and access control.

Design notes

Forwarded header trust boundary (only matters if header injection is enabled). If a service opts into Remote-User/Remote-Groups headers and is also reachable through some other path (host LAN, another container on the same Docker network, unrelated VPN interface), a caller on that path can forge those headers. The backend can't tell the difference. This is the standard forward-auth footgun; Authelia, Authentik, and oauth2-proxy all warn about it. Shipping example backend configs for Traefik, nginx, and common frameworks would help. The core gating feature doesn't have this problem: WireGuard's cryptographic peer identity prevents IP spoofing inside the mesh.

Reachability over the internal channel. This feature only fires when traffic actually arrives at the proxy with a peer source IP, and that's not the default today since public DNS resolves to the gateway's public IP. In practice, users would either set up split-DNS themselves or this feature ships alongside internal-only routes. I'd argue for treating the two as a coordinated pair; the auth story for internal-only routes is incomplete without peer identity, and peer identity is less useful without a way to restrict routes to the mesh.

Originally created by @Seralius on GitHub (May 3, 2026). Original GitHub issue: https://github.com/netbirdio/netbird/issues/6056 ## Problem The reverse proxy treats all traffic the same regardless of how it arrived. A request from a fully enrolled NetBird peer over the mesh and a request from an anonymous browser on the public internet hit the exact same auth wall. The control plane already knows which user a given peer IP belongs to, but the proxy can't use that. So I get prompted for SSO/password/PIN a second time even though I'm already on the mesh, and per-service user-group ACLs can't operate on peer identity at all. Issue #5556 raised the friction side and proposed an IP/CIDR-based auth bypass. That kills the double-login annoyance, but it also throws away identity: user-group filters can no longer match per user, audit logs lose the actor, and backends don't know who's calling. This request proposes the identity-preserving version. ## Proposed solution Add a new auth method on reverse proxy services (call it "NetBird Peer Identity" or "Transparent SSO") that creates a second auth tier alongside the existing public methods: - Public tier (existing): SSO/password/PIN for traffic over the public internet. - Internal tier (new): match the request's source IP against the peer-to-user mapping the management server already maintains. If a peer matches, treat the request as authenticated as that user. No interactive flow. How it works: 1. Resolve source IP to owning user via the existing peer mapping. 2. User-group filters still apply. A peer whose owner isn't in the allowed groups gets denied the same way an SSO user would. This is the difference from #5556. 3. Optionally inject identity headers to the backend (`Remote-User`, `Remote-Groups`, `Remote-Email`), similar to Authelia/Authentik forward-auth. 4. Fall through to whichever public-tier methods are configured if no peer match is found, so external users keep working. 5. Audit events attribute the actor: `auth: peer-identity (user=alice@example.com, peer=alice-laptop)`. One thing to get right: verify per request. Don't issue long-lived session cookies for peer-authenticated requests. Authorization should track live mesh state. If a peer disconnects and the same browser reaches the service over a public path, public-tier auth needs to kick in on the very next request. That only works if peer identity is checked per request rather than baked into a cookie at first contact. The naive "plug it in alongside SSO" implementation would silently break this. ## Most of the pieces already exist - The peer-to-user mapping is already maintained by the management server. - Source-IP/CIDR matching is already implemented as a connection-level access restriction. This proposal is basically the same lookup, but resolving the matched IP to a user and feeding it into the existing auth/ACL pipeline. - The user-group filter mechanism already exists on services; this just gives it another way to populate "current user." No new identity provider, no new mapping infrastructure, no new ACL model. It's wiring three existing things together. ## What this enables I care about this beyond just skipping the second login. NetBird has no concept of an authenticated internal channel at the proxy layer right now. Adding one opens up things that are otherwise awkward to do: - Services only reachable to peers, never published publicly. With peer identity as the auth method, these get identity-aware auth without forcing SSO on users already on the mesh. - Different auth policy by channel: peer auth over the mesh, step-up SSO + MFA from the public internet. Can't express this today. - Backends that want per-user scoping (Nextcloud, Grafana, internal dashboards) can get `Remote-User` headers from the proxy without bolting on Authelia or Authentik for header injection. - Every request through the proxy becomes attributable to a user. Right now in homelab setups, the requests that bypass interactive auth have no actor attached, because nobody wants to SSO into their own Jellyfin from their own laptop. ## Alternatives I looked at - IP/CIDR bypass (#5556): solves the friction, discards identity, weakens group ACLs, degrades the audit trail. - Sidecar header-injecting proxy: duplicates state that already lives in the control plane. I tried this briefly and it was annoying to keep in sync. - External forward-auth (Authelia, Authentik): this is what I'm running today. It works, but maintaining a parallel identity layer that ignores what NetBird already knows about peers and users feels like the wrong answer long term. - Manual split-DNS to the target service: bypasses the proxy entirely, breaks centralized TLS and access control. ## Design notes Forwarded header trust boundary (only matters if header injection is enabled). If a service opts into `Remote-User`/`Remote-Groups` headers and is also reachable through some other path (host LAN, another container on the same Docker network, unrelated VPN interface), a caller on that path can forge those headers. The backend can't tell the difference. This is the standard forward-auth footgun; Authelia, Authentik, and oauth2-proxy all warn about it. Shipping example backend configs for Traefik, nginx, and common frameworks would help. The core gating feature doesn't have this problem: WireGuard's cryptographic peer identity prevents IP spoofing inside the mesh. Reachability over the internal channel. This feature only fires when traffic actually arrives at the proxy with a peer source IP, and that's not the default today since public DNS resolves to the gateway's public IP. In practice, users would either set up split-DNS themselves or this feature ships alongside internal-only routes. I'd argue for treating the two as a coordinated pair; the auth story for internal-only routes is incomplete without peer identity, and peer identity is less useful without a way to restrict routes to the mesh.
saavagebueno added the feature-request label 2026-08-05 01:29:58 -04:00
Sign in to join this conversation.
No Label feature-request
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#11538