AWS CDK Landing Zone

GitHub Actions Deployment

How the generated OIDC-based GitHub Actions workflow deploys the landing zone across two accounts, and what triggers a regeneration.

The landing zone includes a generated GitHub Actions workflow that deploys both phases on every push to main: the organization phase to your management account, then the landing-zone phase to your landing zone account. It authenticates through GitHub's OIDC provider, so no long-lived AWS credentials are stored in GitHub Secrets.

How the pipeline works

Mermaid diagram loading.

The workflow file lives at .github/workflows/cdk-deploy-landing-zone.yml. It is generated by projen from .projenrc.ts, so edit .projenrc.ts to change it, not the workflow file directly.

The pipeline runs the two phases against two different accounts in one run:

  1. It assumes GitHubActionsServiceRole in your management account and runs pnpm run organization:deploy, which deploys LandingZoneOrganizationStack.
  2. It re-configures credentials to assume GitHubActionsServiceRole in your landing zone account and runs pnpm run landingzone:deploy:all, which deploys LandingZoneFoundationStack and LandingZoneAccountProvisioningStack.

Both roles are named GitHubActionsServiceRole, one per account, and both trust only your repository. Dependencies are installed once with pnpm run setup before the organization phase and reused for the landing-zone phase.

The GitHubActionsServiceRole

The landing zone sets this up for you. The management-account role is created by GitHubActionsOidcConstruct inside LandingZoneOrganizationStack; the matching landing-zone-account role is bootstrapped by the same organization phase when it prepares the landing zone account. On the first local deployment of the organization stack, it:

  • Registers token.actions.githubusercontent.com as an OIDC identity provider in each account
  • Creates a GitHubActionsServiceRole with AdministratorAccess in the management account and a deploy role in the landing zone account
  • Scopes each role's trust policy to your repository, resolved automatically from your git remote
  • Sets a short maxSessionDuration so each pipeline run receives temporary credentials that expire automatically, with nothing long-lived stored anywhere

This is a one-time setup. After the first pnpm run organization:deploy completes locally and you have set landingZoneAccountId and re-run projen, the pipeline is authorized to run both phases without any manual IAM configuration.

The role ARNs embedded in the workflow follow the pattern:

arn:aws:iam::<managementAccountId>:role/GitHubActionsServiceRole
arn:aws:iam::<landingZoneAccountId>:role/GitHubActionsServiceRole

These values are baked into the workflow by projen when you run pnpm exec projen. If either account ID changes, re-run projen and the workflow updates automatically.

CodeArtifact access

The workflow runs pnpm run setup before deploying. This script authenticates to the private CodeArtifact registry that hosts @towardsthecloud/cdk-landing-zone-constructs and @towardsthecloud/cdk-landing-zone-foundation, then runs pnpm install --frozen-lockfile. The authentication works because:

  1. The workflow assumes GitHubActionsServiceRole in your management account via OIDC
  2. Towards the Cloud whitelists your management account on the CodeArtifact repository during initial setup
  3. The whitelisted account can fetch an auth token from CodeArtifact without further configuration

No separate secret is needed for package installation. The installed node_modules carry over to the landing-zone phase, so setup runs once.

Trusting additional repositories

By default, only the repository resolved from your git remote can assume GitHubActionsServiceRole. If you need a second repository (for example, a separate infrastructure repo) to deploy the landing zone, add it to GitHubActionsOidcConstruct in src/stacks/landing-zone-organization-stack.ts via the additionalRepositories prop. This requires editing the construct instantiation, which lives in a file you own. The additionalRepositories prop accepts bare repository names under the same GitHub owner.

Regenerating the workflow with projen

The workflow file is managed by projen. Editing it directly works but those changes are overwritten the next time someone runs pnpm exec projen. Instead, make changes through .projenrc.ts:

  • To change the management or landing zone account ID or primary region: update landing-zone-settings.ts and then run pnpm exec projen. Projen reads the settings at synthesis time and embeds the updated account IDs and region into the workflow and the organization:* and landingzone:* npm tasks.
  • To change the Node.js version: update the nodeVersion constant in .projenrc.ts and run pnpm exec projen. Both the setup-node step and the .nvmrc file update together.
  • To add a workflow step: extend the CDK deployment workflow helper in src/bin/cicd-helper.ts or add a projen workflow step in .projenrc.ts.

After any .projenrc.ts change, commit both the updated .projenrc.ts and the regenerated .github/workflows/cdk-deploy-landing-zone.yml.

Manual dispatch

The workflow supports workflow_dispatch, so you can trigger a deploy from the GitHub Actions UI without pushing a commit. This is useful for forcing a re-deploy after a settings change that doesn't touch any tracked file, or for recovering from a partially failed pipeline run.

Concurrency

The workflow sets concurrency.cancel-in-progress: false for the cdk-deploy-landing-zone group. A second push while a deploy is in progress queues rather than cancels the running deploy. CDK deployments are not safe to cancel mid-run (CloudFormation can be left in an UPDATE_IN_PROGRESS state), so this is intentional.