151 lines
3.8 KiB
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
|
|
}
|