Skip to content

About

Terraform AWS landing zone: Organizations with deny-list SCPs, tamper-resistant central logging, GuardDuty and Security Hub across all accounts, Transit Gateway network with prod/non-prod isolation and central egress, Identity Center and keyless GitHub OIDC deploys. Config-as-data, tested with a mocked provider in CI.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

Enterprise AWS Cloud Platform

A Terraform landing zone: the multi-account AWS foundation that application teams deploy onto. It covers AWS Organizations with guardrails, centralized tamper-resistant logging, organization-wide threat detection, a segmented hub-and-spoke network with shared egress, single sign-on for people, and keyless deploys for pipelines. All of it is driven by three YAML files and checked in CI without an AWS account.

Stack: Terraform 1.13 (AWS provider 6.x, native terraform test with a mocked provider) · AWS Organizations, SCPs, tag policies · IAM Identity Center · CloudTrail, Config, GuardDuty, Security Hub, IAM Access Analyzer · Transit Gateway, IPAM, RAM · Python 3.13 (platformctl) · tflint · Checkov · GitHub Actions

Status: designed and tested, not deployed. Nothing here has been applied to a real AWS organization. Every module is validated and unit-tested against a mocked provider, linted, and security-scanned in CI. docs/OPERATIONS.md describes the bootstrap for a real organization.

The problem

Once more than one team builds on AWS, a single account stops working:

  • No blast-radius control. Production and experiments share IAM, quotas and a bill.
  • Weak audit evidence. Logs live in the same account an attacker or a mistake could delete them from.
  • Network sprawl. Every team builds its own VPC with its own NAT gateways, and address ranges overlap, so networks can't be connected later.
  • Long-lived credentials. IAM users and access keys for people and CI pipelines end up in laptops and CI secrets.
  • Drift and inconsistency. Each account is set up by hand, slightly differently, with no record of why.

Organizations need a foundation where adding an account is a reviewed pull request, every account is born with the same security controls, and some things simply can't be done by anyone:

  • disabling logging,
  • leaving the organization,
  • using unapproved regions,
  • production talking to development.

What this platform provides

Capability How
Accounts as code Accounts, OUs and their owners, budgets and VPC sizes live in config/organization.yaml. Terraform vends the accounts; removing an entry never closes an account
Guardrails 8 service control policies (deny-list): keep CloudTrail, Config, GuardDuty and Security Hub on; protect platform roles; region allow-list; no IAM users or access keys; IMDSv2 and encrypted volumes; protect production backups; sandbox spending limits; suspended accounts can do nothing. A tag policy enforces owner, environment and cost-center
Tamper-resistant logs An organization CloudTrail (all accounts, all regions, log-file validation), AWS Config and VPC flow logs all go to one log-archive account. The bucket has S3 Object Lock, a dedicated KMS key, and a policy that only this organization's logging services can write to and only platform roles can change
Threat detection GuardDuty (S3, EKS, malware, RDS) and Security Hub (AWS Foundational Security Best Practices + CIS 3.0), auto-enabled in every new account and administered from a dedicated security account. Findings from all regions are aggregated. Critical findings and public-access findings page the security team. Eight CIS alarms watch the organization trail (root use, guardrail changes, trail tampering…)
Segmented network IPAM pools per region and environment (no overlapping VPCs, ever). A Transit Gateway with one route table per domain, where production and non-production can't reach each other, enforced with blackhole routes against hairpinning. Central egress (one set of NAT gateways for the whole organization). A workload VPC module with private, data and transit subnets, gateway endpoints and flow logs
People: single sign-on IAM Identity Center permission sets (PlatformAdmin with 1-hour sessions, SecurityAudit, Developer with a network/IAM fence, ReadOnly, BillingViewer). Group-to-account assignments come from config/identity.yaml, including ou:<Name> and all targets. Engineers are read-only in production; only on-call can change it
Pipelines: no keys Per-repository GitHub Actions OIDC roles, bound to the matching GitHub environment. They are capped by a permissions boundary that any role they create must also carry, so a pipeline can't escalate its own privileges
Secure defaults per account EBS encryption, an S3 account-level public access block, IMDSv2 by default, a strict password policy, Config recording, and a monthly budget with forecast alerts

Architecture

