In lesson 3.3.5 the Kladde got its first permissions table: one identity for the web app, roles for the group Kladde-Operations, an outlook on worker and pipeline. A lot has been added since. The web app hosts silos, a relay for the outbox and the sales module's subscription. One job fetches tenders at night, a second deletes expired bookings. In Key Vault there is a master key for personal data, in Service Bus a topic with a subscription, in the database a schema per module and an event store that may only grow.

This lesson pulls the rights of all identities down to what each one really needs, writes them down as a table per component, implements them in Bicep and checks the two against each other. At the end you find a role that nobody needs, and remove it.

One identity per component

Every component that runs code gets its own user-assigned managed identity:

Component Identity runs as
web id-kladde-web-<environment> Container App with web app, silo, outbox relay, sales subscription
worker id-kladde-worker-<environment> Container App of the import worker
tedJob id-kladde-tedjob-<environment> Container Apps job that queries TED at night
retention id-kladde-retention-<environment> Container Apps job that deletes expired data at night (lesson 4.8.3)

The pipeline has had its own identity with federated sign-in since module 3.7; it delivers and does not belong in this table of the runtime.

Why not one identity for all? For the three reasons of zero trust:

  • Assume breach: If the TED fetch is taken over, for example through a hole in the code that reads foreign responses, the attacker has exactly its rights: put references into a queue and write raw data. With the web app's identity they would have the contacts.
  • Least privilege: Every identity collects only the rights of its own task. A shared identity collects the sum of all.
  • Verify explicitly: The logs of Key Vault, Service Bus and Azure SQL say which identity did something. With a shared identity they say only “one of the apps”.