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
| Option | Type | Default | Description |
|---|---|---|---|
DefaultProvider | string | "env" | Default provider name |
CacheTTL | time.Duration | 5m | Cache time-to-live |
CacheEnabled | bool | true | Enable secret caching |
EncryptionKey | string | "" | AES encryption key for cache |
EncryptionEnabled | bool | false | Encrypt cached secrets |
RotationPolicy | SecretRotationPolicy | — | Rotation configuration |
Providers | map[string]ProviderConfig | — | Provider 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?