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,86 @@
# Access owner source contract and readiness receipt
reviewer: access-engine owner (agent access_task_execution_policy, Codex)
disposition: bounded source contract acknowledged; executable readiness refused
work_item: FLEX-WP-ADHOC-2026-10-11-T04
The completed own source-preparation anchor was persisted before this receipt:
Access commit 1d3a8727f50d4e855e5ddda9f8746d0f2dabbc12, exact
workplans/ADHOC-2026-10-11.md SHA256
4df32aafda0339a073bc2a512eef8d0db6686617ce6027ed554b3f5c68e4f2c6.
All eight staged hashes were rechecked against immutable source manifest
/tmp/access-execute-ccr-stage/manifest.json SHA256
e7a36575c1e521613cc6092371058833d7f43b6739c3cee5b0929cb90c708d88.
The source maker and this owner-role reviewer are the same agent: this receipt
is not an independent code review or foreign issuer/provider acceptance.
## Bounded source agreement
Access INTENT Responsibility Boundary reserves policy decisions to the sole PDP,
authentication to the identity layer, and protected effects to the consumer.
The separate proposed secrets-engine.task-execution v1 package/action execute-ccr
preserves that boundary: it neither overwrites lifecycle v2 nor translates apply
into scoped task execution. Its literal owner_admission=false keeps the entire
package closed today, including requests asserting verified, human_control or
owner_admission. No consumer boolean can enable it.
The actual Secrets immutable descriptor and existing canonical full/approval-free
digest joins are the appropriate input for exact operation/CCR/grant/catalogue,
source, ordered target/body plan, expected states, budget and deadline binding.
The candidate policy models one two-object GET/PUT/GET plan, not arbitrary
apply/provision/exec. Independent Platform source/body compiler and native ACL
scope remain required; shape/hash matching never establishes owner-source trust.
The source-only cryptographic verifier reuses existing KeyCape RS256 human
access-token claim fields out of band. It uses owner-selected issuer/audience/
public-key pins and trusted server time; rejects service subjects, foreign or
registration-only tenant provenance, missing/stale MFA, expiry/future activation,
wrong issuer/audience/key/signature and ambiguous JSON. It returns opaque identity
only. It is not wired into HTTP evaluation, issues no key/token, selects no real
client, accepts no native issuer and makes no authorization decision.
## Readiness refusal and owner dependencies
No authenticated initiating-human channel, admitted User/Tenant PIP retrieval,
transport-to-human representation or server projection exists in this patch.
The proposed task_execution_projection is an internal integration sketch, not an
accepted public identity claim. Raw human JWTs must not enter CheckRequest,
context, audit/decision records or receipts. Server implementation must reject or
discard caller projection fields, independently verify KeyCape identity, acquire
User membership/canonical-user facts and Tenant lifecycle facts through their
actual authenticated interfaces, and impose the earliest freshness/lifetime
ceiling. Service TokenReview proves transport only. Approval's human approver is
not automatically the initiating human; a registration tenant is not membership.
This receipt does not accept anything on KeyCape, User Engine, Tenant Engine,
Approval Engine, Platform or Secrets Engine's behalf. Actual client/audience,
issuer/key admission, PIP transport and freshness contracts, and representation
must receive their owners' acceptance and integration evidence. Removal of the
hard refusal requires a separately reviewed versioned source successor, never
an unsigned flag, helm value or literal flip in a runtime test.
Served approval claim validation and exact issue/full/submitted digest joins,
trusted consume confirmation, owner compiler, durable journal and task grant/
cleanup mediation remain required independently. Existing SECRETS-WP-0013 and
RPF-WP-0035-T12 drive integration; no clone driver or operational closure is
introduced. Native lifecycle adoption and standing service authority stay false.
## Actual source verification
Maker command in isolated staged Access source:
GOCACHE=/tmp/access-taskexecution-go-cache go test -json
./internal/taskexecution ./internal/policy ./pkg/api.
Terminal exit zero: 118 passing Go test events, including 24 new parent/subtest
events, zero failures. Log /tmp/access-execute-ccr-stage/test-results.jsonl.
Actual Secrets Python ExecutionContract.parse and canonical request/approval-free
digest functions produce the same complete synthetic descriptor and digests as
Go. OPA candidate allow and mutation denials use an isolated in-memory package
with the hard false literal replaced for the test alone. The actual staged
package remains false and denies all requests. These synthetic results prove
source semantics/interoperability, not provider facts or an available runtime
allow path. The cross-repo generator currently relies on the estate's Secrets
virtualenv, and is not claimed to be a portable standalone fixture.
No native call, credential retrieval, package activation, foreign owner checkout
write or privileged-action trial occurred. Exact source publication and independent
code verification remain with the coordinator before adoption.

