Documentation
¶
Overview ¶
Package ipcauth provides the kernel-authenticated identity of a local IPC (gRPC) caller and the transport credentials that surface it into the gRPC context, so the daemon can authorize individual RPCs by caller identity.
On Unix the identity is read from the kernel via SO_PEERCRED (Linux) or LOCAL_PEERCRED (Darwin/FreeBSD). On Windows it is derived from the named-pipe client token. Platforms without a peer-identity primitive get no credentials, and every consumer must fail closed when no identity is available.
Index ¶
- Constants
- func ElevatedCommand(command string) string
- func ForwardIdentityMetadata(id Identity, known bool) metadata.MD
- func IsDaemonSelf(id Identity) bool
- func IsPrivilegedCaller(id Identity) bool
- func IsReservedForwardKey(key string) bool
- func NewTransportCredentials() credentials.TransportCredentials
- func OpenOwnedFile(id Identity, path string) (*os.File, error)
- func PrivilegedActor() string
- func UpCommand(flags string) string
- func WithForwardedIdentity(ctx context.Context, id Identity, known bool) context.Context
- type AuthInfo
- type Identity
- func CallerIdentity(ctx context.Context) (Identity, bool)
- func ConnIdentity(conn net.Conn) (Identity, error)
- func CurrentProcessIdentity() (Identity, error)
- func IdentityFromContext(ctx context.Context) (Identity, bool)
- func PeerIdentity(conn net.Conn) (Identity, error)
- func SelfDelegatesTo() (Identity, bool)
Constants ¶
const ( // ErrorReasonPrivilegeRequired identifies the detail. ErrorReasonPrivilegeRequired = "PRIVILEGE_REQUIRED" // ErrorDomain scopes the reason to the NetBird daemon. ErrorDomain = "daemon.netbird.io" // ErrorMetaSummary is the one-sentence explanation of what was refused. ErrorMetaSummary = "summary" // ErrorMetaCommand is the command that performs the same operation with the // privileges it needs, ready to copy and run. ErrorMetaCommand = "command" )
Fields of the ErrorInfo detail the daemon attaches to a PermissionDenied it raises for an operation that requires root/administrator. Clients match on Reason and Domain rather than on the message text, and render the summary and command themselves so the user gets guidance instead of a gRPC error dump.
Variables ¶
This section is empty.
Functions ¶
func ElevatedCommand ¶
ElevatedCommand renders a command so that running it grants the privileges the operation needs. Windows has no in-line equivalent of sudo, so the command is returned unchanged and the user is expected to run it from an elevated terminal.
func ForwardIdentityMetadata ¶
ForwardIdentityMetadata encodes an HTTP client's identity for the JSON gateway to forward to the daemon. When known is false only the marker is set, which makes the daemon treat the caller as unidentified rather than as the daemon itself.
func IsDaemonSelf ¶
IsDaemonSelf reports whether an identity is this very process. The JSON gateway runs inside the daemon and re-dials it locally, so this is what distinguishes the gateway from any other caller, whatever user the daemon runs as.
func IsPrivilegedCaller ¶
IsPrivilegedCaller reports whether an identity may make the changes the daemon restricts to the platform administrator. This is the daemon's own rule and cannot be evaluated by a client, which does not know what the daemon runs as.
Beyond root/administrator it accepts a caller running as the daemon's own identity when the daemon is itself unprivileged. That keeps a rootless container working, where there is no uid 0 at all, and a Windows daemon in netstack mode, which needs no administrator rights. In those setups a caller sharing the daemon's identity can already rewrite the config files it reads and replace the binary it runs, so refusing it a config change would protect nothing; and an unprivileged daemon cannot hand out a root shell in the first place.
func IsReservedForwardKey ¶
IsReservedForwardKey reports whether a gRPC metadata key belongs to the gateway's identity forwarding, and therefore must be dropped when it arrives from outside.
grpc-gateway maps "Grpc-Metadata-<key>" request headers into gRPC metadata and joins them ahead of the values its own annotators add. Without dropping these, an HTTP client could hand the daemon "x-netbird-fwd-uid: 0" and be believed, because the daemon trusts forwarded metadata when the transport peer is the (privileged) gateway.
func NewTransportCredentials ¶
func NewTransportCredentials() credentials.TransportCredentials
NewTransportCredentials returns gRPC transport credentials that expose the caller's kernel-authenticated identity via IdentityFromContext. It returns nil on platforms that have no peer-identity primitive, which the caller must treat as "authorization cannot be enforced".
The handshake exchanges no bytes on the wire, so a client dialing with insecure credentials interoperates with a server using these. That keeps older CLI and UI binaries working against an upgraded daemon.
func OpenOwnedFile ¶ added in v0.76.1
OpenOwnedFile opens path for reading on behalf of the IPC caller identified by id, and fails unless the opened file is a regular file that id owns.
It exists for the paths a local caller hands to the daemon over the IPC. The daemon runs as root, so opening such a path unchecked lets any local user read any file through it. Ownership is the invariant that keeps the daemon from reading, with its own privileges, a file the caller could not read itself: a symlink or hard link planted at the path resolves to a file someone else owns and is refused.
The check is made against the open descriptor rather than the path, so swapping the path between the check and the read cannot change the answer.
A privileged caller is exempt: it can read the file directly, so refusing it here would protect nothing. The regular-file requirement still applies to everyone, since a fifo or device planted at the path is never a log file.
func PrivilegedActor ¶
func PrivilegedActor() string
PrivilegedActor names the principal a privileged operation requires, for use in messages shown to the user.
func UpCommand ¶
UpCommand renders an elevated `netbird up` with the given flags, preceded by a `down`. The down is what makes the command work on a connected client: `netbird up` prints "Already connected" and returns without applying any config flag, so on its own the command would appear to do nothing. It is a no-op, exit 0, when the client is not connected.
";" rather than "&&" so the line can be pasted into any of the shells a user might have: PowerShell 5.1, still the default on Windows Server, rejects "&&" as a syntax error.
func WithForwardedIdentity ¶
WithForwardedIdentity stamps id onto a context's outgoing metadata for the JSON gateway's call to the daemon, replacing any forwarding keys already present so values supplied from outside cannot survive alongside it.
This is deliberately not done with runtime.WithMetadata: grpc-gateway skips its annotators entirely when no request header maps to metadata ("if len(pairs) == 0 { return ctx, nil, nil }", runtime/context.go), which an HTTP/1.0 request with no Host header over a unix socket achieves. The daemon would then see an unmarked call whose transport peer is the daemon's own identity, and authorize it as the daemon. A client interceptor runs for every RPC regardless of headers.
Types ¶
type AuthInfo ¶
type AuthInfo struct {
credentials.CommonAuthInfo
Identity Identity
}
AuthInfo carries the peer Identity as a gRPC credentials.AuthInfo so handlers can retrieve it from the request context via IdentityFromContext.
type Identity ¶
type Identity struct {
// UID and GID are the caller's Unix user ID and primary group ID. Both are
// zero on Windows, where SID is authoritative instead.
UID uint32
GID uint32
// SID is the caller's Windows security identifier, empty on Unix.
SID string
// Groups holds the caller's Windows group SIDs, captured from the client
// token at handshake time. Only groups that are enabled and not
// deny-only are captured, so a group listed here is one the caller can
// actually exercise. Empty on Unix.
Groups []string
// Elevated reports whether the Windows client token is elevated (running
// as administrator, or an administrator with UAC turned off). Always false
// on Unix, where privilege is uid 0.
Elevated bool
// PID is the caller's process ID where the platform reports it (Linux's
// SO_PEERCRED), and 0 where it does not. It identifies the daemon's own
// process dialling itself, which is what the JSON gateway does, and is never
// used to grant anything.
PID int32
}
Identity is the kernel-authenticated identity of a local IPC caller. The zero value is not a valid identity: consumers must only use one obtained with a true ok/nil error return.
func CallerIdentity ¶
CallerIdentity returns the identity to authorize a request against. For a direct connection that is the transport peer's kernel identity. For a request relayed by the local JSON gateway it is the identity the gateway forwarded, since the transport peer is then the daemon itself.
A forwarded identity is only honoured when the transport peer is the daemon's own identity and the metadata carries this process's forwarding proof, so forged forwarding metadata gains a caller nothing. A forwarded request that carries no identity is reported as unidentified, never as the daemon.
The second return value is false when no identity could be established, and callers MUST fail closed in that case.
func ConnIdentity ¶
ConnIdentity extracts the caller's identity from an accepted local IPC connection. It is shared by the gRPC transport credentials and by the JSON gateway, which reads the identity of its own HTTP clients.
func CurrentProcessIdentity ¶
CurrentProcessIdentity returns this process's identity as the daemon would see it if this process connected to the local IPC. It lets a client (the UI) decide up front whether a privileged operation can succeed, without a round-trip and without duplicating the rules: the answer comes from the same Identity.IsPrivileged the daemon applies.
func IdentityFromContext ¶
IdentityFromContext extracts the caller's kernel-authenticated identity from the gRPC peer context. The second return value is false when no IPC transport credentials were negotiated, which happens on a TCP daemon socket and on platforms without a peer-identity primitive. Callers MUST fail closed in that case.
func PeerIdentity ¶
PeerIdentity reads the kernel-authenticated identity of the process on the other end of a Unix socket via SO_PEERCRED. The credentials are recorded by the kernel at connect() time and cannot be changed for the life of the connection, so they are not spoofable by the caller.
func SelfDelegatesTo ¶
SelfDelegatesTo returns the identity this process delegates its authority to, and whether it delegates at all. Only an unprivileged daemon does: see IsPrivilegedCaller. It exists so a refusal can name who may actually perform the operation, because on such a host root is neither required nor necessarily available.
func (Identity) IsPrivileged ¶
IsPrivileged reports whether the caller is the platform's administrative principal, which is what the daemon requires for changes that cross the user-to-root boundary.
On Windows the decision comes from the caller's token rather than from account names or group RIDs: an elevated token, one of the service accounts the daemon itself may run as, or a token with BUILTIN\Administrators enabled. A UAC-filtered administrator has that group marked deny-only, and deny-only groups are dropped when the identity is captured, so such a caller is correctly reported as unprivileged. Domain group memberships (Domain Admins and friends) are deliberately not consulted: they say nothing about what this token may do on this machine.