Add proposed task execution policy and initiating identity contract
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 58s

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
This commit is contained in:
tegwick 2026-10-11 19:06:01 +02:00
parent 1d3a8727f5
commit 24c1f56f00
8 changed files with 658 additions and 0 deletions

View file

@ -0,0 +1,104 @@
package policy_test
import (
"context"
"encoding/json"
"github.com/netkingdom/access-engine/internal/policy"
"github.com/netkingdom/access-engine/pkg/api"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestTaskExecutionPackageClosedAdmission(t *testing.T) {
p, e := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "secrets-engine-task-execution", "policy_package.md"))
if e != nil || !p.Valid {
t.Fatalf("package: %v %+v", e, p.Validation)
}
for _, action := range []string{"execute-ccr", "apply", "exec"} {
r := api.CheckRequest{Tenant: "tenant:platform", Subject: api.SubjectRef{ID: "secrets-engine", Type: "service"}, Action: action, Resource: api.ResourceRef{ID: "synthetic", Type: "secret-catalog-lane", System: "secrets-engine"}, Context: map[string]any{"owner_admission": true, "verified": true, "human_control": true, "task_execution_projection": map[string]any{"verified": true}}}
d, e := p.Evaluate(context.Background(), r)
if e != nil || d.Effect != api.DecisionEffectDeny {
t.Fatalf("unsigned caller opened admission: %v %+v", e, d)
}
}
}
func TestTaskExecutionSourceHasNoActivationSwitch(t *testing.T) {
raw, e := os.ReadFile(filepath.Join("..", "..", "examples", "secrets-engine-task-execution", "policy_package.md"))
if e != nil {
t.Fatal(e)
}
if !strings.Contains(string(raw), "owner_admission := false") {
t.Fatal("source refusal gate absent")
}
}
func TestTaskExecutionPythonDescriptorAndSyntheticFutureProjection(t *testing.T) {
output, e := exec.Command("/home/worsch/secrets-engine/.venv/bin/python", "../../tools/task_execution_interop_fixture.py").Output()
if e != nil {
t.Fatal("Python actual descriptor/canonical generator failed:", e)
}
var fixture struct {
Request api.CheckRequest `json:"request"`
RequestDigest string `json:"request_digest"`
ApprovalDigest string `json:"approval_binding_digest"`
DescriptorDigest string `json:"descriptor_digest"`
}
if json.Unmarshal(output, &fixture) != nil {
t.Fatal("fixture JSON")
}
r := fixture.Request
if api.RequestDigest(r) != fixture.RequestDigest || api.ApprovalBindingDigest(r) != fixture.ApprovalDigest || api.CanonicalDigest(r.Context["execution_contract"]) != fixture.DescriptorDigest {
t.Fatal("actual Python/Go canonical mismatch")
}
raw, e := os.ReadFile(filepath.Join("..", "..", "examples", "secrets-engine-task-execution", "policy_package.md"))
if e != nil {
t.Fatal(e)
}
// This source replacement is intentionally test-only; no caller/config flag
// can change the production package's hard admission=false constant.
raw = []byte(strings.Replace(string(raw), "owner_admission := false", "owner_admission := true", 1))
p, e := policy.Load(raw, "synthetic-future-provider-test.md")
if e != nil {
t.Fatal(e)
}
d, e := p.Evaluate(context.Background(), r)
if e != nil || d.Effect != api.DecisionEffectAllow {
t.Fatalf("synthetic future projection did not match: %v %+v", e, d)
}
cases := map[string]func(*api.CheckRequest){
"action": func(r *api.CheckRequest) { r.Action = "apply" }, "service subject": func(r *api.CheckRequest) { r.Subject.ID = "other" }, "foreign tenant": func(r *api.CheckRequest) { r.Tenant = "tenant:coulomb" },
"plan hash": func(r *api.CheckRequest) {
r.Context["execution_contract"].(map[string]any)["expected_after"] = "sha256:" + strings.Repeat("b", 64)
},
"unsigned human": func(r *api.CheckRequest) { delete(r.Context, "task_execution_projection") },
"registered tenant": func(r *api.CheckRequest) {
r.Context["task_execution_projection"].(map[string]any)["tenant_source"] = "registration"
},
"membership": func(r *api.CheckRequest) {
r.Context["task_execution_projection"].(map[string]any)["member_active"] = false
},
"transport": func(r *api.CheckRequest) {
r.Context["task_execution_projection"].(map[string]any)["transport_principal"] = "other"
},
"expiry": func(r *api.CheckRequest) {
r.Context["task_execution_projection"].(map[string]any)["not_after_ns"] = float64(1)
},
"no human control": func(r *api.CheckRequest) { r.Context["human_control"] = false },
}
for name, mutate := range cases {
t.Run(name, func(t *testing.T) {
var f struct {
Request api.CheckRequest `json:"request"`
}
json.Unmarshal(output, &f)
mutate(&f.Request)
d, e := p.Evaluate(context.Background(), f.Request)
if e != nil || d.Effect != api.DecisionEffectDeny {
t.Fatalf("mutation allowed: %v %+v", e, d)
}
})
}
}

