Generated by Merlin Studio (https://app.merlin-studio.cloud). Licensed under the Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0).
This guide walks through deploying the landing zone for AcmeCorp using each of the four output formats. Pick the section that matches your tooling — you don't need to use all four.
- AWS Organizations is created and the Management account is identified.
- Identity Center (SSO) is enabled in the management account (or you've decided to skip it for now).
- The deployer has
AdministratorAccesson the management account, plus theOrganizationAccountAccessRolefor cross-account changes. - AWS CLI v2 is installed and configured (
aws --version≥ 2.15). - All
PLACEHOLDER_*tokens have been replaced — seePLACEHOLDERS.mdif present. - Run a search to confirm nothing slipped through:
grep -rE 'PLACEHOLDER_[A-Z_]+' . | grep -v PLACEHOLDERS.md
The landing zone creates Identity Center permission sets (AdministratorAccess, ReadOnlyAccess, BillingAccess, …) but no account assignments — it cannot reference IdP users or groups that do not exist until you connect your identity source and create groups. Until you assign principals, no one can sign in to any account via SSO, even though the permission sets are present.
After deploying and connecting your IdP (or creating groups in Identity Center), create one assignment per group → permission set → account/OU:
- Console: Identity Center → AWS accounts → select account(s) → Assign users/groups.
- CLI:
aws sso-admin create-account-assignment \ --instance-arn <identity-center-instance-arn> \ --permission-set-arn <permission-set-arn> \ --principal-type GROUP --principal-id <group-id> \ --target-type AWS_ACCOUNT --target-id <account-id>
- LZA: populate the (scaffolded-empty)
identityCenterAssignmentsblock inaws-lza/iam-config.yamland re-run the pipeline.
A sensible baseline: AWSAdministrators → AdministratorAccess (Root, break-glass),
Engineers → PowerUserAccess (Workloads), Auditors → ReadOnlyAccess +
SecurityAuditAccess (all), Finance → BillingAccess (Management).
LZA orchestrates Control Tower + a custom CDK app. The 6–8 YAML files in aws-lza/ drive it.
- Control Tower landing zone must be deployed first (via the CT console). LZA layers on top.
- LZA stack deployed from the [official solution template] (https://aws.amazon.com/solutions/implementations/landing-zone-accelerator-on-aws/) — v1.14.x.
- A CodeCommit / GitHub repo to hold these YAMLs.
- Commit the contents of
aws-lza/to the configuredaws-accelerator-configrepository. - Trigger the
AWSAccelerator-Pipelinein CodePipeline (or wait for the next scheduled run). - Each stage runs in sequence: Prepare → Accounts → Organization → Networking → Security → SecurityResources → Operations → Network-Prep → SecurityAudit → Network-VPC → Finalize.
- Monitor in CloudWatch Logs (group
aws-accelerator-installer) and Step Functions.
- All accounts in
accounts-config.yamlappear in AWS Organizations. - The org-level CloudTrail in LogArchive shows events from every member account.
- GuardDuty delegated admin =
Audit; Security Hub aggregator linked tous-east-2.
- Stuck on Prepare: check that the management account's email address in
accounts-config.yamlmatches the actual payer account email. - Networking stage fails: confirm OU IDs in
organization-config.yamlexist before the pipeline runs (LZA creates the OU tree itself, but a partial earlier run can leave stale state). - CMK-encrypted CloudTrail won't decrypt: KMS key policy must include the SCP service principal for cross-account decrypt. LZA wires this; double-check after manual key edits.
| Symptom | Likely cause | Fix |
|---|---|---|
PLACEHOLDER_* literal in deployed resource |
Token wasn't replaced before apply | tofu/cdk destroy, replace tokens, re-apply |
AccessDenied for CloudTrail/Config writes |
Bucket policy missing service principal | Re-run with the bundled bucket policy in s3_log_archive.tf / LoggingStack |
LZA pipeline stuck on Network-Prep |
OU referenced by VPC doesn't exist | Confirm organization-config.yaml runs before network-config.yaml |
cdk deploy fails on LogGroup already exists |
Re-deploying without removing the previous CDK app | Delete the CFN stack manually, then re-deploy |
Two aspects of the landing zone are expressed differently in each format because the underlying tools differ. The deployed shape is equivalent; the operational mechanics are not.
AWS Control Tower / LZA can delete the default VPC in every region of
every account in one declarative line (defaultVpc.delete: true).
Terraform and CDK have no equivalent primitive — aws_default_vpc
manages an existing default VPC rather than purging defaults org-wide.
As a result:
- LZA: handles automatically. No action needed.
- OpenTofu: emits a marker
null_resourcepointing atscripts/delete-default-vpcs.sh. Run the script once after first apply, in every account, in every enabled region. - CDK: not handled. Run the same out-of-band script.
- tfvars: not handled. Run the same out-of-band script.
The deployed result is identical: zero default VPCs in any account.
LZA's flow-log block supports dual destinations natively:
destinations: [cloud-watch-logs, s3]with separate retention (CloudWatch 365 days, S3 lifecycle 7 years).
The Terraform community VPC module and CDK Vpc construct accept one
primary flow-log destination per VPC. Reproducing LZA's dual-destination
pattern in those formats requires emitting a second aws_flow_log /
ec2.FlowLog resource outside the module — a more verbose but
equivalent pattern.
For this bundle, OpenTofu / CDK / tfvars emit CloudWatch-only flow logs (365-day retention). If you require the 7-year S3 archive that LZA provides, either deploy via LZA, or add a second flow-log resource targeting S3 in the format you're using. The generator does not auto-emit the second resource because most CIS-only deployments don't need it; add it explicitly when compliance demands it.
- AWS LZA: GitHub Issues
- Spacelift: support@spacelift.io
- CDK: #aws-cdk on Slack
- Merlin Studio: re-run the wizard with new inputs and diff the outputs.