access-engine/internal/taskexecution/service_input_test.go
tegwick 4f6946bbb3
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Container Image / build-and-push (push) Failing after 1m1s
Bind service task HTTP input to authenticated caller before evaluation
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
2026-10-16 16:25:55 +02:00

113 lines
5.9 KiB
Go

package taskexecution
import (
"bytes"
"context"
"encoding/json"
"github.com/netkingdom/access-engine/internal/callerauth"
"github.com/netkingdom/access-engine/pkg/api"
"testing"
"time"
)
type serviceReviewer struct {
identity callerauth.Identity
err error
calls int
}
func (r *serviceReviewer) Review(_ context.Context, _ string) (callerauth.Identity, error) {
r.calls++
return r.identity, r.err
}
func serviceRequest(t *testing.T, now time.Time) api.CheckRequest {
t.Helper()
r := api.CheckRequest{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": 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(60 * time.Second).Format(time.RFC3339Nano)}}}
return r
}
func TestServicePilotAuthenticatedBinding(t *testing.T) {
now := time.Now()
reviewer := &serviceReviewer{identity: callerauth.Identity{Username: ServicePilotPrincipal, Audiences: []string{"flex-auth"}, NotAfter: now.Add(120 * time.Second)}}
a, e := NewServicePilotInputs(reviewer)
if e != nil {
t.Fatal(e)
}
r := serviceRequest(t, now)
s, e := a.Prepare(context.Background(), "Bearer synthetic", r, now)
if e != nil || reviewer.calls != 1 {
t.Fatal("source TokenReview path")
}
out, e := s.Request()
if e != nil || api.RequestDigest(out) != api.RequestDigest(r) || s.Caller().Principal != ServicePilotPrincipal {
t.Fatal("binding changed")
}
r.Context["service_actor_binding"].(map[string]any)["subject_id"] = "mutated"
again, _ := s.Request()
if again.Context["service_actor_binding"].(map[string]any)["subject_id"] != "secrets-engine" {
t.Fatal("opaque input retained mutable caller map")
}
if _, e := (ServiceInput{}).Request(); e == nil {
t.Fatal("zero input trusted")
}
}
func TestServicePilotRefusals(t *testing.T) {
now := time.Now()
cases := map[string]func(*api.CheckRequest, *serviceReviewer){"human": func(r *api.CheckRequest, _ *serviceReviewer) { r.Subject.Type = "human" }, "subject": func(r *api.CheckRequest, _ *serviceReviewer) { r.Subject.ID = "another" }, "legacy apply": func(r *api.CheckRequest, _ *serviceReviewer) { r.Action = "apply" }, "foreigntenant": func(r *api.CheckRequest, _ *serviceReviewer) { r.Tenant = "tenant:coulomb" }, "wrong service principal": func(_ *api.CheckRequest, p *serviceReviewer) {
p.identity.Username = "system:serviceaccount:other:other"
}, "wrong audience": func(_ *api.CheckRequest, p *serviceReviewer) { p.identity.Audiences = []string{"openbao"} }, "expired transport": func(_ *api.CheckRequest, p *serviceReviewer) { p.identity.NotAfter = now }, "missing expiry": func(_ *api.CheckRequest, p *serviceReviewer) { p.identity.NotAfter = time.Time{} }, "caller verified": func(r *api.CheckRequest, _ *serviceReviewer) { r.Context["verified"] = true }, "human projection": func(r *api.CheckRequest, _ *serviceReviewer) {
r.Context["task_execution_projection"] = map[string]any{"subject": "human"}
}, "delegation": func(r *api.CheckRequest, _ *serviceReviewer) { r.Context["delegation"] = false }, "forged binding": func(r *api.CheckRequest, _ *serviceReviewer) {
r.Context["service_actor_binding"].(map[string]any)["subject_id"] = "other"
}, "lifetime": func(r *api.CheckRequest, _ *serviceReviewer) {
r.Context["execution_contract"].(map[string]any)["operation_deadline"] = now.Add(240 * time.Second).Format(time.RFC3339Nano)
}}
for name, mutate := range cases {
t.Run(name, func(t *testing.T) {
r := serviceRequest(t, now)
p := &serviceReviewer{identity: callerauth.Identity{Username: ServicePilotPrincipal, Audiences: []string{"flex-auth"}, NotAfter: now.Add(120 * time.Second)}}
mutate(&r, p)
a, _ := NewServicePilotInputs(p)
if _, e := a.Prepare(context.Background(), "Bearer synthetic", r, now); e == nil {
t.Fatal("invalid input accepted")
}
})
}
p := &serviceReviewer{identity: callerauth.Identity{Username: ServicePilotPrincipal, Audiences: []string{"flex-auth"}, NotAfter: now.Add(time.Minute)}}
a, _ := NewServicePilotInputs(p)
if _, e := a.Prepare(context.Background(), "", serviceRequest(t, now), now); e == nil {
t.Fatal("missing header accepted")
}
if _, e := NewServicePilotInputs(nil); e == nil {
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":{"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")
}
}
}