Runbook — Secret Rotation & Redeploy¶
This runbook covers rotation of every credential the portfolio uses and the procedure to rebuild the entire infrastructure from scratch. Follow sections in isolation when rotating individual secrets; follow the full-redeploy section when recovering from a deleted subscription or wiped resource group.
Secret Inventory¶
| Secret Name | Storage | Owner | Expiry | Breaks If Stale |
|---|---|---|---|---|
EMAIL_ADDRESS |
GitHub Secret + KV email-address |
Google Account | None (app password) | Contact form silently falls back to demo mode |
EMAIL_PASSWORD |
GitHub Secret + KV email-password |
Google Account | Manual (revoke in Google settings) | Contact form silently falls back to demo mode |
E2E_CLIENT_SECRET |
GitHub dev environment secret |
Entra portfolio-e2e-ci app |
Set in Entra portal (max 24 months) | Playwright E2E tests fail in CI |
entra_app_client_secret (Bicep param) |
GitHub Secret + KV entra-app-client-secret + SWA appsettings |
Entra dev SSO app | Set in Entra portal (max 24 months) | Dev frontend SSO login broken |
AZURE_CLIENT_ID |
GitHub Secret | Entra OIDC app registration | No expiry (federated credential) | All GitHub Actions workflows fail to authenticate |
AZURE_TENANT_ID |
GitHub Secret | Azure AD | No expiry | All GitHub Actions workflows fail to authenticate |
AZURE_SUBSCRIPTION_ID |
GitHub Secret | Azure | No expiry | All GitHub Actions workflows fail |
Email Credential Rotation¶
Email credentials are stored as GitHub Secrets and written to Key Vault on every infrastructure deploy via @secure() Bicep parameters.
Step 1 — Revoke the old Gmail app password¶
- Go to myaccount.google.com/apppasswords.
- Find the app password used for this project and click Remove.
Step 2 — Generate a new app password¶
- Still on the App Passwords page, select Mail as the app and the relevant device.
- Click Generate and copy the 16-character password that appears. Store it securely; Google will not show it again.
Step 3 — Update GitHub Secrets¶
Paste the Gmail address and new app password when prompted.
Step 4 — Re-deploy infrastructure to write new values to Key Vault¶
Trigger deploy-infrastructure.yml for both environments so Bicep overwrites the KV secrets:
Wait for both runs to succeed before continuing.
Step 5 — Force a Container App revision restart¶
The Container App caches KV secret values at startup. Restart the backend revision to pick up the new values:
Repeat for prod (dna-prd-portfolio-we-ca-be in dna-prod-portfolio-we-rg).
Verify¶
Submit a contact form request on both environments and confirm the email arrives or check Application Insights logs for email sent (vs demo mode).
Entra ID Client Secret Rotation¶
Two separate Entra app registrations carry client secrets:
portfolio-e2e-ci(client ID98ffb749-c54b-431a-82f3-df1bf271ed32) — used only by Playwright E2E CI viaE2E_CLIENT_SECRETin thedevGitHub environment.- Dev SSO app (client ID
b30b86ca-b372-4759-b02c-55a622dd19a0) — used by the dev frontend Container App and the dev SWA. Secret travels via Bicep parameterentra_app_client_secret→ KVentra-app-client-secret(Container App) and directly into SWA appsettings (SWA does not support KV reference syntax).
Step 1 — Create the new secret (do not delete the old one yet)¶
- Go to portal.azure.com → Azure Active Directory → App registrations.
- Open the relevant app registration.
- Select Certificates & secrets → New client secret.
- Set an appropriate expiry (maximum 24 months) and click Add.
- Copy the Value immediately.
Step 2 — Update GitHub Secrets¶
For the E2E secret:
For the dev SSO secret (Bicep parameter written to KV and SWA):
Step 3 — Re-deploy infrastructure for dev and verify¶
Wait for the workflow to succeed, then:
- Open the dev frontend and confirm Entra SSO login works.
- Check that the Playwright E2E job in the next PR preview deploy passes.
Step 4 — Delete the old secret¶
Once the new secret is confirmed working, return to the app registration's Certificates & secrets page and delete the previous secret.
SWA appsettings note¶
The dev SWA receives the client secret via swa_auth.bicep as a plain appsettings property (AZURE_CLIENT_SECRET). It does not use a Key Vault reference — SWA does not support the App Service KV reference syntax. The Bicep deploy in step 3 handles both the KV write and the SWA appsettings update automatically.
For prod, the SWA does not use Entra auth, so no prod rotation is required for SSO secrets.
OIDC Federated Credential Refresh¶
GitHub Actions authenticates to Azure using OIDC — no rotating password exists. Federated credentials are attached to the github-actions-portfolio-oidc Entra app registration and expire only if the app registration itself is deleted.
If federated credentials are missing or corrupted, recreate them using the provided scripts:
Verify existing credentials¶
The following subjects must exist:
| Credential name | Subject |
|---|---|
github-actions-main |
repo:SvenRelijveld1995/portfolio:ref:refs/heads/main |
github-actions-pr |
repo:SvenRelijveld1995/portfolio:pull_request |
github-actions-tags |
repo:SvenRelijveld1995/portfolio:ref:refs/tags/* |
github-actions-environment-prod |
repo:SvenRelijveld1995/portfolio:environment:prod |
github-actions-environment-dev |
repo:SvenRelijveld1995/portfolio:environment:dev |
github-actions-environment-shared |
repo:SvenRelijveld1995/portfolio:environment:shared |
github-actions-fix-branches |
repo:SvenRelijveld1995/portfolio:ref:refs/heads/fix/* |
github-actions-fix-deploy |
repo:SvenRelijveld1995/portfolio:ref:refs/heads/fix/deploy |
Recreate from scratch¶
If the app registration must be torn down and rebuilt entirely:
setup-oidc.sh will:
- Create (or reuse) the
github-actions-portfolio-oidcapp registration. - Create a service principal and assign
Contributor+Role Based Access Control Administrator+Key Vault Secrets Officerat subscription scope. - Add all required federated credentials listed above.
- Prompt for
EMAIL_ADDRESS/EMAIL_PASSWORDand set them as GitHub Secrets. - Print
AZURE_CLIENT_ID,AZURE_TENANT_ID, andAZURE_SUBSCRIPTION_IDto copy to GitHub Secrets.
After running the script, update the three OIDC GitHub Secrets if the app registration client ID changed:
Full Redeploy from Scratch¶
Use this section when recovering from a completely wiped subscription, deleted resource groups, or a failed initial setup. Follow the steps in order — skipping stages will cause role-assignment or DNS propagation failures.
Prerequisites¶
- Azure CLI installed and authenticated (
az login) - GitHub CLI installed and authenticated (
gh auth status) - Access to the Gmail account for email app password
- Access to the Entra portal for SSO app secrets
Step 1 — Recreate OIDC infrastructure¶
Update AZURE_CLIENT_ID, AZURE_TENANT_ID, and AZURE_SUBSCRIPTION_ID in GitHub Secrets if the script created a new app registration.
Step 2 — Deploy shared infrastructure (DNS zone)¶
This creates the Azure DNS zone. Wait for it to complete before proceeding — the DNS zone is referenced by the dev and prod infra deploys.
Step 3 — Deploy dev and prod infrastructure (containers off)¶
Run infrastructure for dev first, then prod. Pass deploy_containers=false on the first run to allow managed identity role assignments to propagate before Container Apps are created.
Step 4 — Wait for managed identity propagation¶
Role assignments for managed identities (ACR pull, Key Vault Secrets User) can take 5–10 minutes to propagate across Azure's RBAC cache. Wait before running step 5.
Step 5 — Re-deploy with containers enabled¶
Step 6 — Deploy application images¶
Build and push container images for each environment:
Step 7 — Deploy documentation (SWA)¶
Custom domain caveat¶
Re-attaching a custom domain after a full redeploy requires three separate Bicep stages (as documented in container_apps.bicep):
- Stage 1 — Deploy with
custom_domain=''anddeploy_custom_domain=falseto bring up the Container Apps Environment with no domain binding. This is covered by step 3 above. - Stage 2 — Deploy the shared DNS zone (
deploy-infrastructure-shared.yml), then re-run withcustom_domainset anddeploy_custom_domain=false. This registers the hostname in the Container Apps Environment and creates DNS records. - Stage 3 — Re-run with
deploy_custom_domain=trueonce DNS has propagated. Azure issues a managed certificate and binds it.
Do not attempt to skip to stage 3 directly — the managed certificate issuance will fail if the hostname was not pre-registered in stage 2.
Key Vault soft-delete caveat¶
Key Vault has softDeleteRetentionInDays: 7 and enablePurgeProtection: true. If a vault is deleted and then a redeploy attempts to create a vault with the same name within the 7-day window, the deployment will fail with a conflict.
To recover the existing vault instead of creating a new one:
To check for soft-deleted vaults:
Alternatively, if the vault was deleted more than 7 days ago, purge protection will have expired and the name can be reused:
To recover individual secrets that were soft-deleted within a recovered vault:
Next Steps¶
- Security — KV RBAC, managed identities, email credentials flow
- OIDC & Secrets — full GitHub Secrets reference and federated credential setup
- Pipeline Overview — all workflows and their triggers
Page history
| Field | Value |
|---|---|
| Last updated | 2026-04-09 |
Changelog
| Date | PRs | Summary |
|---|---|---|
| 2026-04-09 | Initial runbook for secret rotation and full redeploy procedures |