package identity import ( "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "errors" "math/big" "net/url" "strings" "testing" "time" ) type mockExec struct { responses []mockResp calls []string } type mockResp struct { match string out []byte err error } func (m *mockExec) Exec(ctx context.Context, peer string, cmd string) ([]byte, error) { m.calls = append(m.calls, cmd) for _, r := range m.responses { if r.match == "" || strings.Contains(cmd, r.match) { return r.out, r.err } } return nil, nil } func containsCall(t *testing.T, mx *mockExec, want string) { t.Helper() for _, c := range mx.calls { if strings.Contains(c, want) { return } } t.Errorf("no exec call contained %q; calls were:\n%s", want, strings.Join(mx.calls, "\n")) } // mintTestSVIDCert builds a self-signed cert carrying the SPIFFE URI // SAN spiffeID and returns its PEM encoding. The private key is // discarded (only the cert is needed for parsing tests). func mintTestSVIDCert(t *testing.T, spiffeID string) []byte { t.Helper() uri, err := url.Parse(spiffeID) if err != nil { t.Fatalf("parse spiffe id: %v", err) } key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatalf("generate key: %v", err) } tmpl := &x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: spiffeID}, URIs: []*url.URL{uri}, NotBefore: time.Now().Add(-time.Minute), NotAfter: time.Now().Add(24 * time.Hour), KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, } der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key) if err != nil { t.Fatalf("create cert: %v", err) } return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}) } func TestSpiffeURI(t *testing.T) { cases := []struct { ns, sa, alloc, want string }{ {"_defaults", "web", "abc123", "spiffe://orca.local/ns/_defaults/sa/web/abc123"}, {"prod", "api", "0", "spiffe://orca.local/ns/prod/sa/api/0"}, } for _, tc := range cases { got := SpiffeURI(tc.ns, tc.sa, tc.alloc) if got != tc.want { t.Errorf("SpiffeURI(%q,%q,%q) = %q, want %q", tc.ns, tc.sa, tc.alloc, got, tc.want) } } } func TestSubjectFromSpiffe(t *testing.T) { ns, sa, alloc, err := SubjectFromSpiffe("spiffe://orca.local/ns/prod/sa/api/0") if err != nil { t.Fatalf("SubjectFromSpiffe: %v", err) } if ns != "prod" || sa != "api" || alloc != "0" { t.Errorf("got ns=%q sa=%q alloc=%q", ns, sa, alloc) } } func TestSubjectFromSpiffe_Malformed(t *testing.T) { cases := []string{ "https://orca.local/ns/prod/sa/api/0", "spiffe://other/ns/prod/sa/api/0", "spiffe://orca.local/ns/prod/api/0", } for _, c := range cases { if _, _, _, err := SubjectFromSpiffe(c); err == nil { t.Errorf("SubjectFromSpiffe(%q): expected error, got nil", c) } } } func TestVerifySVID_Present(t *testing.T) { spiffeID := "spiffe://orca.local/ns/_defaults/sa/web/abc123" certPEM := mintTestSVIDCert(t, spiffeID) if err := VerifySVID(certPEM, spiffeID); err != nil { t.Errorf("VerifySVID: %v", err) } } func TestVerifySVID_Missing(t *testing.T) { certPEM := mintTestSVIDCert(t, "spiffe://orca.local/ns/_defaults/sa/web/abc123") if err := VerifySVID(certPEM, "spiffe://orca.local/ns/prod/sa/api/0"); !errors.Is(err, ErrSpiffeURIMissing) { t.Errorf("VerifySVID wrong id: err = %v, want ErrSpiffeURIMissing", err) } } func TestVerifySVID_BadPEM(t *testing.T) { if err := VerifySVID([]byte("not-a-pem"), "spiffe://orca.local/x"); !errors.Is(err, ErrStepCLI) { t.Errorf("VerifySVID bad pem: err = %v, want ErrStepCLI", err) } } func TestSpiffeIDFromCert(t *testing.T) { spiffeID := "spiffe://orca.local/ns/_defaults/sa/web/abc123" certPEM := mintTestSVIDCert(t, spiffeID) block, _ := pem.Decode(certPEM) cert, err := x509.ParseCertificate(block.Bytes) if err != nil { t.Fatalf("parse cert: %v", err) } got := SpiffeIDFromCert(cert) if got != spiffeID { t.Errorf("SpiffeIDFromCert = %q, want %q", got, spiffeID) } } func TestMintSVID_Success(t *testing.T) { spiffeID := "spiffe://orca.local/ns/_defaults/sa/web/abc123" certPEM := mintTestSVIDCert(t, spiffeID) keyPEM := []byte("-----BEGIN PRIVATE KEY-----\nFAKE\n-----END PRIVATE KEY-----\n") mx := &mockExec{responses: []mockResp{ {match: "step ca certificate", out: nil, err: nil}, {match: "cat '/tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.crt'", out: certPEM, err: nil}, {match: "cat '/tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.key'", out: keyPEM, err: nil}, {match: "rm -f", out: nil, err: nil}, }} gotCert, gotKey, err := MintSVID(context.Background(), mx, "lead:22", "_defaults", "web", "abc123") if err != nil { t.Fatalf("MintSVID: %v", err) } if string(gotCert) != string(certPEM) { t.Error("cert PEM mismatch") } if string(gotKey) != string(keyPEM) { t.Error("key PEM mismatch") } containsCall(t, mx, "step ca certificate") containsCall(t, mx, "--san 'spiffe://orca.local/ns/_defaults/sa/web/abc123'") containsCall(t, mx, "--not-after '24h'") containsCall(t, mx, "--provisioner 'orca-admin'") } func TestMintSVID_StepFails(t *testing.T) { mx := &mockExec{responses: []mockResp{ {match: "step ca certificate", out: nil, err: errors.New("step: exit 1")}, }} _, _, err := MintSVID(context.Background(), mx, "lead:22", "_defaults", "web", "abc123") if err == nil || !strings.Contains(err.Error(), "identity: mint") { t.Errorf("err = %v, want wrapped 'identity: mint'", err) } } func TestMintSVID_NilTransport(t *testing.T) { _, _, err := MintSVID(context.Background(), nil, "lead:22", "_defaults", "web", "abc123") if err == nil || !strings.Contains(err.Error(), "transport is nil") { t.Errorf("err = %v, want 'transport is nil'", err) } } func TestMintSVID_EmptyLead(t *testing.T) { mx := &mockExec{} _, _, err := MintSVID(context.Background(), mx, "", "_defaults", "web", "abc123") if err == nil || !strings.Contains(err.Error(), "lead peer not set") { t.Errorf("err = %v, want 'lead peer not set'", err) } } func TestMintSVID_EmptyCert(t *testing.T) { mx := &mockExec{responses: []mockResp{ {match: "step ca certificate", out: nil, err: nil}, {match: "cat '/tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.crt'", out: nil, err: nil}, {match: "cat '/tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.key'", out: []byte("KEY"), err: nil}, {match: "rm -f", out: nil, err: nil}, }} _, _, err := MintSVID(context.Background(), mx, "lead:22", "_defaults", "web", "abc123") if err == nil || !errors.Is(err, ErrStepCLI) { t.Errorf("err = %v, want ErrStepCLI", err) } } func TestMintSVID_URISANMissing(t *testing.T) { wrongCert := mintTestSVIDCert(t, "spiffe://orca.local/ns/other/sa/api/0") mx := &mockExec{responses: []mockResp{ {match: "step ca certificate", out: nil, err: nil}, {match: "cat '/tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.crt'", out: wrongCert, err: nil}, {match: "cat '/tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.key'", out: []byte("KEY"), err: nil}, {match: "rm -f", out: nil, err: nil}, }} _, _, err := MintSVID(context.Background(), mx, "lead:22", "_defaults", "web", "abc123") if err == nil || !errors.Is(err, ErrSpiffeURIMissing) { t.Errorf("err = %v, want ErrSpiffeURIMissing", err) } } func TestShellQuote(t *testing.T) { if got := shellQuote("a'b"); got != "'a'\\''b'" { t.Errorf("shellQuote = %q", got) } } func TestSanitize(t *testing.T) { got := sanitize("spiffe://orca.local/ns/_defaults/sa/web/abc123") want := "spiffe-orca.local_ns__defaults_sa_web_abc123" if got != want { t.Errorf("sanitize = %q, want %q", got, want) } }