Warmup Guide — Secrets, Key Vault & Managed Identity

Zero-to-principal primer for Phase 12: the one principle that organizes all of cloud secret management (the most secure secret is the one you never store), how a Managed Identity turns that principle into a mechanism via IMDS, what a Key Vault actually is (a versioned, RBAC-gated, soft-deletable store where control plane is a different system from data plane), and the envelope encryption trick that makes key rotation cost a few bytes instead of a petabyte. Every concept goes from what it is to why it exists to the mechanism under the hood (diagrams, tables, code) to production significance to the misconceptions that cause breaches and 2 a.m. pages.

Table of Contents


Chapter 1: The One Principle — Don't Store the Secret

From zero. A secret is anything that, if an attacker has it, lets them act as you: a password, an API key, a connection string, a private key, a client secret. The entire discipline of secret management exists because secrets spread. You put a connection string in a config file; the config file goes into git; git gets cloned to ten laptops; one laptop gets a crash dump uploaded to a support portal; now your database password is in a vendor's ticketing system. Every link in that chain is a place the secret can leak, and you control none of them.

The principal-level reframing — the sentence that organizes this whole phase — is:

The most secure secret is the one you never store.

If there is no stored secret, there is nothing to leak, rotate, or expire by surprise. This is not a slogan; it is an engineering target, and Azure gives you the mechanisms to hit it:

  • For one Azure resource calling another Azure service (a Function calling Storage, a VM calling Key Vault), you can have zero stored secrets — a Managed Identity (Ch. 2) gets a short-lived token from the platform on demand.
  • For secrets you cannot eliminate (a third-party API key, a partner's database password, a TLS cert you must present), you store them in one hardened, audited, access- controlled place — Key Vault (Ch. 4) — and read them using the managed identity, so the only "credential" the app holds is, again, the managed identity (which isn't a stored secret).
  • For encryption, you don't store the data's key with the data; you wrap it with a key that lives in the vault/HSM — envelope encryption (Ch. 5).

The reducible thing — the one credential you can't make disappear — is called secret-zero. The principal's job is to make secret-zero a managed identity (a platform-issued, auto- rotated, audience-scoped token) rather than a string in a file. Hold this principle; every mechanism below is in service of it.

Chapter 2: Managed Identity — An Azure-Managed Service Principal

What it is. A Managed Identity is a special kind of Entra (Azure AD) service principal whose credential is created, stored, and rotated by Azure itself. Your code never sees the credential. When the code needs to call an Azure service, it asks the platform for a short-lived access token (a JWT — you validated those in P03) and presents that. No password, no client secret, no certificate in your code or config.

Why it exists. Before managed identities, an app authenticated to Azure as an Entra app registration with a client secret or certificate — a real credential you had to generate, store somewhere (ironically, often in Key Vault, which you then needed another credential to reach), and rotate before it expired. Expiring client secrets are a perennial outage cause: the secret silently lapses at midnight and the app starts returning 401s with nobody knowing where the secret was even configured. Managed identities delete that entire class of problem by making Azure own the credential lifecycle.

The two kinds — this is an interview staple:

System-assignedUser-assigned
Lifecyclecreated with the resource, deleted with ita standalone Azure resource; independent lifecycle
Countexactly one per resourceattach one identity to many resources
Identity =the resource itselfa shareable identity object
Selected by(implicit — the resource's own)its client_id (or object id / resource id)
Use whenthe identity's life is the resource's; nothing else needs itseveral resources share one identity (grant RBAC once), or the identity must exist before the resource (break a deploy ordering chicken-and-egg)
System-assigned:                    User-assigned:
┌───────────┐                        ┌──────────────────┐
│ Function  │── owns ──▶ Identity    │  Identity (UAI)  │
└───────────┘  (1:1, dies with it)   └───────┬──────────┘
                                       attached to ▼  ▼  ▼
                                     Function   VM   AKS pod   (1 identity → N resources)

The principal nuance. User-assigned wins more often than juniors expect, for two reasons. (1) RBAC grant amplification: if 30 functions need Key Vault Secrets User, 30 system-assigned identities means 30 role assignments to manage; one user-assigned identity means one. (2) Deploy ordering: a system-assigned identity doesn't exist until the resource does, so you can't pre-assign its roles in the same template without a two-phase deploy; a user-assigned identity you create first, grant roles to, then attach — the deploy is clean and idempotent.

Chapter 3: IMDS and the Token Flow

What it is. The Instance Metadata Service (IMDS) is a read-only REST endpoint available inside every Azure VM / App Service / Function / container at the link-local address 169.254.169.254. Link-local means it is non-routable — packets to it never leave the host; only code on that resource can reach it. Among other metadata, IMDS issues OAuth2 access tokens for the resource's managed identity:

GET http://169.254.169.254/metadata/identity/oauth2/token
      ?api-version=2018-02-01
      &resource=https://vault.azure.net          ◀── the AUDIENCE you want a token for
   Header: Metadata: true                         ◀── anti-SSRF guard (a browser can't set it trivially)

Why the IP is non-routable — and why that's the security model. Because 169.254.169.254 is reachable only from the host, possession of "I can call IMDS" is the proof of identity. There is no secret to present because being able to ask is the credential. (This is also why SSRF — tricking a server into making a request on your behalf — is the canonical attack on IMDS: if you can make the app fetch a URL you control, you make it fetch IMDS and steal its token. IMDS v2 mitigations and the Metadata: true header requirement exist for exactly this.)

The flow, end to end:

your code                     IMDS (169.254.169.254)        Entra            Key Vault
   │  get_token("vault")            │                         │                  │
   ├───────────────────────────────▶│ "give me a token,       │                  │
   │                                │  aud=vault.azure.net"    │                  │
   │                                ├─────────────────────────▶│ mint JWT (aud,   │
   │                                │◀─────────────────────────┤  exp, oid, tid)  │
   │◀───────────────────────────────┤  {access_token, expires_on, resource}      │
   │   GET /secrets/db-pwd  (Bearer <token>)  ───────────────────────────────────▶│
   │◀──────────────────────────────────────────────────── 200 {value}  (or 403)  │

The audience (resource/scope) is load-bearing. The token's aud claim names the one service it's valid for. A token minted for https://vault.azure.net is rejected by https://storage.azure.com. The most common "it returns a token but the call 401s" bug is asking IMDS for the wrong audience. In the lab, token_is_valid(token, resource, now) checks both the audience match and the expiry — because those are the two independent ways a token silently fails.

Caching — the production-critical part. A function that reads Key Vault in a hot loop must not call IMDS on every iteration; that would hammer the metadata endpoint and add latency to every call. The credential SDK (DefaultAzureCredential / ManagedIdentityCredential) caches the token per (identity, audience) and only refetches when it's near expiry. The lab models exactly this:

key = (identity.principal_key, resource)
cached = self._cache.get(key)
if cached is not None and token_is_valid(cached, resource, now, skew_s=self.refresh_skew_s):
    return cached            # ◀── cache hit: SAME token, no IMDS call
# else mint, cache, count an imds hit

The refresh skew (refresh_skew_s) makes the SDK refetch before the hard expiry — so a request never races a token expiring mid-flight. In the lab, imds_calls counts real fetches; three acquire_token calls (before expiry, before expiry, after expiry) produce exactly two IMDS hits. That assertion is the caching contract.

Misconception to kill: "Managed identity = no token." No — there is very much a token (a full JWT with aud, exp, oid, tid, xms_mirid). What's missing is a stored credential. The token is short-lived, audience-scoped, and fetched on demand; it just isn't a secret you saved anywhere.

Beyond IMDS — workload identity federation (no secret at all). For AKS pods and CI runners (GitHub Actions, Azure DevOps), there's an even cleaner pattern: the workload presents an external OIDC token (issued by Kubernetes or GitHub), and Entra exchanges it for an Azure access token via a configured federated credential trust — no client secret, no IMDS. The trust is the OIDC issuer + subject (e.g. repo:contoso/app:ref:refs/heads/main). It's the same "don't store the secret" principle projected across a trust boundary; you built the OIDC validation half in P03/P08.

Chapter 4: Key Vault — The Versioned, RBAC-Gated Store

What it is. Azure Key Vault is a managed service for storing and controlling access to three kinds of object, all versioned:

ObjectWhat it isKey data-plane ops
Secretan opaque value (string, ≤ 25 KB) — API keys, connection stringsget, set, list, delete
Keyan asymmetric/symmetric key you use without exportingencrypt/decrypt, wrapKey/unwrapKey, sign/verify
Certificatean X.509 cert + its key, with a lifecyclecreate, import, auto-rotate, CA policy

The critical word is versioned. Every write creates a new immutable version with its own id and URL:

https://myvault.vault.azure.net/secrets/db-password/9f3c...     ◀── a specific version
https://myvault.vault.azure.net/secrets/db-password             ◀── "current" (latest)

Old versions stay readable. This is what makes rotation non-breaking: a client pinned to version 9f3c… keeps working while you set a new current version, and you flip consumers over deliberately.

Why it exists. Centralizing secrets into one hardened, audited, access-controlled service is the only way to make the chaos of Chapter 1 tractable: one place to rotate, one audit log of who read what, one firewall/private-endpoint boundary, one backup. The alternative — secrets scattered across config files, pipelines, and env vars — is unauditable and unrotatable.

The two authorization models. This is a real design decision, not trivia:

  • Azure RBAC (recommended). The same role-assignment engine as the rest of Azure (you built it in P04). Roles like Key Vault Secrets User (read secrets), Key Vault Secrets Officer (read+write), Key Vault Administrator, Key Vault Crypto User. Assignments inherit down the MG → Sub → RG → vault → individual secret hierarchy, are deny-aware, and are auditable like everything else. Default-deny: no matching assignment → no access.
  • Access policies (legacy). A per-vault list of (principal → allowed operations). Simpler to reason about for one vault, but not inheritable, not deny-aware, capped in count, and harder to audit at scale. New vaults should use RBAC.

Control plane vs data plane — Key Vault is the sharpest example in all of Azure. Burn this in:

CONTROL PLANE (ARM + RBAC)              DATA PLANE (vault endpoint + data-plane RBAC)
────────────────────────────           ─────────────────────────────────────────────
create / delete the vault              read a secret's VALUE
configure firewall, networking         wrap / unwrap / sign with a key
SET the authorization model            list secret names
roles: Owner, Contributor              roles: Key Vault Secrets User, Crypto User

An Owner of the vault can delete the entire thing but gets a 403 reading a single secret, because reading a value is a data-plane action and Owner is a control-plane role. This trips up nearly everyone once. In the lab:

owner_only = [RoleAssignment(principal=app, role="Owner")]
kv.get_secret("api-key", principal=app, role_assignments=owner_only)   # ▶ PermissionError
reader = [RoleAssignment(principal=app, role="Key Vault Secrets User")]
kv.get_secret("api-key", principal=app, role_assignments=reader)       # ▶ ok

(There is a deliberate exception in real Azure: with RBAC, a control-plane Owner can grant themselves a data-plane role — but holding Owner alone does not read secrets.)

Chapter 5: Envelope Encryption — DEK, KEK, and the Wrap

The problem. You want "encryption at rest with a key you control" for a 50 TB storage account. The naive approach — encrypt all 50 TB directly with a Key Vault key — fails for two reasons: (1) Key Vault keys (especially RSA) are slow and rate-limited; you can't push 50 TB through them, and (2) when you rotate the key you'd have to re-encrypt all 50 TB, which is absurd.

The solution — envelope encryption. Use two keys:

  • A DEK (data encryption key) — a fast symmetric key (AES) that actually encrypts the data. It can be per-object, generated fresh from a CSPRNG.
  • A KEK (key encryption key) — a key that lives in Key Vault / the HSM and never leaves it. You use it only to wrap (encrypt) the tiny DEK.
            ┌─────────────── stored together at rest ───────────────┐
 data ──▶ [ enc with DEK ] ──▶ ciphertext                            │
 DEK  ──▶ [ wrap with KEK in the vault/HSM ] ──▶ wrapped_DEK         │
            └────────────────────────────────────────────────────────┘
 KEK stays in the vault — only its wrap/unwrap operations are called over the wire.

To decrypt: send wrapped_DEK to the vault, get the DEK back (the vault unwrapKeys it with the KEK that never left), then decrypt the ciphertext locally with the DEK. The lab's envelope_encrypt/envelope_decrypt are exactly this:

def envelope_encrypt(kek, plaintext, dek):
    ciphertext = enc(dek, plaintext)        # fast symmetric over the data
    wrapped    = wrap_dek(kek, dek)         # encrypt the small DEK under the KEK
    return ciphertext, wrapped              # persist BOTH

def envelope_decrypt(kek, ciphertext, wrapped_dek):
    dek = unwrap_dek(kek, wrapped_dek)      # KEK unwraps the DEK
    return dec(dek, ciphertext)             # DEK decrypts the data

⚠️ The lab's cipher is a TEACHING cipher — SHA-256 counter-mode keystream XORed into the plaintext, with no authentication, no nonce, and keystream reuse. It is structurally identical to real envelope encryption (DEK/KEK, wrap/unwrap, rewrap-on- rotate), which is the entire lesson, but it must never encrypt anything real. Azure uses AES-GCM for the data and RSA-OAEP / AES-KW for the wrap, inside FIPS-validated HSMs.

Chapter 6: Key Rotation and Versioning

Why envelope encryption is worth it: rotation becomes cheap. Here is the payoff that makes the whole construction worthwhile. To rotate the KEK (because it's old, or suspected compromised, or policy says rotate every 90 days), you do not re-encrypt the data. You:

def rotate_kek(old_kek, new_kek, wrapped_dek):
    dek = unwrap_dek(old_kek, wrapped_dek)   # 1. unwrap the DEK with the old KEK
    return wrap_dek(new_kek, dek)            # 2. rewrap it with the new KEK
    # the DATA ciphertext is NEVER touched.

The cost of rotating the KEK is O(number of DEKs), not O(bytes of data). Rotating the key for a 50 TB account rewraps a handful of DEKs — milliseconds — and the 50 TB of ciphertext is byte-for-byte unchanged. The lab proves this with an assertion that the data ciphertext object is identical before and after rotation:

ciphertext_before = bytes(ciphertext)
new_wrapped = rotate_kek(old_kek, new_kek, wrapped)
assert ciphertext == ciphertext_before        # ◀── the whole point
assert envelope_decrypt(new_kek, ciphertext, new_wrapped) == plaintext

Key versioning makes rotation non-breaking. When you rotate, the new KEK is a new version of the same key object. Old wrapped DEKs were wrapped against the old version, which still exists, so they still unwrap — there's no "rotate and everything that hasn't been rewrapped breaks" cliff. You rewrap lazily or on a schedule. The same versioning logic that keeps a pinned secret version readable (Ch. 4) keeps old key versions usable for decryption.

Production significance. This is the literal engine behind customer-managed keys (CMK) for Azure Storage, SQL, Disk, and Cosmos: the service stores a wrapped DEK, calls your Key Vault to unwrap it via its managed identity, and when you rotate your KEK it rewraps — your data is never re-encrypted, and if you disable the KEK, the wrapped DEKs can no longer be unwrapped and the data is cryptographically unreadable (the revocation / "crypto-shredding" story). Rotation is a few bytes; revocation is instant; neither touches the data. That is the elegance.

Chapter 7: Soft-Delete and Purge Protection

The problem. Secrets and keys get deleted — by accident (a bad script, a fat finger), by malice (a disgruntled admin, ransomware), or as collateral in a wrong cleanup. If a delete is immediate and permanent, a single mistake or a single compromised credential can destroy the keys protecting your data — which, with envelope encryption, means destroying the data. That is an unacceptable single point of catastrophe.

Soft-delete (always on now). A delete does not remove the bytes; it moves the object to a deleted-but-recoverable state for a retention window (configurable 7–90 days, default 90). Within that window you can recover it. The state machine the lab implements:

        set_secret              delete_secret(now)            recover_secret(now < purge_on)
 (none) ──────────▶ [ active ] ───────────────────▶ [ soft-deleted ] ──────────────────────▶ [ active ]
                        ▲                              │  recoverable until                       
                        │                              │  scheduled_purge_on = deleted_on+retention
                        └──────────────────────────────┘
                                                       │  purge_secret(now)
                                                       ▼
                                              [ gone ]  (permanent)
def delete_secret(self, name, now):
    entry.deleted = True
    entry.scheduled_purge_on = now + self.retention_s   # recoverable until here

While soft-deleted, the secret is not readable (get_secret raises) but its bytes are not gone. After retention elapses it is auto-purged and recover fails.

Purge protection — the security guarantee. Soft-delete alone isn't enough against a determined attacker: if they can delete and purge, they make the deletion permanent immediately. Purge protection, when enabled (and it's irreversible once on), means a soft-deleted object cannot be purged before the retention window elapsesby anyone, including a Global Admin, including the attacker. The lab:

def purge_secret(self, name, now):
    if self.purge_protection and now < entry.scheduled_purge_on:
        raise PermissionError("purge protection: cannot purge until retention elapses")
    del self._secrets[name]

So even a fully compromised admin credential cannot make the deletion permanent — you have a guaranteed window to detect the deletion (it fires an event/alert) and recover. Enable soft-delete and purge protection on any vault that protects something you can't afford to lose. The tradeoff: you cannot reuse a vault/secret name until retention elapses, which occasionally annoys CI that creates-and-destroys vaults — a small price.

Chapter 8: Managed HSM, FIPS, and Certificates

Standard Key Vault vs Managed HSM. Standard Key Vault is multi-tenant and validated to FIPS 140-2 Level 2 (software-protected keys; HSM-backed keys in the Premium SKU). Azure Key Vault Managed HSM is a single-tenant, fully customer-controlled pool of FIPS 140-2 Level 3 hardware HSMs — the key material is generated and used only inside the HSM, never extractable, with cryptographic attestation. You reach for Managed HSM for regulated workloads (PCI, FIPS L3 mandates), the highest key-isolation requirements, or when you need a security-domain you alone control. It costs meaningfully more and has its own local RBAC roles — the cost/isolation tradeoff is a principal decision, not a default.

Key Vault (Standard/Premium)Managed HSM
Tenancymulti-tenantsingle-tenant
FIPS 140-2Level 2 (Premium: HSM-backed keys)Level 3
Key extractableno (Premium HSM keys)never
Use whenmost apps, secrets, certsregulated / highest isolation

Certificates. A Key Vault certificate bundles a private key + an X.509 cert and adds a lifecycle: issuance (self-signed, or via an integrated CA like DigiCert), an issuance policy, and auto-rotation (Key Vault renews the cert a configured number of days before expiry, generating a new version — the same versioning model). The principal value is the same as everywhere in this phase: the renewal is automated and the private key never leaves the vault, so a cert expiring at midnight stops being an outage.

Chapter 9: Where Secrets Sit in the Platform

Secrets and identity are not a corner of the platform — they are the connective tissue that makes every other phase secure:

  • P03 (Entra/JWT) — a managed-identity token is a JWT; IMDS is a token endpoint; the target service validates aud/exp/signature with the validator you built.
  • P04 (RBAC) — Key Vault's recommended authorization is the same role + inheritance + default-deny engine; "read a secret" is a DataAction.
  • P11 (Functions/Durable) — a function authenticates outbound (to Storage, to Key Vault, to another API) with its managed identity; Key Vault references inject secrets into app settings, resolved via that identity, so the code holds nothing.
  • P10 (Event Grid/Service Bus) — Key Vault emits SecretNearExpiry/SecretExpired events; an event-driven rotation function reacts with no human in the loop.
  • P08 (CI/CD) — pipelines authenticate via workload identity federation (OIDC → Entra, no stored secret) and read deploy-time secrets from a scoped vault.
  • P00 (control vs data plane) — Key Vault is the canonical example, and the lesson ("Owner ≠ secret reader") is one you'll apply to Storage, Service Bus, and everything else.

The mental model: identity (a managed identity) replaces the credential; Key Vault holds the irreducible secrets; envelope encryption protects the data; RBAC gates the data plane; and soft-delete/purge-protection make the whole thing recoverable. Those five sentences are the phase.

Lab Walkthrough Guidance

Lab 01 — Key Vault & Managed Identity, suggested order (matches lab.py top-to-bottom):

  1. Identity + system_assigned/user_assigned (Ch. 2) — the validation is the lesson: system-assigned has no client_id; user-assigned requires one. principal_key is the stable cache key.
  2. InstanceMetadataService.acquire_token + token_is_valid (Ch. 3) — the centerpiece of Part 1. Build the cache: look up (identity, resource); return the cached token if still valid with the refresh skew; else mint (expires_on = now + ttl), cache, bump imds_calls. token_is_valid checks audience and expiry. Make test_token_cached_before_expiry_same_token and ..._refreshed_after_expiry_... pass — that pair is the caching contract.
  3. keystream/enc/dec (Ch. 5) — SHA-256 counter mode; dec is just enc. Get the round-trip green first.
  4. generate_dek/wrap_dek/unwrap_dek — thin wrappers over enc/dec; the naming is the teaching (a DEK wrapped under a KEK).
  5. envelope_encrypt/envelope_decrypt/rotate_kek (Ch. 5–6) — the payoff. Make test_kek_rotation_data_ciphertext_object_unchanged pass and understand why it's the whole point: rotation rewraps the DEK only.
  6. KeyVault versioning + RBAC (Ch. 4) — set_secret appends a version; get_secret returns latest or a pinned version and denies by default unless the principal holds a data-plane role (Owner is not one).
  7. Soft-delete state machine (Ch. 7) — deleterecover (within retention) → purge (blocked by purge protection inside retention). Make the purge-protection test pass and you own the security guarantee.

Run it red, make it green: pytest test_lab.py -v, then LAB_MODULE=solution pytest test_lab.py -v, then python solution.py.

Success Criteria

You are ready for the next phase when you can, from memory:

  1. State the one principle ("don't store the secret") and name secret-zero.
  2. Draw the managed-identity token flow code → IMDS (169.254.169.254) → Entra → token (aud=vault) → vault, and explain why the IP is non-routable and why the token is cached.
  3. Pick system- vs user-assigned with a concrete reason for each (RBAC grant amplification; deploy ordering).
  4. Explain why an Owner of a Key Vault gets a 403 reading a secret (control vs data plane).
  5. Choose RBAC over access policies and say why (inheritable, deny-aware, auditable).
  6. Explain envelope encryption (DEK/KEK/wrap) and why KEK rotation is O(#DEKs), not O(bytes) — and never re-encrypts the data.
  7. Explain soft-delete vs purge, and exactly what purge protection guarantees against a compromised admin.

Interview Q&A

Q: "My app needs to read a database password and call a storage account. Walk me through a design with no stored secrets." The storage hop has zero stored secrets: give the app a managed identity and a data-plane role (Storage Blob Data Reader); it gets an audience-scoped token from IMDS on demand. The database password I can't eliminate (the DB isn't Entra-native, say), so it goes in Key Vault — but the app reads it using the same managed identity (granted Key Vault Secrets User), so the only "credential" anywhere is the managed identity, which isn't a stored secret. If the app is a Function, I'd use a Key Vault reference so the secret is injected as an app setting resolved via the identity, and the code never even touches the vault SDK. Net: secret-zero is a managed identity, not a string in a file.

Q: "I gave my service principal Owner on the Key Vault and it still can't read the secret. Why?" Because reading a secret's value is a data-plane action and Owner is a control-plane role. Owner/Contributor let you manage the vault resource — create it, delete it, set its firewall, even change its authorization model — but not read what's inside. You need a data-plane role like Key Vault Secrets User. This control-plane-vs-data-plane split is the single most common Key Vault 403 and shows up identically across Azure (an account Owner isn't a Blob Data Reader either).

Q: "Explain envelope encryption and why you'd use it for encryption at rest." You encrypt the data with a fast symmetric DEK, then wrap (encrypt) the small DEK with a KEK that lives in Key Vault / an HSM and never leaves it; you store the ciphertext and the wrapped DEK together. Two payoffs. Performance: the bulk crypto is local AES, not slow rate-limited vault calls. Rotation: to rotate the KEK you only unwrap the DEK with the old key and rewrap with the new — O(#DEKs), never re-encrypting the data — and key versioning keeps old wrapped DEKs decryptable so rotation is non-breaking. It's the engine behind Azure's customer-managed-key encryption, and it also gives you instant revocation: disable the KEK and every wrapped DEK becomes unusable, so the data is cryptographically unreadable without ever touching it.

Q: "What does soft-delete with purge protection actually protect you from?" Soft-delete makes a delete recoverable for a retention window (default 90 days) — it defends against accidents. Purge protection defends against malice: when it's on (irreversibly), a soft-deleted object cannot be purged before retention elapses by anyone, including a Global Admin or a fully compromised credential. So an attacker who deletes your keys can't make it permanent on demand; you get a guaranteed window — during which the deletion fires an alert — to detect and recover. Without purge protection, delete + purge is an instant, irreversible loss; with it, deletion can't be weaponized. I enable both on anything I can't afford to lose.

Q: "System-assigned or user-assigned managed identity — how do you choose?" System-assigned when the identity's lifecycle is the resource's and nothing else needs that identity — it's created and destroyed with the resource, one-to-one, nothing to clean up. User-assigned when (a) several resources should share one identity so I grant the RBAC role once instead of N times, or (b) the identity must exist before the resource so I can pre-assign its roles and avoid a two-phase deploy (a system-assigned identity doesn't exist until its resource does, which creates a chicken-and-egg in IaC). At scale I lean user-assigned for the RBAC-grant economy and clean deploy ordering.

Q: "How does a CI pipeline authenticate to Azure without a stored secret?" Workload identity federation. The pipeline (GitHub Actions / Azure DevOps) presents the OIDC token its platform issues; I configure a federated credential on an Entra app / user-assigned identity that trusts that issuer + subject (e.g. repo:org/app:ref:refs/heads/main); Entra exchanges the external OIDC token for an Azure access token. No client secret to store, rotate, or leak. It's the same "don't store the secret" principle as a managed identity, extended across the cloud boundary — and it's why secretless OIDC deploys are now the standard over the old service-principal-with-a-secret pattern.

References

  • Microsoft Learn — What are managed identities for Azure resources? (https://learn.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview)
  • Microsoft Learn — Azure Instance Metadata Service (IMDS) and the identity token endpoint (https://learn.microsoft.com/azure/virtual-machines/instance-metadata-service)
  • Microsoft Learn — Azure Key Vault basic concepts and Authentication / RBAC vs access policies (https://learn.microsoft.com/azure/key-vault/general/basic-concepts)
  • Microsoft Learn — Key Vault soft-delete overview and purge protection (https://learn.microsoft.com/azure/key-vault/general/soft-delete-overview)
  • Microsoft Learn — Azure Storage encryption / envelope encryption with customer-managed keys (https://learn.microsoft.com/azure/storage/common/storage-service-encryption)
  • Microsoft Learn — Azure Key Vault Managed HSM and FIPS 140-2 Level 3 (https://learn.microsoft.com/azure/key-vault/managed-hsm/overview)
  • Microsoft Learn — Workload identity federation (https://learn.microsoft.com/azure/active-directory/workload-identities/workload-identity-federation)
  • RFC 6749 (OAuth 2.0) and RFC 7519 (JSON Web Token) — the token the lab models is a JWT (you validate it in P03)
  • The track's own CHEATSHEET.md and GLOSSARY.md