View file

@ -0,0 +1,97 @@
# Proposed Secrets exact execution policy and initiating-provider boundary
Prepared for Secrets SECRETS-WP-0013-T01 and Platform RPF-WP-0035-T12.
This is a proposed Access source contract. It adopts no new identity protocol,
issuer/client/key, runtime provider, source standard or native entitlement.
## Why the separate policy exists
Lifecycle v2 deliberately serves twelve generic operations. Existing apply
ignores execution_contract and its service subject cannot prove which human
initiated a task. The new package secrets-engine.task-execution v1 recognizes
only execute-ccr and is separate from secrets-engine.catalog-lane.lifecycle v2.
The entire execution descriptor, including operation/CCR/grant/catalogue/source,
ordered two-object read/write/readback plan, budget and deadline, participates in
existing canonical request and approval-free digest joins. No translation to
apply and no service administrative grant occurs.
The shipped source package is deliberately closed: owner_admission is a literal
false. An unsigned verified, owner_admission or human_control request flag cannot
open it. Synthetic future-provider tests replace that literal in an isolated
in-memory package only; those allows are not runtime admission or native evidence.
This is an enforced refusal until the identity boundary exists, not a switch to
turn on once a human approves a proposal.
## Reuse the actual providers
* KeyCape owns authentication/MFA and RS256 access-token issuance. Reuse
docs/approval-engine-auth-contract.md and docs/tenant-claim-contract.md from
KeyCape: exact issuer/audience/subject/principal_type/tenant/tenant_source,
expiry and assurance level/mfa/source/at. internal/taskexecution's proposed
source verifier consumes the token out of band using locally owner-pinned RSA
keys, trusted server time and no JWT-selected discovery URL. It returns an
opaque Initiator, never a token or an authorization decision. No real issuer,
key or client is installed in the source. Human initiating audience/client
admission is still missing; an approval-engine access audience cannot silently
be reused for task execution.
* Access's existing callerauth TokenReview choke point authenticates the service
transport and binds secrets-engine to its exact Kubernetes principal. It does
not authenticate the request's subject human or prove representation. Reuse
that authenticated server identity, never caller transport_principal text.
* User Engine owns canonical user and active tenant account/membership facts;
Tenant Engine owns authoritative tenant existence/lifecycle. Their live,
authenticated, freshness-bounded read interfaces must be integrated by Access
(PIP consumption), with unavailable/stale facts reducing authority. No new
parallel identity engine or caller-authored membership provider is introduced.
* Approval Engine supplies the served approval claim and authenticated consumption
facts. Its human approver can differ from the initiating human: a successful
approval is not initiating identity. Existing claim/PDP canonical correspondence
and later independent consume confirmation remain mandatory.
KeyCape explicitly states registration-supplied tenant is store isolation, not
human membership. The source verifier therefore requires directory provenance,
exact tenant:platform, verified MFA within 300 seconds and a currently valid
RS256 token. This strict proposed ceiling currently refuses registration-only
humans; changing that requirement requires owner-reviewed doctrine, not relabeling
registration as directory. Cryptographic verification alone still grants nothing:
User/Tenant membership, representation and exact operation are separate facts.
## Required server injection and activation proof
The task_execution_projection shape in the candidate policy is a proposed
internal server representation, not an accepted public claim format. Access
must acquire initiator token on a separate authenticated channel, verify it,
resolve canonical user/membership/tenant using the owning PIPs, correlate it with
the authenticated transport and full exact operation, and inject a bounded
projection. Raw human JWTs must never enter CheckRequest context or decision/audit
records. An HTTP-supplied projection must be rejected or discarded before any
trusted projection is injected, including nested/duplicate-field collisions.
Projection expiry must be the earliest identity/session expiry, provider
freshness deadline, approved operation deadline and PDP authority ceiling.
Fresh PIP retrieval/refusal and revocation visibility must be recorded; cached
membership alone cannot silently extend a KeyCape session. Package activation
requires an actual authenticated KeyCape origin/client, admitted User/Tenant read
routes/identities, server-controlled provenance and exact request/claim binding.
No raw JWT verifier endpoint, projection injection, transport-to-human binding,
PIP resolver, new server route or native package deployment is implemented here.
Only a reviewed versioned source successor may remove the hard refusal after
these gates have executable counterexamples. There is no unsigned enable flag.
## Tested scope and unfinished integration
Source tests verify RSA signatures and wrong issuer/audience/principal/tenant,
registration provenance, missing/stale MFA, expired/future token and absent key
admission. Strict nested duplicate/trailing JSON is refused. Python uses the
actual Secrets ExecutionContract parser and canonical request/approval digest
functions; Go and OPA evaluate the same complete synthetic two-object descriptor.
Synthetic future-projection allow and mutation denials prove candidate semantics;
current package denies regardless of asserted flags. They do not prove providers.
The candidate predicate is not a full independent typed owner compiler: source
and body provenance, exact estate origin/CCR readiness, bounded parser admission,
platform plan compilation and immutable body mediation remain Platform/Secrets
requirements. Nor does it independently validate served approval claims, consume,
run journal admission, dispatch or issue/revoke native grants. Existing journal,
confirmation and descriptor work must be integrated before this can perform one
metadata action. No existing operational task closes from this source package.

