Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 3775@bnt-lap001 Assistant-Session: cfacb6a5-ad5b-4f79-ac67-053e3b0d34a3
219 lines
7.2 KiB
Go
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/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)
|
|
}
|
|
}
|