Add an optional --allow-group flag restricting the daemon sockets to a group

This commit is contained in:
Viktor Liu
2026-09-16 11:12:10 +02:00
parent abb94ad2d2
commit a64bfc3a81
17 changed files with 755 additions and 25 deletions
+5
View File
@@ -29,6 +29,11 @@ func LookupGroupID(gid string) (*user.Group, error) {
return user.LookupGroupId(gid)
}
// LookupGroupName looks up a group by name.
func LookupGroupName(name string) (*user.Group, error) {
return user.LookupGroup(name)
}
// GroupIDs returns the IDs of the groups the user is a member of; libc's
// getgrouplist handles NSS groups natively.
func GroupIDs(u *user.User) ([]string, error) {
+19
View File
@@ -88,6 +88,25 @@ func LookupGroupID(gid string) (*user.Group, error) {
return g, nil
}
// LookupGroupName looks up a group by name, falling back to getent if os/user
// fails.
func LookupGroupName(name string) (*user.Group, error) {
g, err := user.LookupGroup(name)
if err == nil {
return g, nil
}
stdErr := err
log.Debugf("os/user.LookupGroup(%q) failed, trying getent: %v", name, err)
g, _, getentErr := groupLookup(name)
if getentErr != nil {
log.Debugf("getent fallback for group %q also failed: %v", name, getentErr)
return nil, stdErr
}
return g, nil
}
// GroupIDs returns the IDs of the groups the user is a member of.
// NOTE: unlike the lookups above, which try the standard library first, this
// intentionally tries `id -G` first because without cgo, user.GroupIds only
+44
View File
@@ -7,6 +7,8 @@ import (
"fmt"
"net"
"runtime"
"slices"
"strings"
log "github.com/sirupsen/logrus"
"golang.org/x/sys/windows"
@@ -38,6 +40,48 @@ func DefaultPipeSDDL() string {
return "D:P(A;;GA;;;SY)(A;;GA;;;WD)"
}
// RestrictedPipeSDDL returns the security descriptor for a daemon control pipe
// that only the named principals may open, replacing DefaultPipeSDDL's ACE for
// Everyone. Each SID is a user or group SID in string form; the caller is
// expected to have resolved and validated them already. An empty list yields
// the default descriptor, so a missing configuration cannot silently produce a
// pipe nobody can reach.
//
// Three ACEs are always present besides the configured ones:
//
// SY LocalSystem, the account the daemon runs as when installed as a service
// BA BUILTIN\Administrators, so an elevated caller is never locked out
// the daemon's own user SID, so a daemon an ordinary user runs themselves can
// still dial itself, which is what the JSON gateway does
//
// BUILTIN\Administrators carries no access for a UAC-filtered administrator,
// whose token has that group deny-only, which matches the authorization model:
// such a caller is not privileged either.
func RestrictedPipeSDDL(sids []string) string {
if len(sids) == 0 {
return DefaultPipeSDDL()
}
allowed := []string{"SY", "BA"}
if selfIdentity.Known() && selfIdentity.SID != "" {
allowed = append(allowed, selfIdentity.SID)
}
for _, sid := range sids {
if !slices.Contains(allowed, sid) {
allowed = append(allowed, sid)
}
}
var b strings.Builder
b.WriteString("D:P")
for _, sid := range allowed {
b.WriteString("(A;;GA;;;")
b.WriteString(sid)
b.WriteString(")")
}
return b.String()
}
// NewTransportCredentials returns gRPC transport credentials that derive the
// caller's identity from the named-pipe client token.
//