flowchart TB
  subgraph mgmt[Management account]
    ORG[AWS Organizations<br/>OUs · SCPs · tag policy]
    TRAIL[Organization CloudTrail<br/>+ CIS metric alarms]
    SSO[IAM Identity Center]
  end
  subgraph sec[Security OU]
    LA[log-archive<br/>Object Lock · KMS · 7-year retention]
    SA[security<br/>GuardDuty · Security Hub · Config aggregator · Access Analyzer]
  end
  subgraph infra[Infrastructure OU]
    NET[network<br/>IPAM · Transit Gateway · central egress]
    SS[shared-services]
  end
  subgraph wl[Workloads OU]
    P[Production<br/>payments-prod]
    NP[NonProduction<br/>payments-staging · analytics-dev]
  end
  SB[Sandbox OU<br/>sandbox-research]
  TRAIL -->|all accounts| LA
  P & NP & SS & NET -->|Config · flow logs| LA
  P & NP & SB -.->|findings| SA
  P <-->|TGW: prod domain| NET
  NP <-->|TGW: non-prod domain| NET
  NET -->|NAT| INTERNET((Internet))
  SSO -.->|permission sets| P & NP & SB
Loading
flowchart LR
  RP[production route table] -->|0.0.0.0/0| EG[egress VPC · NAT]
  RN[nonproduction route table] -->|0.0.0.0/0| EG
  RS[shared route table] -->|0.0.0.0/0| EG
  RP <-->|propagated| RS
  RN <-->|propagated| RS
  RP -->|10.32.0.0/12 → blackhole| BH1[dropped]
  RN -->|10.16.0.0/12 → blackhole| BH2[dropped]
Loading

Details: docs/DESIGN.md · decisions: docs/adr/ · the generated inventory of accounts, guardrails, access and addresses: docs/generated/inventory.md.

Repository layout

config/            organization.yaml · network.yaml · identity.yaml   ← the platform, as data
policies/scp/      service control policies (JSON, deny-list)
policies/iam/      inline policies for permission sets
modules/           organization · log-archive · org-trail · security-baseline · account-baseline ·
                   network-hub · workload-vpc · identity-center   (each with tests/*.tftest.hcl)
live/              one Terraform root per account type: management · log-archive · security · network · workload
tools/platformctl  validate · plan-cidrs · cost · inventory
docs/              design, operations runbook, ADRs, known limitations, generated inventory

Quality gates (CI)

Gate What it proves
platformctl validate --strict The config respects AWS hard limits that would otherwise fail half-way through an apply:
• 5 SCPs per target, including FullAWSAccess
• 5,120 characters per SCP
• account root e-mail length
It also checks the design rules:
• deny-only SCPs
• prod/non-prod isolation
• no pool or reserved-range overlap
• pool capacity
• tag-policy values
• identity references and session limits
terraform test (8 modules, 32 tests) Each module behaves as designed, against a mocked AWS provider in plan mode. No credentials, no API calls, no cost. Examples: production blackholes the non-production pool; the log bucket only accepts this organization's services; deploy roles carry the boundary; removing an account never closes it
terraform validate on every live root The roots wire the modules together correctly
tflint · Checkov Lint and security scan; every Checkov skip carries its justification inline (19 skips, 0 failures)
platformctl inventory --check The documented inventory matches the config
gitleaks No secrets anywhere in the history

Try it locally (no AWS account needed)

pip install -e "tools[dev]"
platformctl validate          # all checks on config/ and policies/
platformctl plan-cidrs        # which addresses each workload VPC will get, subnet by subnet
platformctl cost              # what the platform itself costs per month, line by line
cd modules/network-hub && terraform init && terraform test   # Terraform >= 1.11

Baseline cost of the platform itself: about $660 per month for this 9-account example, before any workload runs. The largest items are Transit Gateway attachments, GuardDuty, Security Hub, Config, IPAM and the NAT gateways. The breakdown with every unit price is in the generated inventory. Ways to reduce it are in docs/OPERATIONS.md.

Documentation

docs/DESIGN.md Account structure, guardrails, logging, detection, network, identity, threat model
docs/OPERATIONS.md Bootstrap order, adding and closing accounts, break-glass, incident runbooks, cost
docs/ONBOARDING.md For application teams: requesting an account, deploying from GitHub Actions, networking rules
docs/adr/ Architecture decision records
docs/known-limitations.md What is not covered yet
docs/generated/inventory.md Accounts, guardrails per account, access matrix, address plan, cost (generated)

About

Terraform AWS landing zone: Organizations with deny-list SCPs, tamper-resistant central logging, GuardDuty and Security Hub across all accounts, Transit Gateway network with prod/non-prod isolation and central egress, Identity Center and keyless GitHub OIDC deploys. Config-as-data, tested with a mocked provider in CI.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages