the-custodian/docs/changes/netkingdom-target-2026-10-16/access-native-source/b1/b1-over-native-prep-r2.patch
codex a4c1663558
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Reject generated-only commits / generated-commit-guard (push) Successful in 16s
docs: preserve reviewed NetKingdom session artifacts
2026-11-01 22:40:29 +01:00

766 lines
34 KiB
Diff

diff --git a/cmd/access-engine/main.go b/cmd/access-engine/main.go
--- a/cmd/access-engine/main.go
+++ b/cmd/access-engine/main.go
@@ -22,6 +22,7 @@
"github.com/netkingdom/access-engine/internal/emission"
"github.com/netkingdom/access-engine/internal/policy"
"github.com/netkingdom/access-engine/internal/registry"
+ "github.com/netkingdom/access-engine/internal/taskexecution"
"github.com/netkingdom/access-engine/pkg/api"
)
@@ -397,7 +398,11 @@
return fail(stderr, err)
}
- mux := newServeMuxWithCallerAuth(engine, authenticator)
+ serviceInputs, err := buildServicePilotInputs(engine, callerauth.Mode(*callerAuthMode), *callerAudience, *callerKubernetesURL, *callerReviewerTokenFile, *callerCAFile, callerBindings.StringMap())
+ if err != nil {
+ return fail(stderr, err)
+ }
+ mux := newServeMuxWithServicePilotInputs(engine, authenticator, serviceInputs)
if outbox != nil {
mountEmission(mux, outbox)
}
@@ -448,6 +453,10 @@
}
func newServeMuxWithCallerAuth(engine *decisioncore.Engine, authenticator *callerauth.Authenticator) *http.ServeMux {
+ return newServeMuxWithServicePilotInputs(engine, authenticator, nil)
+}
+
+func newServeMuxWithServicePilotInputs(engine *decisioncore.Engine, authenticator *callerauth.Authenticator, serviceInputs *taskexecution.ServicePilotInputs) *http.ServeMux {
mux := http.NewServeMux()
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("content-type", "application/json")
@@ -458,6 +467,25 @@
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
+ if engine.IsServicePilot() {
+ if serviceInputs == nil {
+ http.Error(w, "service input unavailable", http.StatusServiceUnavailable)
+ return
+ }
+ request, err := taskexecution.DecodeServiceRequest(r.Body)
+ if err != nil || request.Purpose != "" {
+ http.Error(w, "invalid service task request", http.StatusBadRequest)
+ return
+ }
+ input, err := serviceInputs.Prepare(r.Context(), r.Header.Get("Authorization"), request, time.Now().UTC())
+ if err != nil {
+ http.Error(w, "service task input refused", http.StatusForbidden)
+ return
+ }
+ decision, err := engine.CheckServicePilot(r.Context(), input)
+ writeHTTP(w, decision, err)
+ return
+ }
var request api.CheckRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
@@ -478,6 +506,10 @@
writeHTTP(w, decision, err)
})
mux.HandleFunc("/v1/batch_check", func(w http.ResponseWriter, r *http.Request) {
+ if engine.IsServicePilot() {
+ http.Error(w, "service task batch unavailable", http.StatusForbidden)
+ return
+ }
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
@@ -500,6 +532,22 @@
writeHTTP(w, decisions, err)
})
return mux
+}
+
+// Only the existing exact service pilot package selects this source path.
+// This is not an eligibility toggle; successful preparation still only DENYs.
+func buildServicePilotInputs(engine *decisioncore.Engine, mode callerauth.Mode, audience, kubernetesURL, reviewerTokenFile, caFile string, bindings map[string]string) (*taskexecution.ServicePilotInputs, error) {
+ if !engine.IsServicePilot() {
+ return nil, nil
+ }
+ if mode != callerauth.ModeEnforce || audience != "flex-auth" || len(bindings) != 1 || bindings["secrets-engine"] != taskexecution.ServicePilotPrincipal {
+ return nil, fmt.Errorf("service pilot requires exact enforced retained caller mapping")
+ }
+ reviewer, err := callerauth.NewKubernetesTokenReviewer(kubernetesURL, audience, reviewerTokenFile, caFile)
+ if err != nil {
+ return nil, err
+ }
+ return taskexecution.NewServicePilotInputs(reviewer)
}
func buildCallerAuthenticator(mode callerauth.Mode, audience, kubernetesURL, reviewerTokenFile, caFile string, bindings map[string]string, stderr io.Writer) (*callerauth.Authenticator, error) {
@@ -615,7 +663,7 @@
func writeHTTP(w http.ResponseWriter, value any, err error) {
w.Header().Set("content-type", "application/json")
- if errors.Is(err, decisioncore.ErrRecordNotCommitted) {
+ if errors.Is(err, decisioncore.ErrRecordNotCommitted) || errors.Is(err, decisioncore.ErrServicePilotUnavailable) {
// Unavailable evaluation is not an authorization verdict (GH-DEC-2026-024).
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
diff --git a/cmd/access-engine/service_pilot_http_test.go b/cmd/access-engine/service_pilot_http_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/access-engine/service_pilot_http_test.go
@@ -0,0 +1,376 @@
+package main
+
+import (
+ "bytes"
+ "context"
+ "crypto/ed25519"
+ "encoding/base64"
+ "encoding/json"
+ "encoding/pem"
+ "io"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "reflect"
+ "strings"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ "github.com/netkingdom/access-engine/internal/audit"
+ "github.com/netkingdom/access-engine/internal/callerauth"
+ "github.com/netkingdom/access-engine/internal/decision"
+ "github.com/netkingdom/access-engine/internal/policy"
+ "github.com/netkingdom/access-engine/internal/registry"
+ "github.com/netkingdom/access-engine/internal/sign"
+ "github.com/netkingdom/access-engine/internal/taskexecution"
+ "github.com/netkingdom/access-engine/pkg/api"
+)
+
+func pilotRequest(now time.Time) api.CheckRequest {
+ return api.CheckRequest{ID: "source-only-service-http", Tenant: "tenant:platform", Subject: api.SubjectRef{ID: "secrets-engine", Type: "service"}, Action: "execute-ccr", Resource: api.ResourceRef{ID: "catalog:synthetic", Type: "secret-catalog-lane", System: "secrets-engine", Attributes: map[string]any{"stage": "prod"}}, Context: map[string]any{"human_control": true, "service_actor_binding": taskexecution.DeclaredServiceBinding(), "execution_contract": map[string]any{"contract_version": "SECRETS-EXEC-20261011-v1", "operation": "metadata-apply-ccr", "execution_actor": "service:secrets-engine", "operation_deadline": now.Add(30 * time.Second).Format(time.RFC3339Nano)}, "approval": map[string]any{"caller_claim": "unchanged and untrusted in B1"}}}
+}
+func pilotEngine(t *testing.T) *decision.Engine {
+ t.Helper()
+ p, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "secrets-engine-service-task-execution", "policy_package.md"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ engine, err := decision.NewEngine(registry.NewStore(), p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return engine
+}
+func pilotToken(kind string, now time.Time) string {
+ payload := map[string]any{"case": kind, "exp": now.Add(2 * time.Minute).Unix()}
+ if kind == "expired" {
+ payload["exp"] = now.Add(-time.Second).Unix()
+ }
+ if kind == "no-expiry" {
+ delete(payload, "exp")
+ }
+ raw, _ := json.Marshal(payload)
+ return "synthetic." + base64.RawURLEncoding.EncodeToString(raw) + ".not-a-real-token"
+}
+func TestServicePilotRealHTTPAndTokenReviewSignedDeny(t *testing.T) {
+ now := time.Now().UTC()
+ var reviews atomic.Int64
+ reviewer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ reviews.Add(1)
+ if r.URL.Path != "/apis/authentication.k8s.io/v1/tokenreviews" || r.Header.Get("Authorization") != "Bearer synthetic-reviewer" {
+ t.Error("unexpected reviewer route/auth")
+ w.WriteHeader(403)
+ return
+ }
+ var body struct {
+ Spec struct {
+ Token string `json:"token"`
+ Audiences []string `json:"audiences"`
+ } `json:"spec"`
+ }
+ if json.NewDecoder(r.Body).Decode(&body) != nil {
+ w.WriteHeader(400)
+ return
+ }
+ if !reflect.DeepEqual(body.Spec.Audiences, []string{"flex-auth", "access-engine"}) {
+ t.Error("unexpected bounded runtime audience request")
+ }
+ pieces := strings.Split(body.Spec.Token, ".")
+ raw, _ := base64.RawURLEncoding.DecodeString(pieces[1])
+ var claims map[string]any
+ json.Unmarshal(raw, &claims)
+ principal := taskexecution.ServicePilotPrincipal
+ audiences := []string{"flex-auth"}
+ authenticated := true
+ switch claims["case"] {
+ case "foreign":
+ principal = "system:serviceaccount:other:other"
+ case "alternate-audience":
+ audiences = []string{"access-engine"}
+ case "wrong-audience":
+ audiences = []string{"openbao"}
+ case "unauthenticated":
+ authenticated = false
+ }
+ json.NewEncoder(w).Encode(map[string]any{"status": map[string]any{"authenticated": authenticated, "audiences": audiences, "user": map[string]any{"username": principal}}})
+ }))
+ defer reviewer.Close()
+ folder := t.TempDir()
+ ca := filepath.Join(folder, "ca.crt")
+ reviewToken := filepath.Join(folder, "reviewer-token")
+ os.WriteFile(ca, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: reviewer.Certificate().Raw}), 0600)
+ os.WriteFile(reviewToken, []byte("synthetic-reviewer"), 0600)
+ engine := pilotEngine(t)
+ private := ed25519.NewKeyFromSeed(make([]byte, 32))
+ if engine.SetEnvelopeSigningKey(private, "synthetic-b1") != nil {
+ t.Fatal("signer config")
+ }
+ log := audit.NewJSONLDecisionLog(filepath.Join(folder, "decisions.jsonl"))
+ engine.SetDecisionLog(log)
+ bindings := map[string]string{"secrets-engine": taskexecution.ServicePilotPrincipal}
+ inputs, err := buildServicePilotInputs(engine, callerauth.ModeEnforce, "flex-auth", reviewer.URL, reviewToken, ca, bindings)
+ if err != nil {
+ t.Fatal(err)
+ }
+ server := httptest.NewServer(newServeMuxWithServicePilotInputs(engine, callerauth.Disabled(), inputs))
+ defer server.Close()
+ post := func(path string, request api.CheckRequest, token string) (int, []byte) {
+ raw, _ := json.Marshal(request)
+ req, _ := http.NewRequest("POST", server.URL+path, bytes.NewReader(raw))
+ if token != "" {
+ req.Header.Set("Authorization", "Bearer "+token)
+ }
+ response, err := http.DefaultClient.Do(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer response.Body.Close()
+ body, _ := io.ReadAll(response.Body)
+ return response.StatusCode, body
+ }
+ original := pilotRequest(now)
+ status, body := post("/v1/check", original, pilotToken("valid", now))
+ if status != 200 {
+ t.Fatalf("status=%d", status)
+ }
+ var envelope api.DecisionEnvelope
+ if json.Unmarshal(body, &envelope) != nil {
+ t.Fatal("decode")
+ }
+ if envelope.Effect != api.DecisionEffectDeny || envelope.Reason != "service_execution_integration_unadmitted" {
+ t.Fatal("B1 changed policy gate")
+ }
+ if sign.Verify(envelope, map[string]ed25519.PublicKey{"synthetic-b1": private.Public().(ed25519.PublicKey)}) != nil {
+ t.Fatal("actual response signature")
+ }
+ if envelope.Binding.SubmittedRequestDigest != api.RequestDigest(original) || !reflect.DeepEqual(envelope.Binding.Context, original.Context) {
+ t.Fatal("original submitted request/context changed")
+ }
+ if envelope.Provenance.Caller == nil || envelope.Provenance.Caller.Mode != "enforce" || envelope.Provenance.Caller.Principal != taskexecution.ServicePilotPrincipal || envelope.Provenance.Caller.Audience != "flex-auth" {
+ t.Fatal("caller provenance")
+ }
+ records, err := log.ReadAll()
+ if err != nil || len(records) != 1 || !reflect.DeepEqual(records[0], envelope) {
+ t.Fatal("recorded signed response differs")
+ }
+ if reviews.Load() != 1 {
+ t.Fatal("request was reviewed more than once")
+ }
+ for _, kind := range []string{"foreign", "alternate-audience", "wrong-audience", "expired", "no-expiry", "unauthenticated"} {
+ t.Run(kind, func(t *testing.T) {
+ status, _ := post("/v1/check", pilotRequest(now), pilotToken(kind, now))
+ if status == 200 {
+ t.Fatal("invalid source caller accepted")
+ }
+ })
+ }
+ for _, mutate := range []func(*api.CheckRequest){func(r *api.CheckRequest) { r.Subject.ID = "other" }, func(r *api.CheckRequest) { r.Subject.Type = "human" }, func(r *api.CheckRequest) { r.Tenant = "other" }, func(r *api.CheckRequest) { r.Context["service_actor_binding"].(map[string]any)["extra"] = true }, func(r *api.CheckRequest) { r.Context["verified"] = true }, func(r *api.CheckRequest) { r.Context["delegation"] = true }, func(r *api.CheckRequest) {
+ r.Context["execution_contract"].(map[string]any)["operation_deadline"] = now.Add(5 * time.Minute).Format(time.RFC3339Nano)
+ }} {
+ request := pilotRequest(now)
+ mutate(&request)
+ if status, _ := post("/v1/check", request, pilotToken("valid", now)); status == 200 {
+ t.Fatal("invalid public request accepted")
+ }
+ }
+ if status, _ := post("/v1/check", pilotRequest(now), ""); status == 200 {
+ t.Fatal("missing caller accepted")
+ }
+ if status, _ := post("/v1/batch_check", pilotRequest(now), pilotToken("valid", now)); status != 403 {
+ t.Fatal("batch bypass")
+ }
+ records, _ = log.ReadAll()
+ if len(records) != 1 {
+ t.Fatal("refused request entered decision record")
+ }
+
+ // Exercise the actual production serve flag/configuration path in a disposable
+ // subprocess of this compiled test binary. Its environment contains only the
+ // synthetic argument handoff; no ambient backend credentials are inherited.
+ seedFile := filepath.Join(folder, "synthetic-seed")
+ os.WriteFile(seedFile, []byte(base64.RawURLEncoding.EncodeToString(make([]byte, 32))), 0440)
+ listener, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatal(err)
+ }
+ address := listener.Addr().String()
+ listener.Close()
+ registryPath, _ := filepath.Abs(filepath.Join("..", "..", "examples", "secrets-engine", "registry_snapshot.json"))
+ policyPath, _ := filepath.Abs(filepath.Join("..", "..", "examples", "secrets-engine-service-task-execution", "policy_package.md"))
+ cliRecord := filepath.Join(folder, "cli-records.jsonl")
+ cliArgs := []string{"serve", "--addr", address, "--registry", registryPath, "--policy", policyPath, "--log", cliRecord, "--caller-auth-mode", "enforce", "--caller-audience", "flex-auth", "--caller-kubernetes-url", reviewer.URL, "--caller-reviewer-token-file", reviewToken, "--caller-ca-file", ca, "--caller-binding", "secrets-engine=" + taskexecution.ServicePilotPrincipal, "--envelope-signing-key-file", seedFile, "--envelope-signing-kid", "synthetic-b1"}
+ argsJSON, _ := json.Marshal(cliArgs)
+ child := exec.Command(os.Args[0], "-test.run=^TestServicePilotChildCLI$")
+ child.Env = []string{"ACCESS_B1_SERVER_ARGS=" + string(argsJSON)}
+ child.Stdout = io.Discard
+ child.Stderr = io.Discard
+ if child.Start() != nil {
+ t.Fatal("start disposable CLI")
+ }
+ defer func() { child.Process.Kill(); child.Wait() }()
+ ready := false
+ for attempt := 0; attempt < 100; attempt++ {
+ resp, err := http.Get("http://" + address + "/healthz")
+ if err == nil {
+ resp.Body.Close()
+ ready = true
+ break
+ }
+ time.Sleep(20 * time.Millisecond)
+ }
+ if !ready {
+ t.Fatal("disposable production serve path not ready")
+ }
+ raw, _ := json.Marshal(original)
+ cliRequest, _ := http.NewRequest("POST", "http://"+address+"/v1/check", bytes.NewReader(raw))
+ cliRequest.Header.Set("Authorization", "Bearer "+pilotToken("valid", now))
+ cliResponse, err := http.DefaultClient.Do(cliRequest)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer cliResponse.Body.Close()
+ var cliEnvelope api.DecisionEnvelope
+ if cliResponse.StatusCode != 200 || json.NewDecoder(cliResponse.Body).Decode(&cliEnvelope) != nil {
+ t.Fatal("production serve path refused supported source input")
+ }
+ if cliEnvelope.Effect != api.DecisionEffectDeny || cliEnvelope.Reason != "service_execution_integration_unadmitted" || sign.Verify(cliEnvelope, map[string]ed25519.PublicKey{"synthetic-b1": private.Public().(ed25519.PublicKey)}) != nil {
+ t.Fatal("production CLI did not emit signed DENY")
+ }
+ if cliEnvelope.Binding.SubmittedRequestDigest != api.RequestDigest(original) || !reflect.DeepEqual(cliEnvelope.Binding.Context, original.Context) {
+ t.Fatal("production CLI changed original request/context")
+ }
+ cliRecords, err := audit.NewJSONLDecisionLog(cliRecord).ReadAll()
+ if err != nil || len(cliRecords) != 1 || !reflect.DeepEqual(cliRecords[0], cliEnvelope) {
+ t.Fatal("production CLI response/record differ")
+ }
+ for _, mode := range []callerauth.Mode{callerauth.ModeWarn, callerauth.ModeDisabled} {
+ if _, err := buildServicePilotInputs(engine, mode, "flex-auth", reviewer.URL, reviewToken, ca, bindings); err == nil {
+ t.Fatal("non-enforcing task mode accepted")
+ }
+ }
+ if _, err := buildServicePilotInputs(engine, callerauth.ModeEnforce, "access-engine", reviewer.URL, reviewToken, ca, bindings); err == nil {
+ t.Fatal("task pin renamed audience accepted")
+ }
+ missing := httptest.NewRecorder()
+ req := httptest.NewRequest("POST", "/v1/check", bytes.NewReader(body))
+ newServeMuxWithCallerAuth(engine, callerauth.Disabled()).ServeHTTP(missing, req)
+ if missing.Code != 503 {
+ t.Fatal("unwired generic handler accepted task pin")
+ }
+}
+func TestServicePilotGenericEngineAndCLIBypassesRefuse(t *testing.T) {
+ engine := pilotEngine(t)
+ request := pilotRequest(time.Now())
+ if _, err := engine.Check(context.Background(), request); err == nil {
+ t.Fatal("generic Check bypass")
+ }
+ if _, err := engine.BatchCheck(context.Background(), api.BatchCheckRequest{}); err == nil {
+ t.Fatal("generic Batch bypass")
+ }
+ if _, err := engine.ListAllowed(context.Background(), decision.ListAllowedRequest{}); err == nil {
+ t.Fatal("generic List bypass")
+ }
+ if _, err := engine.CheckServicePilot(context.Background(), taskexecution.ServiceInput{}); err == nil {
+ t.Fatal("zero input trusted")
+ }
+ folder := t.TempDir()
+ path := filepath.Join(folder, "request.json")
+ raw, _ := json.Marshal(request)
+ os.WriteFile(path, raw, 0600)
+ var stdout, stderr bytes.Buffer
+ code := run([]string{"check", "--registry", filepath.Join("..", "..", "examples", "secrets-engine", "registry_snapshot.json"), "--policy", filepath.Join("..", "..", "examples", "secrets-engine-service-task-execution", "policy_package.md"), "--request", path}, &stdout, &stderr)
+ if code == 0 || stdout.Len() != 0 {
+ t.Fatal("generic CLI emitted task verdict")
+ }
+}
+
+// The earlier separately named positive fixture policy is not an exception:
+// generic task actions cannot bypass the typed path on any loaded package.
+func TestServicePilotSeparatelyNamedSyntheticAllowCannotBypass(t *testing.T) {
+ p, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "secrets-engine-service-task-execution", "policy_package.md"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ p.Metadata.ID = "synthetic-legacy-stack-positive-only"
+ p.RegoModule = "package " + p.Metadata.Package + "\ndefault decision := {\"effect\":\"allow\",\"reason\":\"synthetic_only\"}\n"
+ request := pilotRequest(time.Now())
+ result, err := p.Evaluate(context.Background(), request)
+ if err != nil || result.Effect != api.DecisionEffectAllow {
+ t.Fatal("synthetic precondition did not allow")
+ }
+ engine, err := decision.NewEngine(registry.NewStore(), p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := engine.Check(context.Background(), request); err == nil {
+ t.Fatal("separately named generic task allow escaped")
+ }
+ if _, err := engine.BatchCheck(context.Background(), api.BatchCheckRequest{Action: "execute-ccr"}); err == nil {
+ t.Fatal("generic task batch escaped")
+ }
+ if _, err := engine.ListAllowed(context.Background(), decision.ListAllowedRequest{Action: "execute-ccr"}); err == nil {
+ t.Fatal("generic task list escaped")
+ }
+ raw, _ := json.Marshal(request)
+ recorder := httptest.NewRecorder()
+ req := httptest.NewRequest("POST", "/v1/check", bytes.NewReader(raw))
+ newServeMux(engine).ServeHTTP(recorder, req)
+ if recorder.Code != 503 {
+ t.Fatal("separate synthetic generic HTTP task route did not refuse")
+ }
+}
+
+func TestServicePilotWithholdsUnsignedOrAccidentalAllowBeforeRecord(t *testing.T) {
+ now := time.Now().UTC()
+ adapter, err := taskexecution.NewServicePilotInputs(fixedTokenReviewer{identity: callerauth.Identity{Username: taskexecution.ServicePilotPrincipal, Audiences: []string{"flex-auth"}, NotAfter: now.Add(time.Minute)}})
+ if err != nil {
+ t.Fatal(err)
+ }
+ input, err := adapter.Prepare(context.Background(), "Bearer synthetic", pilotRequest(now), now)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, accidentalAllow := range []bool{false, true} {
+ t.Run(map[bool]string{false: "unsigned", true: "accidental-allow"}[accidentalAllow], func(t *testing.T) {
+ p, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "secrets-engine-service-task-execution", "policy_package.md"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if accidentalAllow {
+ p.RegoModule = "package " + p.Metadata.Package + "\ndefault decision := {\"effect\":\"allow\",\"reason\":\"synthetic_only\"}\n"
+ }
+ engine, err := decision.NewEngine(registry.NewStore(), p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if accidentalAllow {
+ engine.SetEnvelopeSigningKey(ed25519.NewKeyFromSeed(make([]byte, 32)), "synthetic")
+ }
+ log := audit.NewJSONLDecisionLog(filepath.Join(t.TempDir(), "records.jsonl"))
+ engine.SetDecisionLog(log)
+ if _, err := engine.CheckServicePilot(context.Background(), input); err == nil {
+ t.Fatal("B1 released unsupported result")
+ }
+ records, err := log.ReadAll()
+ if err != nil || len(records) != 0 {
+ t.Fatal("withheld result entered recorder")
+ }
+ })
+ }
+}
+
+func TestServicePilotChildCLI(t *testing.T) {
+ raw := os.Getenv("ACCESS_B1_SERVER_ARGS")
+ if raw == "" {
+ return
+ }
+ var args []string
+ if json.Unmarshal([]byte(raw), &args) != nil {
+ os.Exit(2)
+ }
+ os.Exit(run(args, os.Stdout, os.Stderr))
+}
diff --git a/internal/decision/engine.go b/internal/decision/engine.go
--- a/internal/decision/engine.go
+++ b/internal/decision/engine.go
@@ -20,6 +20,7 @@
"github.com/netkingdom/access-engine/internal/policy"
"github.com/netkingdom/access-engine/internal/registry"
"github.com/netkingdom/access-engine/internal/sign"
+ "github.com/netkingdom/access-engine/internal/taskexecution"
"github.com/netkingdom/access-engine/pkg/api"
)
@@ -153,9 +154,44 @@
return envelope, nil
}
+// IsServicePilot selects the existing exact source package, never a request flag.
+func (e *Engine) IsServicePilot() bool {
+ return e.policy.Metadata.ID == taskexecution.ServicePilotPackageID && e.policy.Metadata.Version == taskexecution.ServicePilotPackageVersion
+}
+
+// ErrServicePilotUnavailable withholds any result that is not this B1 slice's
+// signed DENY. Provider joins and executable eligibility remain unimplemented.
+var ErrServicePilotUnavailable = errors.New("service pilot source path unavailable")
+
+// CheckServicePilot records only a signed DENY after authenticating the exact
+// immutable service input. It does not validate Approval facts or grant an act.
+func (e *Engine) CheckServicePilot(ctx context.Context, input taskexecution.ServiceInput) (api.DecisionEnvelope, error) {
+ if !e.IsServicePilot() {
+ return api.DecisionEnvelope{}, ErrServicePilotUnavailable
+ }
+ request, err := input.RequestAt(e.now())
+ if err != nil {
+ return api.DecisionEnvelope{}, err
+ }
+ envelope, err := e.decide(callerauth.WithRecord(ctx, input.Caller()), request)
+ if err != nil {
+ return api.DecisionEnvelope{}, err
+ }
+ if envelope.Effect != api.DecisionEffectDeny || envelope.Signature == nil || envelope.Signature.Mode != api.EnvelopeSignatureSigned {
+ return api.DecisionEnvelope{}, ErrServicePilotUnavailable
+ }
+ if err := e.recordDecisions([]api.DecisionEnvelope{envelope}); err != nil {
+ return api.DecisionEnvelope{}, err
+ }
+ return envelope, nil
+}
+
// Check evaluates one subject/action/resource request. The decision is
// released only after its required record commits (GH-DEC-2026-024).
func (e *Engine) Check(ctx context.Context, request api.CheckRequest) (api.DecisionEnvelope, error) {
+ if e.IsServicePilot() || request.Action == "execute-ccr" {
+ return api.DecisionEnvelope{}, ErrServicePilotUnavailable
+ }
decision, err := e.decide(ctx, request)
if err != nil {
return api.DecisionEnvelope{}, err
@@ -169,6 +205,9 @@
// BatchCheck evaluates one subject/action/context tuple against resources in
// request order, and commits the whole batch in one durable write.
func (e *Engine) BatchCheck(ctx context.Context, request api.BatchCheckRequest) ([]api.DecisionEnvelope, error) {
+ if e.IsServicePilot() || request.Action == "execute-ccr" {
+ return nil, ErrServicePilotUnavailable
+ }
decisions := make([]api.DecisionEnvelope, 0, len(request.Resources))
for _, resource := range request.Resources {
decision, err := e.decide(ctx, api.CheckRequest{
@@ -193,6 +232,9 @@
// ListAllowed evaluates candidate resources and returns only allow decisions.
func (e *Engine) ListAllowed(ctx context.Context, request ListAllowedRequest) ([]api.DecisionEnvelope, error) {
+ if e.IsServicePilot() || request.Action == "execute-ccr" {
+ return nil, ErrServicePilotUnavailable
+ }
candidates := e.store.ResourceRefs(request.System, request.ResourceType)
decisions := make([]api.DecisionEnvelope, 0, len(candidates))
for _, resource := range candidates {
diff --git a/internal/taskexecution/service_input.go b/internal/taskexecution/service_input.go
--- a/internal/taskexecution/service_input.go
+++ b/internal/taskexecution/service_input.go
@@ -1,20 +1,27 @@
package taskexecution
import (
+ "bytes"
"context"
"encoding/json"
"errors"
"github.com/netkingdom/access-engine/internal/callerauth"
"github.com/netkingdom/access-engine/pkg/api"
+ "io"
"time"
)
var ErrServiceInput = errors.New("service task execution input refused")
+const ServicePilotPackageID = "secrets-engine.task-execution.service-pilot"
+const ServicePilotPackageVersion = "v1"
+
const ServicePilotMappingVersion = "proposed-service-task-caller/v1"
const ServicePilotPrincipal = "system:serviceaccount:secrets-engine:secrets-engine"
-// ServicePilotInputs is source preparation only. It installs the explicit
+// ServicePilotInputs installs the exact B1 service input mapping for signed DENY.
+// It does not validate Approval facts or establish executable eligibility.
+// It installs the explicit
// proposed fixed subject-to-transport mapping, never a request-selected mapping.
// No constructor parameter can select warn/disabled mode or another subject.
type ServicePilotInputs struct{ auth *callerauth.Authenticator }
@@ -46,6 +53,44 @@
return r, nil
}
func (s ServiceInput) Caller() callerauth.Record { return s.caller }
+
+// RequestAt rechecks transport and operation lifetime at the runtime use site.
+// Request() remains a detached source snapshot, never an execution capability.
+func (s ServiceInput) RequestAt(now time.Time) (api.CheckRequest, error) {
+ r, err := s.Request()
+ if err != nil || now.IsZero() || s.caller.Audience != "flex-auth" || !s.caller.NotAfter.After(now) {
+ return api.CheckRequest{}, ErrServiceInput
+ }
+ contract, ok := r.Context["execution_contract"].(map[string]any)
+ if !ok {
+ return api.CheckRequest{}, ErrServiceInput
+ }
+ value, ok := contract["operation_deadline"].(string)
+ if !ok {
+ return api.CheckRequest{}, ErrServiceInput
+ }
+ deadline, err := time.Parse(time.RFC3339Nano, value)
+ if err != nil || !deadline.After(now) || deadline.After(s.caller.NotAfter) {
+ return api.CheckRequest{}, ErrServiceInput
+ }
+ return r, nil
+}
+
+// DecodeServiceRequest reuses the owner strict JSON scanner for this bounded
+// task-only route. It never changes producer canonicalization or legacy input.
+func DecodeServiceRequest(reader io.Reader) (api.CheckRequest, error) {
+ raw, err := io.ReadAll(io.LimitReader(reader, 524289))
+ var request api.CheckRequest
+ if err != nil || len(raw) > 524288 || strictObject(raw, &request) != nil {
+ return request, ErrServiceInput
+ }
+ decoder := json.NewDecoder(bytes.NewReader(raw))
+ decoder.DisallowUnknownFields()
+ if decoder.Decode(&request) != nil {
+ return api.CheckRequest{}, ErrServiceInput
+ }
+ return request, nil
+}
// DeclaredServiceBinding is public immutable metadata to bind at approval issue.
// It is not authentication evidence; Prepare independently verifies its principal.
diff --git a/internal/taskexecution/service_input_test.go b/internal/taskexecution/service_input_test.go
--- a/internal/taskexecution/service_input_test.go
+++ b/internal/taskexecution/service_input_test.go
@@ -1,7 +1,9 @@
package taskexecution
import (
+ "bytes"
"context"
+ "encoding/json"
"github.com/netkingdom/access-engine/internal/callerauth"
"github.com/netkingdom/access-engine/pkg/api"
"testing"
@@ -79,3 +81,35 @@
t.Fatal("missing owner reviewer accepted")
}
}
+
+func TestServiceInputRuntimeRecheckAndStrictHTTPDecode(t *testing.T) {
+ now := time.Now().UTC()
+ r := serviceRequest(t, now)
+ reviewer := &serviceReviewer{identity: callerauth.Identity{Username: ServicePilotPrincipal, Audiences: []string{"flex-auth"}, NotAfter: now.Add(120 * time.Second)}}
+ adapter, _ := NewServicePilotInputs(reviewer)
+ input, err := adapter.Prepare(context.Background(), "Bearer synthetic", r, now)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := input.RequestAt(now); err != nil {
+ t.Fatal(err)
+ }
+ for _, when := range []time.Time{time.Time{}, now.Add(60 * time.Second), now.Add(120 * time.Second)} {
+ if _, err := input.RequestAt(when); err == nil {
+ t.Fatal("stale/invalid runtime input accepted")
+ }
+ }
+ raw, _ := json.Marshal(r)
+ decoded, err := DecodeServiceRequest(bytes.NewReader(raw))
+ if err != nil || api.RequestDigest(decoded) != api.RequestDigest(r) {
+ t.Fatal("valid decode changed canonical submitted input")
+ }
+ bad := [][]byte{append(append([]byte{}, raw...), []byte(" {}")...), []byte(`{"action":"execute-ccr","action":"apply"}`), []byte(`{"context"
+ "bytes"
+ "encoding/json":{"human_control":true,"human_control":false}}`), []byte(`{"unknown_task_flag":true}`), bytes.Repeat([]byte(" "), 524289)}
+ for _, body := range bad {
+ if _, err := DecodeServiceRequest(bytes.NewReader(body)); err == nil {
+ t.Fatal("malformed/oversized task request accepted")
+ }
+ }
+}
diff --git a/internal/taskexecution/identity.go b/internal/taskexecution/identity.go
--- a/internal/taskexecution/identity.go
+++ b/internal/taskexecution/identity.go
@@ -1,5 +1,6 @@
-// Package taskexecution contains proposed source-only identity input validation.
-// It is not registered with HTTP evaluation and grants no executable admission.
+// Package taskexecution contains task input validation, not policy evaluation.
+// The human component remains source-only. The B1 service HTTP path can emit
+// only signed DENY and grants no executable admission.
package taskexecution
import (
diff --git a/docs/service-task-execution-contract.md b/docs/service-task-execution-contract.md
--- a/docs/service-task-execution-contract.md
+++ b/docs/service-task-execution-contract.md
@@ -51,9 +51,32 @@
Maker tests use a synthetic TokenReviewer, not actual Kubernetes authentication.
Actual deployed TokenReview origin/CA/RBAC/audience/projection rotation and mapping
-admission must be proved before runtime use. The adapter is not wired into HTTP
-routes, decision engine evaluation or a native executor; source package remains
-hard-closed. A forged public service_actor_binding alone remains insufficient.
+admission must be proved before runtime use. The B1 source path now wires the actual service adapter into `/v1/check` only
+when the exact installed service-pilot v1 package is selected. Startup requires
+enforced retained `flex-auth` audience and the one exact Secrets mapping. The
+actual reviewer authenticates once; the detached ServiceInput is rechecked for
+caller/operation expiry at use. The single existing decision engine records only
+a signed DENY on this path; unsigned output or an accidental policy allow is
+withheld before recording. No native executor or Approval fact join is installed.
+A forged public service_actor_binding alone remains insufficient.
+
+Generic Check, BatchCheck, ListAllowed and CLI/HTTP evaluation cannot evaluate
+`execute-ccr` under any package; the dedicated typed path is required. The exact
+service pin also refuses generic calls and batch requests. This deliberately
+invalidates the older separately named synthetic positive stack fixture on the
+new source. Its positive proof remains historical at Access `ec21518e`, not proof
+for B1. Do not add a fixture exception; a positive successor must use reviewed
+B2/B3 provider joins. The literal service policy false gate is unchanged.
+
+B1 retains the exact original request context, carried claim, submitted digest
+and producer-owned canonicalization. Its bounded strict JSON decoder refuses
+duplicates, trailing/unknown typed fields and oversized input. The caller's
+Approval blob remains unverified and cannot enable anything. B2 requires the
+Approval owner's fact contract/vector review; B3 additionally requires exact
+server-owned native-act representation and the complete trusted join. Fresh
+provider observations stay out of band: no refreshed context or claim replacement
+may relax strict Secrets request correspondence. Access never invokes a
+credential-carrying Python subprocess, consumes approval or performs the effect.
## Exact operation and independent gates
@@ -129,7 +152,8 @@
admit grant/baseline/estate or perform an effect. Those are explicit implementation
requirements; a matching predicate and reviewed TokenReview input are insufficient.
The service adapter carries authenticated transport evidence outside the canonical
-request but is not connected to HTTP evaluation/provider validation. A caller's
+request. B1 connects it only to signed-DENY HTTP evaluation; provider validation and
+executable eligibility remain absent. A caller's
service_actor_binding remains a public declaration independently compared by the
adapter, never a substitute for runtime provenance or exact operation authority.
diff --git a/workplans/FLEX-WP-0024-decision-envelope-authenticity.md b/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
--- a/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
+++ b/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
@@ -226,3 +226,14 @@
Secrets verifier. No key, trust, image publication or live activation is admitted.
The managed workload's missing rapp declaration remains explicit migration debt;
this source binding does not fabricate a package declaration or raise its grade.
+
+
+2026-10-16 separate B1 source follow-through wires ServicePilotInputs through actual
+HTTP/TokenReview into a signed-DENY-only engine entry. Generic `execute-ccr` calls
+refuse across every policy, including the old separately named positive fixture;
+that proof stays pinned to ec21518e. Original context/submitted digest stay unchanged.
+Actual disposable serve/TLS TokenReview/HTTP signed-DENY and bypass/refusal tests are
+source evidence only. B2 depends on independently reviewed Approval fact contract
+and canonical vectors; B3 additionally needs exact source/normalization numeric
+representation and full trusted provider joins. No current policy false gate,
+production trust, native deployment or protected effect is enabled by B1.