package main import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "github.com/netkingdom/flex-auth/internal/callerauth" "github.com/netkingdom/flex-auth/pkg/api" ) func TestRunVersion(t *testing.T) { var stdout, stderr bytes.Buffer code := run([]string{"version"}, &stdout, &stderr) if code != 0 { t.Fatalf("code = %d, stderr = %s", code, stderr.String()) } if strings.TrimSpace(stdout.String()) == "" { t.Fatal("version output is empty") } } func TestRunTestPolicy(t *testing.T) { var stdout, stderr bytes.Buffer code := run([]string{"test-policy", "--file", examplePath("policy_package.md")}, &stdout, &stderr) if code != 0 { t.Fatalf("code = %d, stderr = %s", code, stderr.String()) } if !strings.Contains(stdout.String(), `"valid": true`) { t.Fatalf("stdout = %s; want valid policy result", stdout.String()) } } func TestRunCheck(t *testing.T) { var stdout, stderr bytes.Buffer code := run([]string{ "check", "--registry", examplePath("registry_snapshot.json"), "--policy", examplePath("policy_package.md"), "--request", examplePath("check_request.yaml"), }, &stdout, &stderr) if code != 0 { t.Fatalf("code = %d, stderr = %s", code, stderr.String()) } var decision api.DecisionEnvelope if err := json.Unmarshal(stdout.Bytes(), &decision); err != nil { t.Fatalf("unmarshal decision: %v\n%s", err, stdout.String()) } if decision.Effect != api.DecisionEffectAllow { t.Fatalf("decision.Effect = %q; want allow", decision.Effect) } } func TestRunBatchCheck(t *testing.T) { var stdout, stderr bytes.Buffer code := run([]string{ "batch-check", "--registry", examplePath("registry_snapshot.json"), "--policy", examplePath("policy_package.md"), "--request", examplePath("batch_check_request.yaml"), }, &stdout, &stderr) if code != 0 { t.Fatalf("code = %d, stderr = %s", code, stderr.String()) } var decisions []api.DecisionEnvelope if err := json.Unmarshal(stdout.Bytes(), &decisions); err != nil { t.Fatalf("unmarshal decisions: %v\n%s", err, stdout.String()) } if len(decisions) != 2 || decisions[0].Effect != api.DecisionEffectAllow || decisions[1].Effect != api.DecisionEffectDeny { t.Fatalf("decisions = %+v; want allow then deny", decisions) } } func TestRunCheckOpsWarden(t *testing.T) { var stdout, stderr bytes.Buffer code := run([]string{ "check", "--registry", opsPath("registry_snapshot.json"), "--policy", opsPath("policy_package.md"), "--request", opsPath("check_request_allow_adm.json"), }, &stdout, &stderr) if code != 0 { t.Fatalf("code = %d, stderr = %s", code, stderr.String()) } var decision api.DecisionEnvelope if err := json.Unmarshal(stdout.Bytes(), &decision); err != nil { t.Fatalf("unmarshal decision: %v\n%s", err, stdout.String()) } if decision.Effect != api.DecisionEffectAllow { t.Fatalf("decision.Effect = %q; want allow", decision.Effect) } if decision.ID == "" { t.Fatal("decision.ID is empty; ops-warden needs a policy_decision_id") } } func TestRunRailiancePlatformCredentialGrantContract(t *testing.T) { var stdout, stderr bytes.Buffer code := run([]string{"test-policy", "--file", railiancePlatformPath("policy_package.md")}, &stdout, &stderr) if code != 0 || !strings.Contains(stdout.String(), `"valid": true`) { t.Fatalf("test-policy code = %d, stderr = %s, stdout = %s", code, stderr.String(), stdout.String()) } stdout.Reset() stderr.Reset() code = run([]string{ "check", "--registry", railiancePlatformPath("registry_snapshot.json"), "--policy", railiancePlatformPath("policy_package.md"), "--request", railiancePlatformPath("check_request_allow.json"), }, &stdout, &stderr) if code != 0 { t.Fatalf("check code = %d, stderr = %s", code, stderr.String()) } var decision api.DecisionEnvelope if err := json.Unmarshal(stdout.Bytes(), &decision); err != nil { t.Fatal(err) } if decision.Effect != api.DecisionEffectAllow || decision.Reason != "credential_grant_allowed" { t.Fatalf("decision = %s/%s; want allow/credential_grant_allowed", decision.Effect, decision.Reason) } if decision.Binding == nil || decision.Binding.Context["requested_ttl_seconds"] != float64(900) { t.Fatalf("binding = %+v; want normalized numeric TTL", decision.Binding) } } func TestServeOpsWardenCheckContract(t *testing.T) { logPath := filepath.Join(t.TempDir(), "decisions.jsonl") engine, err := buildEngine(context.Background(), opsPath("registry_snapshot.json"), opsPath("policy_package.md"), logPath) if err != nil { t.Fatalf("buildEngine: %v", err) } server := httptest.NewServer(newServeMux(engine)) defer server.Close() resp, err := http.Get(server.URL + "/healthz") if err != nil { t.Fatalf("GET /healthz: %v", err) } resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("GET /healthz status = %d; want 200", resp.StatusCode) } allow := postCheck(t, server.URL+"/v1/check", opsPath("check_request_allow_adm.json")) if allow.Effect != api.DecisionEffectAllow || allow.ID == "" { t.Fatalf("allow decision = %+v; want allow with id", allow) } deny := postCheck(t, server.URL+"/v1/check", opsPath("check_request_deny_ttl_above_max.json")) if deny.Effect != api.DecisionEffectDeny || deny.Reason != "ttl_out_of_bounds" { t.Fatalf("deny decision = %+v; want ttl_out_of_bounds deny", deny) } resp, err = http.Get(server.URL + "/v1/check") if err != nil { t.Fatalf("GET /v1/check: %v", err) } resp.Body.Close() if resp.StatusCode != http.StatusMethodNotAllowed { t.Fatalf("GET /v1/check status = %d; want 405", resp.StatusCode) } resp, err = http.Post(server.URL+"/v1/check", "application/json", strings.NewReader(`{"subject":`)) if err != nil { t.Fatalf("POST malformed /v1/check: %v", err) } resp.Body.Close() if resp.StatusCode != http.StatusBadRequest { t.Fatalf("malformed POST status = %d; want 400", resp.StatusCode) } logData, err := os.ReadFile(logPath) if err != nil { t.Fatalf("read decision log: %v", err) } if !strings.Contains(string(logData), allow.ID) || !strings.Contains(string(logData), deny.ID) { t.Fatalf("decision log does not contain both decision ids\nlog: %s\nallow: %s deny: %s", string(logData), allow.ID, deny.ID) } } type fixedTokenReviewer struct { identity callerauth.Identity } func (r fixedTokenReviewer) Review(context.Context, string) (callerauth.Identity, error) { return r.identity, nil } func TestServeCallerAuthenticationBindsSystemToPrincipal(t *testing.T) { engine, err := buildEngine(context.Background(), opsPath("registry_snapshot.json"), opsPath("policy_package.md"), "") if err != nil { t.Fatalf("buildEngine: %v", err) } authenticator, err := callerauth.New(callerauth.ModeEnforce, fixedTokenReviewer{identity: callerauth.Identity{ Username: "system:serviceaccount:ops-warden:ops-warden", Audiences: []string{"flex-auth"}, }}, "flex-auth", map[string]string{ "ops-warden": "system:serviceaccount:ops-warden:ops-warden", }, nil) if err != nil { t.Fatal(err) } server := httptest.NewServer(newServeMuxWithCallerAuth(engine, authenticator)) defer server.Close() body, err := os.ReadFile(opsPath("check_request_allow_adm.json")) if err != nil { t.Fatal(err) } request, _ := http.NewRequest(http.MethodPost, server.URL+"/v1/check", bytes.NewReader(body)) request.Header.Set("content-type", "application/json") resp, err := http.DefaultClient.Do(request) if err != nil { t.Fatal(err) } resp.Body.Close() if resp.StatusCode != http.StatusUnauthorized { t.Fatalf("missing token status = %d; want 401", resp.StatusCode) } request, _ = http.NewRequest(http.MethodPost, server.URL+"/v1/check", bytes.NewReader(body)) request.Header.Set("content-type", "application/json") request.Header.Set("authorization", "Bearer workload-token") resp, err = http.DefaultClient.Do(request) if err != nil { t.Fatal(err) } if resp.StatusCode != http.StatusOK { resp.Body.Close() t.Fatalf("bound caller status = %d; want 200", resp.StatusCode) } var envelope api.DecisionEnvelope if err := json.NewDecoder(resp.Body).Decode(&envelope); err != nil { resp.Body.Close() t.Fatalf("decode decision: %v", err) } resp.Body.Close() if envelope.Provenance.Caller == nil || envelope.Provenance.Caller.Mode != "enforce" { t.Fatalf("caller = %+v; want enforce", envelope.Provenance.Caller) } if envelope.Provenance.Caller.Principal != "system:serviceaccount:ops-warden:ops-warden" { t.Fatalf("principal = %q", envelope.Provenance.Caller.Principal) } if envelope.Provenance.Caller.Audience != "flex-auth" { t.Fatalf("audience = %q", envelope.Provenance.Caller.Audience) } wrong, _ := callerauth.New(callerauth.ModeEnforce, fixedTokenReviewer{identity: callerauth.Identity{ Username: "system:serviceaccount:another:caller", Audiences: []string{"flex-auth"}, }}, "flex-auth", map[string]string{"ops-warden": "system:serviceaccount:ops-warden:ops-warden"}, nil) wrongServer := httptest.NewServer(newServeMuxWithCallerAuth(engine, wrong)) defer wrongServer.Close() request, _ = http.NewRequest(http.MethodPost, wrongServer.URL+"/v1/check", bytes.NewReader(body)) request.Header.Set("content-type", "application/json") request.Header.Set("authorization", "Bearer workload-token") resp, err = http.DefaultClient.Do(request) if err != nil { t.Fatal(err) } resp.Body.Close() if resp.StatusCode != http.StatusForbidden { t.Fatalf("mismatched caller status = %d; want 403", resp.StatusCode) } } func TestRunLoadRegistryOpsWardenProduction(t *testing.T) { var stdout, stderr bytes.Buffer code := run([]string{"load-registry", "--file", opsPath("production_registry_snapshot.json")}, &stdout, &stderr) if code != 0 { t.Fatalf("code = %d, stderr = %s", code, stderr.String()) } var result map[string]any if err := json.Unmarshal(stdout.Bytes(), &result); err != nil { t.Fatalf("unmarshal load-registry output: %v; stdout = %s", err, stdout.String()) } if result["subjects"] != float64(4) || result["relationships"] != float64(4) || result["resource_manifests"] != float64(1) { t.Fatalf("load-registry result = %+v; want production actor registry counts", result) } } func TestOpsWardenProductionRegistryActors(t *testing.T) { engine, err := buildEngine(context.Background(), opsPath("production_registry_snapshot.json"), opsPath("policy_package.md"), "") if err != nil { t.Fatalf("buildEngine: %v", err) } cases := []struct { name string subjectID string actor string actorType string principal string ttlHours float64 wantEffect api.DecisionEffect wantReason string }{ { name: "state hub bridge agent advisory while zone is unknown", subjectID: "agt-state-hub-bridge", actor: "agt-state-hub-bridge", actorType: "agt", principal: "agt-task-bridge", ttlHours: 1, wantEffect: api.DecisionEffectAuditOnly, wantReason: "advisory_would_signing_policy_matched", }, { name: "state hub bridge IAM subject advisory while zone is unknown", subjectID: "iam:agt-state-hub-bridge", actor: "agt-state-hub-bridge", actorType: "agt", principal: "agt-task-bridge", ttlHours: 1, wantEffect: api.DecisionEffectAuditOnly, wantReason: "advisory_would_signing_policy_matched", }, { name: "codex interhub bootstrap advisory while zone is unknown", subjectID: "agt-codex-interhub-bootstrap", actor: "agt-codex-interhub-bootstrap", actorType: "agt", principal: "agt-interhub-bootstrap", ttlHours: 1, wantEffect: api.DecisionEffectAuditOnly, wantReason: "advisory_would_signing_policy_matched", }, { name: "admin actor allow", subjectID: "adm-example", actor: "adm-example", actorType: "adm", principal: "adm-full", ttlHours: 4, wantEffect: api.DecisionEffectAllow, }, { name: "automation actor advisory while zone is unknown", subjectID: "atm-backup-daily", actor: "atm-backup-daily", actorType: "atm", principal: "atm-backup-daily", ttlHours: 1, wantEffect: api.DecisionEffectAuditOnly, wantReason: "advisory_would_signing_policy_matched", }, { name: "ttl above production max is advisory while zone is unknown", subjectID: "agt-state-hub-bridge", actor: "agt-state-hub-bridge", actorType: "agt", principal: "agt-task-bridge", ttlHours: 999, wantEffect: api.DecisionEffectAuditOnly, wantReason: "advisory_would_ttl_out_of_bounds", }, { name: "unregistered production actor is advisory unknown", subjectID: "agt-missing", actor: "agt-missing", actorType: "agt", principal: "agt-missing", ttlHours: 1, wantEffect: api.DecisionEffectAuditOnly, wantReason: "advisory_would_unknown_actor_resource", }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { decision, err := engine.Check(context.Background(), opsWardenProductionSignRequest(tt.subjectID, tt.actor, tt.actorType, tt.principal, tt.ttlHours)) if err != nil { t.Fatalf("Check: %v", err) } if decision.Effect != tt.wantEffect { t.Fatalf("decision.Effect = %q; want %q; decision: %+v", decision.Effect, tt.wantEffect, decision) } if tt.wantReason != "" && decision.Reason != tt.wantReason { t.Fatalf("decision.Reason = %q; want %q; decision: %+v", decision.Reason, tt.wantReason, decision) } if (tt.wantEffect == api.DecisionEffectAllow || tt.wantEffect == api.DecisionEffectAuditOnly) && decision.ID == "" { t.Fatal("proceeding decision ID is empty") } }) } } func TestRunValidateAccessDescriptor(t *testing.T) { var stdout, stderr bytes.Buffer code := run([]string{"validate", "--kind", "access-descriptor", "--file", examplePath("access_descriptor.yaml")}, &stdout, &stderr) if code != 0 { t.Fatalf("code = %d, stderr = %s", code, stderr.String()) } if !strings.Contains(stdout.String(), `"status": "valid"`) { t.Fatalf("stdout = %s; want valid status", stdout.String()) } } func examplePath(name string) string { return filepath.Join("..", "..", "examples", "caring", name) } func opsPath(name string) string { return filepath.Join("..", "..", "examples", "ops-warden", name) } func railiancePlatformPath(name string) string { return filepath.Join("..", "..", "examples", "railiance-platform", name) } func opsWardenProductionSignRequest(subjectID, actor, actorType, principal string, ttlHours float64) api.CheckRequest { return api.CheckRequest{ ID: "check:ops-warden-production-" + actor, Tenant: "tenant:platform", Subject: api.SubjectRef{ ID: subjectID, Type: api.SubjectType(actorType), }, Action: "sign", Resource: api.ResourceRef{ ID: "ssh-cert:actor/" + actor, Type: "ssh-certificate", System: "ops-warden", }, Context: map[string]any{ "principals": []string{principal}, "actor_type": actorType, "ttl_hours": ttlHours, "pubkey_fingerprint": "SHA256:example-production-fingerprint", }, } } func postCheck(t *testing.T, url, path string) api.DecisionEnvelope { t.Helper() body, err := os.ReadFile(path) if err != nil { t.Fatalf("read %s: %v", path, err) } resp, err := http.Post(url, "application/json", bytes.NewReader(body)) if err != nil { t.Fatalf("POST %s: %v", url, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("POST %s status = %d; want 200", path, resp.StatusCode) } var decision api.DecisionEnvelope if err := json.NewDecoder(resp.Body).Decode(&decision); err != nil { t.Fatalf("decode %s response: %v", path, err) } return decision }