iop/packages/go/agentguard/permit.go

151 lines
3.8 KiB
Go

package agentguard
import (
"context"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/json"
"os"
"sync"
)
var (
permitSealOnce sync.Once
permitSealKey [32]byte
permitSealErr error
)
type permitPayload struct {
Workspace CanonicalWorkspace
Profile ProviderProfile
}
// Permit is an opaque, process-local proof of a successful admission. Its
// fields intentionally cannot be constructed or modified by callers.
type Permit struct {
payload permitPayload
seal []byte
pins []canonicalPath
}
// Workspace returns a defensive copy of the canonical task view pinned by the
// permit.
func (p *Permit) Workspace() (CanonicalWorkspace, bool) {
if p == nil || len(p.seal) == 0 {
return CanonicalWorkspace{}, false
}
return cloneWorkspace(p.payload.Workspace), true
}
// Admit validates and seals the current request.
func Admit(req AdmissionRequest) AdmissionResult {
evaluated, blocked := evaluateAdmission(req)
if blocked.Blocker != nil {
return blocked
}
seal, err := sealPayload(evaluated.payload)
if err != nil {
return blockedResult(
req, BlockerCodeInvalidAdmissionRequest,
"admission permit could not be sealed",
)
}
permit := &Permit{
payload: evaluated.payload,
seal: seal,
pins: append([]canonicalPath(nil), evaluated.pins...),
}
workspace := cloneWorkspace(evaluated.workspace)
return AdmissionResult{
Status: AdmissionStatusPermitted,
Workspace: &workspace,
Permit: permit,
}
}
// ValidatePermit re-evaluates all current revisions and filesystem identities
// before invocation.
func ValidatePermit(permit *Permit, req AdmissionRequest) AdmissionResult {
if permit == nil || len(permit.seal) == 0 {
return blockedResult(
req, BlockerCodePermitInvalid,
"provider invocation requires a valid admission permit",
)
}
evaluated, blocked := evaluateAdmission(req)
if blocked.Blocker != nil {
return blocked
}
currentSeal, err := sealPayload(evaluated.payload)
if err != nil {
return blockedResult(
req, BlockerCodePermitInvalid,
"admission permit could not be validated",
)
}
if !hmac.Equal(permit.seal, currentSeal) {
return blockedResult(
req, BlockerCodePermitStale,
"admission permit does not match the current immutable revisions",
)
}
for _, pin := range permit.pins {
current, err := os.Stat(pin.path)
if err != nil || !os.SameFile(pin.info, current) {
return blockedResult(
req, BlockerCodeWorkspaceIdentityMismatch,
"workspace identity changed after admission",
)
}
}
workspace := cloneWorkspace(evaluated.workspace)
return AdmissionResult{
Status: AdmissionStatusPermitted,
Workspace: &workspace,
Permit: permit,
}
}
// Invoke is the mandatory zero-side-effect boundary for unattended provider
// execution. The callback is never called when permit validation blocks.
func Invoke(
ctx context.Context,
permit *Permit,
req AdmissionRequest,
invoke func(context.Context, CanonicalWorkspace) error,
) (AdmissionResult, error) {
result := ValidatePermit(permit, req)
if !result.Allowed() {
return result, nil
}
if invoke == nil {
return blockedResult(
req, BlockerCodeInvalidAdmissionRequest,
"provider invocation callback is missing",
), nil
}
return result, invoke(ctx, cloneWorkspace(*result.Workspace))
}
func sealPayload(payload permitPayload) ([]byte, error) {
permitSealOnce.Do(func() {
_, permitSealErr = rand.Read(permitSealKey[:])
})
if permitSealErr != nil {
return nil, permitSealErr
}
encoded, err := json.Marshal(payload)
if err != nil {
return nil, err
}
mac := hmac.New(sha256.New, permitSealKey[:])
_, _ = mac.Write(encoded)
return mac.Sum(nil), nil
}
func cloneWorkspace(workspace CanonicalWorkspace) CanonicalWorkspace {
workspace.WritableRoots = append([]string(nil), workspace.WritableRoots...)
workspace.VCSMetadataRoots = append([]string(nil), workspace.VCSMetadataRoots...)
return workspace
}