Skip to content

GITHUB_DISPATCH_TOKEN Rotation Runbook

What

Fine-grained GitHub Personal Access Token used by srv/lib/rebuild-trigger.js to fire workflow_dispatch on rebuild-content.yml after admin writes. Keeps /browse/ SSR'd content fresh within minutes of admin saves to mission/group/featured-flag entities.

Set on the deployed tutorials-srv app as the env var GITHUB_DISPATCH_TOKEN. When unset, rebuild-trigger.js no-ops gracefully (logs one boot warning, falls back to the existing push-trigger cadence on the rebuild-content.yml workflow).

Environment-aware dispatch (REBUILD_TARGET_ENV)

The dispatch payload includes an environment input (dev / qa / prod) that the rebuild-content.yml workflow uses to target the right Cloud Foundry approuter. Set this env var alongside GITHUB_DISPATCH_TOKEN on each CF environment so admin writes on QA dispatch a QA rebuild (not a DEV one):

CF spaceREBUILD_TARGET_ENV
devdev (or unset — defaults to dev)
qaqa
prodprod

Validate via the boot log line: [rebuild-trigger] active — admin writes will dispatch with environment='<env>'. If the line shows the wrong env, fix the CF env var and restart.

When to rotate

  • Every 90 days (token expiry default).
  • Immediately if the token is suspected leaked (see "Emergency revocation" below).
  • When the PAT-owning user leaves SAP or changes role.

How to rotate

  1. Generate the new token. GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token.

    • Resource owner: sap-tutorials
    • Repository access: Only select repositories → tutorials-ims only
    • Repository permissions: Actions → Read and write (sole permission needed)
    • Expiration: 90 days
  2. Test the new token before deploying it:

    bash
    curl -X POST -H "Accept: application/vnd.github+json" \
      -H "Authorization: Bearer <NEW_TOKEN>" \
      -H "X-GitHub-Api-Version: 2022-11-28" \
      https://api.github.com/repos/sap-tutorials/tutorials-ims/actions/workflows/rebuild-content.yml/dispatches \
      -d '{"ref":"main","inputs":{"trigger-source":"manual","environment":"dev","slug":""}}'

    Expect: HTTP 204 (no body). A new run should appear in the Actions tab within seconds.

  3. Update the credential store + redeploy each environment. The token lives as the GITHUB_DISPATCH_TOKEN alias in the BTP Credential Store (managed via /admin-ui/#secrets on each env's approuter). Update the alias directly rather than the old GitHub Actions DISPATCH_TOKEN secret — the deploy workflow no longer injects the token via envsubst; the runtime reads it exclusively from credstore on startup. The runtime env var on tutorials-srv is still GITHUB_DISPATCH_TOKEN (that's what srv/lib/rebuild-trigger.js reads). REBUILD_TARGET_ENV is a literal in each deploy/<env>.mtaext and does not need rotation.

    bash
    # Rotate the token in the BTP Credential Store for each env's approuter.
    # This requires XSUAA login to the target env's admin UI — there is no
    # `gh` / `cf` CLI shortcut. Open in a browser:
    #   /admin-ui/#secrets  → find GITHUB_DISPATCH_TOKEN → Rotate → paste <NEW_TOKEN>
    #
    # The runtime picks up the new value on the next resolveSecret() call
    # (5-minute TTL cache). To force immediate propagation, use the
    # "Invalidate cache" action on the same admin UI row — this calls
    # invalidateSecret('GITHUB_DISPATCH_TOKEN') server-side.
    #
    # No cf deploy needed — credstore rotation is a runtime-only change.

    Validate after each deploy: cf logs tutorials-srv --recent | grep rebuild-trigger should show [rebuild-trigger] active — admin writes will dispatch with environment='<env>' with the right env, and the unset-token warning should NOT appear.

    Local rotation validation (optional — verify the new token before merging the secret bump to all envs):

    Rotate through the admin UI at /admin-ui/#secrets on the target env's approuter (alias GITHUB_DISPATCH_TOKEN). The runtime picks up the new value on the next resolveSecret call — the 5-minute TTL means propagation is near-immediate. Confirm via:

    bash
    cf logs tutorials-srv --recent | grep 'rebuild-trigger'
    # Expected: [rebuild-trigger] active — admin writes will dispatch with environment='<env>'
    # NOT expected: [rebuild-trigger] unset-token warning

    If you need to invalidate the resolver cache before the 5-minute TTL, use the admin UI's "invalidate" action on the alias — this calls invalidateSecret('GITHUB_DISPATCH_TOKEN') server-side.

  4. Revoke the old token. GitHub → Settings → Developer settings → Personal access tokens → click old token → Revoke.

  5. Update the rotation calendar reminder for +90 days.

Emergency revocation

If the token is suspected leaked (committed to a repo, posted in a chat, posted in a screenshot, etc.):

  1. Revoke immediately via the GitHub UI. This stops further dispatches even before CF env is updated.
  2. Generate a replacement and update CF env per "How to rotate" above.
  3. Audit the tutorials-ims Actions tab for unexpected workflow runs in the leak window. Any unauthorized dispatch is a possible incident — file per the project's security incident process.

Failure modes

ModeSymptomAction
Token unset[rebuild-trigger] boot warning; admin writes don't trigger rebuildsAcceptable degraded mode — content stays fresh via the existing push trigger only. Confirm gh secret list --repo sap-tutorials/tutorials-ims shows DISPATCH_TOKEN (note: secret is named DISPATCH_TOKEN, not GITHUB_DISPATCH_TOKEN, because GitHub reserves the GITHUB_ prefix); if missing, add it and trigger a redeploy. If present, check the most recent deploy run's Resolve mtaext placeholders step for the GITHUB_DISPATCH_TOKEN env-var declaration and confirm the next step's cf deploy consumed deploy/<env>.resolved.mtaext.
Token expired / revokedGitHub returns 401; admin save logs [rebuild-trigger] dispatch failed: GitHub dispatch 401 ...Rotate per the steps above. Admin saves still succeed; only the auto-rebuild dispatch is broken.
REBUILD_TARGET_ENV mismatchAdmin save on QA srv triggers a DEV rebuild (or vice versa); boot log shows environment='dev' on a non-DEV spaceSet REBUILD_TARGET_ENV to match the space (qa/prod) and cf restart. Until then content stays fresh on the wrong env.
Token over-permissionedToken has scopes beyond actions:write (e.g. contents:write, metadata:read, etc.)Defense-in-depth violation, not an outage. Re-issue with actions:write only on next rotation cycle.
GitHub rate-limitedSporadic 429 in logsThe 60s debounce already collapses bulk admin edits. If 429 recurs, investigate whether some other CI workflow is sharing this PAT — fine-grained PATs should not be shared across services.

Why fine-grained PAT (not GITHUB_APP / OIDC)

The simpler alternatives were considered and rejected for this scope:

  • GitHub App — overkill for a single workflow_dispatch trigger; requires app installation + private-key secret rotation.
  • OIDC from CF — Cloud Foundry doesn't expose OIDC tokens to apps in a way GitHub's actions:write API consumes. Would need a token-broker.
  • Repository secret + workflow — works for CI-triggered rebuilds but doesn't help the admin-write hook (which fires from a deployed CAP, not from CI).

The fine-grained PAT is the simplest fit: scoped, expirable, revocable, and the failure mode (no rebuild dispatch) is graceful.

Where this is documented

There is currently no .env.example file in this repo's root. If one is added in the future, include GITHUB_DISPATCH_TOKEN= (empty) with a pointer to this runbook.

References