Account setup
The account baseline is a CDK app that uses @rpallas/outcrop-account. It provisions what services expect to find in an account and publishes it to the SSM contract. Scaffold one with:
npx @rpallas/outcrop-cli create account my-platform --owner my-org --domain example.com
cd my-platformPrerequisites
npx cdk bootstrap aws://111111111111/eu-west-1in each target account (andaws://111111111111/us-east-1when the DNS module is on and the region is notus-east-1, for the CloudFront certificate stack)- A registered domain and the ability to create NS records for delegation, or a parent hosted zone (same or another account) configured as
parentZone - Administrator credentials for the first deploy
Configure
account.config.ts lists environments and, per environment, the account id, region, domain and the GitHub repositories allowed to deploy:
export default defineAccountConfig({
project: "my-platform",
environments: {
dev: {
account: "111111111111",
region: "eu-west-1",
domain: "dev.example.com",
alertEmails: ["platform-alerts@example.com"],
monthlyBudgetUsd: 200,
github: [{ owner: "my-org", repo: "orders" }], // pull requests allowed by default
},
prod: {
account: "222222222222",
region: "eu-west-1",
domain: "example.com",
protected: true,
github: [{ owner: "my-org", repo: "orders", allowPullRequests: false }],
},
},
});Repositories created after 15 July 2026
GitHub issues OIDC tokens for repositories created (or renamed or transferred) after 15 July 2026 with an immutable subject that includes the numeric owner and repository ids, e.g. repo:my-org@123456/orders@789012:environment:dev. Unlike names, the ids can't be reused by someone who later takes over the name. A deploy role that only trusts repo:my-org/orders:... rejects these tokens, so add the ids to the repository's entry:
github: [{ owner: "my-org", repo: "orders", ownerId: 123456, repoId: 789012 }],With ids set, the roles trust only the immutable subject. To print a repository's subject prefix and ids (uses the GitHub CLI, so private repositories work):
npx @rpallas/outcrop-cli oidc-subject my-org/orderslib/modules.ts chooses the modules. Everything not listed is off:
export const modules: AccountBaselineModules = {
githubOidc: true,
accountSettings: true,
dns: true,
alerting: true,
eventBus: true,
encryption: true,
sharedParameters: { values: { "auth0-domain": "example.eu.auth0.com" } },
sharedSecrets: { secrets: { "neon-api-key": {} } },
budgets: true,
security: true, // disable pieces managed by your organisation: { guardDuty: false }
logRetention: true,
// network: { natGateways: 0 },
};First deploy
npm test
npx cdk deploy --all -c env=devOutputs include:
DeployRoleArn<Owner><Repo>/ReadOnlyRoleArn<Owner><Repo>: set them asAWS_DEPLOY_ROLE_ARN_DEVandAWS_READONLY_ROLE_ARN_DEVrepository variables in each service repositoryBaselineDnsNameServers: NS records to create at your registrar or parent zone (not needed whenparentZoneis configured)EventBusName,PlatformKeyArn,CriticalAlertTopicArn
Confirm the SNS email subscriptions from your inbox.
Ongoing deploys from CI
The baseline creates the service deploy roles, so it cannot deploy itself with one of them the first time. Create one administration role per account trusted by the GitHub OIDC provider (the provider ARN is in the /platform/account/deploy/oidc-provider-arn parameter), store its ARN as AWS_BASELINE_ROLE_ARN_<ENV>, and let the generated .github/workflows/deploy.yml call the account-baseline-deploy.yml reusable workflow on merges to main.
The deploy jobs run in a GitHub environment named after the platform environment, so the OIDC sub claim ends in :environment:<env> rather than the branch ref. Its repo: prefix is repo:my-org/my-platform for older repositories and the immutable repo:my-org@<owner-id>/my-platform@<repo-id> for repositories created after 15 July 2026; npx @rpallas/outcrop-cli oidc-subject my-org/my-platform prints which. Trust that value, and restrict the GitHub environment's deployment branches to main:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<account>:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:my-org/my-platform:environment:dev"
}
}
}
]
}Shared secrets
SharedSecrets creates secrets with generated values and publishes their ARNs. Replace the value out of band so no secret material passes through CloudFormation:
aws secretsmanager put-secret-value --secret-id platform-dev-neon-api-key --secret-string "$NEON_API_KEY"Organisation-wide rollout
Wrap the baseline in PlatformStackSet to deploy it to every account in an OU from a delegated administrator account. See the outcrop-account README.