chore: add x-authz-checks debug header when running in dev mode (#16873)

This commit is contained in:
ケイラ
2025-04-10 10:36:27 -07:00
committed by GitHub
parent 25fb34cabe
commit 46d4b28384
9 changed files with 162 additions and 11 deletions

View File

@ -6,6 +6,7 @@ import (
_ "embed"
"encoding/json"
"errors"
"fmt"
"strings"
"sync"
"time"
@ -362,11 +363,11 @@ func (a RegoAuthorizer) Authorize(ctx context.Context, subject Subject, action p
defer span.End()
err := a.authorize(ctx, subject, action, object)
span.SetAttributes(attribute.Bool("authorized", err == nil))
authorized := err == nil
span.SetAttributes(attribute.Bool("authorized", authorized))
dur := time.Since(start)
if err != nil {
if !authorized {
a.authorizeHist.WithLabelValues("false").Observe(dur.Seconds())
return err
}
@ -741,3 +742,112 @@ func rbacTraceAttributes(actor Subject, action policy.Action, objectType string,
attribute.String("object_type", objectType),
)...)
}
type authRecorder struct {
authz Authorizer
}
// Recorder returns an Authorizer that records any authorization checks made
// on the Context provided for the authorization check.
//
// Requires using the RecordAuthzChecks middleware.
func Recorder(authz Authorizer) Authorizer {
return &authRecorder{authz: authz}
}
func (c *authRecorder) Authorize(ctx context.Context, subject Subject, action policy.Action, object Object) error {
err := c.authz.Authorize(ctx, subject, action, object)
authorized := err == nil
recordAuthzCheck(ctx, action, object, authorized)
return err
}
func (c *authRecorder) Prepare(ctx context.Context, subject Subject, action policy.Action, objectType string) (PreparedAuthorized, error) {
return c.authz.Prepare(ctx, subject, action, objectType)
}
type authzCheckRecorderKey struct{}
type AuthzCheckRecorder struct {
// lock guards checks
lock sync.Mutex
// checks is a list preformatted authz check IDs and their result
checks []recordedCheck
}
type recordedCheck struct {
name string
// true => authorized, false => not authorized
result bool
}
func WithAuthzCheckRecorder(ctx context.Context) context.Context {
return context.WithValue(ctx, authzCheckRecorderKey{}, &AuthzCheckRecorder{})
}
func recordAuthzCheck(ctx context.Context, action policy.Action, object Object, authorized bool) {
r, ok := ctx.Value(authzCheckRecorderKey{}).(*AuthzCheckRecorder)
if !ok {
return
}
// We serialize the check using the following syntax
var b strings.Builder
if object.OrgID != "" {
_, err := fmt.Fprintf(&b, "organization:%v::", object.OrgID)
if err != nil {
return
}
}
if object.AnyOrgOwner {
_, err := fmt.Fprint(&b, "organization:any::")
if err != nil {
return
}
}
if object.Owner != "" {
_, err := fmt.Fprintf(&b, "owner:%v::", object.Owner)
if err != nil {
return
}
}
if object.ID != "" {
_, err := fmt.Fprintf(&b, "id:%v::", object.ID)
if err != nil {
return
}
}
_, err := fmt.Fprintf(&b, "%v.%v", object.RBACObject().Type, action)
if err != nil {
return
}
r.lock.Lock()
defer r.lock.Unlock()
r.checks = append(r.checks, recordedCheck{name: b.String(), result: authorized})
}
func GetAuthzCheckRecorder(ctx context.Context) (*AuthzCheckRecorder, bool) {
checks, ok := ctx.Value(authzCheckRecorderKey{}).(*AuthzCheckRecorder)
if !ok {
return nil, false
}
return checks, true
}
// String serializes all of the checks recorded, using the following syntax:
func (r *AuthzCheckRecorder) String() string {
r.lock.Lock()
defer r.lock.Unlock()
if len(r.checks) == 0 {
return "nil"
}
checks := make([]string, 0, len(r.checks))
for _, check := range r.checks {
checks = append(checks, fmt.Sprintf("%v=%v", check.name, check.result))
}
return strings.Join(checks, "; ")
}