Files
metacrypt/internal/engine/ca/ca_test.go
Kyle Isom bbe382dc10 Migrate module path from kyle/ to mc/ org
All import paths updated to git.wntrmute.dev/mc/. Bumps mcdsl to v1.2.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:05:59 -07:00

1273 lines
32 KiB
Go

package ca
import (
"context"
"crypto/x509"
"encoding/pem"
"errors"
"strings"
"sync"
"testing"
"time"
"git.wntrmute.dev/mc/metacrypt/internal/barrier"
"git.wntrmute.dev/mc/metacrypt/internal/engine"
)
// memBarrier is an in-memory barrier for testing.
type memBarrier struct {
data map[string][]byte
mu sync.RWMutex
}
func newMemBarrier() *memBarrier {
return &memBarrier{data: make(map[string][]byte)}
}
func (m *memBarrier) Unseal(_ []byte) error { return nil }
func (m *memBarrier) Seal() error { return nil }
func (m *memBarrier) IsSealed() bool { return false }
func (m *memBarrier) Get(_ context.Context, path string) ([]byte, error) {
m.mu.RLock()
defer m.mu.RUnlock()
v, ok := m.data[path]
if !ok {
return nil, barrier.ErrNotFound
}
cp := make([]byte, len(v))
copy(cp, v)
return cp, nil
}
func (m *memBarrier) Put(_ context.Context, path string, value []byte) error {
m.mu.Lock()
defer m.mu.Unlock()
cp := make([]byte, len(value))
copy(cp, value)
m.data[path] = cp
return nil
}
func (m *memBarrier) Delete(_ context.Context, path string) error {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.data, path)
return nil
}
func (m *memBarrier) List(_ context.Context, prefix string) ([]string, error) {
m.mu.RLock()
defer m.mu.RUnlock()
var paths []string
for k := range m.data {
if strings.HasPrefix(k, prefix) {
paths = append(paths, strings.TrimPrefix(k, prefix))
}
}
return paths, nil
}
func adminCaller() *engine.CallerInfo {
return &engine.CallerInfo{Username: "admin", Roles: []string{"admin"}, IsAdmin: true}
}
func userCaller() *engine.CallerInfo {
return &engine.CallerInfo{Username: "user", Roles: []string{"user"}, IsAdmin: false}
}
func guestCaller() *engine.CallerInfo {
return &engine.CallerInfo{Username: "guest", Roles: []string{"guest"}, IsAdmin: false}
}
func setupEngine(t *testing.T) (*CAEngine, *memBarrier) {
t.Helper()
b := newMemBarrier()
eng := NewCAEngine().(*CAEngine) //nolint:errcheck
ctx := context.Background()
config := map[string]interface{}{
"organization": "TestOrg",
"key_algorithm": "ecdsa",
"key_size": float64(256),
"root_expiry": "87600h",
}
if err := eng.Initialize(ctx, b, "engine/ca/test/", config); err != nil {
t.Fatalf("Initialize: %v", err)
}
return eng, b
}
func TestInitializeGeneratesRootCA(t *testing.T) {
eng, _ := setupEngine(t)
if eng.rootCert == nil {
t.Fatal("root cert is nil")
}
if eng.rootKey == nil {
t.Fatal("root key is nil")
}
if !eng.rootCert.IsCA {
t.Error("root cert is not a CA")
}
if eng.rootCert.Subject.CommonName != "TestOrg Root CA" {
t.Errorf("root CN: got %q, want %q", eng.rootCert.Subject.CommonName, "TestOrg Root CA")
}
if eng.rootCert.MaxPathLen != 1 {
t.Errorf("root MaxPathLen: got %d, want 1", eng.rootCert.MaxPathLen)
}
}
func TestInitializeWithImportedRoot(t *testing.T) {
// First, generate a root CA to use as the import source.
srcEng, _ := setupEngine(t)
rootPEM, err := srcEng.GetRootCertPEM()
if err != nil {
t.Fatalf("GetRootCertPEM: %v", err)
}
// Get the key PEM from barrier.
srcKeyPEM, err := srcEng.barrier.Get(context.Background(), srcEng.mountPath+"root/key.pem")
if err != nil {
t.Fatalf("get root key: %v", err)
}
// Now initialize a new engine with the imported root.
b := newMemBarrier()
eng := NewCAEngine().(*CAEngine) //nolint:errcheck
ctx := context.Background()
config := map[string]interface{}{
"organization": "ImportOrg",
"root_cert_pem": string(rootPEM),
"root_key_pem": string(srcKeyPEM),
}
if err := eng.Initialize(ctx, b, "engine/ca/imported/", config); err != nil {
t.Fatalf("Initialize with import: %v", err)
}
if eng.rootCert == nil {
t.Fatal("root cert is nil after import")
}
if !eng.rootCert.IsCA {
t.Error("imported root is not a CA")
}
// The CN should be from the original cert, not the new org.
if eng.rootCert.Subject.CommonName != "TestOrg Root CA" {
t.Errorf("imported root CN: got %q, want %q", eng.rootCert.Subject.CommonName, "TestOrg Root CA")
}
// Verify we can create issuers and issue certs from the imported root.
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer from imported root: %v", err)
}
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: adminCaller(),
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "imported.example.com",
"profile": "server",
},
})
if err != nil {
t.Fatalf("issue from imported root: %v", err)
}
}
func TestUnsealSealLifecycle(t *testing.T) {
eng, b := setupEngine(t)
mountPath := "engine/ca/test/"
// Seal and verify state is cleared.
if err := eng.Seal(); err != nil {
t.Fatalf("Seal: %v", err)
}
if eng.rootCert != nil {
t.Error("rootCert should be nil after seal")
}
if eng.rootKey != nil {
t.Error("rootKey should be nil after seal")
}
// Unseal and verify state is restored.
ctx := context.Background()
if err := eng.Unseal(ctx, b, mountPath); err != nil {
t.Fatalf("Unseal: %v", err)
}
if eng.rootCert == nil {
t.Error("rootCert should be non-nil after unseal")
}
if eng.rootKey == nil {
t.Error("rootKey should be non-nil after unseal")
}
if !eng.rootCert.IsCA {
t.Error("root cert should be CA after unseal")
}
}
func TestCreateIssuer(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
req := &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{
"name": "infra",
},
}
resp, err := eng.HandleRequest(ctx, req)
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
if resp.Data["name"] != "infra" {
t.Errorf("issuer name: got %v, want %q", resp.Data["name"], "infra")
}
// Verify the issuer cert is an intermediate CA signed by root.
certPEM := resp.Data["cert_pem"].(string) //nolint:errcheck
block, _ := pem.Decode([]byte(certPEM))
if block == nil {
t.Fatal("failed to decode issuer cert PEM")
}
issuerCert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatalf("parse issuer cert: %v", err)
}
if !issuerCert.IsCA {
t.Error("issuer cert should be a CA")
}
// MaxPathLen 0 with MaxPathLenZero=false parses as -1 in Go's x509.
// Either 0 or -1 is acceptable for a path-length-constrained intermediate.
if issuerCert.MaxPathLen > 0 {
t.Errorf("issuer MaxPathLen: got %d, want 0 or -1", issuerCert.MaxPathLen)
}
if issuerCert.Subject.CommonName != "infra" {
t.Errorf("issuer CN: got %q, want %q", issuerCert.Subject.CommonName, "infra")
}
// Verify issuer is in memory.
if _, ok := eng.issuers["infra"]; !ok {
t.Error("issuer not found in memory")
}
}
func TestCreateIssuerRejectsNonAdmin(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
req := &engine.Request{
Operation: "create-issuer",
CallerInfo: userCaller(),
Data: map[string]interface{}{
"name": "infra",
},
}
_, err := eng.HandleRequest(ctx, req)
if err == nil {
t.Fatal("expected error for non-admin create-issuer")
}
if !errors.Is(err, ErrForbidden) {
t.Errorf("expected ErrForbidden, got: %v", err)
}
}
func TestCreateIssuerRejectsNilCallerInfo(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
req := &engine.Request{
Operation: "create-issuer",
Data: map[string]interface{}{
"name": "infra",
},
}
_, err := eng.HandleRequest(ctx, req)
if !errors.Is(err, ErrUnauthorized) {
t.Errorf("expected ErrUnauthorized, got: %v", err)
}
}
func TestIssueCertificate(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
// Create an issuer first.
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
// Issue a certificate.
resp, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
Path: "infra",
CallerInfo: adminCaller(),
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "web.example.com",
"profile": "server",
"dns_names": []interface{}{"web.example.com", "www.example.com"},
},
})
if err != nil {
t.Fatalf("issue: %v", err)
}
if resp.Data["cn"] != "web.example.com" {
t.Errorf("cn: got %v", resp.Data["cn"])
}
if resp.Data["serial"] == nil || resp.Data["serial"] == "" {
t.Error("serial should not be empty")
}
if resp.Data["cert_pem"] == nil {
t.Error("cert_pem should not be nil")
}
if resp.Data["key_pem"] == nil {
t.Error("key_pem should not be nil")
}
if resp.Data["chain_pem"] == nil {
t.Error("chain_pem should not be nil")
}
// Verify the leaf cert.
certPEM := resp.Data["cert_pem"].(string) //nolint:errcheck
block, _ := pem.Decode([]byte(certPEM))
leafCert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatalf("parse leaf cert: %v", err)
}
if leafCert.IsCA {
t.Error("leaf cert should not be a CA")
}
if leafCert.Subject.CommonName != "web.example.com" {
t.Errorf("leaf CN: got %q", leafCert.Subject.CommonName)
}
if len(leafCert.DNSNames) != 2 {
t.Errorf("leaf DNSNames: got %v", leafCert.DNSNames)
}
}
func TestIssueCertificateWithOverrides(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
// Issue with custom TTL and key usages.
resp, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: adminCaller(),
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "peer.example.com",
"profile": "peer",
"ttl": "720h",
"key_usages": []interface{}{"digital signature"},
"ext_key_usages": []interface{}{"client auth"},
},
})
if err != nil {
t.Fatalf("issue with overrides: %v", err)
}
certPEM := resp.Data["cert_pem"].(string) //nolint:errcheck
block, _ := pem.Decode([]byte(certPEM))
leafCert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatalf("parse leaf: %v", err)
}
// Verify client auth EKU.
hasClientAuth := false
for _, eku := range leafCert.ExtKeyUsage {
if eku == x509.ExtKeyUsageClientAuth {
hasClientAuth = true
}
}
if !hasClientAuth {
t.Error("expected client auth EKU")
}
}
func TestIssueRejectsNilCallerInfo(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "test.example.com",
},
})
if !errors.Is(err, ErrUnauthorized) {
t.Errorf("expected ErrUnauthorized, got: %v", err)
}
}
func TestIssueAllowsUser(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
// Users can issue certs for identifiers not held by others.
resp, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: userCaller(),
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "user-cert.example.com",
"profile": "server",
},
})
if err != nil {
t.Fatalf("expected user to issue cert, got: %v", err)
}
if resp.Data["cn"] != "user-cert.example.com" {
t.Errorf("cn: got %v", resp.Data["cn"])
}
}
func TestIssueRejectsGuest(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: guestCaller(),
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "test.example.com",
"profile": "server",
},
})
if !errors.Is(err, ErrForbidden) {
t.Errorf("expected ErrForbidden, got: %v", err)
}
}
func TestIssueRejectsIdentifierInUse(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
// User A issues a cert.
userA := &engine.CallerInfo{Username: "alice", Roles: []string{"user"}, IsAdmin: false}
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: userA,
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "shared.example.com",
"profile": "server",
"dns_names": []interface{}{"shared.example.com"},
},
})
if err != nil {
t.Fatalf("issue by alice: %v", err)
}
// User B tries to issue for the same CN — should fail.
userB := &engine.CallerInfo{Username: "bob", Roles: []string{"user"}, IsAdmin: false}
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: userB,
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "shared.example.com",
"profile": "server",
},
})
if !errors.Is(err, ErrIdentifierInUse) {
t.Errorf("expected ErrIdentifierInUse, got: %v", err)
}
// User A can issue again for the same CN (re-issuance by same user).
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: userA,
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "shared.example.com",
"profile": "server",
},
})
if err != nil {
t.Fatalf("re-issue by alice should succeed: %v", err)
}
// Admin can always issue regardless of ownership.
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: adminCaller(),
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "shared.example.com",
"profile": "server",
},
})
if err != nil {
t.Fatalf("admin issue should bypass ownership: %v", err)
}
}
func TestIssueRevokedCertFreesIdentifier(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
// Alice issues a cert.
alice := &engine.CallerInfo{Username: "alice", Roles: []string{"user"}, IsAdmin: false}
resp, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: alice,
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "reclaim.example.com",
"profile": "server",
},
})
if err != nil {
t.Fatalf("issue: %v", err)
}
serial := resp.Data["serial"].(string) //nolint:errcheck
// Admin revokes it.
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "revoke-cert",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"serial": serial},
})
if err != nil {
t.Fatalf("revoke: %v", err)
}
// Bob can now issue for the same CN.
bob := &engine.CallerInfo{Username: "bob", Roles: []string{"user"}, IsAdmin: false}
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: bob,
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "reclaim.example.com",
"profile": "server",
},
})
if err != nil {
t.Fatalf("bob should be able to issue after revocation: %v", err)
}
}
func TestRenewOwnership(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
// Alice issues a cert.
alice := &engine.CallerInfo{Username: "alice", Roles: []string{"user"}, IsAdmin: false}
issueResp, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: alice,
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "test.example.com",
"profile": "server",
},
})
if err != nil {
t.Fatalf("issue: %v", err)
}
serial := issueResp.Data["serial"].(string) //nolint:errcheck
// Alice can renew her own cert.
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "renew",
CallerInfo: alice,
Data: map[string]interface{}{"serial": serial},
})
if err != nil {
t.Fatalf("alice should renew her own cert: %v", err)
}
// Bob cannot renew Alice's cert.
bob := &engine.CallerInfo{Username: "bob", Roles: []string{"user"}, IsAdmin: false}
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "renew",
CallerInfo: bob,
Data: map[string]interface{}{"serial": serial},
})
if !errors.Is(err, ErrForbidden) {
t.Errorf("expected ErrForbidden for bob renewing alice's cert, got: %v", err)
}
// Guest cannot renew.
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "renew",
CallerInfo: guestCaller(),
Data: map[string]interface{}{"serial": serial},
})
if !errors.Is(err, ErrForbidden) {
t.Errorf("expected ErrForbidden for guest, got: %v", err)
}
// Admin can always renew.
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "renew",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"serial": serial},
})
if err != nil {
t.Fatalf("admin should renew any cert: %v", err)
}
}
func TestSignCSRRejectsGuest(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "sign-csr",
CallerInfo: guestCaller(),
Data: map[string]interface{}{
"issuer": "infra",
"csr_pem": "dummy",
},
})
if !errors.Is(err, ErrForbidden) {
t.Errorf("expected ErrForbidden for guest, got: %v", err)
}
}
func TestListIssuersRejectsGuest(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "list-issuers",
CallerInfo: guestCaller(),
})
if !errors.Is(err, ErrForbidden) {
t.Errorf("expected ErrForbidden for guest, got: %v", err)
}
// user and admin should succeed
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "list-issuers",
CallerInfo: userCaller(),
})
if err != nil {
t.Errorf("expected user to list issuers, got: %v", err)
}
}
func TestGetCertRejectsGuest(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "get-cert",
CallerInfo: guestCaller(),
Data: map[string]interface{}{"serial": "abc123"},
})
if !errors.Is(err, ErrForbidden) {
t.Errorf("expected ErrForbidden for guest, got: %v", err)
}
}
func TestListCertsRejectsGuest(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "list-certs",
CallerInfo: guestCaller(),
})
if !errors.Is(err, ErrForbidden) {
t.Errorf("expected ErrForbidden for guest, got: %v", err)
}
// user should succeed
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "list-certs",
CallerInfo: userCaller(),
})
if err != nil {
t.Errorf("expected user to list certs, got: %v", err)
}
}
func TestPrivateKeyNotStoredInBarrier(t *testing.T) {
eng, b := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
resp, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: adminCaller(),
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "test.example.com",
"profile": "server",
},
})
if err != nil {
t.Fatalf("issue: %v", err)
}
serial := resp.Data["serial"].(string) //nolint:errcheck
// Check that the cert record does not contain a private key.
recordData, err := b.Get(ctx, "engine/ca/test/certs/"+serial+".json")
if err != nil {
t.Fatalf("get cert record: %v", err)
}
if strings.Contains(string(recordData), "PRIVATE KEY") {
t.Error("cert record should not contain private key")
}
}
func TestRenewCertificate(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
// Issue original cert.
issueResp, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: adminCaller(),
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "renew.example.com",
"profile": "server",
"dns_names": []interface{}{"renew.example.com"},
},
})
if err != nil {
t.Fatalf("issue: %v", err)
}
origSerial := issueResp.Data["serial"].(string) //nolint:errcheck
// Renew.
renewResp, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "renew",
CallerInfo: adminCaller(),
Data: map[string]interface{}{
"serial": origSerial,
},
})
if err != nil {
t.Fatalf("renew: %v", err)
}
newSerial := renewResp.Data["serial"].(string) //nolint:errcheck
if newSerial == origSerial {
t.Error("renewed cert should have different serial")
}
if renewResp.Data["cn"] != "renew.example.com" {
t.Errorf("renewed CN: got %v", renewResp.Data["cn"])
}
if renewResp.Data["cert_pem"] == nil {
t.Error("renewed cert_pem should not be nil")
}
if renewResp.Data["key_pem"] == nil {
t.Error("renewed key_pem should not be nil")
}
}
func TestGetAndListCerts(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
// Issue two certs.
for _, cn := range []string{"a.example.com", "b.example.com"} {
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: adminCaller(),
Data: map[string]interface{}{
"issuer": "infra",
"common_name": cn,
"profile": "server",
},
})
if err != nil {
t.Fatalf("issue %s: %v", cn, err)
}
}
// List certs.
listResp, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "list-certs",
CallerInfo: userCaller(),
})
if err != nil {
t.Fatalf("list-certs: %v", err)
}
certs, ok := listResp.Data["certs"].([]interface{})
if !ok {
t.Fatalf("certs type: %T", listResp.Data["certs"])
}
if len(certs) != 2 {
t.Errorf("expected 2 certs, got %d", len(certs))
}
// Get a specific cert.
serial := certs[0].(map[string]interface{})["serial"].(string) //nolint:errcheck
getResp, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "get-cert",
CallerInfo: userCaller(),
Data: map[string]interface{}{
"serial": serial,
},
})
if err != nil {
t.Fatalf("get-cert: %v", err)
}
if getResp.Data["serial"] != serial {
t.Errorf("get-cert serial: got %v, want %v", getResp.Data["serial"], serial)
}
}
func TestUnsealRestoresIssuers(t *testing.T) {
eng, b := setupEngine(t)
ctx := context.Background()
mountPath := "engine/ca/test/"
// Create issuer.
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
// Seal.
_ = eng.Seal()
// Unseal.
if err := eng.Unseal(ctx, b, mountPath); err != nil {
t.Fatalf("Unseal: %v", err)
}
// Verify issuer was restored.
if _, ok := eng.issuers["infra"]; !ok {
t.Error("issuer 'infra' not restored after unseal")
}
// Verify we can issue from the restored issuer.
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: adminCaller(),
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "after-unseal.example.com",
"profile": "server",
},
})
if err != nil {
t.Fatalf("issue after unseal: %v", err)
}
}
func TestDeleteIssuer(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "delete-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("delete-issuer: %v", err)
}
if _, ok := eng.issuers["infra"]; ok {
t.Error("issuer should be deleted")
}
}
func TestImportRootRejectsValidRoot(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
// Generate a new root to try importing.
other, _ := setupEngine(t)
otherPEM, _ := other.GetRootCertPEM()
otherKeyPEM, _ := other.barrier.Get(ctx, other.mountPath+"root/key.pem")
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "import-root",
CallerInfo: adminCaller(),
Data: map[string]interface{}{
"cert_pem": string(otherPEM),
"key_pem": string(otherKeyPEM),
},
})
if err == nil {
t.Fatal("expected error when importing over a valid root")
}
if !strings.Contains(err.Error(), "still valid") {
t.Errorf("expected 'still valid' error, got: %v", err)
}
}
func TestImportRootReplacesExpiredRoot(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
// Simulate an expired root by setting NotAfter to the past.
eng.mu.Lock()
eng.rootCert.NotAfter = time.Now().Add(-1 * time.Hour)
eng.mu.Unlock()
// Generate a new root to import.
other, _ := setupEngine(t)
newPEM, _ := other.GetRootCertPEM()
newKeyPEM, _ := other.barrier.Get(ctx, other.mountPath+"root/key.pem")
resp, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "import-root",
CallerInfo: adminCaller(),
Data: map[string]interface{}{
"cert_pem": string(newPEM),
"key_pem": string(newKeyPEM),
},
})
if err != nil {
t.Fatalf("import-root: %v", err)
}
if resp.Data["cn"] == nil {
t.Error("expected cn in response")
}
// Verify the root was replaced.
rootPEM, err := eng.GetRootCertPEM()
if err != nil {
t.Fatalf("GetRootCertPEM: %v", err)
}
if string(rootPEM) != string(newPEM) {
t.Error("root cert was not replaced")
}
// Verify we can still create issuers with the new root.
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "new-issuer"},
})
if err != nil {
t.Fatalf("create-issuer after import: %v", err)
}
}
func TestImportRootRequiresAdmin(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "import-root",
CallerInfo: userCaller(),
Data: map[string]interface{}{
"cert_pem": "fake",
"key_pem": "fake",
},
})
if !errors.Is(err, ErrForbidden) {
t.Errorf("expected ErrForbidden, got: %v", err)
}
}
func TestPublicMethods(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
// Test GetRootCertPEM.
rootPEM, err := eng.GetRootCertPEM()
if err != nil {
t.Fatalf("GetRootCertPEM: %v", err)
}
block, _ := pem.Decode(rootPEM)
if block == nil {
t.Fatal("failed to decode root PEM")
}
// Create issuer for chain/issuer tests.
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
// Test GetIssuerCertPEM.
issuerPEM, err := eng.GetIssuerCertPEM("infra")
if err != nil {
t.Fatalf("GetIssuerCertPEM: %v", err)
}
block, _ = pem.Decode(issuerPEM)
if block == nil {
t.Fatal("failed to decode issuer PEM")
}
// Test GetChainPEM.
chainPEM, err := eng.GetChainPEM("infra")
if err != nil {
t.Fatalf("GetChainPEM: %v", err)
}
// Chain should contain two certificates.
certCount := strings.Count(string(chainPEM), "BEGIN CERTIFICATE")
if certCount != 2 {
t.Errorf("chain should contain 2 certs, got %d", certCount)
}
// Test nonexistent issuer.
_, err = eng.GetIssuerCertPEM("nonexistent")
if !errors.Is(err, ErrIssuerNotFound) {
t.Errorf("expected ErrIssuerNotFound, got: %v", err)
}
}
func TestIssuePolicyOverridesOwnership(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
// Alice issues a cert.
alice := &engine.CallerInfo{Username: "alice", Roles: []string{"user"}, IsAdmin: false}
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: alice,
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "shared.example.com",
"profile": "server",
},
})
if err != nil {
t.Fatalf("issue by alice: %v", err)
}
// Bob normally blocked, but policy allows him.
bob := &engine.CallerInfo{Username: "bob", Roles: []string{"user"}, IsAdmin: false}
allowPolicy := func(resource, action string) (string, bool) {
return "allow", true
}
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: bob,
CheckPolicy: allowPolicy,
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "shared.example.com",
"profile": "server",
},
})
if err != nil {
t.Fatalf("bob with allow policy should succeed: %v", err)
}
// Policy deny overrides even for free identifiers.
denyPolicy := func(resource, action string) (string, bool) {
return "deny", true
}
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: bob,
CheckPolicy: denyPolicy,
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "unique-for-bob.example.com",
"profile": "server",
},
})
if !errors.Is(err, ErrForbidden) {
t.Errorf("deny policy should reject: got %v", err)
}
}
func TestRenewPolicyOverridesOwnership(t *testing.T) {
eng, _ := setupEngine(t)
ctx := context.Background()
_, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "create-issuer",
CallerInfo: adminCaller(),
Data: map[string]interface{}{"name": "infra"},
})
if err != nil {
t.Fatalf("create-issuer: %v", err)
}
// Alice issues a cert.
alice := &engine.CallerInfo{Username: "alice", Roles: []string{"user"}, IsAdmin: false}
resp, err := eng.HandleRequest(ctx, &engine.Request{
Operation: "issue",
CallerInfo: alice,
Data: map[string]interface{}{
"issuer": "infra",
"common_name": "policy-renew.example.com",
"profile": "server",
},
})
if err != nil {
t.Fatalf("issue: %v", err)
}
serial := resp.Data["serial"].(string) //nolint:errcheck
// Bob cannot renew without policy.
bob := &engine.CallerInfo{Username: "bob", Roles: []string{"user"}, IsAdmin: false}
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "renew",
CallerInfo: bob,
Data: map[string]interface{}{"serial": serial},
})
if !errors.Is(err, ErrForbidden) {
t.Errorf("expected ErrForbidden, got: %v", err)
}
// Bob with allow policy can renew.
allowPolicy := func(resource, action string) (string, bool) {
return "allow", true
}
_, err = eng.HandleRequest(ctx, &engine.Request{
Operation: "renew",
CallerInfo: bob,
CheckPolicy: allowPolicy,
Data: map[string]interface{}{"serial": serial},
})
if err != nil {
t.Fatalf("bob with policy should renew: %v", err)
}
}