An Azure platform for running a service on AKS with Terraform, Argo CD, CI, observability and incident handling.
View the Project on GitHub Franklindot04/production-platform-Azure
Who and what has access, and how secrets are handled. Each statement names the file that defines it. Where a file does not define a role, the row says so.
Status values:
docs/architecture.md.docs/workflows.md.| Who or what | For | Role or permission | Scope | Defined in | Status |
|---|---|---|---|---|---|
| The maintainer | Owns the subscription and every environment | Owner | Every environment. ADR 0010 does not name the scope | Stated in docs/adr/0010-environment-separation.md. Not defined in code |
Applied |
| The maintainer | Reads and writes Terraform state | Storage Blob Data Contributor | The container tfstate-<env> |
infrastructure/bootstrap/bootstrap.sh (step role) |
Applied |
The maintainer, running terraform apply on the persistent root |
Sets Key Vault secret values by hand | Key Vault Secrets Officer, for the principal that runs apply | The Key Vault | infrastructure/environments/dev/persistent/workload-identity.tf |
Applied |
The maintainer, running terraform apply on the persistent root |
Creates the plan identity and its role assignments | Permission to create applications in the Entra tenant and to create role assignments. No role is named | The Entra tenant, and role assignments | infrastructure/environments/dev/persistent/README.md |
Applied. No role is named |
Pull request plan identity pp-<env>-pr-plan |
terraform plan on pull requests |
Reader | The persistent resource group | infrastructure/environments/dev/persistent/identity.tf |
Applied |
| Pull request plan identity | Reads Terraform state during a plan | Storage Blob Data Reader | The container tfstate-<env> |
infrastructure/environments/dev/persistent/identity.tf |
Applied |
Sample service identity pp-<env>-sample-service |
Reads one Key Vault secret from the pods | Key Vault Secrets User | The Key Vault | infrastructure/environments/dev/persistent/workload-identity.tf |
Applied |
| Workflows Terraform checks, Service, chart and manifest checks, Security scans | Checks on pull requests | contents: read |
The repository | .github/workflows/terraform-checks.yml, app-checks.yml, security-scans.yml |
In the repository |
| Workflow Terraform plan | Logs in to Azure as the plan identity | contents: read and id-token: write, in the one job. No permissions at the top |
The repository, and a token for the federated credential | .github/workflows/terraform-plan.yml |
In the repository |
| Workflow Publish image | Pushes the sample service image | contents: read at the top, and packages: write in the one job |
The repository and its package on ghcr.io |
.github/workflows/image-publish.yml |
In the repository |
| Workflow Open pull request for the image tag | Opens a pull request that changes the tag | contents: write and pull-requests: write, in the one job. No permissions at the top |
The repository | .github/workflows/image-tag-bump.yml |
In the repository |
| Argo CD admin credential | Signs in to Argo CD | The admin credential that Argo CD generates when it starts, read from the cluster when asked | The Argo CD instance of the session cluster | docs/adr/0017-argocd-bootstrap.md, infrastructure/session.sh (argocd-password) |
Written. The session layer is destroyed |
| Administrator credential of the session cluster | Lets session.sh bootstrap and argocd-password reach the cluster |
The local administrator credential from az aks get-credentials --admin |
The session cluster | infrastructure/session.sh (kube_setup), docs/adr/0021-entra-id-cluster-access.md |
Written. The session layer is destroyed |
| People, to the cluster | Day-to-day kubectl |
Entra ID sign-in with Azure RBAC for Kubernetes. The admin group is a run-time input | The session cluster | infrastructure/environments/dev/session/main.tf, variables.tf, docs/adr/0021-entra-id-cluster-access.md |
Written and not applied |
| The Entra ID admin group | Administers the cluster | Azure Kubernetes Service RBAC Cluster Admin | The session cluster | infrastructure/environments/dev/session/access.tf |
Written and not applied |
| The Entra ID admin group | Fetches the kubeconfig with az aks get-credentials |
Azure Kubernetes Service Cluster User Role | The session cluster | infrastructure/environments/dev/session/access.tf |
Written and not applied |
Notes on the rows:
repo:<owner>@<owner-id>/<name>@<repo-id>:pull_request. Pull requests from forks get no token and no secrets (.github/workflows/terraform-plan.yml). The plan identity has no role on the session resource group (docs/workflows.md).AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID and TFSTATE_ACCOUNT_NAME. They hold identifiers and no credential (docs/workflows.md). Publish image logs in to ghcr.io with the built-in token, and the tag workflow uses the built-in token.session.sh fetches is written outside the repository, with mode 600 (infrastructure/session.sh, kube_setup). The script stops if PP_PLAN_DIR is inside the repository.docs/adr/0021-entra-id-cluster-access.md).A new role, scope or permission is added in a pull request that changes this table in the same commit set as the code that grants it. A role that is not in the table is not granted.
sample-service-secret, which the sample service reads as a file (docs/adr/0029-workload-identity-and-vault-access.md).docs/workflows.md). They are not credentials.appinsights-connection is created at run time in the namespace observability (infrastructure/session.sh, create_collector_secret). It lives with the cluster.docs/adr/0029-workload-identity-and-vault-access.md). The alert email, the authorized ranges and the object ID of the admin group are run-time inputs (infrastructure/environments/dev/persistent/README.md, infrastructure/README.md, infrastructure/environments/dev/session/variables.tf)..gitignore covers *.tfvars, state files, kubeconfig, .env files, key and certificate files, and infrastructure/bootstrap/.state-account-name.docs/architecture.md, section 10)..github/workflows/security-scans.yml, ADR 0020). An accepted finding needs a reason in .trivyignore.yaml.session.sh replaces GUIDs and /32 host ranges (infrastructure/session.sh, mask).infrastructure/session.sh).
secrets part pipes the connection string from the Terraform output straight into the cluster.identity part passes the client ID, the tenant ID and the vault name to a helper in its environment, and sends the patch to kubectl on a pipe.argocd-password refuses to print unless the output is a terminal (ADR 0017).TF_VAR_alert_email and TF_VAR_api_authorized_ip_ranges are set in the shell and not written to a file.infrastructure/environments/dev/persistent/outputs.tf), the alert email, and the authorized ranges (infrastructure/README.md). It does not hold the Key Vault secret value (ADR 0029). Access is with Entra ID only, and shared key access is off. The maintainer has Storage Blob Data Contributor on the container. The plan identity will have Storage Blob Data Reader on it, so a pull request plan can read state. The plan output is not saved (docs/workflows.md).ghcr.io, and anyone can pull it.infrastructure/environments/dev/session/main.tf). To change the value, set a new one with the step in infrastructure/README.md. The pods read it after they restart (ADR 0029).appinsights-connection: session.sh bootstrap secrets creates it from the state again. No other rotation process is written.SECURITY.md, through private vulnerability reporting and not a public issue. The repository is public (ADR 0007), so treat a pushed value as exposed even after the commit is removed.infrastructure/README.md. The secret sample-service-secret is set..trivyignore.yaml (ADR 0029).