[PR #6896] [client] Harden daemon IPC #27432

Open
opened 2026-08-05 07:08:42 -04:00 by saavagebueno · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/netbirdio/netbird/pull/6896
Author: @theodorsm
Created: 7/25/2026
Status: 🔄 Open

Base: mainHead: grpc-acl


📝 Commits (10+)

  • 27afbd7 WIP: acl interceptor and named pipe ui
  • 5d9ef01 Add impersonateNamedPipeClient
  • 4de39a8 Clean up windows impersonation/SDDL and some comments
  • 2f84aa3 Remove PID from Identity
  • f60ac9e Wire up json socket as a named pipe with metdata exchange to daemon
  • 57f9cbe Add migration from legacy tcp to named pipes
  • 0137876 Clean up comments
  • 5e07a0c Add migration for legacy profile ownership
  • 0bb73b3 Add warning log for tcp json socket
  • cd98648 Add elevation for dangerous ssh flags

📊 Changes

77 files changed (+4318 additions, -498 deletions)

View changed files

📝 CONTRIBUTING.md (+1 -1)
📝 client/android/profile_manager.go (+1 -1)
client/cmd/elevate.go (+58 -0)
client/cmd/elevate_darwin.go (+16 -0)
client/cmd/elevate_linux.go (+38 -0)
client/cmd/elevate_other.go (+17 -0)
client/cmd/elevate_test.go (+99 -0)
client/cmd/elevate_windows.go (+23 -0)
client/cmd/owner.go (+116 -0)
📝 client/cmd/profile.go (+15 -1)
📝 client/cmd/root.go (+30 -9)
📝 client/cmd/service.go (+8 -1)
📝 client/cmd/service_controller.go (+48 -2)
📝 client/cmd/service_json_gateway.go (+43 -7)
📝 client/cmd/service_params.go (+55 -0)
📝 client/cmd/service_params_test.go (+7 -1)
client/cmd/service_pipe_other.go (+20 -0)
client/cmd/service_pipe_windows.go (+30 -0)
📝 client/cmd/service_socket.go (+45 -2)
client/cmd/service_socket_test.go (+63 -0)

...and 57 more files

📄 Description

Describe your changes

This PR hardens the local daemon IPC so it authenticates and authorizes each caller instead of trusting anyone who can reach the socket.

gRPC interceptor for per-method ACL

  • Every gRPC call passes through an interceptor that authorizes it per method before the handler runs with kernel credentials: Linux SO_PEERCRED, BSD and macOS LOCAL_PEERCRED, Windows named-pipe client token.
  • Deny by default. A caller whose kernel identity cannot be read is denied, and any method with no explicit rule falls back to the active-profile gate.
  • Privileged callers (root, LocalSystem, elevated admin) and the daemon itself are always allowed.
  • Sensitive control-plane calls are audit logged on both allow and deny.
  • Authorization has three tiers:
    • Owner (AddProfile, Down, Status, and owner management: AddOwner, ShareProfile, ResetOwner). Requires a daemon-wide owner. Gating owner management here, not on the active profile, stops a per-profile owner from escalating to daemon owner.
    • Profile (ListProfiles, SwitchProfile, RenameProfile, RemoveProfile, GetActiveProfile). The handler self-authorizes against the target profile, which the caller must own, or against the caller's own profiles.
    • Default (Up, Login, SetConfig, and everything else). Gated on the active profile's ownership.
  • Requests from the JSON gateway are forwarded using gRPC metadata. The gateway connects as the daemon (self or privileged) and forwards the real HTTP client identity, so a direct non-privileged caller cannot forge the forwarding metadata.

Named pipe for the daemon socket (Windows)

  • Replaced loopback TCP with a Windows named pipe for the daemon socket, including the JSON socket.
  • The named pipe is the default now. CLI and UI are updated to use it.
  • Migrate the legacy default tcp://127.0.0.1:41731 to npipe://netbird on upgrade. A custom daemon-addr is left untouched, so TCP can still be set manually.
  • Reason: TCP loopback carries no caller identity, so IPC authorization would silently not apply.

Ownership

  • Two ownership scopes: daemon-wide owners and per-profile owners.
  • Daemon-wide owners govern the owner-tier RPCs and owner management. Any daemon owner may use the shared default profile.
  • Every non-default profile is isolated to its own owner, the user who created it. Only daemon owners or root can create profiles.
  • Trust on first use. When the daemon or a profile is unowned and not shared, the first caller claims it, and the claim is atomic. This keeps existing single-user installs working with no configuration and covers backward compatibility.
  • Admins can seed daemon owners at install time with the owner flag. Owners persist in the service parameters.
  • Manage at runtime with the new owner command (add, reset, share, unshare). Reset is root or administrator only so co-owners cannot evict each other.
  • Shared makes the daemon and its default profile usable by any authenticated local caller.
  • Owner principals are typed: uid, gid, group name (Unix, resolved via NSS/getent), and Windows SID (user or group). This leaves room for flexible group-based ownership later and supports LDAP.
  • The owner field is authoritative and collision-free, which fixes the case where different usernames sanitize to the same profile directory.
  • Legacy profiles predating ownership are authorized by the existing per-username-directory guard, then stamped with the caller as owner on first access.

SSH guarded behind privilege

  • Enabling SSH root login or disabling SSH authentication over IPC now requires root or administrator.
  • When a non-privileged CLI or UI caller sets these flags, NetBird offers to elevate instead of just failing.
  • Elevation re-runs the binary as a hidden set-ssh-config command through the platform prompt. The elevated helper connects to the daemon with a privileged identity and applies only the SSH flags.
  • Linux is implemented with pkexec and polkit. Windows and macOS are stubbed.
  • The unprivileged up and login flow drops the dangerous flags from its own request once the helper has applied them, so it does not re-trip the daemon gate.
  • The UI does the same on a PermissionDenied. It elevates, then re-sends the config with the SSH flags cleared.

Misc

  • Group and user resolution moved into a shared internal package reused by the SSH server and the ownership group resolver. LDAP and AD principals resolve via NSS even in the cgo-less daemon build.
  • status and profile list now surface when another user's profile is active, so an empty active column is not mistaken for nothing being active.

TODOs

  • Add cmd to remove a single owner.
  • Add elevate to admin for guarded ssh features on windows and macos.
  • Update references of legacy TCP sockets to named pipes.
  • Fix error feedback to user via CLI and UI
  • Docs
    • Document the dangers of using tcp://

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)
  • This change does not modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — OR I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See CONTRIBUTING.md.

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)
    TODO

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

