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/flex-auth/internal/callerauth" "github.com/netkingdom/flex-auth/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) } }