Log every authorization decision from the authorizer

Denials were invisible in the audit trail and evaluator explainability
(policy_id, reason) was discarded before reaching logs. Emit a structured
authz decision line on every evaluation in evaluateMultiInTx — allow,
deny, no_match, and assumption errors — using the existing authorizer
logger with opaque IDs only.

Add decision_log.go with DecisionRecord and logDecision. Surface
PolicyID and Reason on EvaluationResult for logging. Audit log
behavior is unchanged (allow-only). Document the convention in
authorization.md.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
Ludovic Vielle
2026-06-15 16:00:04 +02:00
parent c8de75cc03
commit 71e8d662bc
6 changed files with 388 additions and 1 deletions

View File

@@ -445,6 +445,17 @@ func (a *Authorizer) evaluateMultiInTx(
if assumptionErr != nil && !item.SkipAssumptionCheck {
decisions[i] = assumptionErr
a.logDecision(
ctx,
DecisionRecord{
Effect: effectError,
Action: item.Action,
ResourceID: item.Resource,
Principal: params.Principal,
Reason: assumptionErr.Error(),
},
)
continue
}
@@ -458,7 +469,22 @@ func (a *Authorizer) evaluateMultiInTx(
},
}
if !a.evaluator.Evaluate(req, policies).IsAllowed() {
startedAt := time.Now()
result := a.evaluator.Evaluate(req, policies)
a.logDecision(
ctx,
newDecisionRecord(
result,
params.Principal,
item.Resource,
item.Action,
role,
time.Since(startedAt),
),
)
if !result.IsAllowed() {
decisions[i] = NewInsufficientPermissionsError(params.Principal, item.Resource, item.Action)
}
}