View file

@ -0,0 +1,16 @@
- id: unsigned-provider-refused
request:
tenant: tenant:platform
subject: {id: secrets-engine, type: service}
action: execute-ccr
resource: {id: catalog:test, type: secret-catalog-lane, system: secrets-engine}
context: {human_control: true, verified: true, owner_admission: true}
expect: {effect: deny, reason: initiating_provider_unadmitted}
- id: legacy-action-cannot-borrow-task-policy
request:
tenant: tenant:platform
subject: {id: secrets-engine, type: service}
action: apply
resource: {id: catalog:test, type: secret-catalog-lane, system: secrets-engine}
context: {human_control: true}
expect: {effect: deny, reason: initiating_provider_unadmitted}

View file

@ -0,0 +1,113 @@
---
id: secrets-engine.task-execution
name: Secrets exact task execution
namespace: secrets-engine:secret-catalog-lane
version: v1
status: proposed
package: flexauth.secrets_engine.task_execution
allow_ttl: 15s
actions: [execute-ccr]
owner: team:platform-security
fixtures: [policy_fixtures.yaml]
caring:
profile: caring-0.4.0-rc2
enforce: false
activation:
mode: local
---
# Proposed task execution contract
Source only. The owner-admission constant is false because no authenticated
initiating-principal projection or User/Tenant membership provider has been
admitted. Request flags, even `verified=true`, cannot change that constant.
Activating this package today denies every request. It does not replace lifecycle
v2. Any successor that changes owner_admission requires review of the actual
server injection boundary and authoritative providers, not a values switch.
The candidate predicate models the future join, not a trustworthy caller claim.
It is exercised with synthetic server projections in tests. Raw human tokens
must never enter CheckRequest/context/decision records. See the accompanying
source verifier and provider contract; neither is wired into HTTP evaluation.
```rego
owner_admission := false
default decision := {"effect": "deny", "reason": "initiating_provider_unadmitted"}
decision := {"effect": "allow", "reason": "exact_task_execution_matched"} {
owner_admission
candidate_matches
}
hash_ok(x) { is_string(x); regex.match("^sha256:[0-9a-f]{64}$", x) }
candidate_matches {
input.action == "execute-ccr"
input.tenant == "tenant:platform"
input.subject.id == "secrets-engine"
input.subject.type == "service"
input.resource.system == "secrets-engine"
input.resource.type == "secret-catalog-lane"
input.context.human_control == true
c := input.context.execution_contract
c.contract_version == "SECRETS-EXEC-20261011-v1"
c.operation == "metadata-apply-ccr"
c.execution_actor == "service:secrets-engine"
regex.match("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", c.operation_id)
c.ccr_id != c.execution_grant_ccr_id
hash_ok(c.ccr_immutable_sha256)
hash_ok(c.grant_immutable_sha256)
hash_ok(c.catalog_sha256)
hash_ok(c.expected_before)
hash_ok(c.expected_after)
c.backend_budget.ttl_seconds <= 120
c.backend_budget.ttl_seconds >= 30
c.backend_budget.uses == 12
count(c.operations) == 6
c.operations[0].method == "GET"
c.operations[1].method == "PUT"
c.operations[2].method == "GET"
c.operations[3].method == "GET"
c.operations[4].method == "PUT"
c.operations[5].method == "GET"
regex.match("^sys/policies/acl/[A-Za-z0-9_-]+$", c.operations[0].path)
regex.match("^auth/[A-Za-z0-9_-]+/role/[A-Za-z0-9_-]+$", c.operations[3].path)
c.operations[0].path == c.operations[1].path
c.operations[1].path == c.operations[2].path
c.operations[3].path == c.operations[4].path
c.operations[4].path == c.operations[5].path
every_operation_hash_valid
p := input.context.task_execution_projection
p.version == "proposed-task-execution-projection/v1"
p.principal_type == "human"
p.subject != ""
p.tenant == input.tenant
p.tenant_source == "directory"
p.mfa == true
p.transport_principal == "system:serviceaccount:secrets-engine:secrets-engine"
p.execution_contract_digest == concat("", ["sha256:", crypto.sha256(json.marshal(c))])
p.resource_id == input.resource.id
p.membership_source == "user-engine"
p.tenant_state_source == "tenant-engine"
p.member_active == true
p.tenant_active == true
p.not_after_ns > time.now_ns()
p.not_after_ns <= time.parse_rfc3339_ns(c.operation_deadline)
input.context.approval.kind == "approval-claim"
input.context.approval.binding.human_control == true
}
every_operation_hash_valid {
count([x | x := input.context.execution_contract.operations[_]; hash_ok(x.payload_sha256)]) == 6
}
```
```rego test
import data.flexauth.secrets_engine.task_execution
test_unsigned_admission_flags_cannot_open {
task_execution.decision == {"effect": "deny", "reason": "initiating_provider_unadmitted"}
with input as {"context": {"verified": true, "owner_admission": true, "human_control": true}}
}
```

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")
}
}
}

