Skip to content

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.

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.

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

  • 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 |

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
  • SecretsISecretStore, references like vault:secret/data/foo#key, caching
  • Credentials — dynamic database credentials, automatic rotation
  • Encryption — transit encryption keys, wrapping, key rotation
  • Transit MACITransitMacService integrity tags, provider matrix, SecretBackedMacService fallback
  • Providers — HashiCorp Vault, Azure Key Vault, AWS Secrets Manager, GCP Secret Manager