guildhouse-spire-plugins/pkg/config/config.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

93 lines
3.1 KiB
Go

// Package config provides configuration loading for SPIRE plugins.
// SPIRE plugins receive configuration via HCL in the SPIRE server/agent config file.
package config
import (
"fmt"
"strings"
"github.com/hashicorp/hcl/v2/hclsimple"
)
const (
// DefaultGovernanceEpochSeconds is used when governance_epoch_seconds is not set.
DefaultGovernanceEpochSeconds = 300
// MinGovernanceEpochSeconds is the minimum allowed epoch duration.
MinGovernanceEpochSeconds = 10
// MaxGovernanceEpochSeconds is the maximum allowed epoch duration.
MaxGovernanceEpochSeconds = 3600
)
// PluginConfig holds common configuration fields shared by all Guildhouse SPIRE plugins.
type PluginConfig struct {
// GovernanceAddr is the gRPC address of the GovernanceService.
GovernanceAddr string `hcl:"governance_addr"`
// CeremonyAddr is the gRPC address of the CeremonyService.
CeremonyAddr string `hcl:"ceremony_addr"`
// NotaryAddr is the gRPC address of the NotaryService.
NotaryAddr string `hcl:"notary_addr"`
// TrustDomain is the SPIFFE trust domain.
TrustDomain string `hcl:"trust_domain"`
// ClusterID identifies this cluster for notary anchoring.
ClusterID string `hcl:"cluster_id"`
// GovernanceEpochSeconds is the duration of a governance epoch in seconds.
// Controls how frequently merkle anchors are created. Must not exceed 256
// credential events per epoch (see shellstream merkle-proof depth limit).
// Default: 300 (5 minutes).
GovernanceEpochSeconds int `hcl:"governance_epoch_seconds"`
}
// Validate checks that required fields are present and within valid ranges.
func (c *PluginConfig) Validate() error {
if c.TrustDomain == "" {
return fmt.Errorf("config: trust_domain is required")
}
if c.GovernanceAddr == "" {
return fmt.Errorf("config: governance_addr is required")
}
if c.ClusterID == "" {
return fmt.Errorf("config: cluster_id is required")
}
// Apply default and validate range for epoch seconds.
if c.GovernanceEpochSeconds == 0 {
c.GovernanceEpochSeconds = DefaultGovernanceEpochSeconds
}
if c.GovernanceEpochSeconds < MinGovernanceEpochSeconds {
return fmt.Errorf("config: governance_epoch_seconds %d below minimum %d",
c.GovernanceEpochSeconds, MinGovernanceEpochSeconds)
}
if c.GovernanceEpochSeconds > MaxGovernanceEpochSeconds {
return fmt.Errorf("config: governance_epoch_seconds %d exceeds maximum %d",
c.GovernanceEpochSeconds, MaxGovernanceEpochSeconds)
}
// CeremonyAddr and NotaryAddr are optional but must not be whitespace-only if set.
if c.CeremonyAddr != "" && strings.TrimSpace(c.CeremonyAddr) == "" {
return fmt.Errorf("config: ceremony_addr is set but empty")
}
if c.NotaryAddr != "" && strings.TrimSpace(c.NotaryAddr) == "" {
return fmt.Errorf("config: notary_addr is set but empty")
}
return nil
}
// LoadFromHCL parses plugin configuration from HCL bytes.
func LoadFromHCL(data []byte) (*PluginConfig, error) {
cfg := &PluginConfig{}
if err := hclsimple.Decode("plugin.hcl", data, nil, cfg); err != nil {
return nil, fmt.Errorf("config: parse HCL: %w", err)
}
if err := cfg.Validate(); err != nil {
return nil, err
}
return cfg, nil
}