Files
mcr/internal/server/policy.go
Kyle Isom d5580f01f2 Migrate module path from kyle/ to mc/ org
All import paths updated to git.wntrmute.dev/mc/. Bumps mcdsl to v1.2.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:05:59 -07:00

58 lines
1.9 KiB
Go

package server
import (
"net/http"
"github.com/go-chi/chi/v5"
"git.wntrmute.dev/mc/mcr/internal/auth"
"git.wntrmute.dev/mc/mcr/internal/policy"
)
// PolicyEvaluator abstracts the policy engine for testability.
type PolicyEvaluator interface {
Evaluate(input policy.PolicyInput) (policy.Effect, *policy.Rule)
}
// AuditFunc is an optional callback for recording policy deny audit events.
// It follows the same signature as db.WriteAuditEvent but without an error
// return — audit failures should not block request processing.
type AuditFunc func(eventType, actorID, repository, digest, ip string, details map[string]string)
// RequirePolicy returns middleware that checks the policy engine for the
// given action. Claims must already be in the context (set by RequireAuth).
// The repository name is extracted from the chi "name" URL parameter;
// global operations (catalog, version check) have an empty repository.
func RequirePolicy(evaluator PolicyEvaluator, action policy.Action, auditFn AuditFunc) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims := auth.ClaimsFromContext(r.Context())
if claims == nil {
writeOCIError(w, "UNAUTHORIZED", http.StatusUnauthorized, "authentication required")
return
}
input := policy.PolicyInput{
Subject: claims.Subject,
AccountType: claims.AccountType,
Roles: claims.Roles,
Action: action,
Repository: chi.URLParam(r, "name"),
}
effect, _ := evaluator.Evaluate(input)
if effect == policy.Deny {
if auditFn != nil {
auditFn("policy_deny", claims.Subject, input.Repository, "", r.RemoteAddr, map[string]string{
"action": string(action),
})
}
writeOCIError(w, "DENIED", http.StatusForbidden, "access denied by policy")
return
}
next.ServeHTTP(w, r)
})
}
}