Files
orca/internal/cli/acl_test.go
T
Jon Chery b0158c96e9 fix(P01): bump go toolchain to 1.25.12 + fix pre-existing test bugs (REQ-149)
Toolchain:
- go.mod: go 1.25.0 -> 1.25.12 (closes 24 stdlib vulns: archive/tar,
  crypto/tls, crypto/x509, net/http, net/url, encoding/pem, os)
- go mod tidy clean; make build + test + lint pass

Pre-existing test bugs fixed (surfaced by toolchain bump):
- acl_test.go: KindToken always denies (R-021); tests updated to KindOidc
- acl.go: parseIdentity defaults to KindOidc (was KindToken, making
  acl grant/check CLI path non-functional for non-spiffe identities)
- init_test.go: migration version updated to 0008 (was 0007, stale since v0.12)
- doctor.go: CertCA now checks CA cert exists (was only checking file modes,
  passing when no CA present)
- scenarios_test.go: ACL integration test uses KindOidc + acl.json 0600

---ci---
project: orca
phase: 1
milestone: v0.13
status: complete
requirements:
  covered: [149]
---/ci---
2026-08-07 19:07:17 +00:00

387 lines
10 KiB
Go

package cli
import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"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)
}
}