fetch_ml/internal/crypto/kms/config/config.go
Jeremie Fraeys cb25677695
feat(kms): implement core KMS infrastructure with DEK cache
Add KMSProvider interface for external key management systems:
- Encrypt/Decrypt operations for DEK wrapping
- Key lifecycle management (Create, Disable, ScheduleDeletion, Enable)
- HealthCheck and Close methods

Implement MemoryProvider for development/testing:
- XOR encryption with HMAC-SHA256 authentication
- Secure random key generation using crypto/rand
- MAC verification to detect wrong keys

Implement DEKCache per ADR-012:
- 15-minute TTL with configurable grace window (1 hour)
- LRU eviction with 1000 entry limit
- Cache key includes (tenantID, artifactID, kmsKeyID) for isolation
- Thread-safe operations with RWMutex
- Secure memory wiping on eviction/cleanup

Add config package with types:
- ProviderType enum (vault, aws, memory)
- VaultConfig with AppRole/Kubernetes/Token auth
- AWSConfig with region and alias prefix
- CacheConfig with TTL, MaxEntries, GraceWindow
- Validation methods for all config types
2026-03-03 19:13:55 -05:00

168 lines
4.3 KiB
Go

// Package config provides KMS configuration types shared across KMS providers.
package config
import (
"fmt"
"time"
)
// ProviderType identifies the KMS provider implementation.
type ProviderType string
const (
ProviderTypeVault ProviderType = "vault"
ProviderTypeAWS ProviderType = "aws"
ProviderTypeMemory ProviderType = "memory" // Development only
)
// IsValid returns true if the provider type is valid.
func (t ProviderType) IsValid() bool {
switch t {
case ProviderTypeVault, ProviderTypeAWS, ProviderTypeMemory:
return true
}
return false
}
// Config holds KMS provider configuration.
type Config struct {
Provider ProviderType `yaml:"provider"`
Vault VaultConfig `yaml:"vault,omitempty"`
AWS AWSConfig `yaml:"aws,omitempty"`
Cache CacheConfig `yaml:"cache,omitempty"`
}
// VaultConfig holds HashiCorp Vault-specific configuration.
type VaultConfig struct {
Address string `yaml:"address"`
AuthMethod string `yaml:"auth_method"`
RoleID string `yaml:"role_id"`
SecretID string `yaml:"secret_id"`
Token string `yaml:"token"`
TransitMount string `yaml:"transit_mount"`
KeyPrefix string `yaml:"key_prefix"`
Region string `yaml:"region"`
Timeout time.Duration `yaml:"timeout"`
}
// AWSConfig holds AWS KMS-specific configuration.
type AWSConfig struct {
Region string `yaml:"region"`
KeyAliasPrefix string `yaml:"key_alias_prefix"`
RoleARN string `yaml:"role_arn,omitempty"`
Endpoint string `yaml:"endpoint,omitempty"`
}
// CacheConfig holds DEK cache configuration per ADR-012.
type CacheConfig struct {
TTL time.Duration `yaml:"ttl_minutes"`
MaxEntries int `yaml:"max_entries"`
GraceWindow time.Duration `yaml:"grace_window_minutes"`
}
// DefaultCacheConfig returns the default cache configuration per ADR-012/013.
func DefaultCacheConfig() CacheConfig {
return CacheConfig{
TTL: 15 * time.Minute,
MaxEntries: 1000,
GraceWindow: 1 * time.Hour,
}
}
// Validate checks the configuration for errors.
func (c *Config) Validate() error {
if !c.Provider.IsValid() {
return fmt.Errorf("invalid KMS provider: %s", c.Provider)
}
switch c.Provider {
case ProviderTypeVault:
if err := c.Vault.Validate(); err != nil {
return fmt.Errorf("vault config: %w", err)
}
case ProviderTypeAWS:
if err := c.AWS.Validate(); err != nil {
return fmt.Errorf("aws config: %w", err)
}
}
// Apply defaults for cache config
if c.Cache.TTL == 0 {
c.Cache.TTL = DefaultCacheConfig().TTL
}
if c.Cache.MaxEntries == 0 {
c.Cache.MaxEntries = DefaultCacheConfig().MaxEntries
}
if c.Cache.GraceWindow == 0 {
c.Cache.GraceWindow = DefaultCacheConfig().GraceWindow
}
return nil
}
// Validate checks Vault configuration.
func (v *VaultConfig) Validate() error {
if v.Address == "" {
return fmt.Errorf("vault address is required")
}
switch v.AuthMethod {
case "approle":
if v.RoleID == "" || v.SecretID == "" {
return fmt.Errorf("approle auth requires role_id and secret_id")
}
case "kubernetes":
// Kubernetes auth uses service account token
case "token":
if v.Token == "" {
return fmt.Errorf("token auth requires token")
}
default:
return fmt.Errorf("invalid auth_method: %s", v.AuthMethod)
}
// Apply defaults
if v.TransitMount == "" {
v.TransitMount = "transit"
}
if v.KeyPrefix == "" {
v.KeyPrefix = "fetchml-tenant"
}
if v.Timeout == 0 {
v.Timeout = 30 * time.Second
}
return nil
}
// Validate checks AWS configuration.
func (a *AWSConfig) Validate() error {
if a.Region == "" {
return fmt.Errorf("AWS region is required")
}
// Apply defaults
if a.KeyAliasPrefix == "" {
a.KeyAliasPrefix = "alias/fetchml"
}
return nil
}
// KeyIDForTenant generates a KMS key ID for a tenant based on the provider config.
func (c *Config) KeyIDForTenant(tenantID string) string {
switch c.Provider {
case ProviderTypeVault:
if c.Vault.Region != "" {
return fmt.Sprintf("%s-%s-%s", c.Vault.KeyPrefix, c.Vault.Region, tenantID)
}
return fmt.Sprintf("%s-%s", c.Vault.KeyPrefix, tenantID)
case ProviderTypeAWS:
if c.AWS.Region != "" {
return fmt.Sprintf("%s-%s-%s", c.AWS.KeyAliasPrefix, c.AWS.Region, tenantID)
}
return fmt.Sprintf("%s-%s", c.AWS.KeyAliasPrefix, tenantID)
default:
return tenantID
}
}