Secret management in .NET with Granit Vault
Why you shouldn’t store secrets in appsettings.json
Section titled “Why you shouldn’t store secrets in appsettings.json”If your connection string or encryption key lives in a config file, an environment variable, or a CI secret, you have a rotation problem. The day the key leaks, you touch every deployed service — under pressure, without a clear audit trail, and often without knowing which rows were encrypted with the compromised version.
A vault solves that because:
- Keys never leave the vault boundary. Your app sends ciphertext over and gets plaintext back; the key never reaches process memory (HashiCorp Transit, Azure Key Vault RSA, AWS KMS, GCP KMS).
- Database credentials are short-lived. The vault mints a new username/password per lease; if one leaks, it expires on its own within minutes.
- Arbitrary secrets are versioned. mTLS certificates, signing keys, SMTP passwords, third-party API tokens are retrieved on demand, and rotations show up via a version identifier.
Granit.Vault wraps all four major vault providers behind three focused .NET
interfaces so your app doesn’t care which cloud you run on. Provider modules
auto-disable in Development, so local testing needs zero vault infrastructure.
Which abstraction do you need?
Section titled “Which abstraction do you need?”Quickstart — 4 lines to wire HashiCorp Vault
Section titled “Quickstart — 4 lines to wire HashiCorp Vault”// 1. Install one provider package (others follow the same pattern):// dotnet add package Granit.Vault.HashiCorp
// 2. Declare the module (the abstraction module is transitive):[DependsOn(typeof(GranitVaultHashiCorpModule))]public class AppModule : GranitModule { }
// 3. Configure — appsettings.json:// "Vault": { "Address": "https://vault.example.com", "AuthMethod": "Kubernetes" }
// 4. Inject the abstraction you need:public sealed class MyService( ITransitEncryptionService transit, ISecretStore secrets, IDatabaseCredentialProvider dbCreds) { /* ... */ }That’s it. Development uses local AES + no-op credentials; production swaps in the
real vault with no code change.
Granit.Vault at a glance
Section titled “Granit.Vault at a glance”| Need | Interface | What you get |
| ---- | --------- | ------------ |
| Encrypt a C# string | ITransitEncryptionService.EncryptAsync | Provider-specific ciphertext (Vault returns vault:v3:... with an embedded version) |
| Decrypt same | ITransitEncryptionService.DecryptAsync | Original plaintext |
| Rotate encryption key | ITransitEncryptionService.RewrapAsync | Ciphertext re-encrypted to the newest key (server-side on Vault, decrypt-then-encrypt elsewhere) |
| Detect old ciphertext | ITransitEncryptionService.GetKeyVersion + ReEncryptionOptions | Old rows throw RetiredKeyVersionException on decrypt |
| Sign bytes with an integrity tag | ITransitMacService.MacAsync | Opaque HMAC tag + key version (TransitMacResult) — key never leaves the vault |
| Verify an integrity tag | ITransitMacService.VerifyAsync | true for any key version still in the rotation window |
| Encrypt an EF Core column | [Encrypted] attribute + EncryptedStringConverter | Transparent per-entity encryption via Granit.Encryption.EntityFrameworkCore |
| Read a cert / API key | ISecretStore.GetSecretAsync | SecretDescriptor with payload + version + optional ExpiresOn (proactive cert reload) |
| Read a secret without throwing on 404 | ISecretStore.TryGetSecretAsync | null only on not-found — 403 / 503 / config errors bubble |
| Get a DB username/password | IDatabaseCredentialProvider | Current pair + IsReady flag, refreshed in the background |
| Liveness probe for secrets | AddGranitSecretStoreHealthCheck() | Reads an optional canary secret, maps to Healthy / Unhealthy / Degraded |
Package structure
Section titled “Package structure”DirectoryGranit.Encryption/ AES-256-CBC default, IStringEncryptionService
DirectoryGranit.Vault/ Abstractions — ITransitEncryptionService, IDatabaseCredentialProvider, ISecretStore, SecretStoreHealthCheck, localization
- Granit.Vault.HashiCorp HashiCorp Vault provider — VaultSharp client, Transit engine, dynamic DB credentials, KV v2 secrets
- Granit.Vault.Azure Azure Key Vault provider — RSA-OAEP-256 encryption, secrets, dynamic credentials
- Granit.Vault.Aws AWS provider — KMS transit encryption, Secrets Manager for secrets & credentials
- Granit.Vault.GoogleCloud Google Cloud KMS encryption, Secret Manager for secrets & credentials
| Package | Role | Depends on |
|---------|------|------------|
| Granit.Encryption | IStringEncryptionService, AES-256-CBC provider | Granit |
| Granit.Encryption.EntityFrameworkCore | [Encrypted], EncryptedStringConverter, ApplyEncryptionConventions | Granit.Encryption, Granit.Persistence |
| Granit.Encryption.ReEncryption | IReEncryptionJob, DefaultReEncryptionJob<TContext> | Granit.Encryption.EntityFrameworkCore |
| Granit.Vault | Abstractions — ITransitEncryptionService, ITransitMacService (+ SecretBackedMacService fallback), IDatabaseCredentialProvider, ISecretStore, SecretDescriptor, SecretStoreOptions, SecretStoreHealthCheck, ReEncryptionOptions | Granit.Caching, Granit.Encryption |
| Granit.Vault.HashiCorp | VaultSharp client, Transit, dynamic DB credentials, KV v2 secrets | Granit.Vault |
| Granit.Vault.Azure | RSA-OAEP-256 encryption, secrets, dynamic credentials | Granit.Vault |
| Granit.Vault.Aws | KMS transit encryption, Secrets Manager | Granit.Vault |
| Granit.Vault.GoogleCloud | Cloud KMS symmetric encryption, Secret Manager | Granit.Vault |
Dependency graph
Section titled “Dependency graph”graph TD
VH[Granit.Vault.HashiCorp] --> V[Granit.Vault]
VA[Granit.Vault.Azure] --> V
VAW[Granit.Vault.Aws] --> V
VGC[Granit.Vault.GoogleCloud] --> V
V --> E[Granit.Encryption]
V --> CA[Granit.Caching]
E --> C[Granit]
EEFC[Granit.Encryption.EntityFrameworkCore] --> E
EERE[Granit.Encryption.ReEncryption] --> EEFC
Section contents
Section titled “Section contents”- Secrets —
ISecretStore, references likevault:secret/data/foo#key, caching - Credentials — dynamic database credentials, automatic rotation
- Encryption — transit encryption keys, wrapping, key rotation
- Transit MAC —
ITransitMacServiceintegrity tags, provider matrix,SecretBackedMacServicefallback - Providers — HashiCorp Vault, Azure Key Vault, AWS Secrets Manager, GCP Secret Manager
See also
Section titled “See also”- Field-level encryption —
[Encrypted]attribute, EF Core convention, re-encryption job - Privacy export signing —
Granit.Privacy.Vault, the production consumer ofITransitMacService - Caching module — AES-256 encryption for cached sensitive data
- Persistence module — dynamic credentials integration, interceptors
- Encrypt sensitive data guide — step-by-step recipe
- Blog: Secrets management with HashiCorp Vault in .NET 10 — end-to-end walkthrough of the Vault integration powering this module