Secrets Management

Integrate with external secret providers, cache secrets, rotate credentials, and encrypt sensitive values.

Confy includes a built-in secrets manager that resolves, caches, and rotates secrets from external providers like HashiCorp Vault, AWS Secrets Manager, and environment variables.

Quick Start

cfg := confy.New(
    confy.WithSecretsEnabled(true),
)

// Create a secrets manager
secretsMgr := confy.NewSecretsManager(confy.SecretsConfig{
    DefaultProvider: "env",
    CacheTTL:        5 * time.Minute,
    EncryptionKey:   os.Getenv("SECRETS_ENCRYPTION_KEY"),
})

// Start the secrets manager
secretsMgr.Start(context.Background())

// Load config with secret references
source, _ := sources.NewFileSource("config.yaml", sources.FileSourceOptions{
    ExpandSecrets: true,
})
cfg.LoadFrom(source)
# config.yaml
database:
  password: ${secret:db/password}
  api_key: ${secret:services/api_key}

SecretsManager Interface

type SecretsManager interface {
    Start(ctx context.Context) error
    Stop(ctx context.Context) error
    GetSecret(ctx context.Context, key string) (string, error)
    SetSecret(ctx context.Context, key, value string) error
    DeleteSecret(ctx context.Context, key string) error
    ListSecrets(ctx context.Context) ([]string, error)
    RegisterProvider(name string, provider SecretProvider) error
    RotateSecret(ctx context.Context, key string) error
}

Secret Providers

Providers implement the SecretProvider interface to connect to different secret backends:

type SecretProvider interface {
    Name() string
    GetSecret(ctx context.Context, key string) (string, error)
    SetSecret(ctx context.Context, key, value string) error
    DeleteSecret(ctx context.Context, key string) error
    ListSecrets(ctx context.Context, prefix string) ([]string, error)
    IsAvailable(ctx context.Context) bool
}

Built-in Provider: Environment Variables

Secrets are resolved from environment variables by default:

database:
  password: ${secret:DB_PASSWORD}

This looks up the DB_PASSWORD environment variable.

Custom Provider: Vault

type VaultProvider struct {
    client  *vault.Client
    prefix  string
}

func (v *VaultProvider) Name() string { return "vault" }

func (v *VaultProvider) GetSecret(ctx context.Context, key string) (string, error) {
    secret, err := v.client.KVv2("secret").Get(ctx, v.prefix+"/"+key)
    if err != nil {
        return "", err
    }
    value, ok := secret.Data["value"].(string)
    if !ok {
        return "", fmt.Errorf("secret %s has no 'value' field", key)
    }
    return value, nil
}

// Register the provider
secretsMgr.RegisterProvider("vault", &VaultProvider{
    client: vaultClient,
    prefix: "myapp",
})

Configuration

secretsMgr := confy.NewSecretsManager(confy.SecretsConfig{
    DefaultProvider:   "vault",
    CacheTTL:          5 * time.Minute,
    CacheEnabled:      true,
    EncryptionKey:     os.Getenv("SECRETS_KEY"),
    EncryptionEnabled: true,
    RotationPolicy: confy.SecretRotationPolicy{
        Enabled:  true,
        Interval: 24 * time.Hour,
        MaxAge:   7 * 24 * time.Hour,
    },
    Providers: map[string]confy.ProviderConfig{
        "vault": {
            Type:     "vault",
            Enabled:  true,
            Priority: 100,
            Properties: map[string]any{
                "address": "https://vault.example.com",
                "token":   os.Getenv("VAULT_TOKEN"),
            },
        },
    },
})

Configuration Options

OptionTypeDefaultDescription
DefaultProviderstring"env"Default provider name
CacheTTLtime.Duration5mCache time-to-live
CacheEnabledbooltrueEnable secret caching
EncryptionKeystring""AES encryption key for cache
EncryptionEnabledboolfalseEncrypt cached secrets
RotationPolicySecretRotationPolicyRotation configuration
Providersmap[string]ProviderConfigProvider configurations

Secret Caching

Secrets are cached in memory to avoid repeated lookups. The cache supports:

  • TTL-based expiration — secrets expire after CacheTTL
  • Encryption at rest — cached values encrypted with AES-256-GCM
  • Metadata tracking — access counts, last access time, provider info
// CachedSecret structure
type CachedSecret struct {
    Value     string
    Metadata  SecretMetadata
    ExpiresAt time.Time
    Encrypted bool
}

Secret Rotation

Enable automatic secret rotation with a rotation policy:

rotationPolicy := confy.SecretRotationPolicy{
    Enabled:  true,
    Interval: 24 * time.Hour,     // check every 24 hours
    MaxAge:   7 * 24 * time.Hour, // rotate if older than 7 days
    NotifyOn: []string{"rotation_success", "rotation_failure"},
}

Custom Secret Generator

Provide a generator for creating new secret values during rotation:

type PasswordGenerator struct{}

func (g *PasswordGenerator) GenerateSecret(ctx context.Context, key string, meta confy.SecretMetadata) (string, error) {
    // Generate a cryptographically secure password
    return generateSecurePassword(32), nil
}

func (g *PasswordGenerator) ValidateSecret(ctx context.Context, value string) error {
    if len(value) < 16 {
        return fmt.Errorf("password too short: minimum 16 characters")
    }
    return nil
}

Secret Validators

Add validators to ensure rotated secrets meet requirements:

type PasswordValidator struct{}

func (v *PasswordValidator) ValidateSecret(ctx context.Context, key, value string) error {
    if len(value) < 16 {
        return fmt.Errorf("must be at least 16 characters")
    }
    // Check complexity requirements
    return nil
}

func (v *PasswordValidator) GetValidationRules() []string {
    return []string{"min_length:16", "requires_special_char"}
}

Secret Metadata

Track metadata about each secret:

type SecretMetadata struct {
    Key        string
    Provider   string
    CreatedAt  time.Time
    UpdatedAt  time.Time
    AccessedAt time.Time
    Version    int
    Tags       map[string]string
    Encrypted  bool
    TTL        time.Duration
    Properties map[string]any
}

Encryption

Confy uses AES-256-GCM to encrypt cached secrets at rest:

secretsMgr := confy.NewSecretsManager(confy.SecretsConfig{
    EncryptionEnabled: true,
    EncryptionKey:     os.Getenv("SECRETS_ENCRYPTION_KEY"),
    // Key must be 32 bytes for AES-256
})

Store the encryption key securely (e.g., in an environment variable or HSM). If the key is lost, cached encrypted secrets cannot be recovered. The next lookup will fetch fresh values from the provider.

Lifecycle

// Start — initializes providers, loads cache, starts rotation
err := secretsMgr.Start(ctx)

// Use — get, set, delete, list, rotate
value, err := secretsMgr.GetSecret(ctx, "db/password")
err = secretsMgr.SetSecret(ctx, "db/password", "new-password")
err = secretsMgr.RotateSecret(ctx, "db/password")

keys, err := secretsMgr.ListSecrets(ctx)
err = secretsMgr.DeleteSecret(ctx, "old/secret")

// Stop — flushes cache, stops rotation, cleans up
err = secretsMgr.Stop(ctx)

How is this guide?

On this page