access-engine/cmd/access-engine/ops_warden_fetch_test.go
tegwick f758381cea
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 6s
Build and Publish Container Image / build-and-push (push) Successful in 1m23s
FLEX-WP-0036: Go module is github.com/netkingdom/access-engine; T02 consumer inventory
- module path renamed (no external importers); golangci local-prefixes
- ACCESS_ENGINE_RUN_TOPAZ_INTEGRATION replaces FLEX_AUTH_RUN_TOPAZ_INTEGRATION
- README title and identity paragraph reflect the landed renames
- docs/runtime-rename-inventory.md: per-consumer references by kind and order
- chart name, image package and /opt paths deliberately left for T05

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 735967@bnt-lap001
Assistant-Session: 5971967f-77f8-4ced-9e02-c6ef67040ffc
2026-10-10 15:29:00 +02:00

219 lines
7.2 KiB
Go

package main
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"testing"
"gopkg.in/yaml.v3"
"github.com/netkingdom/access-engine/internal/callerauth"
"github.com/netkingdom/access-engine/pkg/api"
)
// The ops-warden secret-read pin (CUST-WP-0075, bug b792c5d0): warden's
// check_fetch_policy sends action read on resource type secret with
// resource.system = the lane's owner_repo.
const opsWardenPrincipal = "system:serviceaccount:ops-warden:ops-warden"
func fetchPath(name string) string {
return filepath.Join("..", "..", "examples", "ops-warden-fetch", name)
}
func loadFetchRequest(t *testing.T, name string) api.CheckRequest {
t.Helper()
raw, err := os.ReadFile(fetchPath(name))
if err != nil {
t.Fatal(err)
}
var request api.CheckRequest
if err := json.Unmarshal(raw, &request); err != nil {
t.Fatalf("%s: %v", name, err)
}
return request
}
func TestOpsWardenFetchDecisions(t *testing.T) {
engine, err := buildEngine(context.Background(), fetchPath("registry_snapshot.json"), fetchPath("policy_package.md"), "")
if err != nil {
t.Fatalf("buildEngine: %v", err)
}
cases := []struct {
file string
effect api.DecisionEffect
reason string
}{
{"check_request_allow_forgejo_admin_api_token.json", api.DecisionEffectAllow, "secret_read_lane_matched"},
{"check_request_allow_openrouter_llm_connect.json", api.DecisionEffectAllow, "secret_read_lane_matched"},
{"check_request_deny_unknown_need.json", api.DecisionEffectDeny, "unknown_need"},
{"check_request_deny_wrong_system.json", api.DecisionEffectDeny, "wrong_system"},
{"check_request_deny_non_secret_type.json", api.DecisionEffectDeny, "wrong_resource_type"},
}
for _, tc := range cases {
t.Run(tc.file, func(t *testing.T) {
decision, err := engine.Check(context.Background(), loadFetchRequest(t, tc.file))
if err != nil {
t.Fatal(err)
}
if decision.Effect != tc.effect || decision.Reason != tc.reason {
t.Fatalf("decision = %s/%s; want %s/%s", decision.Effect, decision.Reason, tc.effect, tc.reason)
}
if decision.ID == "" {
t.Fatal("decision.ID is empty; warden needs a policy_decision_id")
}
})
}
}
// fetchBindings reads the caller bindings exactly as the values file declares
// them, so the test exercises the configuration that would be deployed.
func fetchBindings(t *testing.T) (string, map[string]string) {
t.Helper()
raw, err := os.ReadFile(filepath.Join("..", "..", "values", "ops-warden-fetch.yaml"))
if err != nil {
t.Fatal(err)
}
var values struct {
CallerAuth struct {
Mode string `yaml:"mode"`
Binding string `yaml:"binding"`
ExtraBindings []string `yaml:"extraBindings"`
} `yaml:"callerAuth"`
}
if err := yaml.Unmarshal(raw, &values); err != nil {
t.Fatal(err)
}
bindings := map[string]string{}
for _, pair := range append([]string{values.CallerAuth.Binding}, values.CallerAuth.ExtraBindings...) {
system, principal, ok := strings.Cut(pair, "=")
if !ok {
t.Fatalf("malformed binding %q", pair)
}
bindings[system] = principal
}
return values.CallerAuth.Mode, bindings
}
func TestOpsWardenFetchCallerBindings(t *testing.T) {
mode, bindings := fetchBindings(t)
if mode != "enforce" {
t.Fatalf("callerAuth.mode = %q; the fetch pin must enforce", mode)
}
for system, principal := range bindings {
if principal != opsWardenPrincipal {
t.Fatalf("system %q bound to %q; only ops-warden may ask", system, principal)
}
}
engine, err := buildEngine(context.Background(), fetchPath("registry_snapshot.json"), fetchPath("policy_package.md"), "")
if err != nil {
t.Fatalf("buildEngine: %v", err)
}
serve := func(username string) *httptest.Server {
authenticator, err := callerauth.New(callerauth.ModeEnforce, fixedTokenReviewer{identity: callerauth.Identity{
Username: username,
Audiences: []string{"flex-auth"},
}}, "flex-auth", bindings, nil)
if err != nil {
t.Fatal(err)
}
return httptest.NewServer(newServeMuxWithCallerAuth(engine, authenticator))
}
post := func(server *httptest.Server, request api.CheckRequest, token bool) (int, api.DecisionEnvelope) {
body, _ := json.Marshal(request)
req, _ := http.NewRequest(http.MethodPost, server.URL+"/v1/check", bytes.NewReader(body))
req.Header.Set("content-type", "application/json")
if token {
req.Header.Set("authorization", "Bearer workload-token")
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
var envelope api.DecisionEnvelope
if resp.StatusCode == http.StatusOK {
if err := json.NewDecoder(resp.Body).Decode(&envelope); err != nil {
t.Fatal(err)
}
}
return resp.StatusCode, envelope
}
warden := serve(opsWardenPrincipal)
defer warden.Close()
allow := loadFetchRequest(t, "check_request_allow_forgejo_admin_api_token.json")
if code, envelope := post(warden, allow, true); code != http.StatusOK || envelope.Effect != api.DecisionEffectAllow {
t.Fatalf("ops-warden forgejo read = %d %s/%s; want 200 allow", code, envelope.Effect, envelope.Reason)
}
if code, _ := post(warden, allow, false); code != http.StatusUnauthorized {
t.Fatalf("anonymous = %d; want 401", code)
}
unbound := allow
unbound.Resource.System = "secrets-engine"
unbound.Context = map[string]any{"need_id": "forgejo-admin-api-token", "owner_repo": "secrets-engine"}
if code, _ := post(warden, unbound, true); code != http.StatusForbidden {
t.Fatalf("ops-warden on unbound system = %d; want 403", code)
}
other := serve("system:serviceaccount:another:caller")
defer other.Close()
if code, _ := post(other, allow, true); code != http.StatusForbidden {
t.Fatalf("wrong caller = %d; want 403", code)
}
}
// TestOpsWardenFetchRegistryMatchesPolicy keeps the two generated artifacts in
// step: every catalog lane in the package is a registry resource of its owner
// system, and nothing else is. Drift against the catalog itself is
// tools/gen_ops_warden_fetch.py --check (tests/stage1.sh).
func TestOpsWardenFetchRegistryMatchesPolicy(t *testing.T) {
raw, err := os.ReadFile(fetchPath("policy_package.md"))
if err != nil {
t.Fatal(err)
}
block := regexp.MustCompile(`(?s)catalog_lanes := \{(.*?)\}`).FindSubmatch(raw)
if block == nil {
t.Fatal("catalog_lanes block not found")
}
var fromPolicy []string
for _, m := range regexp.MustCompile(`"([^"]+)": "([^"]+)"`).FindAllSubmatch(block[1], -1) {
fromPolicy = append(fromPolicy, string(m[2])+"/secret:"+string(m[1]))
}
snapRaw, err := os.ReadFile(fetchPath("registry_snapshot.json"))
if err != nil {
t.Fatal(err)
}
var snap struct {
ResourceManifests []struct {
System string `json:"system"`
Resources []struct {
ID string `json:"id"`
} `json:"resources"`
} `json:"resource_manifests"`
}
if err := json.Unmarshal(snapRaw, &snap); err != nil {
t.Fatal(err)
}
var fromRegistry []string
for _, manifest := range snap.ResourceManifests {
for _, resource := range manifest.Resources {
fromRegistry = append(fromRegistry, manifest.System+"/"+resource.ID)
}
}
sort.Strings(fromPolicy)
sort.Strings(fromRegistry)
if len(fromPolicy) == 0 || strings.Join(fromPolicy, ",") != strings.Join(fromRegistry, ",") {
t.Fatalf("policy lanes %v\nregistry %v", fromPolicy, fromRegistry)
}
}