| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- package kms
- import (
- "context"
- "fmt"
- "sync"
- "time"
- "github.com/seaweedfs/seaweedfs/weed/glog"
- "github.com/seaweedfs/seaweedfs/weed/util"
- )
- // KMSManager manages KMS provider instances and configurations
- type KMSManager struct {
- mu sync.RWMutex
- providers map[string]KMSProvider // provider name -> provider instance
- configs map[string]*KMSConfig // provider name -> configuration
- bucketKMS map[string]string // bucket name -> provider name
- defaultKMS string // default KMS provider name
- }
- // KMSConfig represents a complete KMS provider configuration
- type KMSConfig struct {
- Provider string `json:"provider"` // Provider type (aws, azure, gcp, local)
- Config map[string]interface{} `json:"config"` // Provider-specific configuration
- CacheEnabled bool `json:"cache_enabled"` // Enable data key caching
- CacheTTL time.Duration `json:"cache_ttl"` // Cache TTL (default: 1 hour)
- MaxCacheSize int `json:"max_cache_size"` // Maximum cached keys (default: 1000)
- }
- // BucketKMSConfig represents KMS configuration for a specific bucket
- type BucketKMSConfig struct {
- Provider string `json:"provider"` // KMS provider to use
- KeyID string `json:"key_id"` // Default KMS key ID for this bucket
- BucketKey bool `json:"bucket_key"` // Enable S3 Bucket Keys optimization
- Context map[string]string `json:"context"` // Additional encryption context
- Enabled bool `json:"enabled"` // Whether KMS encryption is enabled
- }
- // configAdapter adapts KMSConfig.Config to util.Configuration interface
- type configAdapter struct {
- config map[string]interface{}
- }
- // GetConfigMap returns the underlying configuration map for direct access
- func (c *configAdapter) GetConfigMap() map[string]interface{} {
- return c.config
- }
- func (c *configAdapter) GetString(key string) string {
- if val, ok := c.config[key]; ok {
- if str, ok := val.(string); ok {
- return str
- }
- }
- return ""
- }
- func (c *configAdapter) GetBool(key string) bool {
- if val, ok := c.config[key]; ok {
- if b, ok := val.(bool); ok {
- return b
- }
- }
- return false
- }
- func (c *configAdapter) GetInt(key string) int {
- if val, ok := c.config[key]; ok {
- if i, ok := val.(int); ok {
- return i
- }
- if f, ok := val.(float64); ok {
- return int(f)
- }
- }
- return 0
- }
- func (c *configAdapter) GetStringSlice(key string) []string {
- if val, ok := c.config[key]; ok {
- if slice, ok := val.([]string); ok {
- return slice
- }
- if interfaceSlice, ok := val.([]interface{}); ok {
- result := make([]string, len(interfaceSlice))
- for i, v := range interfaceSlice {
- if str, ok := v.(string); ok {
- result[i] = str
- }
- }
- return result
- }
- }
- return nil
- }
- func (c *configAdapter) SetDefault(key string, value interface{}) {
- if c.config == nil {
- c.config = make(map[string]interface{})
- }
- if _, exists := c.config[key]; !exists {
- c.config[key] = value
- }
- }
- var (
- globalKMSManager *KMSManager
- globalKMSMutex sync.RWMutex
- // Global KMS provider for legacy compatibility
- globalKMSProvider KMSProvider
- )
- // InitializeGlobalKMS initializes the global KMS provider
- func InitializeGlobalKMS(config *KMSConfig) error {
- if config == nil || config.Provider == "" {
- return fmt.Errorf("KMS configuration is required")
- }
- // Adapt the config to util.Configuration interface
- var providerConfig util.Configuration
- if config.Config != nil {
- providerConfig = &configAdapter{config: config.Config}
- }
- provider, err := GetProvider(config.Provider, providerConfig)
- if err != nil {
- return err
- }
- globalKMSMutex.Lock()
- defer globalKMSMutex.Unlock()
- // Close existing provider if any
- if globalKMSProvider != nil {
- globalKMSProvider.Close()
- }
- globalKMSProvider = provider
- return nil
- }
- // GetGlobalKMS returns the global KMS provider
- func GetGlobalKMS() KMSProvider {
- globalKMSMutex.RLock()
- defer globalKMSMutex.RUnlock()
- return globalKMSProvider
- }
- // IsKMSEnabled returns true if KMS is enabled globally
- func IsKMSEnabled() bool {
- return GetGlobalKMS() != nil
- }
- // SetGlobalKMSProvider sets the global KMS provider.
- // This is mainly for backward compatibility.
- func SetGlobalKMSProvider(provider KMSProvider) {
- globalKMSMutex.Lock()
- defer globalKMSMutex.Unlock()
- // Close existing provider if any
- if globalKMSProvider != nil {
- globalKMSProvider.Close()
- }
- globalKMSProvider = provider
- }
- // InitializeKMSManager initializes the global KMS manager
- func InitializeKMSManager() *KMSManager {
- globalKMSMutex.Lock()
- defer globalKMSMutex.Unlock()
- if globalKMSManager == nil {
- globalKMSManager = &KMSManager{
- providers: make(map[string]KMSProvider),
- configs: make(map[string]*KMSConfig),
- bucketKMS: make(map[string]string),
- }
- glog.V(1).Infof("KMS Manager initialized")
- }
- return globalKMSManager
- }
- // GetKMSManager returns the global KMS manager
- func GetKMSManager() *KMSManager {
- globalKMSMutex.RLock()
- manager := globalKMSManager
- globalKMSMutex.RUnlock()
- if manager == nil {
- return InitializeKMSManager()
- }
- return manager
- }
- // AddKMSProvider adds a KMS provider configuration
- func (km *KMSManager) AddKMSProvider(name string, config *KMSConfig) error {
- if name == "" {
- return fmt.Errorf("provider name cannot be empty")
- }
- if config == nil {
- return fmt.Errorf("KMS configuration cannot be nil")
- }
- km.mu.Lock()
- defer km.mu.Unlock()
- // Close existing provider if it exists
- if existingProvider, exists := km.providers[name]; exists {
- if err := existingProvider.Close(); err != nil {
- glog.Errorf("Failed to close existing KMS provider %s: %v", name, err)
- }
- }
- // Create new provider instance
- configAdapter := &configAdapter{config: config.Config}
- provider, err := GetProvider(config.Provider, configAdapter)
- if err != nil {
- return fmt.Errorf("failed to create KMS provider %s: %w", name, err)
- }
- // Store provider and configuration
- km.providers[name] = provider
- km.configs[name] = config
- glog.V(1).Infof("Added KMS provider %s (type: %s)", name, config.Provider)
- return nil
- }
- // SetDefaultKMSProvider sets the default KMS provider
- func (km *KMSManager) SetDefaultKMSProvider(name string) error {
- km.mu.RLock()
- _, exists := km.providers[name]
- km.mu.RUnlock()
- if !exists {
- return fmt.Errorf("KMS provider %s does not exist", name)
- }
- km.mu.Lock()
- km.defaultKMS = name
- km.mu.Unlock()
- glog.V(1).Infof("Set default KMS provider to %s", name)
- return nil
- }
- // SetBucketKMSProvider sets the KMS provider for a specific bucket
- func (km *KMSManager) SetBucketKMSProvider(bucket, providerName string) error {
- if bucket == "" {
- return fmt.Errorf("bucket name cannot be empty")
- }
- km.mu.RLock()
- _, exists := km.providers[providerName]
- km.mu.RUnlock()
- if !exists {
- return fmt.Errorf("KMS provider %s does not exist", providerName)
- }
- km.mu.Lock()
- km.bucketKMS[bucket] = providerName
- km.mu.Unlock()
- glog.V(2).Infof("Set KMS provider for bucket %s to %s", bucket, providerName)
- return nil
- }
- // GetKMSProvider returns the KMS provider for a bucket (or default if not configured)
- func (km *KMSManager) GetKMSProvider(bucket string) (KMSProvider, error) {
- km.mu.RLock()
- defer km.mu.RUnlock()
- // Try bucket-specific provider first
- if bucket != "" {
- if providerName, exists := km.bucketKMS[bucket]; exists {
- if provider, exists := km.providers[providerName]; exists {
- return provider, nil
- }
- }
- }
- // Fall back to default provider
- if km.defaultKMS != "" {
- if provider, exists := km.providers[km.defaultKMS]; exists {
- return provider, nil
- }
- }
- // No provider configured
- return nil, fmt.Errorf("no KMS provider configured for bucket %s", bucket)
- }
- // GetKMSProviderByName returns a specific KMS provider by name
- func (km *KMSManager) GetKMSProviderByName(name string) (KMSProvider, error) {
- km.mu.RLock()
- defer km.mu.RUnlock()
- provider, exists := km.providers[name]
- if !exists {
- return nil, fmt.Errorf("KMS provider %s not found", name)
- }
- return provider, nil
- }
- // ListKMSProviders returns all configured KMS provider names
- func (km *KMSManager) ListKMSProviders() []string {
- km.mu.RLock()
- defer km.mu.RUnlock()
- names := make([]string, 0, len(km.providers))
- for name := range km.providers {
- names = append(names, name)
- }
- return names
- }
- // GetBucketKMSProvider returns the KMS provider name for a bucket
- func (km *KMSManager) GetBucketKMSProvider(bucket string) string {
- km.mu.RLock()
- defer km.mu.RUnlock()
- if providerName, exists := km.bucketKMS[bucket]; exists {
- return providerName
- }
- return km.defaultKMS
- }
- // RemoveKMSProvider removes a KMS provider
- func (km *KMSManager) RemoveKMSProvider(name string) error {
- km.mu.Lock()
- defer km.mu.Unlock()
- provider, exists := km.providers[name]
- if !exists {
- return fmt.Errorf("KMS provider %s does not exist", name)
- }
- // Close the provider
- if err := provider.Close(); err != nil {
- glog.Errorf("Failed to close KMS provider %s: %v", name, err)
- }
- // Remove from maps
- delete(km.providers, name)
- delete(km.configs, name)
- // Remove from bucket associations
- for bucket, providerName := range km.bucketKMS {
- if providerName == name {
- delete(km.bucketKMS, bucket)
- }
- }
- // Clear default if it was this provider
- if km.defaultKMS == name {
- km.defaultKMS = ""
- }
- glog.V(1).Infof("Removed KMS provider %s", name)
- return nil
- }
- // Close closes all KMS providers and cleans up resources
- func (km *KMSManager) Close() error {
- km.mu.Lock()
- defer km.mu.Unlock()
- var allErrors []error
- for name, provider := range km.providers {
- if err := provider.Close(); err != nil {
- allErrors = append(allErrors, fmt.Errorf("failed to close KMS provider %s: %w", name, err))
- }
- }
- // Clear all maps
- km.providers = make(map[string]KMSProvider)
- km.configs = make(map[string]*KMSConfig)
- km.bucketKMS = make(map[string]string)
- km.defaultKMS = ""
- if len(allErrors) > 0 {
- return fmt.Errorf("errors closing KMS providers: %v", allErrors)
- }
- glog.V(1).Infof("KMS Manager closed")
- return nil
- }
- // GenerateDataKeyForBucket generates a data key using the appropriate KMS provider for a bucket
- func (km *KMSManager) GenerateDataKeyForBucket(ctx context.Context, bucket, keyID string, keySpec KeySpec, encryptionContext map[string]string) (*GenerateDataKeyResponse, error) {
- provider, err := km.GetKMSProvider(bucket)
- if err != nil {
- return nil, fmt.Errorf("failed to get KMS provider for bucket %s: %w", bucket, err)
- }
- req := &GenerateDataKeyRequest{
- KeyID: keyID,
- KeySpec: keySpec,
- EncryptionContext: encryptionContext,
- }
- return provider.GenerateDataKey(ctx, req)
- }
- // DecryptForBucket decrypts a data key using the appropriate KMS provider for a bucket
- func (km *KMSManager) DecryptForBucket(ctx context.Context, bucket string, ciphertextBlob []byte, encryptionContext map[string]string) (*DecryptResponse, error) {
- provider, err := km.GetKMSProvider(bucket)
- if err != nil {
- return nil, fmt.Errorf("failed to get KMS provider for bucket %s: %w", bucket, err)
- }
- req := &DecryptRequest{
- CiphertextBlob: ciphertextBlob,
- EncryptionContext: encryptionContext,
- }
- return provider.Decrypt(ctx, req)
- }
- // ValidateKeyForBucket validates that a KMS key exists and is usable for a bucket
- func (km *KMSManager) ValidateKeyForBucket(ctx context.Context, bucket, keyID string) error {
- provider, err := km.GetKMSProvider(bucket)
- if err != nil {
- return fmt.Errorf("failed to get KMS provider for bucket %s: %w", bucket, err)
- }
- req := &DescribeKeyRequest{KeyID: keyID}
- resp, err := provider.DescribeKey(ctx, req)
- if err != nil {
- return fmt.Errorf("failed to validate key %s for bucket %s: %w", keyID, bucket, err)
- }
- // Check key state
- if resp.KeyState != KeyStateEnabled {
- return fmt.Errorf("key %s is not enabled (state: %s)", keyID, resp.KeyState)
- }
- // Check key usage
- if resp.KeyUsage != KeyUsageEncryptDecrypt && resp.KeyUsage != KeyUsageGenerateDataKey {
- return fmt.Errorf("key %s cannot be used for encryption (usage: %s)", keyID, resp.KeyUsage)
- }
- return nil
- }
- // GetKMSHealth returns health status of all KMS providers
- func (km *KMSManager) GetKMSHealth(ctx context.Context) map[string]error {
- km.mu.RLock()
- defer km.mu.RUnlock()
- health := make(map[string]error)
- for name, provider := range km.providers {
- // Try to perform a basic operation to check health
- // We'll use DescribeKey with a dummy key - the error will tell us if KMS is reachable
- req := &DescribeKeyRequest{KeyID: "health-check-dummy-key"}
- _, err := provider.DescribeKey(ctx, req)
- // If it's a "not found" error, KMS is healthy but key doesn't exist (expected)
- if kmsErr, ok := err.(*KMSError); ok && kmsErr.Code == ErrCodeNotFoundException {
- health[name] = nil // Healthy
- } else if err != nil {
- health[name] = err // Unhealthy
- } else {
- health[name] = nil // Healthy (shouldn't happen with dummy key, but just in case)
- }
- }
- return health
- }
|