package smartconfig import ( "fmt" "strings" vaultapi "github.com/hashicorp/vault/api" ) // VaultResolver retrieves secrets from HashiCorp Vault. // Usage: ${VAULT:secret/data/myapp:password} type VaultResolver struct{} // Resolve retrieves the secret value from Vault. func (r *VaultResolver) Resolve(value string) (string, error) { config := vaultapi.DefaultConfig() client, err := vaultapi.NewClient(config) if err != nil { return "", fmt.Errorf("failed to create Vault client: %w", err) } // Expect format: "path:key" e.g., "secret/data/myapp:password" parts := strings.SplitN(value, ":", 2) if len(parts) != 2 { return "", fmt.Errorf("invalid Vault path format, expected PATH:KEY") } path := parts[0] key := parts[1] secret, err := client.Logical().Read(path) if err != nil { return "", fmt.Errorf("failed to read secret from Vault: %w", err) } if secret == nil || secret.Data == nil { return "", fmt.Errorf("no secret found at path %s", path) } // Handle KV v2 format data, ok := secret.Data["data"].(map[string]interface{}) if ok { if val, ok := data[key].(string); ok { return val, nil } } // Handle KV v1 format if val, ok := secret.Data[key].(string); ok { return val, nil } return "", fmt.Errorf("key %s not found in secret", key) }