View file

@ -0,0 +1,168 @@
// Package taskexecution contains proposed source-only identity input validation.
// It is not registered with HTTP evaluation and grants no executable admission.
package taskexecution
import (
"bytes"
"crypto"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"strings"
"time"
)
var ErrIdentity = errors.New("task execution initiating identity refused")
// IssuerPins must come from reviewed owner configuration, never CheckRequest.
// No production pins or default issuer/client are supplied by this package.
type IssuerPins struct {
Issuer, Audience string
Keys map[string]*rsa.PublicKey
}
// Initiator has no exported constructor or mutable fields. Verification proves
// identity only, not membership, representation, approval or policy allowance.
type Initiator struct {
subject string
expires time.Time
}
func (i Initiator) Subject() string { return i.subject }
func (i Initiator) Expires() time.Time { return i.expires }
// strictObject rejects duplicate names (including nested ones), trailing JSON
// and excessive depth. JWT claims are never copied to logs or CheckRequest.
func strictObject(raw []byte, out any) error {
d := json.NewDecoder(bytes.NewReader(raw))
depth := 0
var walk func() error
walk = func() error {
depth++
defer func() { depth-- }()
if depth > 16 {
return ErrIdentity
}
t, e := d.Token()
if e != nil {
return ErrIdentity
}
delim, ok := t.(json.Delim)
if !ok {
return nil
}
switch delim {
case '{':
seen := map[string]bool{}
for d.More() {
k, e := d.Token()
s, ok := k.(string)
if e != nil || !ok || seen[s] {
return ErrIdentity
}
seen[s] = true
if walk() != nil {
return ErrIdentity
}
}
case '[':
for d.More() {
if walk() != nil {
return ErrIdentity
}
}
default:
return ErrIdentity
}
_, e = d.Token()
return e
}
if walk() != nil {
return ErrIdentity
}
if _, e := d.Token(); e == nil {
return ErrIdentity
}
if json.Unmarshal(raw, out) != nil {
return ErrIdentity
}
return nil
}
// VerifyKeyCapeHuman reuses KeyCape's RS256 access-token claim contract. Pins
// and trusted time are server inputs. No URL/key discovery from token headers.
func VerifyKeyCapeHuman(token string, pins IssuerPins, now time.Time) (Initiator, error) {
refuse := func() (Initiator, error) { return Initiator{}, ErrIdentity }
if len(token) > 16384 || len(token) == 0 || pins.Issuer == "" || pins.Audience == "" || now.IsZero() {
return refuse()
}
parts := strings.Split(token, ".")
if len(parts) != 3 {
return refuse()
}
decode := func(s string) ([]byte, error) { return base64.RawURLEncoding.Strict().DecodeString(s) }
header, e := decode(parts[0])
if e != nil {
return refuse()
}
payload, e := decode(parts[1])
if e != nil {
return refuse()
}
signature, e := decode(parts[2])
if e != nil {
return refuse()
}
var h map[string]any
if strictObject(header, &h) != nil || h["alg"] != "RS256" {
return refuse()
}
kid, ok := h["kid"].(string)
if !ok {
return refuse()
}
for k := range h {
if k != "alg" && k != "kid" && k != "typ" {
return refuse()
}
}
key := pins.Keys[kid]
if key == nil || key.N == nil || key.N.BitLen() < 2048 || key.E < 3 {
return refuse()
}
digest := sha256.Sum256([]byte(parts[0] + "." + parts[1]))
if rsa.VerifyPKCS1v15(key, crypto.SHA256, digest[:], signature) != nil {
return refuse()
}
var c struct {
Issuer string `json:"iss"`
Audience json.RawMessage `json:"aud"`
Subject string `json:"sub"`
Principal string `json:"principal_type"`
Tenant string `json:"tenant"`
TenantSource string `json:"tenant_source"`
Expires int64 `json:"exp"`
Issued int64 `json:"iat"`
NotBefore int64 `json:"nbf"`
Assurance struct {
Level string `json:"level"`
MFA bool `json:"mfa"`
Source string `json:"source"`
At int64 `json:"at"`
} `json:"assurance"`
}
if strictObject(payload, &c) != nil {
return refuse()
}
var aud string
if json.Unmarshal(c.Audience, &aud) != nil || aud != pins.Audience {
return refuse()
}
n := now.Unix()
if c.Issuer != pins.Issuer || c.Subject == "" || len(c.Subject) > 512 || strings.ContainsAny(c.Subject, "\r\n") || c.Principal != "human" || c.Tenant != "tenant:platform" || c.TenantSource != "directory" || c.Expires <= n || c.Issued > n || c.Issued <= 0 || c.NotBefore > n || c.Expires <= c.Issued || c.Assurance.Level != "aal2" || !c.Assurance.MFA || c.Assurance.Source != "key-cape" || c.Assurance.At <= 0 || c.Assurance.At > n || n-c.Assurance.At > 300 {
return refuse()
}
return Initiator{subject: c.Subject, expires: time.Unix(c.Expires, 0)}, nil
}

View file

@ -0,0 +1,61 @@
package taskexecution
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"testing"
"time"
)
func TestCryptographicInitiatorContract(t *testing.T) {
key, e := rsa.GenerateKey(rand.Reader, 2048)
if e != nil {
t.Fatal(e)
}
now := time.Now()
pins := IssuerPins{Issuer: "https://synthetic.invalid", Audience: "synthetic-task-test", Keys: map[string]*rsa.PublicKey{"synthetic": &key.PublicKey}}
claims := func() map[string]any {
return map[string]any{"iss": pins.Issuer, "aud": pins.Audience, "sub": "synthetic-human", "principal_type": "human", "tenant": "tenant:platform", "tenant_source": "directory", "iat": now.Unix() - 1, "exp": now.Unix() + 60, "assurance": map[string]any{"level": "aal2", "mfa": true, "source": "key-cape", "at": now.Unix() - 1}}
}
sign := func(c map[string]any) string {
h, _ := json.Marshal(map[string]any{"alg": "RS256", "kid": "synthetic"})
b, _ := json.Marshal(c)
v := base64.RawURLEncoding.EncodeToString(h) + "." + base64.RawURLEncoding.EncodeToString(b)
d := sha256.Sum256([]byte(v))
s, _ := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, d[:])
return v + "." + base64.RawURLEncoding.EncodeToString(s)
}
token := sign(claims())
i, e := VerifyKeyCapeHuman(token, pins, now)
if e != nil || i.Subject() != "synthetic-human" {
t.Fatal("synthetic signature verification failed")
}
cases := map[string]func(map[string]any){"service": func(c map[string]any) { c["principal_type"] = "service" }, "registered tenant": func(c map[string]any) { c["tenant_source"] = "registration" }, "foreign tenant": func(c map[string]any) { c["tenant"] = "tenant:coulomb" }, "issuer": func(c map[string]any) { c["iss"] = "https://wrong.invalid" }, "audience": func(c map[string]any) { c["aud"] = "approval-engine" }, "expired": func(c map[string]any) { c["exp"] = now.Unix() }, "future": func(c map[string]any) { c["nbf"] = now.Unix() + 1 }, "no mfa": func(c map[string]any) { c["assurance"].(map[string]any)["mfa"] = false }, "old mfa": func(c map[string]any) { c["assurance"].(map[string]any)["at"] = now.Unix() - 301 }}
for name, mutate := range cases {
t.Run(name, func(t *testing.T) {
c := claims()
mutate(c)
if _, e := VerifyKeyCapeHuman(sign(c), pins, now); e == nil {
t.Fatal("invalid human accepted")
}
})
}
if _, e := VerifyKeyCapeHuman(token, IssuerPins{}, now); e == nil {
t.Fatal("unadmitted issuer accepted")
}
if _, e := VerifyKeyCapeHuman(token+"x", pins, now); e == nil {
t.Fatal("tampered signature accepted")
}
}
func TestStrictClaimJSON(t *testing.T) {
for _, raw := range []string{`{"iss":"a","iss":"b"}`, `{"assurance":{"mfa":true,"mfa":false}}`, `{"x":1} {"x":2}`} {
var v map[string]any
if strictObject([]byte(raw), &v) == nil {
t.Fatal("ambiguous claims accepted")
}
}
}