mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-01 11:09:15 +02:00
Add known mark to identity, fix credentials comparison bugs
This commit is contained in:
@@ -86,7 +86,7 @@ func TestJSONGateway_ForgedIdentityHeaderIsDropped(t *testing.T) {
|
|||||||
req.Header.Set("Grpc-Metadata-X-Netbird-Fwd", "1")
|
req.Header.Set("Grpc-Metadata-X-Netbird-Fwd", "1")
|
||||||
req.Header.Set("Grpc-Metadata-X-Netbird-Fwd-Sid", "S-1-5-18")
|
req.Header.Set("Grpc-Metadata-X-Netbird-Fwd-Sid", "S-1-5-18")
|
||||||
|
|
||||||
caller := ipcauth.Identity{UID: 31000, GID: 31000}
|
caller := ipcauth.KnownForTest(ipcauth.Identity{UID: 31000, GID: 31000})
|
||||||
md := gatewayMetadata(t, req, clientCtx(caller, true))
|
md := gatewayMetadata(t, req, clientCtx(caller, true))
|
||||||
|
|
||||||
id, ok := ipcauth.CallerIdentity(daemonSideCtx(t, md))
|
id, ok := ipcauth.CallerIdentity(daemonSideCtx(t, md))
|
||||||
@@ -114,7 +114,7 @@ func TestJSONGateway_HeaderlessRequestIsStillMarkedForwarded(t *testing.T) {
|
|||||||
req.Header = http.Header{}
|
req.Header = http.Header{}
|
||||||
req.Host = ""
|
req.Host = ""
|
||||||
|
|
||||||
caller := ipcauth.Identity{UID: 31000, GID: 31000}
|
caller := ipcauth.KnownForTest(ipcauth.Identity{UID: 31000, GID: 31000})
|
||||||
ctx := clientCtx(caller, true)
|
ctx := clientCtx(caller, true)
|
||||||
|
|
||||||
// Pin the skip path itself: if grpc-gateway ever produced a pair here, this
|
// Pin the skip path itself: if grpc-gateway ever produced a pair here, this
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ func identityFromToken(token windows.Token) (Identity, error) {
|
|||||||
SID: user.User.Sid.String(),
|
SID: user.User.Sid.String(),
|
||||||
Groups: groups,
|
Groups: groups,
|
||||||
Elevated: token.IsElevated(),
|
Elevated: token.IsElevated(),
|
||||||
|
known: true,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -216,6 +216,7 @@ func forwardedIdentity(ctx context.Context) (Identity, bool) {
|
|||||||
// the forwarding proof has been verified.
|
// the forwarding proof has been verified.
|
||||||
Groups: md.Get(mdFwdGroup),
|
Groups: md.Get(mdFwdGroup),
|
||||||
Elevated: mdSingle(md, mdFwdElevated) == "1",
|
Elevated: mdSingle(md, mdFwdElevated) == "1",
|
||||||
|
known: true,
|
||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +225,7 @@ func forwardedIdentity(ctx context.Context) (Identity, bool) {
|
|||||||
return Identity{}, false
|
return Identity{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
id := Identity{UID: uint32(uid)}
|
id := Identity{UID: uint32(uid), known: true}
|
||||||
if gid, err := strconv.ParseUint(mdSingle(md, mdFwdGID), 10, 32); err == nil {
|
if gid, err := strconv.ParseUint(mdSingle(md, mdFwdGID), 10, 32); err == nil {
|
||||||
id.GID = uint32(gid)
|
id.GID = uint32(gid)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ func transportCtx(id Identity, md metadata.MD) context.Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
root = Identity{UID: 0}
|
root = Identity{known: true, UID: 0}
|
||||||
unprivUser = Identity{UID: 1000, GID: 1000}
|
unprivUser = Identity{known: true, UID: 1000, GID: 1000}
|
||||||
)
|
)
|
||||||
|
|
||||||
// asDaemon pins which identity counts as this process for the duration of a test.
|
// asDaemon pins which identity counts as this process for the duration of a test.
|
||||||
@@ -35,9 +35,9 @@ var (
|
|||||||
// "the gateway" means.
|
// "the gateway" means.
|
||||||
func asDaemon(t *testing.T, id Identity) {
|
func asDaemon(t *testing.T, id Identity) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
prevID, prevKnown, prevDelegate := selfIdentity, selfKnown, selfMayDelegate
|
prevID, prevDelegate := selfIdentity, selfMayDelegate
|
||||||
t.Cleanup(func() { selfIdentity, selfKnown, selfMayDelegate = prevID, prevKnown, prevDelegate })
|
t.Cleanup(func() { selfIdentity, selfMayDelegate = prevID, prevDelegate })
|
||||||
selfIdentity, selfKnown = id, true
|
selfIdentity = id
|
||||||
selfMayDelegate = !id.IsPrivileged()
|
selfMayDelegate = !id.IsPrivileged()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ func TestCallerIdentity_GatewayForwarding(t *testing.T) {
|
|||||||
injected.Append(mdFwdGroup, sidAdministrators)
|
injected.Append(mdFwdGroup, sidAdministrators)
|
||||||
|
|
||||||
ctx := WithForwardedIdentity(metadata.NewOutgoingContext(context.Background(), injected),
|
ctx := WithForwardedIdentity(metadata.NewOutgoingContext(context.Background(), injected),
|
||||||
Identity{SID: "S-1-5-21-1-2-3-1001"}, true)
|
Identity{known: true, SID: "S-1-5-21-1-2-3-1001"}, true)
|
||||||
out, ok := metadata.FromOutgoingContext(ctx)
|
out, ok := metadata.FromOutgoingContext(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("no outgoing metadata")
|
t.Fatal("no outgoing metadata")
|
||||||
@@ -202,7 +202,7 @@ func TestForwardIdentityMetadata_AlwaysMarksForwarded(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"known unix identity", unprivUser, true},
|
{"known unix identity", unprivUser, true},
|
||||||
{"unknown identity", Identity{}, false},
|
{"unknown identity", Identity{}, false},
|
||||||
{"windows identity", Identity{SID: "S-1-5-21-1-2-3-1001", Elevated: true}, true},
|
{"windows identity", Identity{known: true, SID: "S-1-5-21-1-2-3-1001", Elevated: true}, true},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
md := ForwardIdentityMetadata(tc.id, tc.known)
|
md := ForwardIdentityMetadata(tc.id, tc.known)
|
||||||
|
|||||||
@@ -56,8 +56,15 @@ type Identity struct {
|
|||||||
// process dialling itself, which is what the JSON gateway does, and is never
|
// process dialling itself, which is what the JSON gateway does, and is never
|
||||||
// used to grant anything.
|
// used to grant anything.
|
||||||
PID int32
|
PID int32
|
||||||
|
|
||||||
|
// known marks if an identity was provided by the kernel. Without it, an
|
||||||
|
// empty Identity struct would resolve as root.
|
||||||
|
known bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Known reports whether this identity came from a kernel credential read.
|
||||||
|
func (i Identity) Known() bool { return i.known }
|
||||||
|
|
||||||
// IsWindows reports whether this identity is a Windows principal (SID-based)
|
// IsWindows reports whether this identity is a Windows principal (SID-based)
|
||||||
// rather than a Unix uid/gid principal.
|
// rather than a Unix uid/gid principal.
|
||||||
func (i Identity) IsWindows() bool {
|
func (i Identity) IsWindows() bool {
|
||||||
@@ -77,6 +84,9 @@ func (i Identity) IsWindows() bool {
|
|||||||
// (Domain Admins and friends) are deliberately not consulted: they say
|
// (Domain Admins and friends) are deliberately not consulted: they say
|
||||||
// nothing about what this token may do on this machine.
|
// nothing about what this token may do on this machine.
|
||||||
func (i Identity) IsPrivileged() bool {
|
func (i Identity) IsPrivileged() bool {
|
||||||
|
if !i.known {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if !i.IsWindows() {
|
if !i.IsWindows() {
|
||||||
return i.UID == 0
|
return i.UID == 0
|
||||||
}
|
}
|
||||||
@@ -100,6 +110,9 @@ func (i Identity) IsPrivileged() bool {
|
|||||||
// happen to leave at zero. The zero Identity carries uid 0, so callers must
|
// happen to leave at zero. The zero Identity carries uid 0, so callers must
|
||||||
// establish that both identities are real before the answer means anything.
|
// establish that both identities are real before the answer means anything.
|
||||||
func (i Identity) SameUser(other Identity) bool {
|
func (i Identity) SameUser(other Identity) bool {
|
||||||
|
if !i.known || !other.known {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if i.SID != "" || other.SID != "" {
|
if i.SID != "" || other.SID != "" {
|
||||||
return i.SID == other.SID
|
return i.SID == other.SID
|
||||||
}
|
}
|
||||||
@@ -108,6 +121,11 @@ func (i Identity) SameUser(other Identity) bool {
|
|||||||
|
|
||||||
// String renders the identity for audit logs and denial messages.
|
// String renders the identity for audit logs and denial messages.
|
||||||
func (i Identity) String() string {
|
func (i Identity) String() string {
|
||||||
|
// An unknown identity has a zero UID, which would print as "uid=0" and read
|
||||||
|
// as root in an audit trail.
|
||||||
|
if !i.known {
|
||||||
|
return "unidentified"
|
||||||
|
}
|
||||||
if i.IsWindows() {
|
if i.IsWindows() {
|
||||||
return fmt.Sprintf("sid=%s elevated=%t", i.SID, i.Elevated)
|
return fmt.Sprintf("sid=%s elevated=%t", i.SID, i.Elevated)
|
||||||
}
|
}
|
||||||
@@ -187,17 +205,32 @@ func OwnerPrincipalForIdentity(id Identity) string {
|
|||||||
return UIDPrincipal(id.UID)
|
return UIDPrincipal(id.UID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IdentityFromPrincipal(p Principal) (Identity, error) {
|
// Matches reports whether a kernel-attested caller satisfies this stored owner
|
||||||
|
// principal.
|
||||||
|
//
|
||||||
|
// A principal is a config value, not a caller, so it is never converted into an
|
||||||
|
// Identity.
|
||||||
|
func (p Principal) Matches(id Identity) bool {
|
||||||
|
if !id.Known() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
switch p.Kind {
|
switch p.Kind {
|
||||||
case KindUID:
|
case KindUID:
|
||||||
uid, err := strconv.ParseUint(p.Value, 10, 32)
|
if id.IsWindows() {
|
||||||
if err != nil {
|
return false
|
||||||
return Identity{}, err
|
|
||||||
}
|
}
|
||||||
return Identity{UID: uint32(uid)}, nil
|
uid, err := strconv.ParseUint(p.Value, 10, 32)
|
||||||
|
return err == nil && uint32(uid) == id.UID
|
||||||
case KindSID:
|
case KindSID:
|
||||||
return Identity{SID: p.Value}, nil
|
if !id.IsWindows() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Only the user SID. Group ownership is not supported yet.
|
||||||
|
return id.SID == p.Value
|
||||||
default:
|
default:
|
||||||
return Identity{}, nil
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String renders the principal as the kind:value form it is stored in.
|
||||||
|
func (p Principal) String() string { return string(p.Kind) + ":" + p.Value }
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
package ipcauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The zero Identity carries uid 0, so every predicate that reads UID has to
|
||||||
|
// refuse it explicitly without the known marker.
|
||||||
|
func TestZeroIdentityIsInert(t *testing.T) {
|
||||||
|
var zero Identity
|
||||||
|
|
||||||
|
assert.False(t, zero.Known(), "the zero identity must not be known")
|
||||||
|
assert.False(t, zero.IsPrivileged(), "the zero identity must not read as root")
|
||||||
|
assert.False(t, zero.SameUser(Identity{}), "two unknown identities must not match")
|
||||||
|
assert.False(t, zero.SameUser(Identity{known: true, UID: 0}), "an unknown identity must not match root")
|
||||||
|
assert.False(t, Identity{known: true, UID: 0}.SameUser(zero), "SameUser must be symmetric here too")
|
||||||
|
assert.Equal(t, "unidentified", zero.String(), "an unknown identity must not print as uid=0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDaemonSelf compares UIDs, so on the usual install, where the daemon is root,
|
||||||
|
// an unknown identity would match it.
|
||||||
|
func TestIsDaemonSelfRejectsUnknownIdentity(t *testing.T) {
|
||||||
|
prevID, prevDelegate := selfIdentity, selfMayDelegate
|
||||||
|
t.Cleanup(func() { selfIdentity, selfMayDelegate = prevID, prevDelegate })
|
||||||
|
|
||||||
|
selfIdentity = Identity{known: true, UID: 0}
|
||||||
|
selfMayDelegate = false
|
||||||
|
|
||||||
|
assert.False(t, IsDaemonSelf(Identity{}), "an unknown identity is not the daemon")
|
||||||
|
assert.True(t, IsDaemonSelf(Identity{known: true, UID: 0}), "precondition: a known root caller is the daemon here")
|
||||||
|
}
|
||||||
|
|
||||||
|
// A daemon whose own identity could not be read delegates to nobody, and the
|
||||||
|
// zero selfIdentity is what records that.
|
||||||
|
func TestUnknownSelfDelegatesToNobody(t *testing.T) {
|
||||||
|
prevID, prevDelegate := selfIdentity, selfMayDelegate
|
||||||
|
t.Cleanup(func() { selfIdentity, selfMayDelegate = prevID, prevDelegate })
|
||||||
|
|
||||||
|
selfIdentity = Identity{}
|
||||||
|
selfMayDelegate = true
|
||||||
|
|
||||||
|
assert.False(t, IsDaemonSelf(Identity{known: true, UID: 1000}))
|
||||||
|
_, delegates := SelfDelegatesTo()
|
||||||
|
assert.False(t, delegates, "an unknown self identity must not be delegated to")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKnownForTestMarksIdentity(t *testing.T) {
|
||||||
|
id := KnownForTest(Identity{UID: 1000, GID: 1000})
|
||||||
|
require.True(t, id.Known())
|
||||||
|
assert.Equal(t, uint32(1000), id.UID, "KnownForTest must not alter the identity")
|
||||||
|
}
|
||||||
|
|
||||||
|
type stubState struct {
|
||||||
|
holder Principal
|
||||||
|
held bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s stubState) SessionHolder() (Principal, bool) { return s.holder, s.held }
|
||||||
|
|
||||||
|
func uidHolder(uid uint32) Principal {
|
||||||
|
p, ok := ParsePrincipal(UIDPrincipal(uid))
|
||||||
|
if !ok {
|
||||||
|
panic("bad uid principal")
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// The holder comes from a profile JSON, the caller from a peercred read. When
|
||||||
|
// both were an Identity, the known marker made every such comparison false and
|
||||||
|
// the session holder could never be recognised.
|
||||||
|
func TestRequireSessionHolderMatchesConfigOwner(t *testing.T) {
|
||||||
|
asDaemon(t, KnownForTest(Identity{UID: 0}))
|
||||||
|
|
||||||
|
caller := KnownForTest(Identity{UID: 1000, GID: 1000})
|
||||||
|
assert.NoError(t, RequireSessionHolder(caller, stubState{holder: uidHolder(1000), held: true}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequireSessionHolderRejectsAnotherUser(t *testing.T) {
|
||||||
|
asDaemon(t, KnownForTest(Identity{UID: 0}))
|
||||||
|
|
||||||
|
caller := KnownForTest(Identity{UID: 1000, GID: 1000})
|
||||||
|
err := RequireSessionHolder(caller, stubState{holder: uidHolder(1001), held: true})
|
||||||
|
assert.Equal(t, codes.PermissionDenied, status.Code(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// With nobody connected there is no session to protect.
|
||||||
|
func TestRequireSessionHolderAllowsWhenUnheld(t *testing.T) {
|
||||||
|
asDaemon(t, KnownForTest(Identity{UID: 0}))
|
||||||
|
|
||||||
|
caller := KnownForTest(Identity{UID: 1000, GID: 1000})
|
||||||
|
assert.NoError(t, RequireSessionHolder(caller, stubState{held: false}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// An owner that could not be parsed leaves the zero Principal, which matches
|
||||||
|
// nobody, so the session locks rather than opening.
|
||||||
|
func TestRequireSessionHolderLocksOnUnparseableOwner(t *testing.T) {
|
||||||
|
asDaemon(t, KnownForTest(Identity{UID: 0}))
|
||||||
|
|
||||||
|
caller := KnownForTest(Identity{UID: 1000, GID: 1000})
|
||||||
|
err := RequireSessionHolder(caller, stubState{holder: Principal{}, held: true})
|
||||||
|
assert.Equal(t, codes.PermissionDenied, status.Code(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root takes over whoever holds the session.
|
||||||
|
func TestRequireSessionHolderAllowsPrivilegedCaller(t *testing.T) {
|
||||||
|
asDaemon(t, KnownForTest(Identity{UID: 1000}))
|
||||||
|
|
||||||
|
root := KnownForTest(Identity{UID: 0})
|
||||||
|
assert.NoError(t, RequireSessionHolder(root, stubState{holder: uidHolder(1001), held: true}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A uid:0 owner is a config value, so it grants nothing on its own.
|
||||||
|
func TestConfigOwnerCannotGrantPrivilege(t *testing.T) {
|
||||||
|
root, ok := ParsePrincipal("uid:0")
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.False(t, root.Matches(KnownForTest(Identity{UID: 1000})),
|
||||||
|
"a root owner must not match an unrelated caller")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group ownership is not supported yet, so a group SID in an owner field must
|
||||||
|
// not match a caller who merely belongs to that group.
|
||||||
|
func TestPrincipalDoesNotMatchGroupSID(t *testing.T) {
|
||||||
|
group, ok := ParsePrincipal("sid:S-1-5-21-1-2-3-513")
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
member := KnownForTest(Identity{
|
||||||
|
SID: "S-1-5-21-1-2-3-1001",
|
||||||
|
Groups: []string{"S-1-5-21-1-2-3-513"},
|
||||||
|
})
|
||||||
|
assert.False(t, group.Matches(member), "a group SID owner must not match a group member")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrincipalMatchingIsPlatformScoped(t *testing.T) {
|
||||||
|
unix, ok := ParsePrincipal("uid:1000")
|
||||||
|
require.True(t, ok)
|
||||||
|
windows, ok := ParsePrincipal("sid:S-1-5-21-1-2-3-1001")
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
unixCaller := KnownForTest(Identity{UID: 1000})
|
||||||
|
windowsCaller := KnownForTest(Identity{SID: "S-1-5-21-1-2-3-1001"})
|
||||||
|
|
||||||
|
assert.True(t, unix.Matches(unixCaller))
|
||||||
|
assert.True(t, windows.Matches(windowsCaller))
|
||||||
|
assert.False(t, unix.Matches(windowsCaller), "uid must never match a windows identity")
|
||||||
|
assert.False(t, windows.Matches(unixCaller), "sid must never match a unix identity")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrincipalDoesNotMatchUnknownIdentity(t *testing.T) {
|
||||||
|
p, ok := ParsePrincipal("uid:0")
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.False(t, p.Matches(Identity{}), "an unknown caller matches no principal")
|
||||||
|
}
|
||||||
@@ -15,44 +15,44 @@ func TestIdentitySameUser(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "same uid",
|
name: "same uid",
|
||||||
a: Identity{UID: 1000, GID: 1000},
|
a: Identity{known: true, UID: 1000, GID: 1000},
|
||||||
b: Identity{UID: 1000, GID: 1000},
|
b: Identity{known: true, UID: 1000, GID: 1000},
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "same uid, different gid and pid still the same user",
|
name: "same uid, different gid and pid still the same user",
|
||||||
a: Identity{UID: 1000, GID: 1000, PID: 11},
|
a: Identity{known: true, UID: 1000, GID: 1000, PID: 11},
|
||||||
b: Identity{UID: 1000, GID: 27, PID: 22},
|
b: Identity{known: true, UID: 1000, GID: 27, PID: 22},
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "different uid",
|
name: "different uid",
|
||||||
a: Identity{UID: 1000},
|
a: Identity{known: true, UID: 1000},
|
||||||
b: Identity{UID: 1001},
|
b: Identity{known: true, UID: 1001},
|
||||||
want: false,
|
want: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "same sid",
|
name: "same sid",
|
||||||
a: Identity{SID: "S-1-5-21-1-2-3-1001"},
|
a: Identity{known: true, SID: "S-1-5-21-1-2-3-1001"},
|
||||||
b: Identity{SID: "S-1-5-21-1-2-3-1001"},
|
b: Identity{known: true, SID: "S-1-5-21-1-2-3-1001"},
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "same sid, elevation and groups differ",
|
name: "same sid, elevation and groups differ",
|
||||||
a: Identity{SID: "S-1-5-21-1-2-3-1001", Elevated: true, Groups: []string{sidAdministrators}},
|
a: Identity{known: true, SID: "S-1-5-21-1-2-3-1001", Elevated: true, Groups: []string{sidAdministrators}},
|
||||||
b: Identity{SID: "S-1-5-21-1-2-3-1001"},
|
b: Identity{known: true, SID: "S-1-5-21-1-2-3-1001"},
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "different sid",
|
name: "different sid",
|
||||||
a: Identity{SID: "S-1-5-21-1-2-3-1001"},
|
a: Identity{known: true, SID: "S-1-5-21-1-2-3-1001"},
|
||||||
b: Identity{SID: "S-1-5-21-1-2-3-1002"},
|
b: Identity{known: true, SID: "S-1-5-21-1-2-3-1002"},
|
||||||
want: false,
|
want: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "a windows principal is never a unix one",
|
name: "a windows principal is never a unix one",
|
||||||
a: Identity{SID: "S-1-5-18"},
|
a: Identity{known: true, SID: "S-1-5-18"},
|
||||||
b: Identity{UID: 0},
|
b: Identity{known: true, UID: 0},
|
||||||
want: false,
|
want: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func PeerIdentity(conn net.Conn) (Identity, error) {
|
|||||||
return Identity{}, fmt.Errorf("read LOCAL_PEERCRED: %w", credErr)
|
return Identity{}, fmt.Errorf("read LOCAL_PEERCRED: %w", credErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
id := Identity{UID: cred.Uid}
|
id := Identity{UID: cred.Uid, known: true}
|
||||||
if cred.Ngroups > 0 {
|
if cred.Ngroups > 0 {
|
||||||
id.GID = cred.Groups[0]
|
id.GID = cred.Groups[0]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,5 +35,5 @@ func PeerIdentity(conn net.Conn) (Identity, error) {
|
|||||||
return Identity{}, fmt.Errorf("read SO_PEERCRED: %w", credErr)
|
return Identity{}, fmt.Errorf("read SO_PEERCRED: %w", credErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Identity{UID: cred.Uid, GID: cred.Gid, PID: cred.Pid}, nil
|
return Identity{UID: cred.Uid, GID: cred.Gid, PID: cred.Pid, known: true}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func PipeOwnedBySelf(conn net.Conn) bool {
|
|||||||
log.Debugf("read daemon pipe owner: %v", err)
|
log.Debugf("read daemon pipe owner: %v", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return selfKnown && selfIdentity.SID != "" && owner == selfIdentity.SID
|
return selfIdentity.Known() && selfIdentity.SID != "" && owner == selfIdentity.SID
|
||||||
}
|
}
|
||||||
|
|
||||||
// pipeOwnerSID reads the owner of the pipe object a client is connected to. The
|
// pipeOwnerSID reads the owner of the pipe object a client is connected to. The
|
||||||
@@ -83,5 +83,5 @@ func trustedPipeOwner(owner string) bool {
|
|||||||
case sidLocalSystem, sidLocalService, sidNetworkService, sidAdministrators:
|
case sidLocalSystem, sidLocalService, sidNetworkService, sidAdministrators:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return selfKnown && selfIdentity.SID != "" && owner == selfIdentity.SID
|
return selfIdentity.Known() && selfIdentity.SID != "" && owner == selfIdentity.SID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DaemonState interface {
|
type DaemonState interface {
|
||||||
SessionHolder() (Identity, bool)
|
// SessionHolder returns the principal entitled to the live connection and
|
||||||
|
// whether one is held. A held session whose owner cannot be parsed returns
|
||||||
|
// the zero Principal with true, which matches nobody, so a corrupt owner
|
||||||
|
// field locks the session instead of opening it.
|
||||||
|
SessionHolder() (Principal, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Rule func(id Identity, st DaemonState) error
|
type Rule func(id Identity, st DaemonState) error
|
||||||
@@ -44,12 +48,14 @@ func (g *RuleGate) state() DaemonState {
|
|||||||
return g.st
|
return g.st
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequireSessionHolder allows the caller to act on the live connection. With no
|
||||||
|
// session held there is nothing to protect, and root can always take over.
|
||||||
func RequireSessionHolder(id Identity, st DaemonState) error {
|
func RequireSessionHolder(id Identity, st DaemonState) error {
|
||||||
holder, running := st.SessionHolder()
|
holder, running := st.SessionHolder()
|
||||||
log.Debugf("id : %v, session holder: %v", id, holder)
|
if !running || IsPrivilegedCaller(id) || holder.Matches(id) {
|
||||||
if !running || holder.SameUser(id) || holder.IsPrivileged() {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
log.Debugf("caller %v is not the session holder %v", id, holder)
|
||||||
return status.Errorf(codes.PermissionDenied, "session is held by another user (%v)", holder)
|
return status.Errorf(codes.PermissionDenied, "session is held by another user (%v)", holder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// The identity of the process evaluating callers, captured once because it cannot
|
// The identity of the process evaluating callers, captured once because it cannot
|
||||||
// change. selfKnown is false when it could not be read, in which case nothing is
|
// change. It stays the zero Identity when it could not be read, and the zero
|
||||||
// ever treated as this process. selfMayDelegate additionally requires this
|
// Identity is not Known, so nothing is ever treated as this process.
|
||||||
// process to be unprivileged: see IsPrivilegedCaller.
|
// selfMayDelegate additionally requires this process to be unprivileged: see
|
||||||
|
// IsPrivilegedCaller.
|
||||||
var (
|
var (
|
||||||
selfIdentity Identity
|
selfIdentity Identity
|
||||||
selfKnown bool
|
|
||||||
selfMayDelegate bool
|
selfMayDelegate bool
|
||||||
// selfPID is this process's PID, used to recognise the daemon dialling itself.
|
// selfPID is this process's PID, used to recognise the daemon dialling itself.
|
||||||
selfPID = os.Getpid()
|
selfPID = os.Getpid()
|
||||||
@@ -38,7 +38,7 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
selfIdentity, selfKnown = id, true
|
selfIdentity = id
|
||||||
// Only an unprivileged daemon delegates its authority to its own identity.
|
// Only an unprivileged daemon delegates its authority to its own identity.
|
||||||
// When it is root or LocalSystem, sharing its identity does not mean sharing
|
// When it is root or LocalSystem, sharing its identity does not mean sharing
|
||||||
// its power: on Windows a filtered and a full token carry the same SID, so
|
// its power: on Windows a filtered and a full token carry the same SID, so
|
||||||
@@ -52,7 +52,12 @@ func init() {
|
|||||||
// runs inside the daemon and re-dials it locally, so this is what distinguishes
|
// 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.
|
// the gateway from any other caller, whatever user the daemon runs as.
|
||||||
func IsDaemonSelf(id Identity) bool {
|
func IsDaemonSelf(id Identity) bool {
|
||||||
if !selfKnown || id.IsWindows() != selfIdentity.IsWindows() {
|
// An identity the kernel did not vouch for is nobody, least of all us: the
|
||||||
|
// zero Identity carries uid 0, which would otherwise match a root daemon.
|
||||||
|
if !id.Known() || !selfIdentity.Known() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if id.IsWindows() != selfIdentity.IsWindows() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if id.IsWindows() {
|
if id.IsWindows() {
|
||||||
@@ -85,7 +90,7 @@ func IsPrivilegedCaller(id Identity) bool {
|
|||||||
// operation, because on such a host root is neither required nor necessarily
|
// operation, because on such a host root is neither required nor necessarily
|
||||||
// available.
|
// available.
|
||||||
func SelfDelegatesTo() (Identity, bool) {
|
func SelfDelegatesTo() (Identity, bool) {
|
||||||
if !selfKnown || !selfMayDelegate {
|
if !selfIdentity.Known() || !selfMayDelegate {
|
||||||
return Identity{}, false
|
return Identity{}, false
|
||||||
}
|
}
|
||||||
return selfIdentity, true
|
return selfIdentity, true
|
||||||
|
|||||||
@@ -9,96 +9,85 @@ func TestIsPrivilegedCaller_SelfRule(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
// self stands in for the process the daemon runs as.
|
// self stands in for the process the daemon runs as.
|
||||||
self Identity
|
self Identity
|
||||||
selfKnown bool
|
caller Identity
|
||||||
caller Identity
|
want bool
|
||||||
want bool
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "root is privileged whatever the daemon runs as",
|
name: "root is privileged whatever the daemon runs as",
|
||||||
self: Identity{UID: 1000},
|
self: Identity{known: true, UID: 1000},
|
||||||
selfKnown: true,
|
caller: Identity{known: true, UID: 0},
|
||||||
caller: Identity{UID: 0},
|
want: true,
|
||||||
want: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "an unprivileged daemon delegates to its own user (rootless container)",
|
name: "an unprivileged daemon delegates to its own user (rootless container)",
|
||||||
self: Identity{UID: 1000},
|
self: Identity{known: true, UID: 1000},
|
||||||
selfKnown: true,
|
caller: Identity{known: true, UID: 1000},
|
||||||
caller: Identity{UID: 1000},
|
want: true,
|
||||||
want: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "an unprivileged daemon delegates to nobody else",
|
name: "an unprivileged daemon delegates to nobody else",
|
||||||
self: Identity{UID: 1000},
|
self: Identity{known: true, UID: 1000},
|
||||||
selfKnown: true,
|
caller: Identity{known: true, UID: 1001},
|
||||||
caller: Identity{UID: 1001},
|
want: false,
|
||||||
want: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// The daemon is root on a normal install, so sharing its identity is
|
// The daemon is root on a normal install, so sharing its identity is
|
||||||
// already covered by being root; nothing else may match.
|
// already covered by being root; nothing else may match.
|
||||||
name: "a root daemon delegates to nobody",
|
name: "a root daemon delegates to nobody",
|
||||||
self: Identity{UID: 0},
|
self: Identity{known: true, UID: 0},
|
||||||
selfKnown: true,
|
caller: Identity{known: true, UID: 1000},
|
||||||
caller: Identity{UID: 1000},
|
want: false,
|
||||||
want: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Windows netstack mode: the daemon needs no administrator rights.
|
// Windows netstack mode: the daemon needs no administrator rights.
|
||||||
name: "an unprivileged windows daemon delegates to its own SID",
|
name: "an unprivileged windows daemon delegates to its own SID",
|
||||||
self: Identity{SID: "S-1-5-21-1-2-3-1001"},
|
self: Identity{known: true, SID: "S-1-5-21-1-2-3-1001"},
|
||||||
selfKnown: true,
|
caller: Identity{known: true, SID: "S-1-5-21-1-2-3-1001"},
|
||||||
caller: Identity{SID: "S-1-5-21-1-2-3-1001"},
|
want: true,
|
||||||
want: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "an unprivileged windows daemon delegates to no other SID",
|
name: "an unprivileged windows daemon delegates to no other SID",
|
||||||
self: Identity{SID: "S-1-5-21-1-2-3-1001"},
|
self: Identity{known: true, SID: "S-1-5-21-1-2-3-1001"},
|
||||||
selfKnown: true,
|
caller: Identity{known: true, SID: "S-1-5-21-1-2-3-1002"},
|
||||||
caller: Identity{SID: "S-1-5-21-1-2-3-1002"},
|
want: false,
|
||||||
want: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// The UAC boundary: a filtered and a full token of the same account
|
// The UAC boundary: a filtered and a full token of the same account
|
||||||
// carry the same SID but not the same power, so an elevated daemon must
|
// carry the same SID but not the same power, so an elevated daemon must
|
||||||
// never delegate to its own SID.
|
// never delegate to its own SID.
|
||||||
name: "an elevated windows daemon does not delegate to its own SID",
|
name: "an elevated windows daemon does not delegate to its own SID",
|
||||||
self: Identity{SID: "S-1-5-21-1-2-3-500", Elevated: true},
|
self: Identity{known: true, SID: "S-1-5-21-1-2-3-500", Elevated: true},
|
||||||
selfKnown: true,
|
caller: Identity{known: true, SID: "S-1-5-21-1-2-3-500"},
|
||||||
caller: Identity{SID: "S-1-5-21-1-2-3-500"},
|
want: false,
|
||||||
want: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "LocalSystem is privileged on its own merits, not by delegation",
|
name: "LocalSystem is privileged on its own merits, not by delegation",
|
||||||
self: Identity{SID: sidLocalSystem},
|
self: Identity{known: true, SID: sidLocalSystem},
|
||||||
selfKnown: true,
|
caller: Identity{known: true, SID: sidLocalSystem},
|
||||||
caller: Identity{SID: sidLocalSystem},
|
want: true, // LocalSystem is privileged on its own merits
|
||||||
want: true, // LocalSystem is privileged on its own merits
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "identities of different kinds never match",
|
name: "identities of different kinds never match",
|
||||||
self: Identity{UID: 1000},
|
self: Identity{known: true, UID: 1000},
|
||||||
selfKnown: true,
|
caller: Identity{known: true, SID: "S-1-5-21-1-2-3-1001"},
|
||||||
caller: Identity{SID: "S-1-5-21-1-2-3-1001"},
|
want: false,
|
||||||
want: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "an unknown self identity delegates to nobody",
|
name: "an unknown self identity delegates to nobody",
|
||||||
self: Identity{},
|
self: Identity{},
|
||||||
selfKnown: false,
|
caller: Identity{known: true, UID: 1000},
|
||||||
caller: Identity{UID: 1000},
|
want: false,
|
||||||
want: false,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
prevID, prevKnown, prevDelegate := selfIdentity, selfKnown, selfMayDelegate
|
prevID, prevDelegate := selfIdentity, selfMayDelegate
|
||||||
t.Cleanup(func() { selfIdentity, selfKnown, selfMayDelegate = prevID, prevKnown, prevDelegate })
|
t.Cleanup(func() { selfIdentity, selfMayDelegate = prevID, prevDelegate })
|
||||||
|
|
||||||
selfIdentity, selfKnown = tt.self, tt.selfKnown
|
selfIdentity = tt.self
|
||||||
selfMayDelegate = tt.selfKnown && !tt.self.IsPrivileged()
|
selfMayDelegate = tt.self.Known() && !tt.self.IsPrivileged()
|
||||||
|
|
||||||
if got := IsPrivilegedCaller(tt.caller); got != tt.want {
|
if got := IsPrivilegedCaller(tt.caller); got != tt.want {
|
||||||
t.Fatalf("IsPrivilegedCaller(%v) with daemon %v = %t, want %t",
|
t.Fatalf("IsPrivilegedCaller(%v) with daemon %v = %t, want %t",
|
||||||
@@ -124,9 +113,9 @@ func TestIsPrivilegedCaller_ThisProcess(t *testing.T) {
|
|||||||
|
|
||||||
// A caller that is neither root nor this process must be refused, whatever
|
// A caller that is neither root nor this process must be refused, whatever
|
||||||
// this process happens to be.
|
// this process happens to be.
|
||||||
other := Identity{UID: id.UID + 1}
|
other := Identity{known: true, UID: id.UID + 1}
|
||||||
if id.IsWindows() {
|
if id.IsWindows() {
|
||||||
other = Identity{SID: id.SID + "9"}
|
other = Identity{known: true, SID: id.SID + "9"}
|
||||||
}
|
}
|
||||||
if IsPrivilegedCaller(other) {
|
if IsPrivilegedCaller(other) {
|
||||||
t.Errorf("an unrelated identity %v was treated as privileged", other)
|
t.Errorf("an unrelated identity %v was treated as privileged", other)
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import "os"
|
|||||||
// Identity.IsPrivileged the daemon applies.
|
// Identity.IsPrivileged the daemon applies.
|
||||||
func CurrentProcessIdentity() (Identity, error) {
|
func CurrentProcessIdentity() (Identity, error) {
|
||||||
return Identity{
|
return Identity{
|
||||||
UID: uint32(os.Geteuid()),
|
UID: uint32(os.Geteuid()),
|
||||||
GID: uint32(os.Getegid()),
|
GID: uint32(os.Getegid()),
|
||||||
|
known: true,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,5 +31,6 @@ func CurrentProcessIdentity() (Identity, error) {
|
|||||||
SID: user.User.Sid.String(),
|
SID: user.User.Sid.String(),
|
||||||
Groups: groups,
|
Groups: groups,
|
||||||
Elevated: token.IsElevated(),
|
Elevated: token.IsElevated(),
|
||||||
|
known: true,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package ipcauth
|
||||||
|
|
||||||
|
// KnownForTest returns a copy of id marked as kernel-attested, so packages
|
||||||
|
// outside ipcauth can build identity fixtures. A real Identity is only ever
|
||||||
|
// produced by a peer-credential read, nothing outside a test should call this.
|
||||||
|
func KnownForTest(id Identity) Identity {
|
||||||
|
id.known = true
|
||||||
|
return id
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ type Profile struct {
|
|||||||
// loader so callers do not have to reconstruct it from ID + dir.
|
// loader so callers do not have to reconstruct it from ID + dir.
|
||||||
Path string
|
Path string
|
||||||
IsActive bool
|
IsActive bool
|
||||||
Owners []ipcauth.Identity
|
Owners []ipcauth.Principal
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Profile) FilePath() (string, error) {
|
func (p *Profile) FilePath() (string, error) {
|
||||||
|
|||||||
@@ -506,7 +506,7 @@ func (s *ServiceManager) loadAllProfiles(username string) ([]Profile, error) {
|
|||||||
Path: DefaultConfigPath,
|
Path: DefaultConfigPath,
|
||||||
IsActive: activeIsDefault,
|
IsActive: activeIsDefault,
|
||||||
// TODO: determine how to seed default owners
|
// TODO: determine how to seed default owners
|
||||||
Owners: []ipcauth.Identity{},
|
Owners: []ipcauth.Principal{},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
configDir, err := s.getConfigDir(username)
|
configDir, err := s.getConfigDir(username)
|
||||||
@@ -585,28 +585,32 @@ func readProfileName(path string) string {
|
|||||||
return meta.Name
|
return meta.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func readProfileOwners(path string) ([]ipcauth.Identity, error) {
|
// readProfileOwners parses the owner principals from a profile JSON. Owners stay
|
||||||
|
// principals so they are never mistaken for a kernel-attested caller.
|
||||||
|
//
|
||||||
|
// Only the first entry is read. The field is a list on disk so multiple owners
|
||||||
|
// can be added later without a format change, but multiple owners are not
|
||||||
|
// supported yet.
|
||||||
|
func readProfileOwners(path string) ([]ipcauth.Principal, error) {
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []ipcauth.Identity{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var meta ownerMeta
|
var meta ownerMeta
|
||||||
if err := json.Unmarshal(data, &meta); err != nil {
|
if err := json.Unmarshal(data, &meta); err != nil {
|
||||||
return []ipcauth.Identity{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(meta.Owners) < 1 {
|
if len(meta.Owners) == 0 {
|
||||||
return []ipcauth.Identity{}, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
owner := meta.Owners[0]
|
|
||||||
principal, ok := ipcauth.ParsePrincipal(owner)
|
principal, ok := ipcauth.ParsePrincipal(meta.Owners[0])
|
||||||
if !ok {
|
if !ok {
|
||||||
return []ipcauth.Identity{}, fmt.Errorf("unexpected owner principal: %s", owner)
|
// A malformed entry is ignored rather than trusted.
|
||||||
|
log.Warnf("ignoring unparseable owner %q in %s", meta.Owners[0], path)
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
id, err := ipcauth.IdentityFromPrincipal(principal)
|
return []ipcauth.Principal{principal}, nil
|
||||||
if err != nil {
|
|
||||||
return []ipcauth.Identity{id}, fmt.Errorf("parsing identity from principal failed: %w", err)
|
|
||||||
}
|
|
||||||
return []ipcauth.Identity{id}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint: unused,unusedfunc
|
// nolint: unused,unusedfunc
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ import (
|
|||||||
const testTTL = time.Minute
|
const testTTL = time.Minute
|
||||||
|
|
||||||
func unixCaller(uid uint32) ipcauth.Identity {
|
func unixCaller(uid uint32) ipcauth.Identity {
|
||||||
return ipcauth.Identity{UID: uid, GID: uid}
|
return ipcauth.KnownForTest(ipcauth.Identity{UID: uid, GID: uid})
|
||||||
}
|
}
|
||||||
|
|
||||||
func windowsCaller(sid string) ipcauth.Identity {
|
func windowsCaller(sid string) ipcauth.Identity {
|
||||||
return ipcauth.Identity{SID: sid}
|
return ipcauth.KnownForTest(ipcauth.Identity{SID: sid})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJWTCache_ServesTheOwner(t *testing.T) {
|
func TestJWTCache_ServesTheOwner(t *testing.T) {
|
||||||
|
|||||||
+15
-16
@@ -2699,29 +2699,28 @@ func (s *Server) authorizeAndPrepareLogin(callerCtx context.Context, msg *proto.
|
|||||||
return ctx, activeProf, nil
|
return ctx, activeProf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionHolder returns the Identity that owns the active and connected
|
// SessionHolder returns the principal that owns the active profile while it is
|
||||||
// profile. The boolean indicates if the session is connected and an owner
|
// connected. The owner is a config value, so it stays a principal and is never
|
||||||
// is defined in the config.
|
// turned into an identity.
|
||||||
func (s *Server) SessionHolder() (ipcauth.Identity, bool) {
|
//
|
||||||
|
// Only the first owner is read. The field is a list on disk so multiple owners
|
||||||
|
// can be added later without a format change, but multiple owners are not
|
||||||
|
// supported yet.
|
||||||
|
func (s *Server) SessionHolder() (ipcauth.Principal, bool) {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
activeOwners := s.config.Owners
|
|
||||||
|
|
||||||
if !s.clientRunning || len(activeOwners) < 1 {
|
if !s.clientRunning || len(s.config.Owners) == 0 {
|
||||||
return ipcauth.Identity{}, false
|
return ipcauth.Principal{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
principal, ok := ipcauth.ParsePrincipal(activeOwners[0])
|
// The zero Principal matches nobody, so an unparseable owner locks the
|
||||||
|
// session rather than opening it.
|
||||||
|
principal, ok := ipcauth.ParsePrincipal(s.config.Owners[0])
|
||||||
if !ok {
|
if !ok {
|
||||||
return ipcauth.Identity{}, false
|
log.Warnf("active profile has an unparseable owner %q", s.config.Owners[0])
|
||||||
}
|
}
|
||||||
|
return principal, true
|
||||||
id, err := ipcauth.IdentityFromPrincipal(principal)
|
|
||||||
if err != nil {
|
|
||||||
return ipcauth.Identity{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return id, true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func persistLoginOverrides(activeProf *profilemanager.ActiveProfileState, managementURL string, preSharedKey *string) error {
|
func persistLoginOverrides(activeProf *profilemanager.ActiveProfileState, managementURL string, preSharedKey *string) error {
|
||||||
|
|||||||
@@ -44,18 +44,18 @@ func userCtx() context.Context { return ctxWithIdentity(unprivilegedIdentity())
|
|||||||
func privilegedIdentity() ipcauth.Identity {
|
func privilegedIdentity() ipcauth.Identity {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
// LocalSystem, which is what the Windows service account is.
|
// LocalSystem, which is what the Windows service account is.
|
||||||
return ipcauth.Identity{SID: "S-1-5-18"}
|
return ipcauth.KnownForTest(ipcauth.Identity{SID: "S-1-5-18"})
|
||||||
}
|
}
|
||||||
return ipcauth.Identity{UID: 0}
|
return ipcauth.KnownForTest(ipcauth.Identity{UID: 0})
|
||||||
}
|
}
|
||||||
|
|
||||||
func unprivilegedIdentity() ipcauth.Identity {
|
func unprivilegedIdentity() ipcauth.Identity {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
// A plain user SID: no groups, so no BUILTIN\Administrators, and not
|
// A plain user SID: no groups, so no BUILTIN\Administrators, and not
|
||||||
// elevated.
|
// elevated.
|
||||||
return ipcauth.Identity{SID: "S-1-5-21-1-2-3-1001"}
|
return ipcauth.KnownForTest(ipcauth.Identity{SID: "S-1-5-21-1-2-3-1001"})
|
||||||
}
|
}
|
||||||
return ipcauth.Identity{UID: unprivUID, GID: unprivUID}
|
return ipcauth.KnownForTest(ipcauth.Identity{UID: unprivUID, GID: unprivUID})
|
||||||
}
|
}
|
||||||
func noIdentityCtx() context.Context { return context.Background() }
|
func noIdentityCtx() context.Context { return context.Background() }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user