Skip to main content

Policy as code

Policy as code means the rules an organization wants enforced are written as files, versioned with the infrastructure, and applied by machines: by AWS at request time, by AWS Config after the fact, and by scanners and guard scripts before a pull request can merge. In the BuiltForProd AWS Baseline every rule has a file, and most have a check that fails the build when the rule is broken.

Why it matters in production

A rule that lives in a wiki is a suggestion. A rule that lives in a service control policy (SCP) is enforced against every principal in every member account, including administrators, and its history is in Git. Rules as code also produce evidence automatically: an auditor can read the policy file, the conformance pack results and the pipeline logs instead of collecting screenshots for two weeks.

How the platform applies it

Rules AWS enforces at request time

Three SCPs are defined in modules/organizations/scps.tf and attached to both organizational units by the organizations unit:

PolicyDeniesExceptions
acme-security-guardrailsCreating IAM users, login profiles and access keys; deleting or changing S3 Block Public Access; any action without MFARoot for IAM users; AWSReservedSSO_*, acme-terraform-* and acme-github-actions-* roles for the block; SSO sessions, acme-* roles, service-linked roles, root and roles tagged mfa-exempt = true for MFA
acme-region-restrictionAny action outside the allowed regions (global services excluded)Allowed regions are the home region, every region with folders and us-east-1, computed by the unit
acme-audit-protectionStopping, deleting or updating CloudTrail; stopping or deleting the Config recorder or delivery channelRoot

The allowed-region list is computed from the same folder discovery IPAM uses, so a new region folder extends the policy without editing it. AWS allows five SCPs per OU including FullAWSAccess; the platform uses three.

The organization tag policy in modules/organizations/tags.tf pins the key casing and allowed values of Namespace, Environment, Stage, ManagedBy and Repository (and the casing of Team). It is report-only until enforce_tag_policy = true, because enforcement rejects tagging calls from the console, auto scaling and EKS that use a value outside the list; the switch is flipped only once the compliance report is clean. The Environment list is fed from every region.hcl in the tree.

Rules AWS evaluates continuously

AWS Config records every supported resource type in every account and region and aggregates in core-audit. The aws-config unit of the management account deploys organization conformance packs from a library of 29 in modules/aws-config/conformance-packs/. BuiltForProd-soc2-baseline (13 rules, such as encrypted volumes, multi-region CloudTrail, S3 SSL-only, MFA for console and VPC flow logs) is on by default; the other 28 (HIPAA, PCI DSS v4, NIST CSF, CMMC 2.0 levels 1 and 2, CIS Critical Security Controls v8 IG1 to IG3, the Well-Architected security and reliability pillars, and service packs for S3, IAM, EKS, RDS, Lambda, WAF and more) are switched on per compliance requirement in the unit's conformance_packs map. plat-sandbox is excluded. Security Hub standards (CIS v5, AWS Foundational Security Best Practices, PCI DSS v4, the tagging standard) are separate switches in security.hcl.

Findings flow to Security Hub in core-security; selected non-compliance and high-severity findings reach the alerts topic, and a public S3 bucket is remediated automatically, as described under defense in depth.

Rules the pipeline enforces before merge

Every infrastructure repository runs the same checks in pre-commit and in the lint-and-scan job of plan.yml, without AWS credentials:

CheckRule
scripts/check-no-hardcoded-cidrs.pyNo private CIDR literal in .hcl or .tf outside comments, 0.0.0.0/0, /32 routes and mock blocks (landing zone)
scripts/check-vpc-maps.pyBoth VPC maps valid, nested, non-overlapping, interchangeable; every unit's ipam_key resolves (landing zone)
scripts/check-mock-outputs.pyEvery dependency has complete mock_outputs allowed for validate and plan
scripts/check-stack-layout.pyA stack file beside every region.hcl, no hand-written unit under environments/, every definition referenced, repo-root sources
scripts/check-module-versions.pyProvider pins live in root.hcl; a module versions.tf repeats them exactly
scripts/check-required-inputs.pyEvery generated unit supplies its module's required inputs (runs after AWS authentication, because rendering needs Terragrunt)
tofu fmt, terragrunt hcl format, tflint v0.64.0Formatting and the recommended ruleset plus the AWS ruleset 0.48.0
Checkov, TrivyInsecure resource configuration in modules/; Checkov is a hard fail

Skips are allowed only with a reason on the same line (#checkov:skip=CKV_AWS_7: <why>, # tflint-ignore: <rule> # <why>). Code ownership routes reviews by blast radius: environments/core/, the VPC maps, root.hcl, common.hcl, .github/ and scripts/ to the infra admins; security.hcl to the infra admins and the security team; plat/prod to platform leads and security.

The application chart carries its own policy in code: a default-deny Kubernetes NetworkPolicy with an explicit allow-list, enforced by the VPC CNI's network policy agent.

Worked example: a pull request that hard-codes a subnet

An engineer adds private_subnets = ["10.2.0.0/18", ...] to a unit to work around an IPAM lookup. Pre-commit fails on the CIDR literal before the commit exists. If the hook was skipped, plan.yml fails in the first job, before any AWS call, with the file and line. The fix is to read the address from dependency.ipam.outputs as every other VPC unit does. Nothing about the rule was discussed in review, because the rule is not a matter of opinion; it is infrastructure as code applied to the rules themselves.

Common mistakes

  • Relaxing an SCP to unblock a deployment. The exceptions are principal-based; name the role acme-* or tag it mfa-exempt = true.
  • Enforcing the tag policy before the report is clean. Enforcement rejects console, auto scaling and EKS tagging calls that use unlisted values.
  • Enabling every conformance pack at once. Each pack adds Config rule evaluations and findings; packs are switched on per compliance requirement.
  • Skipping a scanner finding without a reason. A skip needs its reason on the same line; reviewers read it.
  • Running the guards only in CI. The pre-commit hooks run the same scripts locally, which is cheaper than a failed pipeline.