Secrets & Access Management Runbook
Secrets & Access Management Runbook
Sección titulada «Secrets & Access Management Runbook»Policy: All secrets are stored as GitHub Actions Secrets — never in code or config files.
1. Secrets Inventory
Sección titulada «1. Secrets Inventory»| Secret Name | What It Grants | Used By Workflows | Expiry | Where Stored |
|---|---|---|---|---|
INEGI_API_TOKEN | Read access to INEGI data APIs (census indicators, DENUE economic units) | data-ingest.yml | Check INEGI developer portal (typically no hard expiry, but can be revoked) | GitHub Actions Secret |
R2_ACCOUNT_ID | Cloudflare Account ID scoping R2 API calls | data-ingest.yml, data-transform.yml | Never (static identifier) | GitHub Actions Secret |
R2_ACCESS_KEY_ID | S3-compatible access key for urban-transparency-pipeline-rw token | data-ingest.yml, data-transform.yml | No expiry unless rotated manually | GitHub Actions Secret |
R2_SECRET_ACCESS_KEY | Secret half of the urban-transparency-pipeline-rw R2 token | data-ingest.yml, data-transform.yml | No expiry unless rotated manually | GitHub Actions Secret |
CF_ACCOUNT_ID | Cloudflare Account ID for Pages deployment | deploy.yml | Never (static identifier) | GitHub Actions Secret |
CF_API_TOKEN | Cloudflare API token scoped to Pages deploy only | deploy.yml | No expiry unless revoked manually | GitHub Actions Secret |
GITHUB_TOKEN | GitHub API token for posting PR preview comments | deploy.yml | Auto-rotated per workflow run | Auto-injected by GitHub Actions — no setup required |
Note: R2_ACCOUNT_ID and CF_ACCOUNT_ID hold the same Cloudflare Account ID value. They are separate secrets because they are consumed by workflows that authenticate to different Cloudflare services with different API tokens.
2. Token Scoping
Sección titulada «2. Token Scoping»urban-transparency-pipeline-rw (R2 token → R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY)
Sección titulada «urban-transparency-pipeline-rw (R2 token → R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY)»- Permissions:
Account → R2 Storage → Edit(read, write, delete objects) - Scope:
urban-transparency-raw+urban-transparency-processedbuckets only - Do NOT grant Workers KV, D1, or other Cloudflare services
CF_API_TOKEN (Pages deploy token)
Sección titulada «CF_API_TOKEN (Pages deploy token)»- Permissions:
Account → Cloudflare Pages → Edit - Scope: the
urban-transparency-platformPages project only - Do NOT grant R2, Workers, or DNS edit permissions
INEGI_API_TOKEN
Sección titulada «INEGI_API_TOKEN»- Obtained from the INEGI developer portal
- Read-only access to public statistical APIs
- No write capabilities
3. Rotation Procedures
Sección titulada «3. Rotation Procedures»Rotate R2 API Keys (R2_ACCESS_KEY_ID + R2_SECRET_ACCESS_KEY)
Sección titulada «Rotate R2 API Keys (R2_ACCESS_KEY_ID + R2_SECRET_ACCESS_KEY)»- Go to Cloudflare Dashboard → R2 → Manage R2 API Tokens
- Select
urban-transparency-pipeline-rw - Click Roll Token (generates new Access Key ID + Secret)
- Note both values immediately (shown once)
- Update GitHub Actions Secrets:
R2_ACCESS_KEY_ID← new Access Key IDR2_SECRET_ACCESS_KEY← new Secret Access Key
- Delete the old token entry
- Verify next pipeline run succeeds
Rotate CF_API_TOKEN
Sección titulada «Rotate CF_API_TOKEN»- Go to Cloudflare Dashboard → My Profile → API Tokens
- Find the Pages deploy token, click Roll
- Note the new token value
- Update GitHub Actions Secret
CF_API_TOKEN - Verify next
deploy.ymlrun succeeds
Rotate INEGI_API_TOKEN
Sección titulada «Rotate INEGI_API_TOKEN»- Log into INEGI developer portal
- Navigate to your application → Regenerate Token
- Update GitHub Actions Secret
INEGI_API_TOKEN - Verify next
data-ingest.ymlrun completes theinegi-ingestjob
4. Onboarding Runbook
Sección titulada «4. Onboarding Runbook»For a New Board Member (human) who needs to manage secrets
Sección titulada «For a New Board Member (human) who needs to manage secrets»Prerequisites: GitHub organization owner or admin role on the repository.
- Cloudflare access: Request a Cloudflare account invitation with the
Administratorrole scoped to the relevant account from the existing account owner. - GitHub repo access: Request
Adminrole on the GitHub repository to be able to manage Actions Secrets. - Review existing secrets: Go to
GitHub repo → Settings → Secrets and variables → Actionsto view secret names (values are masked). - Review this document to understand what each secret does before rotating or modifying.
For a New Agent (automated) that needs to run pipelines
Sección titulada «For a New Agent (automated) that needs to run pipelines»Agents do not receive direct secret access. All pipeline execution is via GitHub Actions. An agent that needs to trigger workflows should:
- Receive the
GITHUB_TOKENpattern via the workflow’s built-in token (no additional setup). - If a custom PAT is needed to trigger
workflow_dispatchfrom another workflow, request a scoped fine-grained PAT from a board member:- Scope:
Actions → Writeon the specific repository only - Store as a new named secret (e.g.,
GH_DISPATCH_TOKEN)
- Scope:
Secrets an operator needs when provisioning from scratch
Sección titulada «Secrets an operator needs when provisioning from scratch»If standing up a fresh environment (new Cloudflare account, new GitHub repo), collect these secrets in order:
| Step | Action | Secret(s) Created |
|---|---|---|
| 1 | Note your Cloudflare Account ID (Dashboard → top-right account menu) | R2_ACCOUNT_ID, CF_ACCOUNT_ID |
| 2 | Create R2 buckets (docs/r2-setup.md § 1) | — |
| 3 | Create R2 API token urban-transparency-pipeline-rw (docs/r2-setup.md § 3) | R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY |
| 4 | Create Cloudflare Pages deploy token (docs/r2-setup.md § 3 pattern, Pages scope) | CF_API_TOKEN |
| 5 | Obtain INEGI API token from INEGI developer portal | INEGI_API_TOKEN |
| 6 | Add all secrets to GitHub repo → Settings → Secrets and variables → Actions | — |
| 7 | Trigger workflow_dispatch on data-ingest.yml to validate end-to-end | — |
5. Emergency Revocation
Sección titulada «5. Emergency Revocation»If R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY are compromised
Sección titulada «If R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY are compromised»- Immediately go to Cloudflare Dashboard → R2 → Manage R2 API Tokens
- Delete the
urban-transparency-pipeline-rwtoken — this invalidates all calls immediately - Check R2 access logs for unauthorized reads/writes (Cloudflare Dashboard → R2 → bucket → Metrics)
- Re-provision a new token following the rotation procedure (§ 3)
- File an incident note in the relevant GitHub issue
If CF_API_TOKEN is compromised
Sección titulada «If CF_API_TOKEN is compromised»- Go to Cloudflare Dashboard → My Profile → API Tokens
- Click Revoke next to the Pages deploy token — effective immediately
- No deployed assets are affected (Cloudflare Pages retains the last successful deploy)
- Re-provision following § 3
If INEGI_API_TOKEN is compromised
Sección titulada «If INEGI_API_TOKEN is compromised»- Log into INEGI developer portal and revoke the token
- Request a new token
- Update GitHub Actions Secret
If the GitHub repository is compromised
Sección titulada «If the GitHub repository is compromised»- Go to GitHub repo → Settings → Secrets and variables → Actions
- Delete all secrets
- Rotate all underlying credentials (all Cloudflare tokens via Dashboard)
- Re-add secrets after the incident is contained
6. Secret Audit Checklist
Sección titulada «6. Secret Audit Checklist»Review this checklist quarterly or after any incident:
- All secrets listed in § 1 are present in GitHub Actions Secrets
-
urban-transparency-pipeline-rwR2 token is scoped to R2 Storage only (no extra permissions) -
CF_API_TOKENis scoped to Cloudflare Pages only - No credentials appear in git history (
git log -p | grep -i "token\|secret\|key\|password") - No credentials appear in
.envfiles or committed config files - INEGI token is still active (test: trigger
data-ingest.ymlmanually) - All workflows complete without credential errors