The Problem With Compliance as a Document
For most federal programs, compliance lives in a Word document or a shared spreadsheet. A controls spreadsheet gets updated quarterly, reviewed by a security officer, and filed somewhere in a SharePoint library. Meanwhile, engineers are deploying CloudFormation stacks and Terraform modules that do whatever the code says — not whatever the document says.
The gap between the two is where breaches happen, where audit findings accumulate, and where programs spend millions on remediation sprints the month before an Authority to Operate (ATO) renewal.
I ran into this pattern repeatedly during nine years of federal AWS delivery. The controls framework — FedRAMP, FISMA, NIST 800-53 — would be meticulously documented. The actual infrastructure? It was whatever a developer pushed last Tuesday.
The fix is not better documentation. The fix is treating compliance like code.
cfn-guard: Policy as Executable Rules
AWS CloudFormation Guard (cfn-guard) lets you write declarative rules that validate CloudFormation templates and Terraform plan JSON before anything is deployed. If a rule fails, the pipeline fails — hard stop, no exceptions.
Here is what a rule looks like in practice. This one enforces encryption-at-rest on every S3 bucket in your stack:
``` rule s3_bucket_encryption_enabled { AWS::S3::Bucket { Properties { BucketEncryption exists BucketEncryption.ServerSideEncryptionConfiguration[*].ServerSideEncryptionByDefault.SSEAlgorithm in ["aws:kms", "AES256"] } } } ```
That rule maps directly to NIST 800-53 SC-28 (Protection of Information at Rest). When the pipeline runs cfn-guard against a template that has an unencrypted bucket, the build breaks with an explicit, human-readable error message. The engineer fixes it. The finding never reaches an environment.
Wiring cfn-guard Into CI/CD as a Hard Blocker
The key word is *hard blocker* — not a warning, not a Slack notification, not an email to the security team. A gate that the pipeline cannot pass until the violation is resolved.
In a CodePipeline or GitHub Actions setup, the guard step runs immediately after the template synthesis step and before any changeset creation:
```yaml
- name: Run cfn-guard policy check
The `--show-summary fail` flag exits non-zero on any rule violation, which is what kills the pipeline stage.
Where this gets powerful is the rule set. On a federal program I was part of, we built a rule library that covered:
- Encryption controls: S3, RDS, EBS, EFS, DynamoDB — all required KMS with customer-managed keys
- Logging controls: CloudTrail enabled with log validation, VPC Flow Logs on every VPC, Config recorder active
- Network controls: No 0.0.0.0/0 ingress on security groups, no public RDS instances, no open S3 buckets
- IAM controls: No inline policies, no wildcard actions on production resources, MFA required for console roles
- Tagging controls: Every resource required a set of mandatory tags (environment, data-classification, owner, cost-center)
That last one — mandatory tagging — saved us in an audit. When the ISSO asked for a list of all production resources handling PII, we could run a Config query instead of manually reviewing 400 CloudFormation templates.
Version-Controlling the Rules Alongside the Infrastructure
The rules live in the same repository as the infrastructure code. This is not optional — it is the entire point. When a new NIST control gets scoped into your boundary, you add a rule, commit it, and every subsequent deployment enforces it retroactively across the entire codebase.
It also creates an audit trail that auditors actually understand. The commit history for a rule file is evidence of when a control was implemented. The pipeline run logs are evidence that every deployment was checked. This is dramatically more defensible than "yes, we reviewed the templates before deploying."
Beyond CloudFormation: Terraform and OPA
cfn-guard covers CloudFormation. For Terraform-heavy shops, the equivalent is Open Policy Agent (OPA) with Conftest, or Sentinel if you are on Terraform Enterprise. The philosophy is identical: write policies as code, run them in CI, fail the build on violations.
On one migration program using Terragrunt across multiple environments, we used a combination of OPA policies for Terraform plan validation and cfn-guard for any native CloudFormation stacks (mostly managed services that did not have a mature Terraform provider at the time). Both gate sets ran in parallel in the pipeline; both had to pass before a plan could be applied.
The Cultural Shift: From Audit to Engineering
The hardest part of this approach is not the tooling — cfn-guard is straightforward. The hardest part is getting engineers to treat compliance rules the same way they treat unit tests.
A unit test that fails means the code is wrong. A compliance rule that fails means the infrastructure is wrong. Both block the merge. Neither is optional.
Once that mindset takes hold, something interesting happens: engineers start writing compliant infrastructure *by default* because they have seen the rule set. They know the S3 bucket needs encryption. They know the security group cannot be wide open. The rules become internalized, and the pipeline gate becomes a safety net rather than a surprise obstacle.
What You Get Out the Other Side
When compliance is a programmable control plane rather than a document, you get three things:
Continuous assurance: Every deployment is checked. There is no window between the document saying "encryption required" and the actual bucket being encrypted.
Audit evidence by default: The pipeline logs, rule pass/fail reports, and commit history constitute a continuous evidence stream. ATO renewals stop being a fire drill.
Faster delivery: Counterintuitively, teams move faster because they stop getting compliance findings late in the process. Finding a missing encryption setting in a pipeline check takes seconds to fix. Finding it in an ATO assessment means a remediation sprint, a re-assessment, and weeks of delay.
Compliance as code does not eliminate the need for security officers or auditors. It eliminates the manual, error-prone translation between what the controls document says and what the infrastructure actually does.