TODO


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Summary by CodeRabbit

  • New Features

    • Added profile and daemon ownership controls, including claiming, sharing, owner management, and access protection.
    • Added support for Windows named-pipe daemon connections.
    • Added privileged handling for sensitive SSH configuration changes.
    • Added owner options to profile and service commands.
  • Bug Fixes

    • Improved profile listings and status output when profiles are active under another account.
    • Preserved client identity through the JSON gateway for consistent authorization.
  • Documentation

    • Updated live-reload and daemon address examples for named-pipe support.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/netbirdio/netbird/pull/6896 **Author:** [@theodorsm](https://github.com/theodorsm) **Created:** 7/25/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `grpc-acl` --- ### 📝 Commits (10+) - [`27afbd7`](https://github.com/netbirdio/netbird/commit/27afbd79528a9a79cb3de300402c697fd2a2ac5a) WIP: acl interceptor and named pipe ui - [`5d9ef01`](https://github.com/netbirdio/netbird/commit/5d9ef0123fa41774fd8da3f6a966c7fbc279d8bd) Add impersonateNamedPipeClient - [`4de39a8`](https://github.com/netbirdio/netbird/commit/4de39a80f8a8632c02916c9fe82cb479f5e14d0d) Clean up windows impersonation/SDDL and some comments - [`2f84aa3`](https://github.com/netbirdio/netbird/commit/2f84aa3d20d4537f350521f3ac83912678e08bd8) Remove PID from Identity - [`f60ac9e`](https://github.com/netbirdio/netbird/commit/f60ac9e746d8d032eab4dc7853f4807398a5b093) Wire up json socket as a named pipe with metdata exchange to daemon - [`57f9cbe`](https://github.com/netbirdio/netbird/commit/57f9cbe5ff3381255d2c2b9c172a66b4570b166a) Add migration from legacy tcp to named pipes - [`0137876`](https://github.com/netbirdio/netbird/commit/0137876618b14ef4846b186f29a22596f757b507) Clean up comments - [`5e07a0c`](https://github.com/netbirdio/netbird/commit/5e07a0c27b7e6c34743ae71417dee96f6b221213) Add migration for legacy profile ownership - [`0bb73b3`](https://github.com/netbirdio/netbird/commit/0bb73b3730a024dd95db3894e1bf34433ad1f67b) Add warning log for tcp json socket - [`cd98648`](https://github.com/netbirdio/netbird/commit/cd98648a670f8830f2ef9c3aa367d71b4f12172e) Add elevation for dangerous ssh flags ### 📊 Changes **77 files changed** (+4318 additions, -498 deletions) <details> <summary>View changed files</summary> 📝 `CONTRIBUTING.md` (+1 -1) 📝 `client/android/profile_manager.go` (+1 -1) ➕ `client/cmd/elevate.go` (+58 -0) ➕ `client/cmd/elevate_darwin.go` (+16 -0) ➕ `client/cmd/elevate_linux.go` (+38 -0) ➕ `client/cmd/elevate_other.go` (+17 -0) ➕ `client/cmd/elevate_test.go` (+99 -0) ➕ `client/cmd/elevate_windows.go` (+23 -0) ➕ `client/cmd/owner.go` (+116 -0) 📝 `client/cmd/profile.go` (+15 -1) 📝 `client/cmd/root.go` (+30 -9) 📝 `client/cmd/service.go` (+8 -1) 📝 `client/cmd/service_controller.go` (+48 -2) 📝 `client/cmd/service_json_gateway.go` (+43 -7) 📝 `client/cmd/service_params.go` (+55 -0) 📝 `client/cmd/service_params_test.go` (+7 -1) ➕ `client/cmd/service_pipe_other.go` (+20 -0) ➕ `client/cmd/service_pipe_windows.go` (+30 -0) 📝 `client/cmd/service_socket.go` (+45 -2) ➕ `client/cmd/service_socket_test.go` (+63 -0) _...and 57 more files_ </details> ### 📄 Description ## Describe your changes This PR hardens the local daemon IPC so it authenticates and authorizes each caller instead of trusting anyone who can reach the socket. #### gRPC interceptor for per-method ACL - Every gRPC call passes through an interceptor that authorizes it per method before the handler runs with kernel credentials: Linux SO_PEERCRED, BSD and macOS LOCAL_PEERCRED, Windows named-pipe client token. - Deny by default. A caller whose kernel identity cannot be read is denied, and any method with no explicit rule falls back to the active-profile gate. - Privileged callers (root, LocalSystem, elevated admin) and the daemon itself are always allowed. - Sensitive control-plane calls are audit logged on both allow and deny. - Authorization has three tiers: - Owner (AddProfile, Down, Status, and owner management: AddOwner, ShareProfile, ResetOwner). Requires a daemon-wide owner. Gating owner management here, not on the active profile, stops a per-profile owner from escalating to daemon owner. - Profile (ListProfiles, SwitchProfile, RenameProfile, RemoveProfile, GetActiveProfile). The handler self-authorizes against the target profile, which the caller must own, or against the caller's own profiles. - Default (Up, Login, SetConfig, and everything else). Gated on the active profile's ownership. - Requests from the JSON gateway are forwarded using gRPC metadata. The gateway connects as the daemon (self or privileged) and forwards the real HTTP client identity, so a direct non-privileged caller cannot forge the forwarding metadata. #### Named pipe for the daemon socket (Windows) - Replaced loopback TCP with a Windows named pipe for the daemon socket, including the JSON socket. - The named pipe is the default now. CLI and UI are updated to use it. - Migrate the legacy default `tcp://127.0.0.1:41731` to `npipe://netbird` on upgrade. A custom daemon-addr is left untouched, so TCP can still be set manually. - Reason: TCP loopback carries no caller identity, so IPC authorization would silently not apply. #### Ownership - Two ownership scopes: daemon-wide owners and per-profile owners. - Daemon-wide owners govern the owner-tier RPCs and owner management. Any daemon owner may use the shared default profile. - Every non-default profile is isolated to its own owner, the user who created it. Only daemon owners or root can create profiles. - Trust on first use. When the daemon or a profile is unowned and not shared, the first caller claims it, and the claim is atomic. This keeps existing single-user installs working with no configuration and covers backward compatibility. - Admins can seed daemon owners at install time with the owner flag. Owners persist in the service parameters. - Manage at runtime with the new owner command (add, reset, share, unshare). Reset is root or administrator only so co-owners cannot evict each other. - Shared makes the daemon and its default profile usable by any authenticated local caller. - Owner principals are typed: uid, gid, group name (Unix, resolved via NSS/getent), and Windows SID (user or group). This leaves room for flexible group-based ownership later and supports LDAP. - The owner field is authoritative and collision-free, which fixes the case where different usernames sanitize to the same profile directory. - Legacy profiles predating ownership are authorized by the existing per-username-directory guard, then stamped with the caller as owner on first access. #### SSH guarded behind privilege - Enabling SSH root login or disabling SSH authentication over IPC now requires root or administrator. - When a non-privileged CLI or UI caller sets these flags, NetBird offers to elevate instead of just failing. - Elevation re-runs the binary as a hidden set-ssh-config command through the platform prompt. The elevated helper connects to the daemon with a privileged identity and applies only the SSH flags. - Linux is implemented with pkexec and polkit. Windows and macOS are stubbed. - The unprivileged up and login flow drops the dangerous flags from its own request once the helper has applied them, so it does not re-trip the daemon gate. - The UI does the same on a PermissionDenied. It elevates, then re-sends the config with the SSH flags cleared. #### Misc - Group and user resolution moved into a shared internal package reused by the SSH server and the ownership group resolver. LDAP and AD principals resolve via NSS even in the cgo-less daemon build. - status and profile list now surface when another user's profile is active, so an empty active column is not mistaken for nothing being active. #### TODOs - [ ] Add cmd to remove a single owner. - [ ] Add elevate to admin for guarded ssh features on windows and macos. - [ ] Update references of legacy TCP sockets to named pipes. - [ ] Fix error feedback to user via CLI and UI - [ ] Docs - Document the dangers of using `tcp://` ## Issue ticket number and link ## Stack <!-- branch-stack --> ### Checklist - [x] Is it a bug fix - [ ] Is a typo/documentation fix - [x] Is a feature enhancement - [ ] It is a refactor - [ ] Created tests that fail without the change (if possible) - [x] This change does **not** modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — **OR** I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See [CONTRIBUTING.md](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTING.md#discuss-changes-with-the-netbird-team-first). > By submitting this pull request, you confirm that you have read and agree to the terms of the [Contributor License Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md). ## Documentation Select exactly one: - [ ] I added/updated documentation for this change - [x] Documentation is **not needed** for this change (explain why) TODO ### Docs PR URL (required if "docs added" is checked) Paste the PR link from https://github.com/netbirdio/docs here: TODO <!-- codesmith:footer --> --- <a href="https://app.blacksmith.sh/netbirdio/codesmith/netbird/pr/6896"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img alt="View with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"></picture></a> <a href="https://backend.blacksmith.sh/track/enable-autofix?expires=1787599861&installation_model_id=427504&pr_number=6896&repository=netbirdio%2Fnetbird&return_to=https%3A%2F%2Fgithub.com%2Fnetbirdio%2Fnetbird%2Fpull%2F6896&signature=c11d9e426fc4b626b26f59174641bc93d37e07e5da220c4144c4526b57aadd3d"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img alt="Autofix with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a> <sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you need. Autofix is disabled.</sup> <!-- codesmith:autofix:disabled --> <!-- /codesmith:footer --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added profile and daemon ownership controls, including claiming, sharing, owner management, and access protection. * Added support for Windows named-pipe daemon connections. * Added privileged handling for sensitive SSH configuration changes. * Added owner options to profile and service commands. * **Bug Fixes** * Improved profile listings and status output when profiles are active under another account. * Preserved client identity through the JSON gateway for consistent authorization. * **Documentation** * Updated live-reload and daemon address examples for named-pipe support. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
saavagebueno added the pull-request label 2026-08-05 07:08:42 -04:00
Sign in to join this conversation.
No Label pull-request
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#27432