guildhouse-spire-plugins/pkg/sshcert/sshcert_test.go
Tyler King a58d548518 feat: network-policy extension, governance lifecycle, audit remediation
- Network-policy SPIRE plugin extension
- Governance event notification with merkle anchoring
- Shellstream specs for consent channels + HFL embedded ABI
- All 17 audit findings from AUDIT.md remediated
- SSH credential composer + substrate key manager updates
- Test coverage for config + sshcert packages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 15:54:46 -04:00

244 lines
7 KiB
Go

package sshcert
import (
"strings"
"testing"
)
func TestNewBuilderRequiresTrustDomain(t *testing.T) {
_, err := NewBuilder(Config{})
if err == nil {
t.Fatal("expected error for empty trust domain")
}
}
func TestNewBuilderAcceptsValidConfig(t *testing.T) {
b, err := NewBuilder(Config{TrustDomain: "example.org"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if b == nil {
t.Fatal("builder should not be nil")
}
}
func TestNewBuilderAppliesSpecDefaults(t *testing.T) {
b, err := NewBuilder(Config{TrustDomain: "example.org"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if b.config.MaxValidSeconds != 3600 {
t.Errorf("MaxValidSeconds: got %d, want 3600", b.config.MaxValidSeconds)
}
if b.config.MinValidSeconds != 30 {
t.Errorf("MinValidSeconds: got %d, want 30", b.config.MinValidSeconds)
}
if b.config.DefaultValidSeconds != 300 {
t.Errorf("DefaultValidSeconds: got %d, want 300", b.config.DefaultValidSeconds)
}
}
func TestNewBuilderRejectsInvalidTTLRange(t *testing.T) {
_, err := NewBuilder(Config{
TrustDomain: "example.org",
MinValidSeconds: 500,
MaxValidSeconds: 100,
})
if err == nil {
t.Fatal("expected error when min > max")
}
if !strings.Contains(err.Error(), "min TTL") {
t.Errorf("expected min/max error, got: %v", err)
}
}
func TestNewBuilderRejectsDefaultOutsideRange(t *testing.T) {
_, err := NewBuilder(Config{
TrustDomain: "example.org",
MinValidSeconds: 60,
MaxValidSeconds: 3600,
DefaultValidSeconds: 10, // below min
})
if err == nil {
t.Fatal("expected error when default < min")
}
if !strings.Contains(err.Error(), "default TTL") {
t.Errorf("expected default-outside-range error, got: %v", err)
}
}
func TestBuildRequiresSpiffeID(t *testing.T) {
b, _ := NewBuilder(Config{TrustDomain: "example.org"})
_, err := b.Build(&CertRequest{})
if err == nil {
t.Fatal("expected error for empty spiffe ID")
}
}
func TestBuildRejectsTTLAboveMax(t *testing.T) {
b, _ := NewBuilder(Config{
TrustDomain: "example.org",
MaxValidSeconds: 3600,
})
_, err := b.Build(&CertRequest{
SpiffeID: "spiffe://example.org/ns/prod/sa/web",
ValidSeconds: 86400, // 24 hours
})
if err == nil {
t.Fatal("expected error for TTL above max")
}
if !strings.Contains(err.Error(), "exceeds maximum") {
t.Errorf("expected max TTL error, got: %v", err)
}
}
func TestBuildRejectsTTLBelowMin(t *testing.T) {
b, _ := NewBuilder(Config{
TrustDomain: "example.org",
MinValidSeconds: 30,
})
_, err := b.Build(&CertRequest{
SpiffeID: "spiffe://example.org/ns/prod/sa/web",
ValidSeconds: 5,
})
if err == nil {
t.Fatal("expected error for TTL below min")
}
if !strings.Contains(err.Error(), "below minimum") {
t.Errorf("expected min TTL error, got: %v", err)
}
}
func TestBuildDefaultsTTL(t *testing.T) {
b, _ := NewBuilder(Config{
TrustDomain: "example.org",
DefaultValidSeconds: 300,
})
// ValidSeconds 0 should use default — Build should succeed past TTL checks
_, err := b.Build(&CertRequest{
SpiffeID: "spiffe://example.org/ns/prod/sa/web",
ValidSeconds: 0,
})
// Should get "not yet implemented" (past TTL validation)
if err == nil || !strings.Contains(err.Error(), "not yet implemented") {
t.Fatalf("expected not-yet-implemented with default TTL, got: %v", err)
}
}
func TestBuildAcceptsTTLInRange(t *testing.T) {
b, _ := NewBuilder(Config{
TrustDomain: "example.org",
MinValidSeconds: 30,
MaxValidSeconds: 3600,
})
_, err := b.Build(&CertRequest{
SpiffeID: "spiffe://example.org/ns/prod/sa/web",
ValidSeconds: 600,
})
// Should get past TTL checks to "not yet implemented"
if err == nil || !strings.Contains(err.Error(), "not yet implemented") {
t.Fatalf("expected not-yet-implemented with valid TTL, got: %v", err)
}
}
func TestBuildRejectsUnauthorizedPrincipal(t *testing.T) {
b, _ := NewBuilder(Config{
TrustDomain: "example.org",
AllowedPrincipals: []string{"web", "api"},
})
_, err := b.Build(&CertRequest{
SpiffeID: "spiffe://example.org/ns/prod/sa/web",
Principals: []string{"root"},
})
if err == nil {
t.Fatal("expected error for unauthorized principal")
}
if !strings.Contains(err.Error(), "not in allowed list") {
t.Errorf("expected principal error, got: %v", err)
}
}
func TestBuildAcceptsAllowedPrincipal(t *testing.T) {
b, _ := NewBuilder(Config{
TrustDomain: "example.org",
AllowedPrincipals: []string{"web", "api"},
})
_, err := b.Build(&CertRequest{
SpiffeID: "spiffe://example.org/ns/prod/sa/web",
Principals: []string{"web"},
})
// Should get past principal checks to "not yet implemented"
if err == nil || !strings.Contains(err.Error(), "not yet implemented") {
t.Fatalf("expected not-yet-implemented with allowed principal, got: %v", err)
}
}
func TestBuildDefaultsPrincipalToSPIFFELeaf(t *testing.T) {
b, _ := NewBuilder(Config{TrustDomain: "example.org"})
// Empty Principals should default to SPIFFE ID leaf
_, err := b.Build(&CertRequest{
SpiffeID: "spiffe://example.org/ns/prod/sa/web-server",
})
// Should get past principal checks to "not yet implemented"
if err == nil || !strings.Contains(err.Error(), "not yet implemented") {
t.Fatalf("expected not-yet-implemented with default principal, got: %v", err)
}
}
func TestBuildRejectsInvalidSPIFFEID(t *testing.T) {
b, _ := NewBuilder(Config{TrustDomain: "example.org"})
tests := []struct {
name string
spiffeID string
}{
{"http scheme", "http://example.org/sa/web"},
{"no path", "spiffe://example.org"},
{"trailing slash", "spiffe://example.org/sa/web/"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := b.Build(&CertRequest{SpiffeID: tt.spiffeID})
if err == nil {
t.Fatalf("expected error for SPIFFE ID %q", tt.spiffeID)
}
if !strings.Contains(err.Error(), "SPIFFE ID") && !strings.Contains(err.Error(), "spiffe") {
t.Errorf("expected SPIFFE ID error, got: %v", err)
}
})
}
}
func TestSpiffeIDLeaf(t *testing.T) {
tests := []struct {
input string
wantLeaf string
wantErr bool
}{
{"spiffe://guildhouse.dev/ns/prod/sa/web-server", "web-server", false},
{"spiffe://guildhouse.dev/operator/tyler", "tyler", false},
{"spiffe://guildhouse.dev/service", "service", false},
{"spiffe://guildhouse.dev", "", true}, // no path
{"spiffe://guildhouse.dev/", "", true}, // empty path
{"spiffe://guildhouse.dev/ns/prod/sa/web/", "", true}, // trailing slash
{"http://example.com/web", "", true}, // wrong scheme
{"not-a-uri", "", true}, // no scheme at all
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
leaf, err := SpiffeIDLeaf(tt.input)
if tt.wantErr {
if err == nil {
t.Fatalf("expected error for %q, got leaf %q", tt.input, leaf)
}
} else {
if err != nil {
t.Fatalf("unexpected error for %q: %v", tt.input, err)
}
if leaf != tt.wantLeaf {
t.Errorf("SpiffeIDLeaf(%q) = %q, want %q", tt.input, leaf, tt.wantLeaf)
}
}
})
}
}