Small refactor of Authz

This commit is contained in:
Theodor S. Midtlien
2026-09-18 11:28:16 +02:00
parent 6dcb1374e9
commit cb9d2ee3bb
3 changed files with 109 additions and 105 deletions
+38 -35
View File
@@ -1,6 +1,7 @@
package ipcauth
import (
"google.golang.org/grpc/codes"
gstatus "google.golang.org/grpc/status"
)
@@ -46,45 +47,47 @@ func (l AuthzLevel) String() string {
}
}
// resolveLevel is the authority the caller holds over the profile the request
// resolved to. A profile the caller does not own confers nothing beyond being
// identified, which is also what an unresolved handle leaves them with.
func resolveLevel(id Identity, target Target, st DaemonState) AuthzLevel {
if !id.Known() {
return AuthzLevelNone
// RequireLevel builds a rule from a level, for composing inside another rule.
func RequireLevel(want AuthzLevel) Rule {
return func(r Request) error {
if r.Level >= want {
return nil
}
return denyLevel(r, want)
}
if IsPrivilegedCaller(id) {
return AuthzLevelPrivileged
}
if !target.Owned {
return AuthzLevelIdentified
}
if holder, running := st.SessionHolder(); !running || holder.Matches(id) {
return AuthzLevelSessionHolder
}
return AuthzLevelProfileOwner
}
// presentableHandleError keeps a resolution failure only when the gate can put
// it in front of the caller in place of its own refusal. Everything else is
// dropped, and the caller gets the refusal their level earned.
func presentableHandleError(handle string, err error) error {
if err == nil {
return nil
}
func denyLevel(r Request, want AuthzLevel) error {
return gstatus.Errorf(codes.PermissionDenied,
"%s requires %s, caller %s is %s", r.Method, want, r.Identity, r.Level)
}
// denyPolicyLevel refuses a caller at the gate, where the policy is in hand.
//
// Requiring privilege is the one denial a caller can act on, so it carries the
// elevated command rather than a bare refusal. A privileged method that declares
// no action keeps the plain message. Rules deny through denyLevel instead: they
// cannot reach the policy table without an initialization cycle, and no rule
// requires privilege.
func denyPolicyLevel(r Request, p MethodPolicy) error {
switch p.Level {
case AuthzLevelPrivileged:
if p.Action != "" {
actor, command := RequiredActor(p.Command)
return PrivilegeError(PrivilegeSummary(p.Action, actor), command)
}
case AuthzLevelSessionHolder:
// resolveLevel stops at profile owner only when a session is running and
// somebody else holds it.
if r.Level == AuthzLevelProfileOwner {
return SessionHeldError(p.Action)
}
return NotOwnerError(p.Action)
// An empty handle is the active profile rather than something the caller
// typed, so a failure to resolve it is not theirs to correct.
if handle == "" {
return nil
case AuthzLevelProfileOwner:
return NotOwnerError(p.Action)
}
// Only a gRPC status reaches the caller as a sentence the CLI and the UI
// render. A plain error is a daemon-side failure, and putting it on the
// wire would tell the caller about the daemon rather than about the handle
// they gave.
if _, ok := gstatus.FromError(err); !ok {
return nil
}
return err
return denyLevel(r, p.Level)
}