package cli import ( "bytes" "encoding/json" "os" "path/filepath" "strings" "testing" "git.cloudinit.dev/coreci/orca/internal/acl" "git.cloudinit.dev/coreci/orca/internal/paths" ) func resetACLFlags() { aclGrantNamespace = "" aclGrantPermissions = "read" aclRevokeNamespace = "" aclCheckNamespace = "" aclCheckPermission = "read" } func TestACLCommandRegistered(t *testing.T) { registered := make(map[string]bool) for _, cmd := range rootCmd.Commands() { registered[cmd.Name()] = true } if !registered["acl"] { t.Fatal("acl command not registered on root") } } func TestACLSubcommands(t *testing.T) { expected := []string{"grant", "revoke", "list", "check"} registered := make(map[string]bool) for _, cmd := range aclCmd.Commands() { registered[cmd.Name()] = true } for _, name := range expected { if !registered[name] { t.Errorf("expected acl subcommand %q not registered", name) } } } func TestParseIdentity_Spiffe(t *testing.T) { id, err := parseIdentity("spiffe://orca.local/ns/myapp/sa/svc1/alloc-1") if err != nil { t.Fatalf("parseIdentity: %v", err) } if id.Kind != "spiffe" { t.Errorf("kind = %q, want spiffe", id.Kind) } if id.Namespace != "myapp" { t.Errorf("namespace = %q, want myapp", id.Namespace) } } func TestParseIdentity_Oidc(t *testing.T) { id, err := parseIdentity("operator-1") if err != nil { t.Fatalf("parseIdentity: %v", err) } if id.Kind != "oidc" { t.Errorf("kind = %q, want oidc", id.Kind) } if id.ID != "operator-1" { t.Errorf("id = %q, want operator-1", id.ID) } if id.Namespace != "" { t.Errorf("namespace = %q, want empty (set via --namespace)", id.Namespace) } } func TestParseIdentity_Empty(t *testing.T) { if _, err := parseIdentity(""); err == nil { t.Errorf("parseIdentity(\"\"): expected error, got nil") } } func TestParsePermissions(t *testing.T) { cases := []struct { in string want uint8 }{ {"", 1}, {"read", 1}, {"write", 2}, {"admin", 4}, {"read,write", 3}, {"read,write,admin", 7}, {"READ,Write", 3}, } for _, c := range cases { got, err := parsePermissions(c.in) if err != nil { t.Errorf("parsePermissions(%q): unexpected err %v", c.in, err) continue } if uint8(got) != c.want { t.Errorf("parsePermissions(%q) = %d, want %d", c.in, uint8(got), c.want) } } } func TestParsePermissions_Unknown(t *testing.T) { if _, err := parsePermissions("read,delete"); err == nil { t.Errorf("parsePermissions(read,delete): expected error, got nil") } } func TestACLGrantAndCheck(t *testing.T) { t.Setenv("ORCA_HOME", t.TempDir()) resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--namespace", "prod", "--permissions", "read,write"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("acl grant: %v", err) } if _, err := os.Stat(paths.ACLPath()); err != nil { t.Fatalf("acl.json not written: %v", err) } resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "check", "operator-1", "--namespace", "prod", "--permission", "read"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("acl check read: %v", err) } resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "check", "operator-1", "--namespace", "prod", "--permission", "admin"}) err := rootCmd.Execute() if err == nil { t.Fatalf("acl check admin: expected denied error, got nil") } } func TestACLCheckDeniedExits1(t *testing.T) { t.Setenv("ORCA_HOME", t.TempDir()) resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "check", "ghost", "--namespace", "prod", "--permission", "read"}) err := rootCmd.Execute() if err == nil { t.Fatal("expected denied error for un-granted identity, got nil") } } func TestACLRevoke(t *testing.T) { t.Setenv("ORCA_HOME", t.TempDir()) resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--namespace", "prod", "--permissions", "read"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("grant: %v", err) } resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "revoke", "operator-1", "--namespace", "prod"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("revoke: %v", err) } resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "check", "operator-1", "--namespace", "prod", "--permission", "read"}) if err := rootCmd.Execute(); err == nil { t.Fatalf("check after revoke: expected denied, got nil") } } func TestACLListEmpty(t *testing.T) { t.Setenv("ORCA_HOME", t.TempDir()) resetRootFlags(t) resetACLFlags() var buf bytes.Buffer rootCmd.SetOut(&buf) rootCmd.SetArgs([]string{"acl", "list"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("acl list empty: %v", err) } if !strings.Contains(buf.String(), "No ACL entries") { t.Errorf("acl list empty: %s", buf.String()) } } func TestACLListWithEntries(t *testing.T) { t.Setenv("ORCA_HOME", t.TempDir()) resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--namespace", "prod", "--permissions", "read,write"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("grant: %v", err) } resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "grant", "spiffe://orca.local/ns/myapp/sa/svc1/alloc-1", "--namespace", "myapp", "--permissions", "admin"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("grant spiffe: %v", err) } resetRootFlags(t) resetACLFlags() var buf bytes.Buffer rootCmd.SetOut(&buf) rootCmd.SetArgs([]string{"acl", "list"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("acl list: %v", err) } out := buf.String() if !strings.Contains(out, "operator-1") || !strings.Contains(out, "prod") { t.Errorf("list missing operator-1/prod: %s", out) } if !strings.Contains(out, "spiffe://orca.local/ns/myapp") || !strings.Contains(out, "myapp") { t.Errorf("list missing spiffe entry: %s", out) } if !strings.Contains(out, "read,write") || !strings.Contains(out, "admin") { t.Errorf("list missing permissions: %s", out) } } func TestACLListJSON(t *testing.T) { t.Setenv("ORCA_HOME", t.TempDir()) resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--namespace", "prod", "--permissions", "read"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("grant: %v", err) } resetRootFlags(t) resetACLFlags() var buf bytes.Buffer rootCmd.SetOut(&buf) rootCmd.SetArgs([]string{"acl", "list", "--json"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("acl list --json: %v", err) } var entries []map[string]any if err := json.Unmarshal(bytes.TrimSpace(buf.Bytes()), &entries); err != nil { t.Fatalf("unmarshal: %v\n%s", err, buf.String()) } if len(entries) != 1 { t.Fatalf("entries len = %d, want 1", len(entries)) } id, _ := entries[0]["identity"].(map[string]any) if id == nil || id["id"] != "operator-1" { t.Errorf("identity = %v, want operator-1", entries[0]["identity"]) } } func TestACLGrantSpiffeNamespaceMismatch(t *testing.T) { t.Setenv("ORCA_HOME", t.TempDir()) resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "grant", "spiffe://orca.local/ns/myapp/sa/svc1/alloc-1", "--namespace", "other"}) err := rootCmd.Execute() if err == nil { t.Fatal("expected mismatch error, got nil") } if !strings.Contains(err.Error(), "does not match") { t.Errorf("error = %q, want contains 'does not match'", err.Error()) } } func TestACLGrantSpiffeDefaultsNamespaceFromPath(t *testing.T) { t.Setenv("ORCA_HOME", t.TempDir()) resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "grant", "spiffe://orca.local/ns/myapp/sa/svc1/alloc-1", "--permissions", "read"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("grant spiffe (no --namespace): %v", err) } resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "check", "spiffe://orca.local/ns/myapp/sa/svc1/alloc-1", "--namespace", "myapp", "--permission", "read"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("check spiffe: %v", err) } } func TestACLGrantTokenRequiresNamespace(t *testing.T) { t.Setenv("ORCA_HOME", t.TempDir()) resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--permissions", "read"}) err := rootCmd.Execute() if err == nil { t.Fatal("expected error for token grant without --namespace, got nil") } } func TestACLStatePersists(t *testing.T) { t.Setenv("ORCA_HOME", t.TempDir()) resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--namespace", "prod", "--permissions", "read"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("grant: %v", err) } data, err := os.ReadFile(paths.ACLPath()) if err != nil { t.Fatalf("read acl.json: %v", err) } if !strings.Contains(string(data), "operator-1") || !strings.Contains(string(data), "prod") { t.Errorf("acl.json missing entry: %s", string(data)) } } func TestACLAtomicWriteNoPartialFile(t *testing.T) { t.Setenv("ORCA_HOME", t.TempDir()) resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--namespace", "prod", "--permissions", "read"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("grant: %v", err) } entries, err := os.ReadDir(filepath.Dir(paths.ACLPath())) if err != nil { t.Fatalf("readdir cluster: %v", err) } for _, e := range entries { if strings.HasPrefix(e.Name(), ".acl-tmp-") { t.Errorf("leftover temp file: %s", e.Name()) } } } func TestACLCheckJSONDenied(t *testing.T) { t.Setenv("ORCA_HOME", t.TempDir()) resetRootFlags(t) resetACLFlags() var buf bytes.Buffer rootCmd.SetOut(&buf) rootCmd.SetArgs([]string{"acl", "check", "ghost", "--namespace", "prod", "--permission", "read", "--json"}) _ = rootCmd.Execute() var result map[string]any if err := json.Unmarshal(bytes.TrimSpace(buf.Bytes()), &result); err != nil { t.Fatalf("unmarshal: %v\n%s", err, buf.String()) } if result["allowed"] != false { t.Errorf("allowed = %v, want false", result["allowed"]) } } func TestACLAdminImpliesReadCheck(t *testing.T) { t.Setenv("ORCA_HOME", t.TempDir()) resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--namespace", "prod", "--permissions", "admin"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("grant admin: %v", err) } resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "check", "operator-1", "--namespace", "prod", "--permission", "read"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("check read (admin grant): %v", err) } resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "check", "operator-1", "--namespace", "prod", "--permission", "write"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("check write (admin grant): %v", err) } } // TestACLGrantWritesMode0600 (P04, T6) verifies that saveACL writes // acl.json with mode 0600 (operator-only). Previously 0644 leaked // SPIFFE IDs + OIDC subs to other local users. func TestACLGrantWritesMode0600(t *testing.T) { t.Setenv("ORCA_HOME", t.TempDir()) resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--namespace", "prod", "--permissions", "read"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("grant: %v", err) } info, err := os.Stat(paths.ACLPath()) if err != nil { t.Fatalf("stat acl.json: %v", err) } if info.Mode().Perm()&0o077 != 0 { t.Errorf("acl.json mode = %o, want 0600 (no group/other bits)", info.Mode().Perm()) } } // TestACLGrantCreatesLockFile (P04, T7) verifies that the flock // mechanism creates an acl.json.lock file alongside acl.json. The // lock prevents concurrent grant/revoke races. func TestACLGrantCreatesLockFile(t *testing.T) { t.Setenv("ORCA_HOME", t.TempDir()) resetRootFlags(t) resetACLFlags() rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--namespace", "prod", "--permissions", "read"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("grant: %v", err) } if _, err := os.Stat(paths.ACLPath() + ".lock"); err != nil { t.Errorf("acl.json.lock not created: %v", err) } } // TestACLBootstrapGrantsAdminGroup (P04, T8, C-40) verifies that // bootstrapACL grants cluster-admin to the orca-admins OIDC group on // the default namespace. This prevents operator lockout after // `orca init`. func TestACLBootstrapGrantsAdminGroup(t *testing.T) { t.Setenv("ORCA_HOME", t.TempDir()) if err := os.MkdirAll(paths.ClusterDir(), 0o755); err != nil { t.Fatalf("mkdir: %v", err) } // bootstrapACL reads the cert at certPath; a missing cert is // non-fatal (the SVID grant is skipped, the group grant still // applies). Pass a nonexistent path to exercise that path. if err := bootstrapACL(filepath.Join(t.TempDir(), "missing.crt")); err != nil { t.Fatalf("bootstrapACL: %v", err) } a, err := loadACL() if err != nil { t.Fatalf("loadACL: %v", err) } entries := a.List() found := false for _, e := range entries { if e.Identity.Kind == "oidc" && e.Identity.ID == "group:orca-admins" && e.Namespace == paths.DefaultNamespace() { if e.Permissions != acl.AllPermissions { t.Errorf("orca-admins permissions = %d, want %d (AllPermissions)", e.Permissions, acl.AllPermissions) } found = true } } if !found { t.Errorf("bootstrapACL did not grant cluster-admin to group:orca-admins; entries: %+v", entries) } }