<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Janita Madramootoo - GRC Engineering</title>
    <subtitle>Janita Madramootoo is a software architect based in Melbourne, FL.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://janita.me/tags/grc-engineering/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://janita.me"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-07-13T00:00:00+00:00</updated>
    <id>https://janita.me/tags/grc-engineering/atom.xml</id>
    <entry xml:lang="en">
        <title>Building a Compliant AWS S3 Module with Terraform (CGE-P Lab 2.3)</title>
        <published>2026-07-13T00:00:00+00:00</published>
        <updated>2026-07-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Janita
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://janita.me/blog/terraform-compliant-aws-s3-module-nist-800-53/"/>
        <id>https://janita.me/blog/terraform-compliant-aws-s3-module-nist-800-53/</id>
        
        <content type="html" xml:base="https://janita.me/blog/terraform-compliant-aws-s3-module-nist-800-53/">&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;&#x2F;h2&gt;
&lt;p&gt;I am working through the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;cert.grcengclub.com&#x2F;&quot;&gt;Certified GRC Engineer - Practitioner (CGE-P)&lt;&#x2F;a&gt;
course. This is the first lab on Policy-as-Code (PaC) where I build my first Terraform compliant module. This is a learning-in-public post, so expect rough edges. Here&#x27;s what I built and what I noticed along the way.&lt;&#x2F;p&gt;
&lt;p&gt;Code for this lab is in my &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;JanitaM&#x2F;CGE-P_Capstone&quot;&gt;CGE-P Capstone repo&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;lab-2-3-building-your-first-compliant-resource-aws-s3&quot;&gt;Lab 2.3: Building Your First Compliant Resource (AWS S3)&lt;&#x2F;h2&gt;
&lt;p&gt;We are building a Terraform module called &lt;code&gt;compliant-s3&lt;&#x2F;code&gt;. It is a reusable building block that creates an AWS S3 bucket pre-wired with compliance controls from NIST SP 800-53 Rev 5, implemented from the start.&lt;&#x2F;p&gt;
&lt;p&gt;The &quot;primitives&quot; directory is called by other configurations. A primitive here means a small, self-contained module that other configurations reference instead of rewriting. The idea is you build the compliance logic once, in one place, and every future bucket in the project inherits it instead of someone copy-pasting Terraform and forgetting a control.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;terraform&#x2F;primitives&#x2F;compliant-s3&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── variables.tf   (inputs)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── main.tf        (resources)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;└── outputs.tf     (exports)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;variables.tf&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;All callers must provide: &lt;code&gt;project_name&lt;&#x2F;code&gt;, &lt;code&gt;environment&lt;&#x2F;code&gt;, &lt;code&gt;bucket_suffix&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;These are validation rules for CM-6 (Configuration Management) control. They prevent misconfiguration starting at deployment.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;main.tf&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;It creates two S3 buckets with compliance attached to each. The two buckets have a dependency: the primary data bucket logs into the log bucket. The log bucket must exist first.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Code block&lt;&#x2F;th&gt;&lt;th&gt;NIST Control&lt;&#x2F;th&gt;&lt;th&gt;What it does&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;aws_s3_bucket_server_side_encryption_configuration&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;SC-28&lt;&#x2F;td&gt;&lt;td&gt;AES-256 encryption at rest on both buckets&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;aws_s3_bucket_versioning&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;CM-6&lt;&#x2F;td&gt;&lt;td&gt;Preserves all object versions for audit&#x2F;recovery&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;aws_s3_bucket_public_access_block&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;AC-3&lt;&#x2F;td&gt;&lt;td&gt;Denies all four public access vectors&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;aws_s3_bucket.log&lt;&#x2F;code&gt; + &lt;code&gt;aws_s3_bucket_logging&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;AU-3 &#x2F; AU-6&lt;&#x2F;td&gt;&lt;td&gt;Dedicated audit log bucket that collects S3 access logs&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Default provider tags&lt;&#x2F;td&gt;&lt;td&gt;CM-6&lt;&#x2F;td&gt;&lt;td&gt;Forces compliance tags onto every resource automatically&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Here, each row is a control satisfied by writing the resource correctly. No separate policy engine checking anything yet. The Terraform code is the policy.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;outputs.tf&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The return values: &lt;code&gt;bucket_arn&lt;&#x2F;code&gt;, &lt;code&gt;bucket_name&lt;&#x2F;code&gt;, &lt;code&gt;log_bucket_arn&lt;&#x2F;code&gt; and &lt;code&gt;encryption_algorithm&lt;&#x2F;code&gt; which acts as attestation that SC-28 is active.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;two-red-flags-i-noticed&quot;&gt;Two Red Flags I Noticed&lt;&#x2F;h2&gt;
&lt;p&gt;This is just a learning exercise on creating policy as code. However, two red flags stood out to me as I read through the instructions.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Exporting credentials into shell environment variables is rarely a good idea. These are readable by any process running under your account. If anything is compromised (e.g. an npm package, VS Code extension, etc.), it can read &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;&#x2F;code&gt; directly from the environment.&lt;&#x2F;p&gt;
&lt;p&gt;I opted to use AWS CloudShell and ran Terraform inside the AWS console instead. Nothing touches my laptop at all. There is no &lt;code&gt;aws configure&lt;&#x2F;code&gt; or keys to manage. CloudShell ships with AWS CLI v2 already configured, though Terraform isn&#x27;t preinstalled. I installed it in the session. I then uploaded my three &lt;code&gt;.tf&lt;&#x2F;code&gt; files and ran the lab as written, minus all the credential setup, then downloaded a zip of the evidence at the end.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;The log bucket lives in the same AWS account as the data bucket. In the real world, this creates a critical weakness. Any IAM principal (user, role, or admin) with broad account permissions can delete the log bucket and overwrite&#x2F;expire log objects to erase their own tracks after a security incident.&lt;&#x2F;p&gt;
&lt;p&gt;This violates AU-9: Protection of Audit Information, which requires audit logs to be protected from the principals being audited.&lt;&#x2F;p&gt;
&lt;p&gt;How to improve this:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;At the very least, use a strict IAM policy that denies &lt;code&gt;DeleteObject&lt;&#x2F;code&gt; and &lt;code&gt;DeleteBucket&lt;&#x2F;code&gt; to everyone, and enable S3 Object Lock in Compliance mode. Set an S3 Lifecycle policy for cleanups depending on compliance frameworks (the minimum values), legal and regulatory requirements, and GRC&#x2F;Security teams.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Separate AWS accounts with a dedicated logging account that the data account writes to via a cross-account bucket policy. This is recommended by the AWS Well-Architected Framework for compliance workloads.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Separate AWS Organizations with SCPs. The logging account is governed by SCPs that prevent its own admins from disabling logging.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;This is the part that stuck with me as someone new to Terraform and PaC. The module technically satisfies every control on the checklist, yet still has a hole a real auditor would flag on sight. Writing the policy is not the same as thinking like the bad actor the policy is supposed to stop.&lt;&#x2F;p&gt;
&lt;p&gt;The question I&#x27;m carrying into the next lab: what does &quot;compliant&quot; actually mean if the checklist can be satisfied while the threat model isn&#x27;t?&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
