Refactor gatePolicy to gateRule

This commit is contained in:
Theodor S. Midtlien
2026-09-03 12:27:36 +02:00
parent d03b8b3a92
commit 70f075806b
4 changed files with 47 additions and 40 deletions
+1 -1
View File
@@ -46,7 +46,7 @@ type program struct {
jsonServMu sync.Mutex jsonServMu sync.Mutex
serverInstance *server.Server serverInstance *server.Server
serverInstanceMu sync.Mutex serverInstanceMu sync.Mutex
policyGate *ipcauth.PolicyGate ruleGate *ipcauth.RuleGate
} }
func init() { func init() {
+4 -4
View File
@@ -80,12 +80,12 @@ func (p *program) Start(svc service.Service) error {
return fmt.Errorf("parse daemon address: %w", err) return fmt.Errorf("parse daemon address: %w", err)
} }
p.policyGate = ipcauth.NewPolicyGate() p.ruleGate = ipcauth.NewRuleGate()
// in any case, even if configuration does not exists we run daemon to serve CLI gRPC API. // in any case, even if configuration does not exists we run daemon to serve CLI gRPC API.
opts := append(daemonServerOptions(network), opts := append(daemonServerOptions(network),
grpc.ChainUnaryInterceptor(p.policyGate.UnaryPolicyInterceptor()), grpc.ChainUnaryInterceptor(p.ruleGate.UnaryPolicyInterceptor()),
grpc.ChainStreamInterceptor(p.policyGate.StreamPolicyInterceptor()), grpc.ChainStreamInterceptor(p.ruleGate.StreamPolicyInterceptor()),
) )
p.serv = grpc.NewServer(opts...) p.serv = grpc.NewServer(opts...)
@@ -151,7 +151,7 @@ func (p *program) serve(daemonListener, jsonListener *socketListener) error {
} }
serverInstance := server.New(p.ctx, util.FindFirstLogPath(logFiles), configPath, profilesDisabled, updateSettingsDisabled, captureEnabled, networksDisabled) serverInstance := server.New(p.ctx, util.FindFirstLogPath(logFiles), configPath, profilesDisabled, updateSettingsDisabled, captureEnabled, networksDisabled)
p.policyGate.SetPolicy(serverInstance) p.ruleGate.SetState(serverInstance)
if err := serverInstance.Start(); err != nil { if err := serverInstance.Start(); err != nil {
return fmt.Errorf("start daemon: %w", err) return fmt.Errorf("start daemon: %w", err)
} }
+42 -34
View File
@@ -10,70 +10,78 @@ import (
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )
type Policy interface { type DaemonState interface {
SessionHolder() (Identity, bool) SessionHolder() (Identity, bool)
} }
type PolicyGate struct { type Rule func(id Identity, st DaemonState) error
mu sync.Mutex
policy Policy type RuleGate struct {
mu sync.Mutex
rules []Rule
st DaemonState
} }
func NewPolicyGate() *PolicyGate { func NewRuleGate() *RuleGate {
return &PolicyGate{} return &RuleGate{}
} }
func (g *PolicyGate) SetPolicy(p Policy) { func (g *RuleGate) SetState(st DaemonState) {
g.mu.Lock() g.mu.Lock()
defer g.mu.Unlock() defer g.mu.Unlock()
g.policy = p g.st = st
} }
func (g *PolicyGate) SessionHolder() (Identity, bool) { func (g *RuleGate) SetRule(r Rule) {
g.mu.Lock() g.mu.Lock()
defer g.mu.Unlock() defer g.mu.Unlock()
if g.policy == nil { g.rules = append(g.rules, r)
return Identity{}, false }
func (g *RuleGate) state() DaemonState {
g.mu.Lock()
defer g.mu.Unlock()
return g.st
}
func RequireSessionHolder(id Identity, st DaemonState) error {
holder, running := st.SessionHolder()
log.Debugf("id : %v, session holder: %v", id, holder)
if !running || holder.SameUser(id) || holder.IsPrivileged() {
return nil
} }
return g.policy.SessionHolder() return status.Errorf(codes.PermissionDenied, "session is held by another user (%v)", holder)
} }
func (g *PolicyGate) StreamPolicyInterceptor() grpc.StreamServerInterceptor { func (g *RuleGate) StreamPolicyInterceptor() grpc.StreamServerInterceptor {
return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
if !g.authorize(ss.Context()) { if authErr := g.authorize(ss.Context()); authErr != nil {
return status.Error(codes.PermissionDenied, "caller is not session owner") return authErr
} }
return handler(ss.Context(), ss) return handler(ss.Context(), ss)
} }
} }
func (g *PolicyGate) UnaryPolicyInterceptor() grpc.UnaryServerInterceptor { func (g *RuleGate) UnaryPolicyInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) { return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
if !g.authorize(ctx) { if authErr := g.authorize(ctx); authErr != nil {
return nil, status.Error(codes.PermissionDenied, "caller is not session owner") return nil, authErr
} }
return handler(ctx, req) return handler(ctx, req)
} }
} }
func (g *PolicyGate) authorize(ctx context.Context) bool { func (g *RuleGate) authorize(ctx context.Context) error {
id, ok := CallerIdentity(ctx) id, ok := CallerIdentity(ctx)
if !ok { if !ok {
return false return status.Error(codes.PermissionDenied, "caller cannot be verified")
} }
g.mu.Lock() state := g.state()
if g.policy == nil { for _, rule := range g.rules {
g.mu.Unlock() ruleErr := rule(id, state)
return false if ruleErr != nil {
return ruleErr
}
} }
sessionId, running := g.policy.SessionHolder() return nil
// TODO improve logging
log.Infof("id : %v, session holder: %v", id, sessionId)
g.mu.Unlock()
// TODO windows
// TODO allow root
if running && sessionId.UID != id.UID {
return false
}
return true
} }
-1
View File
@@ -35,7 +35,6 @@ import (
"github.com/netbirdio/netbird/shared/management/domain" "github.com/netbirdio/netbird/shared/management/domain"
"github.com/netbirdio/netbird/client/internal" "github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/internal/ipcauth"
"github.com/netbirdio/netbird/client/internal/peer" "github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/client/internal/statemanager" "github.com/netbirdio/netbird/client/internal/statemanager"
"github.com/netbirdio/netbird/client/internal/updater" "github.com/netbirdio/netbird/client/internal/updater"