Building a Compliant GCP Storage Module with Terraform (CGE-P Lab 2.4)
Summary
Continuing through the Certified GRC Engineer - Practitioner (CGE-P) course. Lab 2.3 built a single compliant S3 bucket on AWS. Lab 2.4 moves to GCP and shifts from writing one bucket to writing a module that deploys buckets, with compliance sitting inside the module where a consumer can't reach it.
Code for this lab is in my CGE-P Capstone repo.
Lab 2.4: Terraform Modules for Compliance (GCP)
The module is called compliant-gcs-bucket. It is a reusable building block that creates a GCS bucket pre-wired with NIST SP 800-53 controls, plus its own Cloud KMS keyring and key for encryption. Two consumers, dev and prod, call the same module with different environment and retention values. Both get the same security posture either way.
terraform/
├── modules/
│ └── compliant-gcs-bucket/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── README.md
└── primitives/
└── compliant-gcs/
├── main.tf
└── variables.tf
The module has three files:
main.tfhardcodes anything compliance-relevant. A consumer cannot turn it off.variables.tfexposes only what business actually changes: project, environment, retention, naming.outputs.tfreturns evidence, including a computedcompliance_attestationmap.
The shift from Lab 2.3 is that compliance stops being something you remember to add per-resource, and becomes something the module forces on every consumer by construction.
| Code block | NIST Control | What it does |
|---|---|---|
google_kms_key_ring + google_kms_crypto_key | SC-12 | The module owns its own encryption key instead of relying on Google-managed encryption |
encryption { default_kms_key_name = ... } on the bucket | SC-13 / SC-28 | Customer-managed encryption key (CMEK) applied at rest |
versioning + retention_policy | AU-11 | Object versions preserved, retention period enforced per environment |
uniform_bucket_level_access + public_access_prevention = "enforced" | AC-3 | IAM-only access, no ACLs, no accidental public exposure |
locals.required_labels merged into labels | CM-6 | Compliance labels forced onto every bucket; a consumer can add labels but not remove the required four |
Gotchas I Hit
Unlike Lab 2.3, the issue I ran into wasn't a compliance gap in the design, it was drift in the documentation. If a compliance module's own supporting docs can drift from its code, what does that imply about trusting a vendor module's documentation without reading the .tf files directly? A vendor module's README makes compliance claims too ("this enforces encryption at rest"). The lesson here is: don't treat a README as proof of what a module does. Read the resource blocks yourself before you rely on the claim.