Forge
1.x
Docs/Forge/Secrets Management
Open

Reading7 min
Updated31 Jul 2026
Sourcev1/confy/secrets.mdx

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 Start01

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 Interface02

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 Providers03

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",
})

Configuration04

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 Caching05

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 Rotation06

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 Metadata07

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
}

Encryption08

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
})
Warning

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.

Lifecycle09

// 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)