GMI · TECHNOLOGY OBSERVATORY // ALL SYSTEMS NOMINAL
ENGINEERED BY LEOPARD DATA

Secrets Management with Azure Key Vault

Azure Key Vault is the single source of truth for every secret. No credentials live in code or appsettings — identities authenticate themselves and the values map straight into .NET configuration.

SECRET FLOW
rendering diagram…
flowchart LR
    subgraph Consumers[Compute Identities]
        A([Web Apps]):::app
        B([Azure Functions]):::app
        C([Report VMs]):::app
    end
    ID{Managed Identity<br/>DefaultAzureCredential}:::sec
    KV[(Azure Key Vault<br/>soft-delete 90d<br/>purge protection)]:::sec
    CFG[GmiKeyVaultSecretManager<br/>kebab-case to nested<br/>IConfiguration]:::svc
    subgraph Downstream[Mapped Configuration Targets]
        S1[Stripe]:::ext
        S2[SendGrid]:::ext
        S3[Twilio]:::ext
        S4[FMP Market Data]:::ext
        S5[Anthropic Claude]:::ext
        DB[(Database)]:::data
    end
    A --> ID
    B --> ID
    C --> ID
    ID -->|read-only access| KV
    KV --> CFG
    CFG --> S1
    CFG --> S2
    CFG --> S3
    CFG --> S4
    CFG --> S5
    CFG --> DB
classDef app fill:#1f2630,stroke:#C0894E,color:#F3EFE8;
classDef svc fill:#241c14,stroke:#E2B583,color:#F3EFE8;
classDef data fill:#22201a,stroke:#C0894E,color:#EBD3AE;
classDef ext fill:#1b1f27,stroke:#5A616B,color:#C4C0B8;
classDef sec fill:#1d2733,stroke:#5cc8e0,color:#eafaff;
Apps, Functions and VMs authenticate with a managed identity, pull secrets from Key Vault, and have them mapped into IConfiguration to reach each downstream service.

A Single Source of Truth

Every secret the platform needs lives in Azure Key Vault and nowhere else. A custom GmiKeyVaultSecretManager bridges the gap between how secrets are named in the vault and how .NET configuration expects to see them, and the whole pipeline is wired up through a single AddGmiKeyVault() extension method.

Name Normalization

Azure's default secret manager flattens vault names in a way that does not match nested .NET configuration. The custom manager overrides that normalization to map kebab-case vault secret names onto nested configuration paths.

Vault secret name .NET configuration path
stripe-api-key Stripe:ApiKey
Opt-In by Environment

Key Vault integration only activates when the KEY_VAULT_NAME environment variable is set. This keeps local builds simple while making the vault authoritative wherever it is configured.

  • Trigger variable KEY_VAULT_NAME
  • Wired via AddGmiKeyVault()
  • Secrets in code None

Authentication

Authentication uses DefaultAzureCredential from Azure.Identity, which automatically picks the right credential for the running environment — no secret is ever needed to fetch the secrets.

  • In Azure — managed identity No credentials provisioned or rotated by hand
  • Locally — developer credentials The signed-in developer's Azure identity
  • No secrets in code or appsettings The bootstrap is itself secret-free

Packages

The integration is built on the official Azure SDK configuration packages:

Package Version
Azure.Identity 1.13.2
Azure.Extensions.AspNetCore.Configuration.Secrets 1.3.2

What Is Stored

Roughly ten secret types are stored in the vault, covering data, messaging, payments, market data, AI, and authentication:

  • Database connection string
  • SendGrid (email)
  • Stripe (payments)
  • Twilio (SMS)
  • FMP market data API key
  • Anthropic Claude API key
  • Google OAuth
  • and more

Vault Hardening

The vault itself is configured defensively against accidental loss and over-broad access:

  • Soft-delete retention 90 days
  • Purge protection Enabled

Soft-delete keeps deleted secrets recoverable for 90 days, and purge protection prevents anyone — even an administrator — from permanently deleting the vault or its secrets before that window elapses.

Access Policies — Scoped per Identity
Identity Access Rationale
Deployer Full Provisions and rotates secrets during deployment
Web apps Read-only Consume secrets at runtime, never write
VMs Read-only Background workers consume secrets at runtime