mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-02 11:39:06 +02:00
Wire up json socket as a named pipe with metdata exchange to daemon
This commit is contained in:
@@ -2,7 +2,13 @@
|
||||
|
||||
package ipcauth
|
||||
|
||||
import "google.golang.org/grpc/credentials"
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
// NewTransportCredentials returns nil on platforms without a peer-identity
|
||||
// primitive. The daemon falls back to insecure credentials and skips per-RPC
|
||||
@@ -10,3 +16,8 @@ import "google.golang.org/grpc/credentials"
|
||||
func NewTransportCredentials() credentials.TransportCredentials {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConnIdentity is unsupported on platforms without a peer-identity primitive.
|
||||
func ConnIdentity(net.Conn) (Identity, error) {
|
||||
return Identity{}, fmt.Errorf("peer identity not supported on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
@@ -27,9 +27,16 @@ func (unixCreds) ClientHandshake(_ context.Context, _ string, conn net.Conn) (ne
|
||||
return conn, AuthInfo{}, nil
|
||||
}
|
||||
|
||||
// ConnIdentity extracts the caller's identity from an accepted local IPC
|
||||
// connection. On Unix it reads peer credentials from the socket. It is shared by
|
||||
// the gRPC transport credentials and the JSON gateway (which forwards it).
|
||||
func ConnIdentity(conn net.Conn) (Identity, error) {
|
||||
return PeerIdentity(conn)
|
||||
}
|
||||
|
||||
// ServerHandshake extracts the peer identity and fails closed if it cannot be read.
|
||||
func (unixCreds) ServerHandshake(conn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||
id, err := PeerIdentity(conn)
|
||||
id, err := ConnIdentity(conn)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -42,17 +42,23 @@ func (winpipeCreds) ClientHandshake(_ context.Context, _ string, conn net.Conn)
|
||||
return conn, AuthInfo{}, nil
|
||||
}
|
||||
|
||||
// ServerHandshake extracts the connecting client's identity from the pipe. Fails
|
||||
// closed if the handle or token cannot be read.
|
||||
func (winpipeCreds) ServerHandshake(conn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||
// ConnIdentity extracts the caller's identity from an accepted named-pipe
|
||||
// connection by impersonating the pipe client and reading its token. It is
|
||||
// shared by the gRPC transport credentials and the JSON gateway (which forwards
|
||||
// it). Requires the client to have connected at SECURITY_IDENTIFICATION.
|
||||
func ConnIdentity(conn net.Conn) (Identity, error) {
|
||||
// go-winio's pipe connection embeds *win32File, which exposes Fd().
|
||||
fdConn, ok := conn.(interface{ Fd() uintptr })
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("connection %T does not expose a pipe handle", conn)
|
||||
return Identity{}, fmt.Errorf("connection %T does not expose a pipe handle", conn)
|
||||
}
|
||||
handle := windows.Handle(fdConn.Fd())
|
||||
return pipeClientIdentity(windows.Handle(fdConn.Fd()))
|
||||
}
|
||||
|
||||
id, err := pipeClientIdentity(handle)
|
||||
// ServerHandshake extracts the connecting client's identity from the pipe. Fails
|
||||
// closed if the handle or token cannot be read.
|
||||
func (winpipeCreds) ServerHandshake(conn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||
id, err := ConnIdentity(conn)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -12,16 +12,28 @@ import (
|
||||
// itself the daemon (self/privileged) — i.e. the loopback gateway — so a direct
|
||||
// gRPC caller cannot forge them.
|
||||
const (
|
||||
mdFwdUID = "x-netbird-fwd-uid"
|
||||
mdFwdGID = "x-netbird-fwd-gid"
|
||||
mdFwdUID = "x-netbird-fwd-uid" // Unix
|
||||
mdFwdGID = "x-netbird-fwd-gid" // Unix
|
||||
mdFwdSID = "x-netbird-fwd-sid" // Windows user SID
|
||||
mdFwdGroup = "x-netbird-fwd-group" // Windows group SID (repeated)
|
||||
mdFwdElevated = "x-netbird-fwd-elevated" // Windows, "1" if elevated
|
||||
)
|
||||
|
||||
// ForwardIdentityMetadata encodes a Unix identity for the gateway to forward to
|
||||
// the daemon. Windows identities are not forwarded (the gateway cannot read a
|
||||
// pipe token for an HTTP client); nil is returned in that case.
|
||||
// ForwardIdentityMetadata encodes an identity for the gateway to forward to the
|
||||
// daemon — Unix uid/gid, or the Windows user SID + enabled group SIDs +
|
||||
// elevation. Both are supported so the gateway works whether the JSON socket is
|
||||
// a Unix socket or a named pipe.
|
||||
func ForwardIdentityMetadata(id Identity) metadata.MD {
|
||||
if id.IsWindows() {
|
||||
return nil
|
||||
md := metadata.MD{}
|
||||
md.Set(mdFwdSID, id.SID)
|
||||
if len(id.Groups) > 0 {
|
||||
md.Set(mdFwdGroup, id.Groups...)
|
||||
}
|
||||
if id.Elevated {
|
||||
md.Set(mdFwdElevated, "1")
|
||||
}
|
||||
return md
|
||||
}
|
||||
return metadata.Pairs(
|
||||
mdFwdUID, strconv.FormatUint(uint64(id.UID), 10),
|
||||
@@ -29,13 +41,22 @@ func ForwardIdentityMetadata(id Identity) metadata.MD {
|
||||
)
|
||||
}
|
||||
|
||||
// forwardedIdentity extracts a forwarded Unix identity from incoming gRPC
|
||||
// metadata, if present and well-formed.
|
||||
// forwardedIdentity extracts a forwarded identity from incoming gRPC metadata,
|
||||
// if present and well-formed. Windows (SID) takes precedence over Unix (uid).
|
||||
func forwardedIdentity(ctx context.Context) (Identity, bool) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return Identity{}, false
|
||||
}
|
||||
|
||||
if sid := mdFirst(md, mdFwdSID); sid != "" {
|
||||
return Identity{
|
||||
SID: sid,
|
||||
Groups: md.Get(mdFwdGroup),
|
||||
Elevated: mdFirst(md, mdFwdElevated) == "1",
|
||||
}, true
|
||||
}
|
||||
|
||||
uidStr := mdFirst(md, mdFwdUID)
|
||||
if uidStr == "" {
|
||||
return Identity{}, false
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package ipcauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
func TestForwardIdentityRoundTrip(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
id Identity
|
||||
}{
|
||||
{"unix uid/gid", Identity{UID: 1000, GID: 1000}},
|
||||
{"windows sid+groups+elevated", Identity{
|
||||
SID: "S-1-5-21-1-2-3-1001",
|
||||
Groups: []string{"S-1-5-32-544", "S-1-1-0"},
|
||||
Elevated: true,
|
||||
}},
|
||||
{"windows sid only", Identity{SID: "S-1-5-21-9"}},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx := metadata.NewIncomingContext(context.Background(), ForwardIdentityMetadata(tc.id))
|
||||
got, ok := forwardedIdentity(ctx)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, tc.id, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardedIdentity_None(t *testing.T) {
|
||||
_, ok := forwardedIdentity(context.Background())
|
||||
assert.False(t, ok, "no metadata → no forwarded identity")
|
||||
|
||||
// Empty metadata (no forwarding keys) → none.
|
||||
ctx := metadata.NewIncomingContext(context.Background(), metadata.Pairs("other", "x"))
|
||||
_, ok = forwardedIdentity(ctx)
|
||||
assert.False(t, ok)
|
||||
}
|
||||
Reference in New Issue
Block a user