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.

NeedInterfaceWhat you get
Encrypt a C# stringITransitEncryptionService.EncryptAsyncProvider-specific ciphertext (Vault returns vault:v3:... with an embedded version)
Decrypt sameITransitEncryptionService.DecryptAsyncOriginal plaintext
Rotate encryption keyITransitEncryptionService.RewrapAsyncCiphertext re-encrypted to the newest key (server-side on Vault, decrypt-then-encrypt elsewhere)
Detect old ciphertextITransitEncryptionService.GetKeyVersion + ReEncryptionOptionsOld rows throw RetiredKeyVersionException on decrypt
Encrypt an EF Core column[Encrypted] attribute + EncryptedStringConverterTransparent per-entity encryption via Granit.Encryption.EntityFrameworkCore
Read a cert / API keyISecretStore.GetSecretAsyncSecretDescriptor with payload + version + optional ExpiresOn (proactive cert reload)
Read a secret without throwing on 404ISecretStore.TryGetSecretAsyncnull only on not-found — 403 / 503 / config errors bubble
Get a DB username/passwordIDatabaseCredentialProviderCurrent pair + IsReady flag, refreshed in the background
Liveness probe for secretsAddGranitSecretStoreHealthCheck()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
PackageRoleDepends on
Granit.EncryptionIStringEncryptionService, AES-256-CBC providerGranit
Granit.Encryption.EntityFrameworkCore[Encrypted], EncryptedStringConverter, ApplyEncryptionConventionsGranit.Encryption, Granit.Persistence
Granit.Encryption.ReEncryptionIReEncryptionJob, DefaultReEncryptionJob<TContext>Granit.Encryption.EntityFrameworkCore
Granit.VaultAbstractions — ITransitEncryptionService, IDatabaseCredentialProvider, ISecretStore, SecretDescriptor, SecretStoreOptions, SecretStoreHealthCheck, ReEncryptionOptionsGranit.Caching, Granit.Encryption
Granit.Vault.HashiCorpVaultSharp client, Transit, dynamic DB credentials, KV v2 secretsGranit.Vault
Granit.Vault.AzureRSA-OAEP-256 encryption, secrets, dynamic credentialsGranit.Vault
Granit.Vault.AwsKMS transit encryption, Secrets ManagerGranit.Vault
Granit.Vault.GoogleCloudCloud KMS symmetric encryption, Secret ManagerGranit.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