View file

@ -0,0 +1,13 @@
"""Synthetic interoperability generator; never obtains credentials or native state."""
import hashlib, json, sys
from datetime import datetime, timedelta, timezone
sys.path.insert(0, '/home/worsch/secrets-engine/src')
from secrets_engine.execution_contract import ExecutionContract, catalog_target_digest
from secrets_engine.authorization import request_digest, approval_binding_digest
h='sha256:'+'a'*64
catalog={'lane':'synthetic','stage':'prod'}
c={'contract_version':'SECRETS-EXEC-20261011-v1','operation_id':'cbfbced0-09d5-48c8-a1e7-a2ea4dc69153','operation':'metadata-apply-ccr','execution_actor':'service:secrets-engine','ccr_id':'CCR-2099-0001','execution_grant_ccr_id':'CCR-2099-0002','ccr_immutable_sha256':h,'grant_immutable_sha256':h,'catalog_sha256':catalog_target_digest(catalog),'expected_before':h,'expected_after':h,'source_bundle':{r:{'path':'/synthetic/'+r+'.py','sha256':h} for r in ['engine_dispatcher','platform_mediator','typed_ccr','policy_auth']},'backend_origin':'http://127.0.0.1:18200','cluster_uid':'synthetic-not-native','backend_budget':{'ttl_seconds':120,'uses':12},'operation_deadline':(datetime.now(timezone.utc)+timedelta(seconds=100)).isoformat(),'operations':[{'method':method,'path':path,'payload_sha256':h} for path in ['sys/policies/acl/synthetic','auth/synthetic/role/synthetic'] for method in ['GET','PUT','GET']]}
c=ExecutionContract.parse(c).as_dict()
p={'version':'proposed-task-execution-projection/v1','subject':'synthetic-human','principal_type':'human','tenant':'tenant:platform','tenant_source':'directory','mfa':True,'transport_principal':'system:serviceaccount:secrets-engine:secrets-engine','execution_contract_digest':catalog_target_digest(c),'resource_id':'catalog:synthetic','membership_source':'user-engine','tenant_state_source':'tenant-engine','member_active':True,'tenant_active':True,'not_after_ns':int((datetime.now(timezone.utc)+timedelta(seconds=60)).timestamp())*1000000000}
r={'tenant':'tenant:platform','subject':{'id':'secrets-engine','type':'service'},'action':'execute-ccr','resource':{'id':'catalog:synthetic','type':'secret-catalog-lane','system':'secrets-engine'},'context':{'execution_contract':c,'catalog_target':catalog,'human_control':True,'task_execution_projection':p,'approval':{'kind':'approval-claim','binding':{'human_control':True}}}}
print(json.dumps({'request':r,'request_digest':request_digest(r),'approval_binding_digest':approval_binding_digest(r),'descriptor_digest':catalog_target_digest(c)}))