<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://www.red-team.sh/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.red-team.sh/" rel="alternate" type="text/html" /><updated>2026-08-10T10:19:11-07:00</updated><id>https://www.red-team.sh/feed.xml</id><title type="html">red-team.sh</title><subtitle>We are dedicated to helping organizations of all sizes improve their security posture. Our team of experienced security professionals shares knowledge through our DevSecOps blog covering AWS security, threat detection, and defensive security practices.</subtitle><author><name>Jon (JR) Price</name></author><entry><title type="html">Vulnerability Intelligence Is a Prioritization Problem, Not a Feed Problem</title><link href="https://www.red-team.sh/posts/vulnerability-intelligence-prioritization/" rel="alternate" type="text/html" title="Vulnerability Intelligence Is a Prioritization Problem, Not a Feed Problem" /><published>2026-05-13T09:00:00-07:00</published><updated>2026-05-13T09:00:00-07:00</updated><id>https://www.red-team.sh/posts/vulnerability-intelligence-prioritization</id><content type="html" xml:base="https://www.red-team.sh/posts/vulnerability-intelligence-prioritization/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#introduction" id="markdown-toc-introduction">Introduction</a>    <ul>
      <li><a href="#current-landscape-statistics" id="markdown-toc-current-landscape-statistics">Current Landscape Statistics</a></li>
    </ul>
  </li>
  <li><a href="#the-feed-trap-more-data-does-not-equal-better-decisions" id="markdown-toc-the-feed-trap-more-data-does-not-equal-better-decisions">The Feed Trap: More Data Does Not Equal Better Decisions</a></li>
  <li><a href="#start-with-a-triage-contract" id="markdown-toc-start-with-a-triage-contract">Start with a Triage Contract</a></li>
  <li><a href="#use-nvd-for-baseline-metadata-not-final-priority" id="markdown-toc-use-nvd-for-baseline-metadata-not-final-priority">Use NVD for Baseline Metadata, Not Final Priority</a>    <ul>
      <li><a href="#what-nvd-is-good-for" id="markdown-toc-what-nvd-is-good-for">What NVD Is Good For</a></li>
      <li><a href="#where-nvd-needs-context" id="markdown-toc-where-nvd-needs-context">Where NVD Needs Context</a></li>
    </ul>
  </li>
  <li><a href="#use-cisa-kev-as-a-fast-escalation-signal" id="markdown-toc-use-cisa-kev-as-a-fast-escalation-signal">Use CISA KEV as a Fast Escalation Signal</a>    <ul>
      <li><a href="#kev-triage-questions" id="markdown-toc-kev-triage-questions">KEV Triage Questions</a></li>
    </ul>
  </li>
  <li><a href="#build-a-context-weighted-priority-model" id="markdown-toc-build-a-context-weighted-priority-model">Build a Context-Weighted Priority Model</a>    <ul>
      <li><a href="#example-priority-bands" id="markdown-toc-example-priority-bands">Example Priority Bands</a></li>
    </ul>
  </li>
  <li><a href="#prevent-alert-fatigue-with-suppression-and-routing-rules" id="markdown-toc-prevent-alert-fatigue-with-suppression-and-routing-rules">Prevent Alert Fatigue with Suppression and Routing Rules</a>    <ul>
      <li><a href="#suppress-non-actionable-findings" id="markdown-toc-suppress-non-actionable-findings">Suppress Non-Actionable Findings</a></li>
      <li><a href="#route-with-remediation-context" id="markdown-toc-route-with-remediation-context">Route with Remediation Context</a></li>
    </ul>
  </li>
  <li><a href="#aws-centric-implementation-pattern" id="markdown-toc-aws-centric-implementation-pattern">AWS-Centric Implementation Pattern</a>    <ul>
      <li><a href="#reference-service-map" id="markdown-toc-reference-service-map">Reference Service Map</a></li>
      <li><a href="#example-priority-logic" id="markdown-toc-example-priority-logic">Example Priority Logic</a></li>
    </ul>
  </li>
  <li><a href="#where-vulndb-style-intelligence-fits" id="markdown-toc-where-vulndb-style-intelligence-fits">Where VulnDB-Style Intelligence Fits</a></li>
  <li><a href="#implementation-roadmap" id="markdown-toc-implementation-roadmap">Implementation Roadmap</a>    <ul>
      <li><a href="#phase-1-stabilize-the-basics-weeks-1-2" id="markdown-toc-phase-1-stabilize-the-basics-weeks-1-2">Phase 1: Stabilize the Basics (Weeks 1-2)</a></li>
      <li><a href="#phase-2-add-context-and-automation-weeks-3-6" id="markdown-toc-phase-2-add-context-and-automation-weeks-3-6">Phase 2: Add Context and Automation (Weeks 3-6)</a></li>
      <li><a href="#phase-3-optimize-the-feedback-loop-weeks-7-12" id="markdown-toc-phase-3-optimize-the-feedback-loop-weeks-7-12">Phase 3: Optimize the Feedback Loop (Weeks 7-12)</a></li>
    </ul>
  </li>
  <li><a href="#best-practices-for-sustainable-vulnerability-intelligence" id="markdown-toc-best-practices-for-sustainable-vulnerability-intelligence">Best Practices for Sustainable Vulnerability Intelligence</a>    <ul>
      <li><a href="#make-priority-explainable" id="markdown-toc-make-priority-explainable">Make Priority Explainable</a></li>
      <li><a href="#separate-discovery-from-demand" id="markdown-toc-separate-discovery-from-demand">Separate Discovery from Demand</a></li>
      <li><a href="#keep-suppression-rules-temporary" id="markdown-toc-keep-suppression-rules-temporary">Keep Suppression Rules Temporary</a></li>
      <li><a href="#measure-outcomes-not-feed-volume" id="markdown-toc-measure-outcomes-not-feed-volume">Measure Outcomes, Not Feed Volume</a></li>
      <li><a href="#preserve-human-judgment-for-edge-cases" id="markdown-toc-preserve-human-judgment-for-edge-cases">Preserve Human Judgment for Edge Cases</a></li>
    </ul>
  </li>
  <li><a href="#related-articles" id="markdown-toc-related-articles">Related Articles</a></li>
  <li><a href="#additional-resources" id="markdown-toc-additional-resources">Additional Resources</a>    <ul>
      <li><a href="#official-documentation" id="markdown-toc-official-documentation">Official Documentation</a></li>
      <li><a href="#tools-and-frameworks" id="markdown-toc-tools-and-frameworks">Tools and Frameworks</a></li>
    </ul>
  </li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ul>

<h2 id="introduction">Introduction</h2>

<p>Most vulnerability programs do not fail because they lack feeds. They fail because every new feed adds more work than the team can absorb.</p>

<p>NVD entries, vendor advisories, CISA Known Exploited Vulnerabilities (KEV), scanner findings, cloud posture alerts, container image results, software bill of materials (SBOM) data, and threat intelligence feeds all describe pieces of the same problem. But without a prioritization model, they become an expensive notification system: more tickets, more dashboards, more exceptions, and less confidence about what must be fixed first.</p>

<p>Vulnerability intelligence should answer one operational question:</p>

<blockquote>
  <p>Which vulnerabilities create the most plausible risk to the systems that matter right now?</p>
</blockquote>

<p>That is a prioritization question, not a feed aggregation question. A useful vulnerability-intelligence program combines public sources such as NVD and CISA KEV with internal context: asset criticality, internet exposure, compensating controls, exploit maturity, ownership, business function, and remediation path. The result is fewer alerts, clearer service-level objectives, and better conversations between security, engineering, and operations.</p>

<h3 id="current-landscape-statistics">Current Landscape Statistics</h3>

<ul>
  <li><strong>25,000+ CVEs</strong> are published annually in recent years, and the volume continues to grow across operating systems, applications, cloud services, and open-source dependencies.</li>
  <li><strong>1,000+ entries</strong> are listed in the CISA KEV catalog, representing vulnerabilities with confirmed exploitation in the wild.</li>
  <li><strong>Single CVSS scores are insufficient</strong> for operational triage because they describe technical severity, not whether the vulnerable software is present, exposed, exploitable, or business-critical in your environment.</li>
  <li><strong>Alert fatigue is a security risk</strong>: when every vulnerability looks urgent, teams either ignore the queue or create informal prioritization rules that are hard to audit.</li>
  <li><strong>Context-rich programs reduce noise</strong> by suppressing non-applicable findings, escalating externally exposed critical assets, and routing work directly to accountable owners.</li>
</ul>

<h2 id="the-feed-trap-more-data-does-not-equal-better-decisions">The Feed Trap: More Data Does Not Equal Better Decisions</h2>

<p>Adding another vulnerability feed feels productive because it increases visibility. The problem is that visibility without decision logic increases cognitive load.</p>

<p>A typical security team may receive vulnerability signals from:</p>

<ul>
  <li>NVD CVE records and CVSS metadata</li>
  <li>CISA KEV entries for known exploited vulnerabilities</li>
  <li>Vendor advisories and emergency bulletins</li>
  <li>Container and package scanners</li>
  <li>Endpoint detection and response tooling</li>
  <li>Cloud security posture management findings</li>
  <li>Web application scanners</li>
  <li>Asset inventory systems</li>
  <li>SBOM and dependency graphs</li>
  <li>Threat intelligence reports</li>
</ul>

<p>Each source is useful. None of them is sufficient by itself.</p>

<p>NVD can tell you that a vulnerability exists and provide standardized scoring metadata. CISA KEV can tell you that a vulnerability has been exploited in the wild. A scanner can tell you that a package or configuration appears vulnerable. Your asset inventory can tell you where the affected technology exists. Your cloud telemetry can tell you whether the system is internet-facing. Your change-management data can tell you who owns it and whether a patch is deployable.</p>

<p>The mistake is treating all of those signals as separate queues. That creates duplicate work and competing definitions of urgency. A better model normalizes the signals into one decision pipeline.</p>

<h2 id="start-with-a-triage-contract">Start with a Triage Contract</h2>

<p>Before adding automation, define a triage contract that everyone can understand. The contract should describe how vulnerability intelligence becomes action.</p>

<p>At minimum, document:</p>

<ol>
  <li><strong>Intake sources</strong> — Which sources are authoritative for CVE metadata, exploitation status, asset inventory, and ownership?</li>
  <li><strong>Applicability rules</strong> — How do you prove a finding is relevant to an asset, package, image, runtime, or exposed service?</li>
  <li><strong>Priority model</strong> — Which factors can raise or lower urgency?</li>
  <li><strong>Service-level objectives</strong> — How quickly must each priority level be remediated or mitigated?</li>
  <li><strong>Exception rules</strong> — Who can accept residual risk, for how long, and with what compensating controls?</li>
  <li><strong>Feedback loop</strong> — How are false positives, duplicate tickets, and stale ownership records corrected?</li>
</ol>

<p>A triage contract prevents the vulnerability program from becoming a personality-driven escalation process. It also makes automation safer because the logic is explicit before it is encoded into workflows.</p>

<h2 id="use-nvd-for-baseline-metadata-not-final-priority">Use NVD for Baseline Metadata, Not Final Priority</h2>

<p>The National Vulnerability Database is a foundational source for CVE enrichment. It provides standardized identifiers, descriptions, references, affected products through Common Platform Enumeration (CPE), and CVSS scoring. NVD is excellent for baseline metadata.</p>

<p>But NVD should not be the last word on urgency.</p>

<p>CVSS answers, “How severe could this vulnerability be under standardized assumptions?” Operational prioritization asks, “How likely is this vulnerability to affect our environment in a meaningful way?”</p>

<p>Those are different questions.</p>

<h3 id="what-nvd-is-good-for">What NVD Is Good For</h3>

<p>Use NVD to normalize and enrich:</p>

<ul>
  <li>CVE identifiers and descriptions</li>
  <li>CVSS base metrics</li>
  <li>Affected product references</li>
  <li>Vendor advisory links</li>
  <li>Weakness mappings such as CWE</li>
  <li>Initial severity bands for routing and reporting</li>
</ul>

<h3 id="where-nvd-needs-context">Where NVD Needs Context</h3>

<p>NVD does not know whether:</p>

<ul>
  <li>The vulnerable component is deployed in production</li>
  <li>The system is reachable from the internet</li>
  <li>The vulnerable feature is enabled</li>
  <li>The asset supports a critical business process</li>
  <li>Existing controls block exploit paths</li>
  <li>An exploit is reliable and actively used by attackers</li>
  <li>A patch would break a revenue-impacting workflow</li>
</ul>

<p>That missing context is where your internal intelligence layer matters.</p>

<h2 id="use-cisa-kev-as-a-fast-escalation-signal">Use CISA KEV as a Fast Escalation Signal</h2>

<p>CISA KEV is one of the most operationally useful public vulnerability sources because inclusion means the vulnerability is known to be exploited in the wild. That does not mean every KEV item is equally urgent for every organization, but it does mean the finding deserves faster evaluation.</p>

<p>A practical KEV rule looks like this:</p>

<blockquote>
  <p>If a KEV-listed vulnerability is present on an internet-facing or mission-critical asset, escalate it above routine scanner backlog and require mitigation, patching, or documented risk acceptance.</p>
</blockquote>

<p>This works because KEV changes the conversation from theoretical severity to demonstrated attacker behavior.</p>

<h3 id="kev-triage-questions">KEV Triage Questions</h3>

<p>For each KEV match, answer:</p>

<ul>
  <li>Is the affected product or component present?</li>
  <li>Is the affected version actually running?</li>
  <li>Is the vulnerable interface reachable?</li>
  <li>Is the asset internet-facing, partner-facing, or internal-only?</li>
  <li>Is the asset tied to identity, remote access, perimeter security, build infrastructure, or sensitive data?</li>
  <li>Are compensating controls already reducing exploitability?</li>
  <li>Is there a vendor patch, configuration workaround, or isolation option?</li>
</ul>

<p>The goal is not to panic every time KEV updates. The goal is to create a reliable fast lane for vulnerabilities that have crossed from “could be exploited” to “has been exploited.”</p>

<h2 id="build-a-context-weighted-priority-model">Build a Context-Weighted Priority Model</h2>

<p>A good prioritization model separates signal from decision. Each source contributes evidence. The model turns evidence into action.</p>

<p>Consider scoring vulnerabilities across five dimensions:</p>

<table>
  <thead>
    <tr>
      <th>Dimension</th>
      <th>Question</th>
      <th>Example Signal</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Exploitation</td>
      <td>Is exploitation observed or likely?</td>
      <td>CISA KEV, vendor advisory, public exploit maturity</td>
    </tr>
    <tr>
      <td>Exposure</td>
      <td>Can an attacker reach the vulnerable path?</td>
      <td>Internet-facing service, VPN-only, internal segment</td>
    </tr>
    <tr>
      <td>Asset criticality</td>
      <td>How much does the asset matter?</td>
      <td>Identity, payment, build, production, regulated data</td>
    </tr>
    <tr>
      <td>Applicability</td>
      <td>Is the finding actually relevant?</td>
      <td>Installed version, enabled feature, vulnerable package path</td>
    </tr>
    <tr>
      <td>Remediation complexity</td>
      <td>How quickly can the team reduce risk?</td>
      <td>Patch available, config workaround, isolation option</td>
    </tr>
  </tbody>
</table>

<p>This produces better outcomes than raw severity. A medium CVSS vulnerability on an exposed identity system with active exploitation may deserve faster action than a critical CVSS vulnerability in a disabled library on a non-production host.</p>

<h3 id="example-priority-bands">Example Priority Bands</h3>

<p>Use plain-language bands that map to operational behavior:</p>

<p><strong>Priority 1: Active Risk</strong></p>

<ul>
  <li>KEV-listed or confirmed active exploitation</li>
  <li>Present on internet-facing, identity, build, security, or business-critical assets</li>
  <li>Requires immediate mitigation, patching, isolation, or formal risk acceptance</li>
</ul>

<p><strong>Priority 2: Likely Risk</strong></p>

<ul>
  <li>Public exploit code or strong exploit indicators</li>
  <li>Present on production assets or reachable internal services</li>
  <li>Requires near-term remediation with clear ownership</li>
</ul>

<p><strong>Priority 3: Managed Risk</strong></p>

<ul>
  <li>High technical severity but limited exposure or strong compensating controls</li>
  <li>Requires scheduled remediation and monitoring</li>
</ul>

<p><strong>Priority 4: Hygiene Backlog</strong></p>

<ul>
  <li>Low exploitability, non-production scope, or uncertain applicability</li>
  <li>Remediate through normal patch cycles or dependency refreshes</li>
</ul>

<p>The exact labels matter less than the behavior they trigger. Every band should define ownership, SLA, escalation path, and closure evidence.</p>

<h2 id="prevent-alert-fatigue-with-suppression-and-routing-rules">Prevent Alert Fatigue with Suppression and Routing Rules</h2>

<p>Alert fatigue is not just a human problem. It is usually a systems-design problem.</p>

<p>A vulnerability-intelligence pipeline should reduce noise before it reaches engineers. That means suppressing findings that are not actionable, deduplicating repeated signals, and routing tickets to the right owner with the context needed to fix the issue.</p>

<h3 id="suppress-non-actionable-findings">Suppress Non-Actionable Findings</h3>

<p>Suppress or downgrade findings when:</p>

<ul>
  <li>The affected package is present only in a build cache or unused layer</li>
  <li>The vulnerable feature is disabled and cannot be reached</li>
  <li>The asset has been decommissioned or is outside the supported scope</li>
  <li>A compensating control blocks the known exploit path</li>
  <li>The finding duplicates an existing tracked ticket</li>
</ul>

<p>Suppression must be auditable. Avoid silent drops. Record why the finding was suppressed, who approved the logic, and when the rule expires or must be revalidated.</p>

<h3 id="route-with-remediation-context">Route with Remediation Context</h3>

<p>A useful ticket should include:</p>

<ul>
  <li>CVE identifier and summary</li>
  <li>Priority band and reasons for the priority</li>
  <li>Affected asset, package, image, or service</li>
  <li>Evidence of exposure or exploitability</li>
  <li>CISA KEV status if applicable</li>
  <li>Vendor advisory and patch reference</li>
  <li>Recommended mitigation path</li>
  <li>Asset owner or service team</li>
  <li>Due date based on the triage contract</li>
</ul>

<p>If engineers have to open five dashboards to understand why a ticket exists, the pipeline is creating toil instead of reducing risk.</p>

<h2 id="aws-centric-implementation-pattern">AWS-Centric Implementation Pattern</h2>

<p>You can implement a context-driven vulnerability-intelligence workflow using managed AWS services without building a heavy platform on day one.</p>

<p>A practical architecture looks like this:</p>

<ol>
  <li><strong>Ingest</strong> public vulnerability metadata from NVD, CISA KEV, vendor advisories, and scanner outputs.</li>
  <li><strong>Normalize</strong> records into a common schema keyed by CVE, package, product, image, and asset identifier.</li>
  <li><strong>Enrich</strong> with asset inventory, cloud tags, ownership, exposure, environment, and business criticality.</li>
  <li><strong>Score</strong> findings using your priority model.</li>
  <li><strong>Route</strong> actionable items to ticketing, chat, or security orchestration workflows.</li>
  <li><strong>Measure</strong> aging, exceptions, false positives, and remediation outcomes.</li>
</ol>

<h3 id="reference-service-map">Reference Service Map</h3>

<table>
  <thead>
    <tr>
      <th>Function</th>
      <th>AWS Service Option</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Scheduled ingestion</td>
      <td>Amazon EventBridge Scheduler, AWS Lambda</td>
    </tr>
    <tr>
      <td>Raw advisory storage</td>
      <td>Amazon S3</td>
    </tr>
    <tr>
      <td>Normalized finding store</td>
      <td>Amazon DynamoDB or Amazon OpenSearch Service</td>
    </tr>
    <tr>
      <td>Workflow orchestration</td>
      <td>AWS Step Functions</td>
    </tr>
    <tr>
      <td>Event routing</td>
      <td>Amazon EventBridge</td>
    </tr>
    <tr>
      <td>Secrets and API keys</td>
      <td>AWS Secrets Manager or Systems Manager Parameter Store</td>
    </tr>
    <tr>
      <td>Metrics and dashboards</td>
      <td>Amazon CloudWatch, Amazon QuickSight</td>
    </tr>
    <tr>
      <td>Security aggregation</td>
      <td>AWS Security Hub</td>
    </tr>
  </tbody>
</table>

<p>This pattern keeps the system modular. You can start with scheduled KEV and NVD enrichment, then add scanner outputs, SBOM data, and cloud exposure over time.</p>

<h3 id="example-priority-logic">Example Priority Logic</h3>

<p>The following pseudocode shows the shape of a context-weighted score. The specific weights should match your triage contract.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
</pre></td><td class="rouge-code"><pre><span class="k">def</span> <span class="nf">prioritize</span><span class="p">(</span><span class="n">finding</span><span class="p">):</span>
    <span class="n">score</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">reasons</span> <span class="o">=</span> <span class="p">[]</span>

    <span class="k">if</span> <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">cisa_kev</span><span class="sh">"</span><span class="p">)</span> <span class="ow">is</span> <span class="bp">True</span><span class="p">:</span>
        <span class="n">score</span> <span class="o">+=</span> <span class="mi">40</span>
        <span class="n">reasons</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="sh">"</span><span class="s">listed in CISA KEV</span><span class="sh">"</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">internet_exposed</span><span class="sh">"</span><span class="p">)</span> <span class="ow">is</span> <span class="bp">True</span><span class="p">:</span>
        <span class="n">score</span> <span class="o">+=</span> <span class="mi">25</span>
        <span class="n">reasons</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="sh">"</span><span class="s">internet-facing asset</span><span class="sh">"</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">asset_tier</span><span class="sh">"</span><span class="p">)</span> <span class="ow">in</span> <span class="p">[</span><span class="sh">"</span><span class="s">identity</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">build</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">security</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">critical-production</span><span class="sh">"</span><span class="p">]:</span>
        <span class="n">score</span> <span class="o">+=</span> <span class="mi">20</span>
        <span class="n">reasons</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">critical asset tier: </span><span class="si">{</span><span class="n">finding</span><span class="p">[</span><span class="sh">'</span><span class="s">asset_tier</span><span class="sh">'</span><span class="p">]</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">public_exploit</span><span class="sh">"</span><span class="p">)</span> <span class="ow">is</span> <span class="bp">True</span><span class="p">:</span>
        <span class="n">score</span> <span class="o">+=</span> <span class="mi">15</span>
        <span class="n">reasons</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="sh">"</span><span class="s">public exploit available</span><span class="sh">"</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">compensating_control</span><span class="sh">"</span><span class="p">)</span> <span class="ow">is</span> <span class="bp">True</span><span class="p">:</span>
        <span class="n">score</span> <span class="o">-=</span> <span class="mi">15</span>
        <span class="n">reasons</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="sh">"</span><span class="s">compensating control documented</span><span class="sh">"</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">applicability</span><span class="sh">"</span><span class="p">)</span> <span class="o">==</span> <span class="sh">"</span><span class="s">not-affected</span><span class="sh">"</span><span class="p">:</span>
        <span class="k">return</span> <span class="p">{</span>
            <span class="sh">"</span><span class="s">priority</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">suppressed</span><span class="sh">"</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">reasons</span><span class="sh">"</span><span class="p">:</span> <span class="p">[</span><span class="sh">"</span><span class="s">not applicable to deployed configuration</span><span class="sh">"</span><span class="p">]</span>
        <span class="p">}</span>

    <span class="k">if</span> <span class="n">score</span> <span class="o">&gt;=</span> <span class="mi">70</span><span class="p">:</span>
        <span class="n">priority</span> <span class="o">=</span> <span class="sh">"</span><span class="s">P1 Active Risk</span><span class="sh">"</span>
    <span class="k">elif</span> <span class="n">score</span> <span class="o">&gt;=</span> <span class="mi">45</span><span class="p">:</span>
        <span class="n">priority</span> <span class="o">=</span> <span class="sh">"</span><span class="s">P2 Likely Risk</span><span class="sh">"</span>
    <span class="k">elif</span> <span class="n">score</span> <span class="o">&gt;=</span> <span class="mi">20</span><span class="p">:</span>
        <span class="n">priority</span> <span class="o">=</span> <span class="sh">"</span><span class="s">P3 Managed Risk</span><span class="sh">"</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">priority</span> <span class="o">=</span> <span class="sh">"</span><span class="s">P4 Hygiene Backlog</span><span class="sh">"</span>

    <span class="k">return</span> <span class="p">{</span><span class="sh">"</span><span class="s">priority</span><span class="sh">"</span><span class="p">:</span> <span class="n">priority</span><span class="p">,</span> <span class="sh">"</span><span class="s">score</span><span class="sh">"</span><span class="p">:</span> <span class="n">score</span><span class="p">,</span> <span class="sh">"</span><span class="s">reasons</span><span class="sh">"</span><span class="p">:</span> <span class="n">reasons</span><span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The important part is explainability. A priority value without reasons will be challenged. A priority value with clear evidence becomes a decision record.</p>

<h2 id="where-vulndb-style-intelligence-fits">Where VulnDB-Style Intelligence Fits</h2>

<p>Commercial vulnerability intelligence can add value when it improves timeliness, affected-version precision, exploit context, or remediation guidance. A source such as VulnDB is most useful when it fills gaps between public CVE publication, vendor disclosure, and operational scanner coverage.</p>

<p>The pattern still matters more than the product. Treat any intelligence source as evidence, not as an automatic ticket generator. If a feed cannot be mapped to affected assets, exploitability, exposure, and ownership, it will add noise faster than it adds value.</p>

<h2 id="implementation-roadmap">Implementation Roadmap</h2>

<h3 id="phase-1-stabilize-the-basics-weeks-1-2">Phase 1: Stabilize the Basics (Weeks 1-2)</h3>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Define authoritative sources for CVE metadata, KEV status, scanner findings, assets, and ownership</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Create a four-level priority model with clear remediation expectations</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Identify internet-facing, identity, build, and security-control assets</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Document exception and suppression requirements</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Establish reporting for vulnerability age by priority band</li>
</ul>

<h3 id="phase-2-add-context-and-automation-weeks-3-6">Phase 2: Add Context and Automation (Weeks 3-6)</h3>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Ingest NVD and CISA KEV updates on a scheduled basis</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Normalize scanner findings by CVE, package, image, and asset</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Enrich findings with exposure, environment, business function, and owner</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Deduplicate repeated findings before ticket creation</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Route P1 and P2 findings with evidence and remediation guidance</li>
</ul>

<h3 id="phase-3-optimize-the-feedback-loop-weeks-7-12">Phase 3: Optimize the Feedback Loop (Weeks 7-12)</h3>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Track false positives and convert recurring patterns into suppression rules</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Review exception aging and compensating-control evidence</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Measure mean time to remediate by priority, team, and asset tier</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Tune scoring weights based on incidents, exploit activity, and remediation outcomes</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Publish a monthly vulnerability-intelligence review for engineering leadership</li>
</ul>

<h2 id="best-practices-for-sustainable-vulnerability-intelligence">Best Practices for Sustainable Vulnerability Intelligence</h2>

<h3 id="make-priority-explainable">Make Priority Explainable</h3>

<p>Every escalated finding should explain why it matters. “Critical CVSS” is not enough. “CISA KEV, exposed perimeter appliance, production identity dependency, patch available” is actionable.</p>

<h3 id="separate-discovery-from-demand">Separate Discovery from Demand</h3>

<p>Discovery systems should find as much as possible. Demand systems should ask humans to fix only what has been triaged into an actionable state. Mixing those functions creates noise.</p>

<h3 id="keep-suppression-rules-temporary">Keep Suppression Rules Temporary</h3>

<p>Permanent suppression is risky. Use expiration dates, revalidation checks, and owner review. A finding that is not exploitable today may become exploitable after an architecture change.</p>

<h3 id="measure-outcomes-not-feed-volume">Measure Outcomes, Not Feed Volume</h3>

<p>Do not celebrate ingesting more advisories. Measure reduced exposure windows, fewer duplicate tickets, faster remediation for exploited vulnerabilities, and better exception quality.</p>

<h3 id="preserve-human-judgment-for-edge-cases">Preserve Human Judgment for Edge Cases</h3>

<p>Automation should handle routing, enrichment, and obvious priority decisions. Humans still need to evaluate business impact, complex compensating controls, outage risk, and ambiguous exploitability.</p>

<h2 id="related-articles">Related Articles</h2>

<ul>
  <li><a href="/posts/event-driven-security-automation-eventbridge-misp/">Event-Driven Security Automation: EventBridge + Step Functions with Open-Source Threat Intel</a></li>
  <li><a href="/posts/sbom-supply-chain-security-syft-grype-aws/">SBOM + Supply Chain Security: Syft, Grype, and AWS Inspector</a></li>
  <li><a href="/posts/security-observability-opentelemetry-aws/">Security Observability with OpenTelemetry and AWS</a></li>
  <li><a href="/posts/aws-devsecops-pipeline-security-automation-implementation/">AWS DevSecOps Pipeline Security Automation: Complete Implementation Guide</a></li>
</ul>

<h2 id="additional-resources">Additional Resources</h2>

<h3 id="official-documentation">Official Documentation</h3>

<ul>
  <li><a href="https://nvd.nist.gov/">National Vulnerability Database</a> - CVE enrichment, CVSS data, references, and vulnerability metadata</li>
  <li><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog">CISA Known Exploited Vulnerabilities Catalog</a> - Public catalog of vulnerabilities known to be exploited in the wild</li>
  <li><a href="https://www.first.org/cvss/">FIRST CVSS</a> - Common Vulnerability Scoring System documentation</li>
  <li><a href="https://docs.aws.amazon.com/securityhub/latest/userguide/what-is-securityhub.html">AWS Security Hub</a> - Aggregation and prioritization for AWS security findings</li>
</ul>

<h3 id="tools-and-frameworks">Tools and Frameworks</h3>

<ul>
  <li><a href="https://github.com/openvex/spec">OpenVEX</a> - Machine-readable vulnerability exploitability exchange format</li>
  <li><a href="https://cyclonedx.org/capabilities/vex/">CycloneDX VEX</a> - VEX support for communicating affected and not-affected status</li>
  <li><a href="https://github.com/anchore/grype">Grype</a> - Open-source vulnerability scanner for container images and filesystems</li>
  <li><a href="https://osv.dev/">OSV</a> - Vulnerability database focused on open-source ecosystems</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>Vulnerability intelligence is valuable when it changes decisions. NVD gives you baseline metadata. CISA KEV gives you a fast signal for known exploitation. Scanner outputs tell you where vulnerable components may exist. Internal context tells you whether the issue is reachable, important, owned, and fixable.</p>

<p>The winning pattern is not “more feeds.” It is a context-rich prioritization system that turns vulnerability data into focused remediation work without exhausting the people responsible for fixing it.</p>

<p>If you are building a vulnerability-intelligence program or refining how security context reaches engineering teams, connect through <a href="https://www.jonprice.io/">jonprice.io</a> for practical cloud security and DevSecOps guidance.</p>]]></content><author><name>Jon (JR) Price</name></author><category term="Security" /><category term="DevSecOps" /><category term="Threat Intelligence" /><category term="cisa-kev" /><category term="nvd" /><category term="prioritization" /><category term="threat-intelligence" /><category term="vulnerability-intelligence" /><category term="vulnerability-management" /><summary type="html"><![CDATA[Use NVD, CISA KEV, exploit context, and asset exposure to prioritize vulnerabilities without creating alert fatigue.]]></summary></entry><entry><title type="html">AWS Network Firewall + Suricata: Leveraging Open-Source IDS Rules for Cloud Defense</title><link href="https://www.red-team.sh/posts/aws-network-firewall-suricata-open-source-ids/" rel="alternate" type="text/html" title="AWS Network Firewall + Suricata: Leveraging Open-Source IDS Rules for Cloud Defense" /><published>2026-05-06T09:00:00-07:00</published><updated>2026-05-06T09:00:00-07:00</updated><id>https://www.red-team.sh/posts/aws-network-firewall-suricata-open-source-ids</id><content type="html" xml:base="https://www.red-team.sh/posts/aws-network-firewall-suricata-open-source-ids/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#introduction" id="markdown-toc-introduction">Introduction</a>    <ul>
      <li><a href="#current-landscape-statistics" id="markdown-toc-current-landscape-statistics">Current Landscape Statistics</a></li>
    </ul>
  </li>
  <li><a href="#aws-network-firewall-architecture--it-is-suricata-under-the-hood" id="markdown-toc-aws-network-firewall-architecture--it-is-suricata-under-the-hood">AWS Network Firewall Architecture — It Is Suricata Under the Hood</a>    <ul>
      <li><a href="#the-suricata-engine-inside" id="markdown-toc-the-suricata-engine-inside">The Suricata Engine Inside</a></li>
      <li><a href="#key-architecture-components" id="markdown-toc-key-architecture-components">Key Architecture Components</a></li>
      <li><a href="#vpc-architecture-with-network-firewall-inspection" id="markdown-toc-vpc-architecture-with-network-firewall-inspection">VPC Architecture with Network Firewall Inspection</a></li>
    </ul>
  </li>
  <li><a href="#managed-rule-groups--what-aws-provides-out-of-the-box" id="markdown-toc-managed-rule-groups--what-aws-provides-out-of-the-box">Managed Rule Groups — What AWS Provides Out of the Box</a>    <ul>
      <li><a href="#aws-managed-threat-signatures" id="markdown-toc-aws-managed-threat-signatures">AWS Managed Threat Signatures</a></li>
      <li><a href="#aws-marketplace-partner-rules" id="markdown-toc-aws-marketplace-partner-rules">AWS Marketplace Partner Rules</a></li>
      <li><a href="#what-you-get-with-managed-rules" id="markdown-toc-what-you-get-with-managed-rules">What You Get with Managed Rules</a></li>
    </ul>
  </li>
  <li><a href="#why-managed-rules-are-not-enough" id="markdown-toc-why-managed-rules-are-not-enough">Why Managed Rules Are Not Enough</a></li>
  <li><a href="#suricata-rule-format-deep-dive" id="markdown-toc-suricata-rule-format-deep-dive">Suricata Rule Format Deep Dive</a>    <ul>
      <li><a href="#rule-structure" id="markdown-toc-rule-structure">Rule Structure</a></li>
      <li><a href="#actions-supported-by-aws-network-firewall" id="markdown-toc-actions-supported-by-aws-network-firewall">Actions Supported by AWS Network Firewall</a></li>
      <li><a href="#example-detecting-dns-exfiltration" id="markdown-toc-example-detecting-dns-exfiltration">Example: Detecting DNS Exfiltration</a></li>
      <li><a href="#key-rule-keywords-for-cloud-detection" id="markdown-toc-key-rule-keywords-for-cloud-detection">Key Rule Keywords for Cloud Detection</a></li>
      <li><a href="#aws-network-firewall-limitations" id="markdown-toc-aws-network-firewall-limitations">AWS Network Firewall Limitations</a></li>
    </ul>
  </li>
  <li><a href="#emerging-threats-et-open--the-community-rule-ecosystem" id="markdown-toc-emerging-threats-et-open--the-community-rule-ecosystem">Emerging Threats (ET Open) — The Community Rule Ecosystem</a>    <ul>
      <li><a href="#et-open-rule-categories" id="markdown-toc-et-open-rule-categories">ET Open Rule Categories</a></li>
      <li><a href="#downloading-et-open-rules" id="markdown-toc-downloading-et-open-rules">Downloading ET Open Rules</a></li>
    </ul>
  </li>
  <li><a href="#importing-custom-suricata-rules-into-network-firewall" id="markdown-toc-importing-custom-suricata-rules-into-network-firewall">Importing Custom Suricata Rules into Network Firewall</a>    <ul>
      <li><a href="#terraform-configuration-for-custom-rule-groups" id="markdown-toc-terraform-configuration-for-custom-rule-groups">Terraform Configuration for Custom Rule Groups</a></li>
      <li><a href="#custom-cloud-threat-detection-rules" id="markdown-toc-custom-cloud-threat-detection-rules">Custom Cloud Threat Detection Rules</a></li>
      <li><a href="#aws-cli-for-rule-group-management" id="markdown-toc-aws-cli-for-rule-group-management">AWS CLI for Rule Group Management</a></li>
    </ul>
  </li>
  <li><a href="#rule-management-automation--lambda-for-et-open-updates" id="markdown-toc-rule-management-automation--lambda-for-et-open-updates">Rule Management Automation — Lambda for ET Open Updates</a>    <ul>
      <li><a href="#lambda-function-automated-et-open-rule-updater" id="markdown-toc-lambda-function-automated-et-open-rule-updater">Lambda Function: Automated ET Open Rule Updater</a></li>
      <li><a href="#terraform-for-the-lambda-deployment" id="markdown-toc-terraform-for-the-lambda-deployment">Terraform for the Lambda Deployment</a></li>
    </ul>
  </li>
  <li><a href="#vpc-flow-logs-integration-for-network-visibility" id="markdown-toc-vpc-flow-logs-integration-for-network-visibility">VPC Flow Logs Integration for Network Visibility</a>    <ul>
      <li><a href="#enable-vpc-flow-logs-to-cloudwatch" id="markdown-toc-enable-vpc-flow-logs-to-cloudwatch">Enable VPC Flow Logs to CloudWatch</a></li>
    </ul>
  </li>
  <li><a href="#comparison-managed-rules-vs-custom-suricata-rules" id="markdown-toc-comparison-managed-rules-vs-custom-suricata-rules">Comparison: Managed Rules vs Custom Suricata Rules</a></li>
  <li><a href="#detection-scenarios" id="markdown-toc-detection-scenarios">Detection Scenarios</a>    <ul>
      <li><a href="#scenario-1-command-and-control-communication" id="markdown-toc-scenario-1-command-and-control-communication">Scenario 1: Command-and-Control Communication</a></li>
      <li><a href="#scenario-2-data-exfiltration" id="markdown-toc-scenario-2-data-exfiltration">Scenario 2: Data Exfiltration</a></li>
      <li><a href="#scenario-3-lateral-movement" id="markdown-toc-scenario-3-lateral-movement">Scenario 3: Lateral Movement</a></li>
      <li><a href="#scenario-4-cryptomining" id="markdown-toc-scenario-4-cryptomining">Scenario 4: Cryptomining</a></li>
    </ul>
  </li>
  <li><a href="#performance-considerations-and-rule-optimization" id="markdown-toc-performance-considerations-and-rule-optimization">Performance Considerations and Rule Optimization</a>    <ul>
      <li><a href="#1-filter-rules-by-relevance" id="markdown-toc-1-filter-rules-by-relevance">1. Filter Rules by Relevance</a></li>
      <li><a href="#2-use-rule-priorities-and-fast-pattern-matching" id="markdown-toc-2-use-rule-priorities-and-fast-pattern-matching">2. Use Rule Priorities and Fast Pattern Matching</a></li>
      <li><a href="#3-capacity-planning" id="markdown-toc-3-capacity-planning">3. Capacity Planning</a></li>
      <li><a href="#4-rule-testing-and-validation" id="markdown-toc-4-rule-testing-and-validation">4. Rule Testing and Validation</a></li>
    </ul>
  </li>
  <li><a href="#monitoring-and-alerting-on-network-firewall-findings" id="markdown-toc-monitoring-and-alerting-on-network-firewall-findings">Monitoring and Alerting on Network Firewall Findings</a>    <ul>
      <li><a href="#cloudwatch-metric-filter-and-alarm" id="markdown-toc-cloudwatch-metric-filter-and-alarm">CloudWatch Metric Filter and Alarm</a></li>
      <li><a href="#querying-network-firewall-alerts" id="markdown-toc-querying-network-firewall-alerts">Querying Network Firewall Alerts</a></li>
    </ul>
  </li>
  <li><a href="#implementation-roadmap" id="markdown-toc-implementation-roadmap">Implementation Roadmap</a>    <ul>
      <li><a href="#phase-1-foundation-week-1-2" id="markdown-toc-phase-1-foundation-week-1-2">Phase 1: Foundation (Week 1-2)</a></li>
      <li><a href="#phase-2-custom-rules-week-3-4" id="markdown-toc-phase-2-custom-rules-week-3-4">Phase 2: Custom Rules (Week 3-4)</a></li>
      <li><a href="#phase-3-automation-week-5-6" id="markdown-toc-phase-3-automation-week-5-6">Phase 3: Automation (Week 5-6)</a></li>
      <li><a href="#phase-4-active-protection-week-7-8" id="markdown-toc-phase-4-active-protection-week-7-8">Phase 4: Active Protection (Week 7-8)</a></li>
    </ul>
  </li>
  <li><a href="#related-articles" id="markdown-toc-related-articles">Related Articles</a></li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ul>

<h2 id="introduction">Introduction</h2>

<p>Cloud migration has fundamentally shifted where workloads run, but it has not eliminated network-layer threats. Attackers still rely on command-and-control channels, lateral movement across VPC subnets, data exfiltration over DNS, and cryptomining traffic that hides in plain sight. According to the IBM X-Force 2025 Threat Intelligence Index, network-based intrusion attempts remain a top initial access vector, and the intrusion detection and prevention systems market is projected to reach USD 9.11 billion by 2030.</p>

<p>AWS Network Firewall gives you a managed, scalable network inspection service sitting inline with your VPC traffic. What many teams do not realize is that under the hood, Network Firewall runs <strong>Suricata</strong>, the open-source network threat detection engine maintained by the Open Information Security Foundation (OISF). This means you can move far beyond the AWS-managed rule groups and tap into the massive open-source Suricata rule community — tens of thousands of detection signatures maintained by researchers worldwide, updated daily, and completely free.</p>

<p>This guide walks you through the architecture, shows you how to import community rules from the Emerging Threats (ET Open) ruleset, automates rule updates with Lambda, and demonstrates detection scenarios for the most common cloud threats. The approach follows our core philosophy: <strong>build it yourself with open source for maximum freedom and flexibility</strong>.</p>

<h3 id="current-landscape-statistics">Current Landscape Statistics</h3>

<ul>
  <li><strong>USD 9.11B</strong> projected IDPS market size by 2030, growing at 5.4% CAGR</li>
  <li><strong>60% faster</strong> threat detection for organizations using AI-augmented IDS systems</li>
  <li><strong>108 days</strong> sooner breach discovery with automated detection versus manual approaches</li>
  <li><strong>30,000</strong> maximum Suricata rules supported per AWS Network Firewall policy</li>
  <li><strong>Daily updates</strong> from the Emerging Threats Open ruleset covering active threat campaigns</li>
</ul>

<h2 id="aws-network-firewall-architecture--it-is-suricata-under-the-hood">AWS Network Firewall Architecture — It Is Suricata Under the Hood</h2>

<p>AWS Network Firewall is a managed, stateful network inspection and filtering service. It sits in a dedicated firewall subnet within your VPC and inspects traffic as it flows between your workloads and the internet, between VPCs, or between subnets.</p>

<p><img src="/assets/img/aws-network-firewall-suricata-rules.png" alt="AWS Network Firewall + Suricata Architecture" class="shadow" w="800" />
<em>AWS Network Firewall architecture with custom Suricata rules and CloudWatch alerting</em></p>

<h3 id="the-suricata-engine-inside">The Suricata Engine Inside</h3>

<p>The stateful inspection engine in AWS Network Firewall is Suricata. As of November 2024, AWS upgraded from Suricata 6.0.9 to <strong>Suricata 7.0</strong>, bringing PCRE2 regex support and improved protocol detection. This is the same engine used by thousands of organizations running on-premises IDS/IPS deployments, which means:</p>

<ul>
  <li><strong>Full deep packet inspection (DPI)</strong> of traffic flows</li>
  <li><strong>Application-layer protocol detection</strong> for HTTP, TLS, DNS, SSH, and more</li>
  <li><strong>Stateful connection tracking</strong> with flow-aware rule evaluation</li>
  <li><strong>Suricata-compatible rule syntax</strong> for writing custom detection logic</li>
</ul>

<h3 id="key-architecture-components">Key Architecture Components</h3>

<p><strong>Firewall Policy</strong>: The top-level container that defines rule evaluation order and default actions. Use <strong>strict order</strong> evaluation so rules execute in your defined sequence rather than Suricata’s default action-order processing.</p>

<p><strong>Stateful Rule Groups</strong>: Collections of Suricata-compatible rules. You can have up to 20 rule groups (managed + custom combined) per firewall policy.</p>

<p><strong>Firewall Endpoints</strong>: Gateway Load Balancer endpoints deployed in firewall subnets that route traffic through the inspection engine.</p>

<p><strong>Logging Configuration</strong>: Flow logs and alert logs that ship to CloudWatch Logs, S3, or Kinesis Data Firehose.</p>

<h3 id="vpc-architecture-with-network-firewall-inspection">VPC Architecture with Network Firewall Inspection</h3>

<p>Here is a Terraform module that deploys the foundational VPC architecture with Network Firewall inline inspection:</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
</pre></td><td class="rouge-code"><pre><span class="c1"># vpc.tf - VPC with Network Firewall inspection architecture</span>
<span class="nx">resource</span> <span class="s2">"aws_vpc"</span> <span class="s2">"protected"</span> <span class="p">{</span>
  <span class="nx">cidr_block</span>           <span class="o">=</span> <span class="s2">"10.0.0.0/16"</span>
  <span class="nx">enable_dns_support</span>   <span class="o">=</span> <span class="kc">true</span>
  <span class="nx">enable_dns_hostnames</span> <span class="o">=</span> <span class="kc">true</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span>        <span class="o">=</span> <span class="s2">"protected-vpc"</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"internal"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"network-firewall-demo"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="s2">"production"</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"security-team"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security"</span>
  <span class="err">}</span>
<span class="p">}</span>

<span class="c1"># Firewall subnet - dedicated to Network Firewall endpoints</span>
<span class="nx">resource</span> <span class="s2">"aws_subnet"</span> <span class="s2">"firewall"</span> <span class="p">{</span>
  <span class="nx">count</span>             <span class="o">=</span> <span class="mi">2</span>
  <span class="nx">vpc_id</span>            <span class="o">=</span> <span class="nx">aws_vpc</span><span class="p">.</span><span class="nx">protected</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">cidr_block</span>        <span class="o">=</span> <span class="nx">cidrsubnet</span><span class="p">(</span><span class="s2">"10.0.0.0/16"</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="nx">count</span><span class="p">.</span><span class="nx">index</span><span class="p">)</span>
  <span class="nx">availability_zone</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">aws_availability_zones</span><span class="p">.</span><span class="nx">available</span><span class="p">.</span><span class="nx">names</span><span class="p">[</span><span class="nx">count</span><span class="p">.</span><span class="nx">index</span><span class="p">]</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span> <span class="o">=</span> <span class="s2">"firewall-subnet-${count.index}"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># Public subnet - internet-facing resources</span>
<span class="nx">resource</span> <span class="s2">"aws_subnet"</span> <span class="s2">"public"</span> <span class="p">{</span>
  <span class="nx">count</span>                   <span class="o">=</span> <span class="mi">2</span>
  <span class="nx">vpc_id</span>                  <span class="o">=</span> <span class="nx">aws_vpc</span><span class="p">.</span><span class="nx">protected</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">cidr_block</span>              <span class="o">=</span> <span class="nx">cidrsubnet</span><span class="p">(</span><span class="s2">"10.0.0.0/16"</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="nx">count</span><span class="p">.</span><span class="nx">index</span> <span class="o">+</span> <span class="mi">10</span><span class="p">)</span>
  <span class="nx">availability_zone</span>       <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">aws_availability_zones</span><span class="p">.</span><span class="nx">available</span><span class="p">.</span><span class="nx">names</span><span class="p">[</span><span class="nx">count</span><span class="p">.</span><span class="nx">index</span><span class="p">]</span>
  <span class="nx">map_public_ip_on_launch</span> <span class="o">=</span> <span class="kc">true</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span> <span class="o">=</span> <span class="s2">"public-subnet-${count.index}"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># Private subnet - workloads</span>
<span class="nx">resource</span> <span class="s2">"aws_subnet"</span> <span class="s2">"private"</span> <span class="p">{</span>
  <span class="nx">count</span>             <span class="o">=</span> <span class="mi">2</span>
  <span class="nx">vpc_id</span>            <span class="o">=</span> <span class="nx">aws_vpc</span><span class="p">.</span><span class="nx">protected</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">cidr_block</span>        <span class="o">=</span> <span class="nx">cidrsubnet</span><span class="p">(</span><span class="s2">"10.0.0.0/16"</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="nx">count</span><span class="p">.</span><span class="nx">index</span> <span class="o">+</span> <span class="mi">20</span><span class="p">)</span>
  <span class="nx">availability_zone</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">aws_availability_zones</span><span class="p">.</span><span class="nx">available</span><span class="p">.</span><span class="nx">names</span><span class="p">[</span><span class="nx">count</span><span class="p">.</span><span class="nx">index</span><span class="p">]</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span> <span class="o">=</span> <span class="s2">"private-subnet-${count.index}"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_internet_gateway"</span> <span class="s2">"main"</span> <span class="p">{</span>
  <span class="nx">vpc_id</span> <span class="o">=</span> <span class="nx">aws_vpc</span><span class="p">.</span><span class="nx">protected</span><span class="p">.</span><span class="nx">id</span>
<span class="p">}</span>

<span class="c1"># Route table for IGW - sends return traffic through firewall</span>
<span class="nx">resource</span> <span class="s2">"aws_route_table"</span> <span class="s2">"igw"</span> <span class="p">{</span>
  <span class="nx">vpc_id</span> <span class="o">=</span> <span class="nx">aws_vpc</span><span class="p">.</span><span class="nx">protected</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">route</span> <span class="p">{</span>
    <span class="nx">destination_cidr_block</span> <span class="o">=</span> <span class="nx">aws_subnet</span><span class="p">.</span><span class="nx">public</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">cidr_block</span>
    <span class="nx">vpc_endpoint_id</span>        <span class="o">=</span> <span class="nx">tolist</span><span class="p">(</span><span class="nx">aws_networkfirewall_firewall</span><span class="p">.</span><span class="nx">main</span><span class="p">.</span><span class="nx">firewall_status</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">sync_states</span><span class="p">[*].</span><span class="nx">attachment</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">endpoint_id</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
  <span class="p">}</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span> <span class="o">=</span> <span class="s2">"igw-route-table"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_route_table_association"</span> <span class="s2">"igw"</span> <span class="p">{</span>
  <span class="nx">gateway_id</span>     <span class="o">=</span> <span class="nx">aws_internet_gateway</span><span class="p">.</span><span class="nx">main</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">route_table_id</span> <span class="o">=</span> <span class="nx">aws_route_table</span><span class="p">.</span><span class="nx">igw</span><span class="p">.</span><span class="nx">id</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="managed-rule-groups--what-aws-provides-out-of-the-box">Managed Rule Groups — What AWS Provides Out of the Box</h2>

<p>AWS offers several categories of managed rule groups through Network Firewall and via AWS Marketplace partners:</p>

<h3 id="aws-managed-threat-signatures">AWS Managed Threat Signatures</h3>

<p>AWS provides managed stateful rule groups that cover common threat categories:</p>

<ul>
  <li><strong>Malware signatures</strong> — known malware communication patterns</li>
  <li><strong>Botnet command-and-control</strong> — signatures for known C2 infrastructure</li>
  <li><strong>Threat intelligence feeds</strong> — AWS-curated indicators of compromise</li>
</ul>

<h3 id="aws-marketplace-partner-rules">AWS Marketplace Partner Rules</h3>

<p>Third-party providers like CrowdStrike, Fortinet, and Palo Alto Networks offer managed rule groups through AWS Marketplace with additional coverage.</p>

<h3 id="what-you-get-with-managed-rules">What You Get with Managed Rules</h3>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>AWS Managed Rules</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Update frequency</td>
      <td>AWS-determined schedule</td>
    </tr>
    <tr>
      <td>Rule visibility</td>
      <td>Opaque — you cannot see individual rule content</td>
    </tr>
    <tr>
      <td>Customization</td>
      <td>Limited — override actions only</td>
    </tr>
    <tr>
      <td>HOME_NET variable</td>
      <td>Cannot be overridden in managed groups</td>
    </tr>
    <tr>
      <td>Coverage categories</td>
      <td>Broad but general</td>
    </tr>
    <tr>
      <td>Cost</td>
      <td>Per-policy pricing + data processing</td>
    </tr>
  </tbody>
</table>

<h2 id="why-managed-rules-are-not-enough">Why Managed Rules Are Not Enough</h2>

<p>Managed rule groups provide a baseline, but they leave significant gaps that matter for serious security operations:</p>

<p><strong>No rule visibility</strong>: You cannot inspect the actual Suricata signatures in managed rule groups. You see category names and can override actions (alert vs. drop), but you cannot read, modify, or understand the detection logic. This is a black box.</p>

<p><strong>No HOME_NET customization</strong>: Managed rule groups do not let you override Suricata’s <code class="language-plaintext highlighter-rouge">$HOME_NET</code> variable. If your VPC CIDR does not align with the default, directional rules may not match correctly.</p>

<p><strong>Limited coverage for targeted threats</strong>: AWS managed rules cover broad categories but do not include signatures for many specific threat campaigns, emerging exploits, or industry-specific attack patterns that the open-source community tracks daily.</p>

<p><strong>Update timing is opaque</strong>: You do not control when managed rules update, and AWS does not publish a changelog. The ET Open community publishes updates daily on weekdays with full transparency.</p>

<p><strong>Rule capacity constraints</strong>: With a maximum of 20 combined rule groups per policy, every managed group you add reduces your capacity for custom detection logic.</p>

<p><strong>No tuning for your environment</strong>: Every environment has unique traffic patterns. Custom rules let you write signatures specific to your protocols, internal services, and known-good traffic patterns to minimize false positives.</p>

<h2 id="suricata-rule-format-deep-dive">Suricata Rule Format Deep Dive</h2>

<p>Understanding Suricata rule syntax is essential for writing effective custom detections. Every rule consists of three parts: <strong>action</strong>, <strong>header</strong>, and <strong>options</strong>.</p>

<h3 id="rule-structure">Rule Structure</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>action protocol source_ip source_port -&gt; destination_ip destination_port (options;)
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="actions-supported-by-aws-network-firewall">Actions Supported by AWS Network Firewall</h3>

<table>
  <thead>
    <tr>
      <th>Action</th>
      <th>Behavior</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">pass</code></td>
      <td>Allow the packet without further inspection</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">drop</code></td>
      <td>Silently discard the packet</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">reject</code></td>
      <td>Drop the packet and send a TCP RST or ICMP unreachable</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">alert</code></td>
      <td>Allow the packet but generate an alert log entry</td>
    </tr>
  </tbody>
</table>

<h3 id="example-detecting-dns-exfiltration">Example: Detecting DNS Exfiltration</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>alert dns $HOME_NET any -&gt; any any (msg:"ET POLICY Possible DNS Tunneling - Long TXT Query"; dns.query; content:"."; offset:40; sid:9000001; rev:1; classtype:policy-violation;)
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Breaking this down:</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">alert</code></strong> — generate an alert, do not block</li>
  <li><strong><code class="language-plaintext highlighter-rouge">dns</code></strong> — match DNS protocol traffic</li>
  <li><strong><code class="language-plaintext highlighter-rouge">$HOME_NET any -&gt; any any</code></strong> — from any internal host/port to any destination</li>
  <li><strong><code class="language-plaintext highlighter-rouge">dns.query</code></strong> — inspect the DNS query field specifically</li>
  <li><strong><code class="language-plaintext highlighter-rouge">content:"."; offset:40</code></strong> — look for a dot character 40+ bytes into the query (unusually long subdomain, common in DNS tunneling)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">sid:9000001</code></strong> — signature ID above 9,000,000 to avoid collisions with community rules</li>
  <li><strong><code class="language-plaintext highlighter-rouge">classtype:policy-violation</code></strong> — categorize the alert</li>
</ul>

<h3 id="key-rule-keywords-for-cloud-detection">Key Rule Keywords for Cloud Detection</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre># TLS inspection - detect suspicious certificate properties
alert tls $HOME_NET any -&gt; $EXTERNAL_NET any (msg:"Suspicious Self-Signed Certificate"; tls.cert_subject; content:"O="; nocase; content:"Let"; distance:0; tls.cert_issuer; content:"O="; nocase; content:"Let"; distance:0; sid:9000010; rev:1;)

# HTTP inspection - detect beacon behavior
alert http $HOME_NET any -&gt; $EXTERNAL_NET any (msg:"Possible C2 Beacon - Fixed Interval HTTP POST"; flow:established,to_server; http.method; content:"POST"; http.uri; content:"/api/"; pcre:"/^\/api\/[a-f0-9]{8}$/"; threshold:type both, track by_src, count 5, seconds 60; sid:9000020; rev:1;)

# SSH anomaly - detect SSH over non-standard ports
alert ssh any any -&gt; $EXTERNAL_NET !22 (msg:"SSH Traffic on Non-Standard Port"; flow:established; sid:9000030; rev:1; classtype:policy-violation;)
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="aws-network-firewall-limitations">AWS Network Firewall Limitations</h3>

<p>Not all Suricata features work in Network Firewall. These are <strong>not supported</strong>:</p>

<ul>
  <li>Datasets (<code class="language-plaintext highlighter-rouge">dataset</code> and <code class="language-plaintext highlighter-rouge">datarep</code> keywords)</li>
  <li>ENIP/CIP protocol keywords</li>
  <li>File extraction</li>
  <li>FTP-data protocol detection</li>
  <li>IP reputation (<code class="language-plaintext highlighter-rouge">iprep</code> keyword)</li>
  <li>Lua scripting</li>
  <li>PCRE1 format (must use PCRE2 as of Suricata 7.0)</li>
</ul>

<p>Always test your rules against these constraints before deploying.</p>

<h2 id="emerging-threats-et-open--the-community-rule-ecosystem">Emerging Threats (ET Open) — The Community Rule Ecosystem</h2>

<p>The <strong>Emerging Threats Open</strong> (ET Open) ruleset is the gold standard for community-maintained Suricata detection signatures. Maintained by Proofpoint’s Emerging Threats team, ET Open provides:</p>

<ul>
  <li><strong>Tens of thousands of signatures</strong> covering malware, exploits, policy violations, and protocol anomalies</li>
  <li><strong>Daily updates</strong> on weekdays, with out-of-band updates for critical threats</li>
  <li><strong>Categorized rule files</strong> organized by threat type (malware, exploit, policy, info, etc.)</li>
  <li><strong>Free and open source</strong> under a permissive license</li>
  <li><strong>Suricata 7.0 compatible</strong> rulesets available</li>
</ul>

<h3 id="et-open-rule-categories">ET Open Rule Categories</h3>

<table>
  <thead>
    <tr>
      <th>Category</th>
      <th>Description</th>
      <th>Example</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">emerging-malware.rules</code></td>
      <td>Known malware signatures and C2 patterns</td>
      <td>CobaltStrike, Emotet, Qbot beacons</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">emerging-exploit.rules</code></td>
      <td>Exploit attempt detection</td>
      <td>Log4Shell, ProxyLogon, CVE-specific</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">emerging-trojan.rules</code></td>
      <td>Trojan communication patterns</td>
      <td>RAT callbacks, dropper activity</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">emerging-policy.rules</code></td>
      <td>Policy violation detection</td>
      <td>Tor usage, cryptocurrency mining</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">emerging-dns.rules</code></td>
      <td>DNS-based threats</td>
      <td>DGA domains, DNS tunneling</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">emerging-hunting.rules</code></td>
      <td>Threat hunting signatures</td>
      <td>Suspicious patterns for investigation</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">emerging-info.rules</code></td>
      <td>Informational signatures</td>
      <td>Software identification, protocol anomalies</td>
    </tr>
  </tbody>
</table>

<h3 id="downloading-et-open-rules">Downloading ET Open Rules</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="c"># Download the latest ET Open ruleset for Suricata 7.0</span>
curl <span class="nt">-LO</span> https://rules.emergingthreats.net/open/suricata-7.0/emerging.rules.tar.gz

<span class="c"># Extract and inspect</span>
<span class="nb">tar </span>xzf emerging.rules.tar.gz
<span class="nb">ls </span>rules/

<span class="c"># Count total rules</span>
<span class="nb">grep</span> <span class="nt">-c</span> <span class="s2">"^alert</span><span class="se">\|</span><span class="s2">^drop</span><span class="se">\|</span><span class="s2">^pass</span><span class="se">\|</span><span class="s2">^reject"</span> rules/<span class="k">*</span>.rules
<span class="c"># Typical output: 30,000+ signatures</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="importing-custom-suricata-rules-into-network-firewall">Importing Custom Suricata Rules into Network Firewall</h2>

<h3 id="terraform-configuration-for-custom-rule-groups">Terraform Configuration for Custom Rule Groups</h3>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
</pre></td><td class="rouge-code"><pre><span class="c1"># network_firewall.tf - Network Firewall with custom Suricata rules</span>

<span class="nx">resource</span> <span class="s2">"aws_networkfirewall_firewall"</span> <span class="s2">"main"</span> <span class="p">{</span>
  <span class="nx">name</span>                <span class="o">=</span> <span class="s2">"suricata-nfw"</span>
  <span class="nx">firewall_policy_arn</span> <span class="o">=</span> <span class="nx">aws_networkfirewall_firewall_policy</span><span class="p">.</span><span class="nx">main</span><span class="p">.</span><span class="nx">arn</span>
  <span class="nx">vpc_id</span>              <span class="o">=</span> <span class="nx">aws_vpc</span><span class="p">.</span><span class="nx">protected</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">dynamic</span> <span class="s2">"subnet_mapping"</span> <span class="p">{</span>
    <span class="nx">for_each</span> <span class="o">=</span> <span class="nx">aws_subnet</span><span class="p">.</span><span class="nx">firewall</span><span class="p">[*].</span><span class="nx">id</span>
    <span class="nx">content</span> <span class="p">{</span>
      <span class="nx">subnet_id</span> <span class="o">=</span> <span class="nx">subnet_mapping</span><span class="p">.</span><span class="nx">value</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span>        <span class="o">=</span> <span class="s2">"suricata-nfw"</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"internal"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"network-security"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="s2">"production"</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"security-team"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security"</span>
  <span class="err">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_networkfirewall_firewall_policy"</span> <span class="s2">"main"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"suricata-policy"</span>

  <span class="nx">firewall_policy</span> <span class="p">{</span>
    <span class="nx">stateless_default_actions</span>          <span class="o">=</span> <span class="p">[</span><span class="s2">"aws:forward_to_sfe"</span><span class="p">]</span>
    <span class="nx">stateless_fragment_default_actions</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"aws:forward_to_sfe"</span><span class="p">]</span>

    <span class="nx">stateful_engine_options</span> <span class="p">{</span>
      <span class="nx">rule_order</span> <span class="o">=</span> <span class="s2">"STRICT_ORDER"</span>
    <span class="p">}</span>

    <span class="c1"># Custom ET Open rules - highest priority</span>
    <span class="nx">stateful_rule_group_reference</span> <span class="p">{</span>
      <span class="nx">priority</span>     <span class="o">=</span> <span class="mi">1</span>
      <span class="nx">resource_arn</span> <span class="o">=</span> <span class="nx">aws_networkfirewall_rule_group</span><span class="p">.</span><span class="nx">et_malware</span><span class="p">.</span><span class="nx">arn</span>
    <span class="p">}</span>

    <span class="nx">stateful_rule_group_reference</span> <span class="p">{</span>
      <span class="nx">priority</span>     <span class="o">=</span> <span class="mi">2</span>
      <span class="nx">resource_arn</span> <span class="o">=</span> <span class="nx">aws_networkfirewall_rule_group</span><span class="p">.</span><span class="nx">et_trojan</span><span class="p">.</span><span class="nx">arn</span>
    <span class="p">}</span>

    <span class="nx">stateful_rule_group_reference</span> <span class="p">{</span>
      <span class="nx">priority</span>     <span class="o">=</span> <span class="mi">3</span>
      <span class="nx">resource_arn</span> <span class="o">=</span> <span class="nx">aws_networkfirewall_rule_group</span><span class="p">.</span><span class="nx">et_exploit</span><span class="p">.</span><span class="nx">arn</span>
    <span class="p">}</span>

    <span class="nx">stateful_rule_group_reference</span> <span class="p">{</span>
      <span class="nx">priority</span>     <span class="o">=</span> <span class="mi">4</span>
      <span class="nx">resource_arn</span> <span class="o">=</span> <span class="nx">aws_networkfirewall_rule_group</span><span class="p">.</span><span class="nx">custom_cloud_rules</span><span class="p">.</span><span class="nx">arn</span>
    <span class="p">}</span>

    <span class="c1"># AWS managed rules as supplementary coverage</span>
    <span class="nx">stateful_rule_group_reference</span> <span class="p">{</span>
      <span class="nx">priority</span>     <span class="o">=</span> <span class="mi">10</span>
      <span class="nx">resource_arn</span> <span class="o">=</span> <span class="s2">"arn:aws:network-firewall:us-east-1:aws-managed:stateful-rulegroup/ThreatSignaturesMalware"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># Custom rule group: ET Open Malware signatures</span>
<span class="nx">resource</span> <span class="s2">"aws_networkfirewall_rule_group"</span> <span class="s2">"et_malware"</span> <span class="p">{</span>
  <span class="nx">capacity</span> <span class="o">=</span> <span class="mi">5000</span>
  <span class="nx">name</span>     <span class="o">=</span> <span class="s2">"et-open-malware"</span>
  <span class="nx">type</span>     <span class="o">=</span> <span class="s2">"STATEFUL"</span>

  <span class="nx">rule_group</span> <span class="p">{</span>
    <span class="nx">rule_variables</span> <span class="p">{</span>
      <span class="nx">ip_sets</span> <span class="p">{</span>
        <span class="nx">key</span> <span class="o">=</span> <span class="s2">"HOME_NET"</span>
        <span class="nx">ip_set</span> <span class="p">{</span>
          <span class="nx">definition</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"10.0.0.0/16"</span><span class="p">]</span>
        <span class="p">}</span>
      <span class="p">}</span>
      <span class="nx">ip_sets</span> <span class="p">{</span>
        <span class="nx">key</span> <span class="o">=</span> <span class="s2">"EXTERNAL_NET"</span>
        <span class="nx">ip_set</span> <span class="p">{</span>
          <span class="nx">definition</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"0.0.0.0/0"</span><span class="p">]</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">}</span>

    <span class="nx">rules_source</span> <span class="p">{</span>
      <span class="nx">rules_string</span> <span class="o">=</span> <span class="nx">file</span><span class="p">(</span><span class="s2">"${path.module}/rules/emerging-malware-filtered.rules"</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">stateful_rule_options</span> <span class="p">{</span>
      <span class="nx">capacity</span> <span class="o">=</span> <span class="mi">5000</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span>   <span class="o">=</span> <span class="s2">"et-open-malware"</span>
    <span class="nx">Source</span> <span class="o">=</span> <span class="s2">"emerging-threats-open"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># Custom rule group: Cloud-specific threat detections</span>
<span class="nx">resource</span> <span class="s2">"aws_networkfirewall_rule_group"</span> <span class="s2">"custom_cloud_rules"</span> <span class="p">{</span>
  <span class="nx">capacity</span> <span class="o">=</span> <span class="mi">1000</span>
  <span class="nx">name</span>     <span class="o">=</span> <span class="s2">"custom-cloud-threats"</span>
  <span class="nx">type</span>     <span class="o">=</span> <span class="s2">"STATEFUL"</span>

  <span class="nx">rule_group</span> <span class="p">{</span>
    <span class="nx">rule_variables</span> <span class="p">{</span>
      <span class="nx">ip_sets</span> <span class="p">{</span>
        <span class="nx">key</span> <span class="o">=</span> <span class="s2">"HOME_NET"</span>
        <span class="nx">ip_set</span> <span class="p">{</span>
          <span class="nx">definition</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"10.0.0.0/16"</span><span class="p">]</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">}</span>

    <span class="nx">rules_source</span> <span class="p">{</span>
      <span class="nx">rules_string</span> <span class="o">=</span> <span class="nx">file</span><span class="p">(</span><span class="s2">"${path.module}/rules/custom-cloud-threats.rules"</span><span class="p">)</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="custom-cloud-threat-detection-rules">Custom Cloud Threat Detection Rules</h3>

<p>Save these as <code class="language-plaintext highlighter-rouge">rules/custom-cloud-threats.rules</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
</pre></td><td class="rouge-code"><pre># =====================================================
# Custom Cloud Threat Detection Rules for AWS
# SID Range: 9000001 - 9000999
# =====================================================

# --- C2 Communication Detection ---
# Detect Cobalt Strike default HTTP beacon pattern
alert http $HOME_NET any -&gt; $EXTERNAL_NET any (msg:"CLOUD C2 - Possible Cobalt Strike HTTP Beacon"; flow:established,to_server; http.method; content:"GET"; http.uri; pcre:"/^\/[a-zA-Z]{4}$/"; http.header; content:"Cookie:"; pcre:"/SESSIONID=[a-f0-9]{32}/"; threshold:type both, track by_src, count 3, seconds 300; sid:9000001; rev:2; classtype:trojan-activity;)

# Detect DNS-over-HTTPS to known DoH providers (potential C2 evasion)
alert tls $HOME_NET any -&gt; $EXTERNAL_NET 443 (msg:"CLOUD POLICY - DNS-over-HTTPS to Public Resolver"; tls.sni; content:"dns.google"; sid:9000002; rev:1; classtype:policy-violation;)

alert tls $HOME_NET any -&gt; $EXTERNAL_NET 443 (msg:"CLOUD POLICY - DNS-over-HTTPS to Cloudflare"; tls.sni; content:"cloudflare-dns.com"; sid:9000003; rev:1; classtype:policy-violation;)

# --- Data Exfiltration Detection ---
# Large DNS TXT responses (possible DNS exfiltration)
alert dns any any -&gt; $HOME_NET any (msg:"CLOUD EXFIL - Large DNS TXT Response"; dns.opcode:0; content:"|00 10|"; offset:40; dsize:&gt;512; threshold:type both, track by_dst, count 10, seconds 60; sid:9000010; rev:1; classtype:bad-unknown;)

# HTTP POST with large body to uncommon TLD
alert http $HOME_NET any -&gt; $EXTERNAL_NET any (msg:"CLOUD EXFIL - Large HTTP POST to Uncommon TLD"; flow:established,to_server; http.method; content:"POST"; http.host; pcre:"/\.(xyz|top|club|work|buzz|tk|ml|ga|cf)\//i"; http.content_len; content:"1"; byte_test:6,&gt;,100000,0,string; sid:9000011; rev:1; classtype:bad-unknown;)

# --- Cryptomining Detection ---
# Stratum mining protocol
alert tcp $HOME_NET any -&gt; $EXTERNAL_NET any (msg:"CLOUD CRYPTO - Stratum Mining Protocol"; flow:established,to_server; content:"{"; content:"\"method\""; distance:0; content:"\"mining."; distance:0; sid:9000020; rev:1; classtype:policy-violation;)

# Known mining pool domains via TLS SNI
alert tls $HOME_NET any -&gt; $EXTERNAL_NET 443 (msg:"CLOUD CRYPTO - Connection to Mining Pool"; tls.sni; pcre:"/(pool\.|mine\.|mining\.|xmr\.|monero|nicehash|nanopool|ethermine|f2pool)/i"; sid:9000021; rev:1; classtype:policy-violation;)

# --- Lateral Movement Detection ---
# SMB traffic between unexpected subnets
alert tcp $HOME_NET any -&gt; $HOME_NET 445 (msg:"CLOUD LATERAL - SMB Traffic Between Subnets"; flow:established,to_server; threshold:type both, track by_src, count 5, seconds 10; sid:9000030; rev:1; classtype:bad-unknown;)

# RDP brute force detection
alert tcp $EXTERNAL_NET any -&gt; $HOME_NET 3389 (msg:"CLOUD LATERAL - Possible RDP Brute Force"; flow:established,to_server; threshold:type both, track by_src, count 10, seconds 60; sid:9000031; rev:1; classtype:attempted-admin;)

# --- IMDS Abuse Detection ---
# Detect attempts to reach EC2 metadata service from unexpected sources
alert http any any -&gt; 169.254.169.254 any (msg:"CLOUD SSRF - EC2 Instance Metadata Access Attempt"; flow:established,to_server; http.uri; content:"/latest/meta-data"; sid:9000040; rev:1; classtype:web-application-attack;)

# IMDSv1 token theft attempt
alert http any any -&gt; 169.254.169.254 any (msg:"CLOUD SSRF - IMDSv1 Credential Access"; flow:established,to_server; http.uri; content:"/latest/meta-data/iam/security-credentials"; sid:9000041; rev:1; classtype:web-application-attack;)
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="aws-cli-for-rule-group-management">AWS CLI for Rule Group Management</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
</pre></td><td class="rouge-code"><pre><span class="c"># Create a rule group from a local rules file</span>
aws network-firewall create-rule-group <span class="se">\</span>
  <span class="nt">--rule-group-name</span> <span class="s2">"et-open-malware"</span> <span class="se">\</span>
  <span class="nt">--type</span> STATEFUL <span class="se">\</span>
  <span class="nt">--capacity</span> 5000 <span class="se">\</span>
  <span class="nt">--rules</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">cat </span>rules/emerging-malware-filtered.rules<span class="si">)</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nt">--tags</span> <span class="nv">Key</span><span class="o">=</span>Source,Value<span class="o">=</span>emerging-threats-open <span class="se">\</span>
  <span class="nt">--region</span> us-east-1

<span class="c"># Update an existing rule group with new rules</span>
aws network-firewall update-rule-group <span class="se">\</span>
  <span class="nt">--rule-group-name</span> <span class="s2">"et-open-malware"</span> <span class="se">\</span>
  <span class="nt">--type</span> STATEFUL <span class="se">\</span>
  <span class="nt">--rules</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">cat </span>rules/emerging-malware-filtered.rules<span class="si">)</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nt">--update-token</span> <span class="s2">"</span><span class="si">$(</span>aws network-firewall describe-rule-group <span class="se">\</span>
    <span class="nt">--rule-group-name</span> et-open-malware <span class="se">\</span>
    <span class="nt">--type</span> STATEFUL <span class="se">\</span>
    <span class="nt">--query</span> <span class="s1">'UpdateToken'</span> <span class="nt">--output</span> text<span class="si">)</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nt">--region</span> us-east-1

<span class="c"># List all rule groups</span>
aws network-firewall list-rule-groups <span class="nt">--type</span> STATEFUL <span class="nt">--region</span> us-east-1

<span class="c"># Describe a specific rule group to check capacity usage</span>
aws network-firewall describe-rule-group <span class="se">\</span>
  <span class="nt">--rule-group-name</span> <span class="s2">"et-open-malware"</span> <span class="se">\</span>
  <span class="nt">--type</span> STATEFUL <span class="se">\</span>
  <span class="nt">--query</span> <span class="s1">'{Name: RuleGroupResponse.RuleGroupName, Capacity: RuleGroupResponse.Capacity, NumberOfAssociations: RuleGroupResponse.NumberOfAssociations}'</span> <span class="se">\</span>
  <span class="nt">--region</span> us-east-1
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="rule-management-automation--lambda-for-et-open-updates">Rule Management Automation — Lambda for ET Open Updates</h2>

<p>Manual rule updates do not scale. Here is a Lambda function that automatically downloads the latest ET Open rules, filters them for AWS Network Firewall compatibility, and updates your rule groups daily.</p>

<h3 id="lambda-function-automated-et-open-rule-updater">Lambda Function: Automated ET Open Rule Updater</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
ET Open Rule Updater for AWS Network Firewall
Automatically downloads, filters, and deploys Suricata rules from Emerging Threats.
</span><span class="sh">"""</span>
<span class="kn">import</span> <span class="n">boto3</span>
<span class="kn">import</span> <span class="n">io</span>
<span class="kn">import</span> <span class="n">json</span>
<span class="kn">import</span> <span class="n">logging</span>
<span class="kn">import</span> <span class="n">os</span>
<span class="kn">import</span> <span class="n">re</span>
<span class="kn">import</span> <span class="n">tarfile</span>
<span class="kn">import</span> <span class="n">urllib.request</span>

<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="p">.</span><span class="nf">getLogger</span><span class="p">()</span>
<span class="n">logger</span><span class="p">.</span><span class="nf">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="p">.</span><span class="n">INFO</span><span class="p">)</span>

<span class="n">NFW_CLIENT</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="nf">client</span><span class="p">(</span><span class="sh">"</span><span class="s">network-firewall</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># Configuration from environment variables
</span><span class="n">SURICATA_VERSION</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">SURICATA_VERSION</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">7.0</span><span class="sh">"</span><span class="p">)</span>
<span class="n">RULE_GROUP_MAPPING</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="nf">loads</span><span class="p">(</span><span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">RULE_GROUP_MAPPING</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">{}</span><span class="sh">"</span><span class="p">))</span>
<span class="n">HOME_NET_CIDR</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">HOME_NET_CIDR</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">10.0.0.0/16</span><span class="sh">"</span><span class="p">)</span>
<span class="n">MAX_RULES_PER_GROUP</span> <span class="o">=</span> <span class="nf">int</span><span class="p">(</span><span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">MAX_RULES_PER_GROUP</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">5000</span><span class="sh">"</span><span class="p">))</span>

<span class="c1"># Keywords not supported by AWS Network Firewall
</span><span class="n">UNSUPPORTED_KEYWORDS</span> <span class="o">=</span> <span class="p">[</span>
    <span class="sh">"</span><span class="s">dataset:</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">datarep:</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">iprep</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">lua:</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">filestore</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">fileext:</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">filemagic:</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">ftpdata_command:</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">enip_command:</span><span class="sh">"</span><span class="p">,</span>
<span class="p">]</span>


<span class="k">def</span> <span class="nf">download_et_open</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
    <span class="sh">"""</span><span class="s">Download and extract ET Open ruleset.</span><span class="sh">"""</span>
    <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="sh">"</span><span class="s">https://rules.emergingthreats.net/open/suricata-</span><span class="si">{</span><span class="n">SURICATA_VERSION</span><span class="si">}</span><span class="s">/emerging.rules.tar.gz</span><span class="sh">"</span>
    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Downloading ET Open from </span><span class="si">{</span><span class="n">url</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

    <span class="n">response</span> <span class="o">=</span> <span class="n">urllib</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="nf">urlopen</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>
    <span class="n">tar_bytes</span> <span class="o">=</span> <span class="n">io</span><span class="p">.</span><span class="nc">BytesIO</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="nf">read</span><span class="p">())</span>

    <span class="n">rules_by_file</span> <span class="o">=</span> <span class="p">{}</span>
    <span class="k">with</span> <span class="n">tarfile</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="n">fileobj</span><span class="o">=</span><span class="n">tar_bytes</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="sh">"</span><span class="s">r:gz</span><span class="sh">"</span><span class="p">)</span> <span class="k">as</span> <span class="n">tar</span><span class="p">:</span>
        <span class="k">for</span> <span class="n">member</span> <span class="ow">in</span> <span class="n">tar</span><span class="p">.</span><span class="nf">getmembers</span><span class="p">():</span>
            <span class="k">if</span> <span class="n">member</span><span class="p">.</span><span class="n">name</span><span class="p">.</span><span class="nf">endswith</span><span class="p">(</span><span class="sh">"</span><span class="s">.rules</span><span class="sh">"</span><span class="p">):</span>
                <span class="n">f</span> <span class="o">=</span> <span class="n">tar</span><span class="p">.</span><span class="nf">extractfile</span><span class="p">(</span><span class="n">member</span><span class="p">)</span>
                <span class="k">if</span> <span class="n">f</span><span class="p">:</span>
                    <span class="n">filename</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="nf">basename</span><span class="p">(</span><span class="n">member</span><span class="p">.</span><span class="n">name</span><span class="p">)</span>
                    <span class="n">rules_by_file</span><span class="p">[</span><span class="n">filename</span><span class="p">]</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="nf">read</span><span class="p">().</span><span class="nf">decode</span><span class="p">(</span><span class="sh">"</span><span class="s">utf-8</span><span class="sh">"</span><span class="p">)</span>

    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Extracted </span><span class="si">{</span><span class="nf">len</span><span class="p">(</span><span class="n">rules_by_file</span><span class="p">)</span><span class="si">}</span><span class="s"> rule files</span><span class="sh">"</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">rules_by_file</span>


<span class="k">def</span> <span class="nf">filter_rules</span><span class="p">(</span><span class="n">rules_content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Remove rules with unsupported keywords and comments.</span><span class="sh">"""</span>
    <span class="n">filtered_lines</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="n">total</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">removed</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">rules_content</span><span class="p">.</span><span class="nf">splitlines</span><span class="p">():</span>
        <span class="n">stripped</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="nf">strip</span><span class="p">()</span>

        <span class="c1"># Keep comments and empty lines
</span>        <span class="k">if</span> <span class="ow">not</span> <span class="n">stripped</span> <span class="ow">or</span> <span class="n">stripped</span><span class="p">.</span><span class="nf">startswith</span><span class="p">(</span><span class="sh">"</span><span class="s">#</span><span class="sh">"</span><span class="p">):</span>
            <span class="k">continue</span>

        <span class="c1"># Skip rules with unsupported keywords
</span>        <span class="k">if</span> <span class="nf">any</span><span class="p">(</span><span class="n">kw</span> <span class="ow">in</span> <span class="n">stripped</span> <span class="k">for</span> <span class="n">kw</span> <span class="ow">in</span> <span class="n">UNSUPPORTED_KEYWORDS</span><span class="p">):</span>
            <span class="n">removed</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="k">continue</span>

        <span class="n">total</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="n">filtered_lines</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">stripped</span><span class="p">)</span>

    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Filtered rules: </span><span class="si">{</span><span class="n">total</span><span class="si">}</span><span class="s"> kept, </span><span class="si">{</span><span class="n">removed</span><span class="si">}</span><span class="s"> removed (unsupported keywords)</span><span class="sh">"</span><span class="p">)</span>
    <span class="k">return</span> <span class="sh">"</span><span class="se">\n</span><span class="sh">"</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">filtered_lines</span><span class="p">[:</span><span class="n">MAX_RULES_PER_GROUP</span><span class="p">])</span>


<span class="k">def</span> <span class="nf">set_home_net</span><span class="p">(</span><span class="n">rules_content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">cidr</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Replace $HOME_NET references with the actual CIDR if needed.</span><span class="sh">"""</span>
    <span class="c1"># Network Firewall handles HOME_NET via rule_variables, so we
</span>    <span class="c1"># leave $HOME_NET in the rules and set it in the rule group config.
</span>    <span class="k">return</span> <span class="n">rules_content</span>


<span class="k">def</span> <span class="nf">update_rule_group</span><span class="p">(</span><span class="n">rule_group_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">rules_string</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Update an AWS Network Firewall rule group with new rules.</span><span class="sh">"""</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="c1"># Get current update token
</span>        <span class="n">response</span> <span class="o">=</span> <span class="n">NFW_CLIENT</span><span class="p">.</span><span class="nf">describe_rule_group</span><span class="p">(</span>
            <span class="n">RuleGroupName</span><span class="o">=</span><span class="n">rule_group_name</span><span class="p">,</span>
            <span class="n">Type</span><span class="o">=</span><span class="sh">"</span><span class="s">STATEFUL</span><span class="sh">"</span><span class="p">,</span>
        <span class="p">)</span>
        <span class="n">update_token</span> <span class="o">=</span> <span class="n">response</span><span class="p">[</span><span class="sh">"</span><span class="s">UpdateToken</span><span class="sh">"</span><span class="p">]</span>
        <span class="n">current_arn</span> <span class="o">=</span> <span class="n">response</span><span class="p">[</span><span class="sh">"</span><span class="s">RuleGroupResponse</span><span class="sh">"</span><span class="p">][</span><span class="sh">"</span><span class="s">RuleGroupArn</span><span class="sh">"</span><span class="p">]</span>

        <span class="c1"># Build rule group configuration with HOME_NET
</span>        <span class="n">rule_group</span> <span class="o">=</span> <span class="p">{</span>
            <span class="sh">"</span><span class="s">RuleVariables</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span>
                <span class="sh">"</span><span class="s">IPSets</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span>
                    <span class="sh">"</span><span class="s">HOME_NET</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span>
                        <span class="sh">"</span><span class="s">Definition</span><span class="sh">"</span><span class="p">:</span> <span class="p">[</span><span class="n">HOME_NET_CIDR</span><span class="p">],</span>
                    <span class="p">},</span>
                    <span class="sh">"</span><span class="s">EXTERNAL_NET</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span>
                        <span class="sh">"</span><span class="s">Definition</span><span class="sh">"</span><span class="p">:</span> <span class="p">[</span><span class="sh">"</span><span class="s">0.0.0.0/0</span><span class="sh">"</span><span class="p">],</span>
                    <span class="p">},</span>
                <span class="p">},</span>
            <span class="p">},</span>
            <span class="sh">"</span><span class="s">RulesSource</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span>
                <span class="sh">"</span><span class="s">RulesString</span><span class="sh">"</span><span class="p">:</span> <span class="n">rules_string</span><span class="p">,</span>
            <span class="p">},</span>
        <span class="p">}</span>

        <span class="n">NFW_CLIENT</span><span class="p">.</span><span class="nf">update_rule_group</span><span class="p">(</span>
            <span class="n">UpdateToken</span><span class="o">=</span><span class="n">update_token</span><span class="p">,</span>
            <span class="n">RuleGroupArn</span><span class="o">=</span><span class="n">current_arn</span><span class="p">,</span>
            <span class="n">RuleGroup</span><span class="o">=</span><span class="n">rule_group</span><span class="p">,</span>
            <span class="n">Type</span><span class="o">=</span><span class="sh">"</span><span class="s">STATEFUL</span><span class="sh">"</span><span class="p">,</span>
        <span class="p">)</span>
        <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Successfully updated rule group: </span><span class="si">{</span><span class="n">rule_group_name</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

    <span class="k">except</span> <span class="n">NFW_CLIENT</span><span class="p">.</span><span class="n">exceptions</span><span class="p">.</span><span class="n">ResourceNotFoundException</span><span class="p">:</span>
        <span class="n">logger</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Rule group not found: </span><span class="si">{</span><span class="n">rule_group_name</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">raise</span>
    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="n">logger</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Failed to update </span><span class="si">{</span><span class="n">rule_group_name</span><span class="si">}</span><span class="s">: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">raise</span>


<span class="k">def</span> <span class="nf">handler</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">context</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">Lambda handler - download ET Open rules and update Network Firewall.</span><span class="sh">"""</span>
    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sh">"</span><span class="s">Starting ET Open rule update</span><span class="sh">"</span><span class="p">)</span>

    <span class="c1"># Download latest rules
</span>    <span class="n">rules_by_file</span> <span class="o">=</span> <span class="nf">download_et_open</span><span class="p">()</span>

    <span class="c1"># Update each mapped rule group
</span>    <span class="c1"># RULE_GROUP_MAPPING format: {"et-open-malware": ["emerging-malware.rules", "emerging-trojan.rules"]}
</span>    <span class="n">results</span> <span class="o">=</span> <span class="p">{}</span>
    <span class="k">for</span> <span class="n">group_name</span><span class="p">,</span> <span class="n">rule_files</span> <span class="ow">in</span> <span class="n">RULE_GROUP_MAPPING</span><span class="p">.</span><span class="nf">items</span><span class="p">():</span>
        <span class="n">combined_rules</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="k">for</span> <span class="n">rule_file</span> <span class="ow">in</span> <span class="n">rule_files</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">rule_file</span> <span class="ow">in</span> <span class="n">rules_by_file</span><span class="p">:</span>
                <span class="n">filtered</span> <span class="o">=</span> <span class="nf">filter_rules</span><span class="p">(</span><span class="n">rules_by_file</span><span class="p">[</span><span class="n">rule_file</span><span class="p">])</span>
                <span class="n">combined_rules</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">filtered</span><span class="p">)</span>
                <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Added </span><span class="si">{</span><span class="n">rule_file</span><span class="si">}</span><span class="s"> to </span><span class="si">{</span><span class="n">group_name</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">logger</span><span class="p">.</span><span class="nf">warning</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Rule file not found in ET Open: </span><span class="si">{</span><span class="n">rule_file</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

        <span class="k">if</span> <span class="n">combined_rules</span><span class="p">:</span>
            <span class="n">rules_string</span> <span class="o">=</span> <span class="sh">"</span><span class="se">\n</span><span class="sh">"</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">combined_rules</span><span class="p">)</span>
            <span class="nf">update_rule_group</span><span class="p">(</span><span class="n">group_name</span><span class="p">,</span> <span class="n">rules_string</span><span class="p">)</span>
            <span class="n">results</span><span class="p">[</span><span class="n">group_name</span><span class="p">]</span> <span class="o">=</span> <span class="sh">"</span><span class="s">updated</span><span class="sh">"</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">results</span><span class="p">[</span><span class="n">group_name</span><span class="p">]</span> <span class="o">=</span> <span class="sh">"</span><span class="s">no rules found</span><span class="sh">"</span>

    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Update complete: </span><span class="si">{</span><span class="n">json</span><span class="p">.</span><span class="nf">dumps</span><span class="p">(</span><span class="n">results</span><span class="p">)</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="k">return</span> <span class="p">{</span><span class="sh">"</span><span class="s">statusCode</span><span class="sh">"</span><span class="p">:</span> <span class="mi">200</span><span class="p">,</span> <span class="sh">"</span><span class="s">body</span><span class="sh">"</span><span class="p">:</span> <span class="n">results</span><span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="terraform-for-the-lambda-deployment">Terraform for the Lambda Deployment</h3>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
</pre></td><td class="rouge-code"><pre><span class="c1"># lambda_rule_updater.tf</span>

<span class="nx">resource</span> <span class="s2">"aws_lambda_function"</span> <span class="s2">"et_rule_updater"</span> <span class="p">{</span>
  <span class="nx">function_name</span> <span class="o">=</span> <span class="s2">"et-open-rule-updater"</span>
  <span class="nx">runtime</span>       <span class="o">=</span> <span class="s2">"python3.12"</span>
  <span class="nx">handler</span>       <span class="o">=</span> <span class="s2">"et_rule_updater.handler"</span>
  <span class="nx">timeout</span>       <span class="o">=</span> <span class="mi">120</span>
  <span class="nx">memory_size</span>   <span class="o">=</span> <span class="mi">512</span>

  <span class="nx">filename</span>         <span class="o">=</span> <span class="nx">data</span><span class="err">.</span><span class="nx">archive_file</span><span class="p">.</span><span class="nx">et_updater</span><span class="p">.</span><span class="nx">output_path</span>
  <span class="nx">source_code_hash</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">archive_file</span><span class="p">.</span><span class="nx">et_updater</span><span class="p">.</span><span class="nx">output_base64sha256</span>

  <span class="nx">role</span> <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">et_updater_role</span><span class="p">.</span><span class="nx">arn</span>

  <span class="nx">environment</span> <span class="p">{</span>
    <span class="nx">variables</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">SURICATA_VERSION</span>   <span class="o">=</span> <span class="s2">"7.0"</span>
      <span class="nx">HOME_NET_CIDR</span>      <span class="o">=</span> <span class="s2">"10.0.0.0/16"</span>
      <span class="nx">MAX_RULES_PER_GROUP</span> <span class="o">=</span> <span class="s2">"5000"</span>
      <span class="nx">RULE_GROUP_MAPPING</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
        <span class="s2">"et-open-malware"</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"emerging-malware.rules"</span><span class="p">,</span> <span class="s2">"emerging-trojan.rules"</span><span class="p">]</span>
        <span class="s2">"et-open-exploit"</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"emerging-exploit.rules"</span><span class="p">,</span> <span class="s2">"emerging-web_server.rules"</span><span class="p">]</span>
        <span class="s2">"et-open-policy"</span>  <span class="p">=</span> <span class="p">[</span><span class="s2">"emerging-policy.rules"</span><span class="p">,</span> <span class="s2">"emerging-dns.rules"</span><span class="p">]</span>
      <span class="p">})</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span>        <span class="o">=</span> <span class="s2">"et-open-rule-updater"</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"internal"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"network-security"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="s2">"production"</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"security-team"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security"</span>
  <span class="err">}</span>
<span class="p">}</span>

<span class="c1"># Schedule daily updates at 6 AM UTC</span>
<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_event_rule"</span> <span class="s2">"daily_rule_update"</span> <span class="p">{</span>
  <span class="nx">name</span>                <span class="o">=</span> <span class="s2">"et-open-daily-update"</span>
  <span class="nx">schedule_expression</span> <span class="o">=</span> <span class="s2">"cron(0 6 ? * MON-FRI *)"</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_event_target"</span> <span class="s2">"lambda_target"</span> <span class="p">{</span>
  <span class="nx">rule</span>      <span class="o">=</span> <span class="nx">aws_cloudwatch_event_rule</span><span class="p">.</span><span class="nx">daily_rule_update</span><span class="p">.</span><span class="nx">name</span>
  <span class="nx">target_id</span> <span class="o">=</span> <span class="s2">"ETRuleUpdater"</span>
  <span class="nx">arn</span>       <span class="o">=</span> <span class="nx">aws_lambda_function</span><span class="p">.</span><span class="nx">et_rule_updater</span><span class="p">.</span><span class="nx">arn</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_lambda_permission"</span> <span class="s2">"allow_eventbridge"</span> <span class="p">{</span>
  <span class="nx">statement_id</span>  <span class="o">=</span> <span class="s2">"AllowEventBridge"</span>
  <span class="nx">action</span>        <span class="o">=</span> <span class="s2">"lambda:InvokeFunction"</span>
  <span class="nx">function_name</span> <span class="o">=</span> <span class="nx">aws_lambda_function</span><span class="p">.</span><span class="nx">et_rule_updater</span><span class="p">.</span><span class="nx">function_name</span>
  <span class="nx">principal</span>     <span class="o">=</span> <span class="s2">"events.amazonaws.com"</span>
  <span class="nx">source_arn</span>    <span class="o">=</span> <span class="nx">aws_cloudwatch_event_rule</span><span class="p">.</span><span class="nx">daily_rule_update</span><span class="p">.</span><span class="nx">arn</span>
<span class="p">}</span>

<span class="c1"># IAM role for the Lambda function</span>
<span class="nx">resource</span> <span class="s2">"aws_iam_role"</span> <span class="s2">"et_updater_role"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"et-rule-updater-role"</span>

  <span class="nx">assume_role_policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">Version</span> <span class="o">=</span> <span class="s2">"2012-10-17"</span>
    <span class="nx">Statement</span> <span class="o">=</span> <span class="p">[{</span>
      <span class="nx">Action</span> <span class="o">=</span> <span class="s2">"sts:AssumeRole"</span>
      <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
      <span class="nx">Principal</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nx">Service</span> <span class="o">=</span> <span class="s2">"lambda.amazonaws.com"</span>
      <span class="p">}</span>
    <span class="p">}]</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_iam_role_policy"</span> <span class="s2">"et_updater_policy"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"et-rule-updater-policy"</span>
  <span class="nx">role</span> <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">et_updater_role</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">Version</span> <span class="o">=</span> <span class="s2">"2012-10-17"</span>
    <span class="nx">Statement</span> <span class="o">=</span> <span class="p">[</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"network-firewall:DescribeRuleGroup"</span><span class="p">,</span>
          <span class="s2">"network-firewall:UpdateRuleGroup"</span><span class="p">,</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="s2">"arn:aws:network-firewall:*:*:stateful-rulegroup/*"</span>
      <span class="p">},</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"logs:CreateLogGroup"</span><span class="p">,</span>
          <span class="s2">"logs:CreateLogStream"</span><span class="p">,</span>
          <span class="s2">"logs:PutLogEvents"</span><span class="p">,</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="s2">"arn:aws:logs:*:*:*"</span>
      <span class="p">},</span>
    <span class="p">]</span>
  <span class="p">})</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="vpc-flow-logs-integration-for-network-visibility">VPC Flow Logs Integration for Network Visibility</h2>

<p>VPC Flow Logs complement Network Firewall by providing connection-level metadata for traffic that the firewall inspects. Together, they give you both deep packet inspection (Network Firewall) and flow-level visibility (Flow Logs).</p>

<h3 id="enable-vpc-flow-logs-to-cloudwatch">Enable VPC Flow Logs to CloudWatch</h3>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
</pre></td><td class="rouge-code"><pre><span class="c1"># flow_logs.tf</span>
<span class="nx">resource</span> <span class="s2">"aws_flow_log"</span> <span class="s2">"vpc_flow_log"</span> <span class="p">{</span>
  <span class="nx">vpc_id</span>               <span class="o">=</span> <span class="nx">aws_vpc</span><span class="p">.</span><span class="nx">protected</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">traffic_type</span>         <span class="o">=</span> <span class="s2">"ALL"</span>
  <span class="nx">log_destination_type</span> <span class="o">=</span> <span class="s2">"cloud-watch-logs"</span>
  <span class="nx">log_destination</span>      <span class="o">=</span> <span class="nx">aws_cloudwatch_log_group</span><span class="p">.</span><span class="nx">flow_logs</span><span class="p">.</span><span class="nx">arn</span>
  <span class="nx">iam_role_arn</span>         <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">flow_log_role</span><span class="p">.</span><span class="nx">arn</span>
  <span class="nx">max_aggregation_interval</span> <span class="o">=</span> <span class="mi">60</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span> <span class="o">=</span> <span class="s2">"vpc-flow-logs"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_log_group"</span> <span class="s2">"flow_logs"</span> <span class="p">{</span>
  <span class="nx">name</span>              <span class="o">=</span> <span class="s2">"/aws/vpc/flow-logs/protected-vpc"</span>
  <span class="nx">retention_in_days</span> <span class="o">=</span> <span class="mi">30</span>
<span class="p">}</span>

<span class="c1"># Network Firewall alert logging</span>
<span class="nx">resource</span> <span class="s2">"aws_networkfirewall_logging_configuration"</span> <span class="s2">"main"</span> <span class="p">{</span>
  <span class="nx">firewall_arn</span> <span class="o">=</span> <span class="nx">aws_networkfirewall_firewall</span><span class="p">.</span><span class="nx">main</span><span class="p">.</span><span class="nx">arn</span>

  <span class="nx">logging_configuration</span> <span class="p">{</span>
    <span class="nx">log_destination_config</span> <span class="p">{</span>
      <span class="nx">log_destination</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nx">logGroup</span> <span class="o">=</span> <span class="nx">aws_cloudwatch_log_group</span><span class="p">.</span><span class="nx">nfw_alerts</span><span class="p">.</span><span class="nx">name</span>
      <span class="p">}</span>
      <span class="nx">log_destination_type</span> <span class="o">=</span> <span class="s2">"CloudWatchLogs"</span>
      <span class="nx">log_type</span>             <span class="o">=</span> <span class="s2">"ALERT"</span>
    <span class="p">}</span>

    <span class="nx">log_destination_config</span> <span class="p">{</span>
      <span class="nx">log_destination</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nx">logGroup</span> <span class="o">=</span> <span class="nx">aws_cloudwatch_log_group</span><span class="p">.</span><span class="nx">nfw_flow</span><span class="p">.</span><span class="nx">name</span>
      <span class="p">}</span>
      <span class="nx">log_destination_type</span> <span class="o">=</span> <span class="s2">"CloudWatchLogs"</span>
      <span class="nx">log_type</span>             <span class="o">=</span> <span class="s2">"FLOW"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_log_group"</span> <span class="s2">"nfw_alerts"</span> <span class="p">{</span>
  <span class="nx">name</span>              <span class="o">=</span> <span class="s2">"/aws/network-firewall/alerts"</span>
  <span class="nx">retention_in_days</span> <span class="o">=</span> <span class="mi">90</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_log_group"</span> <span class="s2">"nfw_flow"</span> <span class="p">{</span>
  <span class="nx">name</span>              <span class="o">=</span> <span class="s2">"/aws/network-firewall/flow"</span>
  <span class="nx">retention_in_days</span> <span class="o">=</span> <span class="mi">30</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="comparison-managed-rules-vs-custom-suricata-rules">Comparison: Managed Rules vs Custom Suricata Rules</h2>

<table>
  <thead>
    <tr>
      <th>Dimension</th>
      <th>AWS Managed Rules</th>
      <th>Custom Suricata (ET Open)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Rule visibility</strong></td>
      <td>Opaque — category-level only</td>
      <td>Full transparency — read every signature</td>
    </tr>
    <tr>
      <td><strong>Update frequency</strong></td>
      <td>AWS-determined, unpublished schedule</td>
      <td>Daily on weekdays, out-of-band for critical threats</td>
    </tr>
    <tr>
      <td><strong>HOME_NET control</strong></td>
      <td>Cannot override</td>
      <td>Full control via rule_variables</td>
    </tr>
    <tr>
      <td><strong>Customization</strong></td>
      <td>Override actions only</td>
      <td>Write, modify, tune any rule</td>
    </tr>
    <tr>
      <td><strong>Coverage breadth</strong></td>
      <td>Broad categories</td>
      <td>30,000+ specific signatures</td>
    </tr>
    <tr>
      <td><strong>Cloud-specific rules</strong></td>
      <td>Limited</td>
      <td>Write your own for IMDS, cloud APIs, etc.</td>
    </tr>
    <tr>
      <td><strong>False positive tuning</strong></td>
      <td>Limited options</td>
      <td>Full suppression, threshold, and flow control</td>
    </tr>
    <tr>
      <td><strong>Cost</strong></td>
      <td>Included in NFW pricing</td>
      <td>Free (ET Open) + Lambda compute for updates</td>
    </tr>
    <tr>
      <td><strong>Compliance evidence</strong></td>
      <td>Vendor-provided attestation</td>
      <td>Full audit trail of every rule deployed</td>
    </tr>
    <tr>
      <td><strong>Community support</strong></td>
      <td>AWS support channels</td>
      <td>OISF community, Suricata forums, ET mailing lists</td>
    </tr>
  </tbody>
</table>

<p><strong>Recommendation</strong>: Use both. AWS managed rules provide a baseline with zero effort. Layer custom ET Open rules on top for depth, transparency, and threat-specific detection that managed rules miss.</p>

<h2 id="detection-scenarios">Detection Scenarios</h2>

<h3 id="scenario-1-command-and-control-communication">Scenario 1: Command-and-Control Communication</h3>

<p>Modern C2 frameworks like Cobalt Strike, Sliver, and Mythic use HTTPS with domain fronting, malleable C2 profiles, and encrypted channels. ET Open tracks hundreds of C2 framework signatures:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre># ET Open rules detect known C2 patterns
# emerging-malware.rules contains signatures for:
# - Cobalt Strike beacon HTTP/HTTPS patterns
# - Sliver implant communication
# - Metasploit Meterpreter sessions
# - Brute Ratel C4 callbacks

# Custom rule: detect potential domain fronting
alert tls $HOME_NET any -&gt; $EXTERNAL_NET 443 (msg:"CLOUD C2 - Possible Domain Fronting (SNI/Host Mismatch Indicator)"; flow:established,to_server; tls.sni; content:".cloudfront.net"; sid:9000050; rev:1; classtype:trojan-activity;)
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="scenario-2-data-exfiltration">Scenario 2: Data Exfiltration</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre># Detect large outbound transfers to cloud storage providers
alert tls $HOME_NET any -&gt; $EXTERNAL_NET 443 (msg:"CLOUD EXFIL - Large Transfer to External Cloud Storage"; tls.sni; pcre:"/(dropbox\.com|drive\.google\.com|onedrive\.live\.com|mega\.nz|transfer\.sh)/i"; flow:established,to_server; threshold:type both, track by_src, count 20, seconds 300; sid:9000060; rev:1; classtype:policy-violation;)

# Detect base64-encoded data in DNS queries (DNS exfiltration)
alert dns $HOME_NET any -&gt; any any (msg:"CLOUD EXFIL - Possible Base64 in DNS Query"; dns.query; pcre:"/^[a-zA-Z0-9+\/]{30,}\./"; threshold:type both, track by_src, count 5, seconds 60; sid:9000061; rev:1; classtype:bad-unknown;)
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="scenario-3-lateral-movement">Scenario 3: Lateral Movement</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre># Detect port scanning behavior within VPC
alert tcp $HOME_NET any -&gt; $HOME_NET any (msg:"CLOUD LATERAL - Internal Port Scan Detected"; flags:S,12; threshold:type both, track by_src, count 25, seconds 10; sid:9000070; rev:1; classtype:attempted-recon;)

# Detect WinRM/PSRemoting lateral movement
alert tcp $HOME_NET any -&gt; $HOME_NET 5985 (msg:"CLOUD LATERAL - WinRM HTTP Connection"; flow:established,to_server; content:"POST"; content:"wsman"; distance:0; within:50; sid:9000071; rev:1; classtype:bad-unknown;)
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="scenario-4-cryptomining">Scenario 4: Cryptomining</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre># Stratum protocol variants
alert tcp $HOME_NET any -&gt; any any (msg:"CLOUD CRYPTO - XMRig Mining Communication"; flow:established,to_server; content:"mining.submit"; sid:9000080; rev:1; classtype:policy-violation;)

# Detect connections to known mining pool IPs via ET Open
# emerging-policy.rules contains extensive mining pool coverage
# These rules are automatically imported by the Lambda updater
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="performance-considerations-and-rule-optimization">Performance Considerations and Rule Optimization</h2>

<p>AWS Network Firewall supports up to <strong>30,000 rules</strong> per policy, but loading every ET Open rule blindly wastes capacity and increases inspection latency. Optimize your deployment with these practices:</p>

<h3 id="1-filter-rules-by-relevance">1. Filter Rules by Relevance</h3>

<p>Not every ET Open category applies to your environment. If you run no Windows workloads, skip <code class="language-plaintext highlighter-rouge">emerging-activex.rules</code> and Windows-specific exploit rules. If you have no FTP services, skip FTP rules.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
</pre></td><td class="rouge-code"><pre><span class="c1"># Rule filtering by environment profile
</span><span class="n">LINUX_CLOUD_PROFILE</span> <span class="o">=</span> <span class="p">[</span>
    <span class="sh">"</span><span class="s">emerging-malware.rules</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">emerging-trojan.rules</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">emerging-exploit.rules</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">emerging-dns.rules</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">emerging-policy.rules</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">emerging-hunting.rules</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">emerging-web_server.rules</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">emerging-web_client.rules</span><span class="sh">"</span><span class="p">,</span>
<span class="p">]</span>

<span class="c1"># Skip these for Linux-only cloud workloads
</span><span class="n">SKIP_FOR_LINUX</span> <span class="o">=</span> <span class="p">[</span>
    <span class="sh">"</span><span class="s">emerging-activex.rules</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">emerging-netbios.rules</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">emerging-shellcode.rules</span><span class="sh">"</span><span class="p">,</span>  <span class="c1"># x86-specific shellcode
</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="2-use-rule-priorities-and-fast-pattern-matching">2. Use Rule Priorities and Fast Pattern Matching</h3>

<p>Place high-confidence, low-false-positive rules first in strict evaluation order. Suricata uses the <code class="language-plaintext highlighter-rouge">fast_pattern</code> keyword to optimize multi-pattern matching:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre># Use fast_pattern on the most unique content match
alert http $HOME_NET any -&gt; $EXTERNAL_NET any (msg:"Malware Callback"; flow:established,to_server; http.uri; content:"/gate.php"; fast_pattern; content:"bot_id="; sid:9000090; rev:1;)
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="3-capacity-planning">3. Capacity Planning</h3>

<table>
  <thead>
    <tr>
      <th>Rule Group</th>
      <th>Recommended Capacity</th>
      <th>Use Case</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>ET Malware + Trojan</td>
      <td>5,000</td>
      <td>Known malware signatures</td>
    </tr>
    <tr>
      <td>ET Exploit</td>
      <td>3,000</td>
      <td>Vulnerability exploit detection</td>
    </tr>
    <tr>
      <td>ET Policy + DNS</td>
      <td>2,000</td>
      <td>Policy violations and DNS threats</td>
    </tr>
    <tr>
      <td>Custom Cloud Rules</td>
      <td>1,000</td>
      <td>Environment-specific detections</td>
    </tr>
    <tr>
      <td><strong>Total</strong></td>
      <td><strong>11,000</strong></td>
      <td>Well within the 30,000 limit</td>
    </tr>
  </tbody>
</table>

<h3 id="4-rule-testing-and-validation">4. Rule Testing and Validation</h3>

<p>Before deploying rules to production, validate them with this script:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
Validate Suricata rules for AWS Network Firewall compatibility.
Checks for unsupported keywords and syntax issues.
</span><span class="sh">"""</span>
<span class="kn">import</span> <span class="n">re</span>
<span class="kn">import</span> <span class="n">sys</span>

<span class="n">UNSUPPORTED_KEYWORDS</span> <span class="o">=</span> <span class="p">[</span>
    <span class="sh">"</span><span class="s">dataset:</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">datarep:</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">iprep</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">lua:</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">filestore</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">fileext:</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">filemagic:</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">ftpdata_command:</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">enip_command:</span><span class="sh">"</span><span class="p">,</span>
<span class="p">]</span>

<span class="n">VALID_ACTIONS</span> <span class="o">=</span> <span class="p">{</span><span class="sh">"</span><span class="s">alert</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">pass</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">drop</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">reject</span><span class="sh">"</span><span class="p">}</span>

<span class="n">SID_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="nf">compile</span><span class="p">(</span><span class="sa">r</span><span class="sh">"</span><span class="s">sid:\s*(\d+)</span><span class="sh">"</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">validate_rule</span><span class="p">(</span><span class="n">line</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">line_num</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
    <span class="sh">"""</span><span class="s">Validate a single Suricata rule line.</span><span class="sh">"""</span>
    <span class="n">errors</span> <span class="o">=</span> <span class="p">[]</span>

    <span class="c1"># Check action
</span>    <span class="n">action</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="nf">split</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span> <span class="k">if</span> <span class="n">line</span><span class="p">.</span><span class="nf">split</span><span class="p">()</span> <span class="k">else</span> <span class="sh">""</span>
    <span class="k">if</span> <span class="n">action</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">VALID_ACTIONS</span><span class="p">:</span>
        <span class="n">errors</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Line </span><span class="si">{</span><span class="n">line_num</span><span class="si">}</span><span class="s">: Invalid action </span><span class="sh">'</span><span class="si">{</span><span class="n">action</span><span class="si">}</span><span class="sh">'"</span><span class="p">)</span>

    <span class="c1"># Check for unsupported keywords
</span>    <span class="k">for</span> <span class="n">kw</span> <span class="ow">in</span> <span class="n">UNSUPPORTED_KEYWORDS</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">kw</span> <span class="ow">in</span> <span class="n">line</span><span class="p">:</span>
            <span class="n">errors</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Line </span><span class="si">{</span><span class="n">line_num</span><span class="si">}</span><span class="s">: Unsupported keyword </span><span class="sh">'</span><span class="si">{</span><span class="n">kw</span><span class="si">}</span><span class="sh">'"</span><span class="p">)</span>

    <span class="c1"># Check for SID
</span>    <span class="k">if</span> <span class="ow">not</span> <span class="n">SID_PATTERN</span><span class="p">.</span><span class="nf">search</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
        <span class="n">errors</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Line </span><span class="si">{</span><span class="n">line_num</span><span class="si">}</span><span class="s">: Missing sid</span><span class="sh">"</span><span class="p">)</span>

    <span class="c1"># Check for balanced parentheses
</span>    <span class="k">if</span> <span class="n">line</span><span class="p">.</span><span class="nf">count</span><span class="p">(</span><span class="sh">"</span><span class="s">(</span><span class="sh">"</span><span class="p">)</span> <span class="o">!=</span> <span class="n">line</span><span class="p">.</span><span class="nf">count</span><span class="p">(</span><span class="sh">"</span><span class="s">)</span><span class="sh">"</span><span class="p">):</span>
        <span class="n">errors</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Line </span><span class="si">{</span><span class="n">line_num</span><span class="si">}</span><span class="s">: Unbalanced parentheses</span><span class="sh">"</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">errors</span>


<span class="k">def</span> <span class="nf">validate_file</span><span class="p">(</span><span class="n">filepath</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">int</span><span class="p">,</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]]:</span>
    <span class="sh">"""</span><span class="s">Validate all rules in a file.</span><span class="sh">"""</span>
    <span class="n">all_errors</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="n">total_rules</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">sids_seen</span> <span class="o">=</span> <span class="nf">set</span><span class="p">()</span>

    <span class="k">with</span> <span class="nf">open</span><span class="p">(</span><span class="n">filepath</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="k">for</span> <span class="n">line_num</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="mi">1</span><span class="p">):</span>
            <span class="n">stripped</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="nf">strip</span><span class="p">()</span>
            <span class="k">if</span> <span class="ow">not</span> <span class="n">stripped</span> <span class="ow">or</span> <span class="n">stripped</span><span class="p">.</span><span class="nf">startswith</span><span class="p">(</span><span class="sh">"</span><span class="s">#</span><span class="sh">"</span><span class="p">):</span>
                <span class="k">continue</span>

            <span class="n">total_rules</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="n">errors</span> <span class="o">=</span> <span class="nf">validate_rule</span><span class="p">(</span><span class="n">stripped</span><span class="p">,</span> <span class="n">line_num</span><span class="p">)</span>
            <span class="n">all_errors</span><span class="p">.</span><span class="nf">extend</span><span class="p">(</span><span class="n">errors</span><span class="p">)</span>

            <span class="c1"># Check for duplicate SIDs
</span>            <span class="n">sid_match</span> <span class="o">=</span> <span class="n">SID_PATTERN</span><span class="p">.</span><span class="nf">search</span><span class="p">(</span><span class="n">stripped</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">sid_match</span><span class="p">:</span>
                <span class="n">sid</span> <span class="o">=</span> <span class="n">sid_match</span><span class="p">.</span><span class="nf">group</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
                <span class="k">if</span> <span class="n">sid</span> <span class="ow">in</span> <span class="n">sids_seen</span><span class="p">:</span>
                    <span class="n">all_errors</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Line </span><span class="si">{</span><span class="n">line_num</span><span class="si">}</span><span class="s">: Duplicate SID </span><span class="si">{</span><span class="n">sid</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
                <span class="n">sids_seen</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">sid</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">total_rules</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">all_errors</span><span class="p">),</span> <span class="n">all_errors</span>


<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="sh">"</span><span class="s">__main__</span><span class="sh">"</span><span class="p">:</span>
    <span class="k">if</span> <span class="nf">len</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">2</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Usage: python validate_rules.py &lt;rules_file&gt;</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">sys</span><span class="p">.</span><span class="nf">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>

    <span class="n">filepath</span> <span class="o">=</span> <span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
    <span class="n">total</span><span class="p">,</span> <span class="n">error_count</span><span class="p">,</span> <span class="n">errors</span> <span class="o">=</span> <span class="nf">validate_file</span><span class="p">(</span><span class="n">filepath</span><span class="p">)</span>

    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Validated </span><span class="si">{</span><span class="n">total</span><span class="si">}</span><span class="s"> rules from </span><span class="si">{</span><span class="n">filepath</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">errors</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Found </span><span class="si">{</span><span class="n">error_count</span><span class="si">}</span><span class="s"> errors:</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">err</span> <span class="ow">in</span> <span class="n">errors</span><span class="p">:</span>
            <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">  - </span><span class="si">{</span><span class="n">err</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">sys</span><span class="p">.</span><span class="nf">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">All rules passed validation.</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">sys</span><span class="p">.</span><span class="nf">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="monitoring-and-alerting-on-network-firewall-findings">Monitoring and Alerting on Network Firewall Findings</h2>

<p>Network Firewall alert logs contain Suricata’s alert output including the rule message, SID, classification, and flow metadata. Set up CloudWatch alarms to surface high-priority detections.</p>

<h3 id="cloudwatch-metric-filter-and-alarm">CloudWatch Metric Filter and Alarm</h3>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
</pre></td><td class="rouge-code"><pre><span class="c1"># monitoring.tf - CloudWatch alerting for Network Firewall</span>

<span class="c1"># Metric filter for high-severity alerts</span>
<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_log_metric_filter"</span> <span class="s2">"nfw_high_severity"</span> <span class="p">{</span>
  <span class="nx">name</span>           <span class="o">=</span> <span class="s2">"nfw-high-severity-alerts"</span>
  <span class="nx">pattern</span>        <span class="o">=</span> <span class="s2">"{ $.event.alert.severity &lt;= 2 }"</span>
  <span class="nx">log_group_name</span> <span class="o">=</span> <span class="nx">aws_cloudwatch_log_group</span><span class="p">.</span><span class="nx">nfw_alerts</span><span class="p">.</span><span class="nx">name</span>

  <span class="nx">metric_transformation</span> <span class="p">{</span>
    <span class="nx">name</span>          <span class="o">=</span> <span class="s2">"HighSeverityAlerts"</span>
    <span class="nx">namespace</span>     <span class="o">=</span> <span class="s2">"NetworkFirewall"</span>
    <span class="nx">value</span>         <span class="o">=</span> <span class="s2">"1"</span>
    <span class="nx">default_value</span> <span class="o">=</span> <span class="s2">"0"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># Alarm for high-severity alerts</span>
<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_metric_alarm"</span> <span class="s2">"nfw_critical"</span> <span class="p">{</span>
  <span class="nx">alarm_name</span>          <span class="o">=</span> <span class="s2">"network-firewall-critical-alert"</span>
  <span class="nx">comparison_operator</span> <span class="o">=</span> <span class="s2">"GreaterThanThreshold"</span>
  <span class="nx">evaluation_periods</span>  <span class="o">=</span> <span class="mi">1</span>
  <span class="nx">metric_name</span>         <span class="o">=</span> <span class="s2">"HighSeverityAlerts"</span>
  <span class="nx">namespace</span>           <span class="o">=</span> <span class="s2">"NetworkFirewall"</span>
  <span class="nx">period</span>              <span class="o">=</span> <span class="mi">300</span>
  <span class="nx">statistic</span>           <span class="o">=</span> <span class="s2">"Sum"</span>
  <span class="nx">threshold</span>           <span class="o">=</span> <span class="mi">0</span>
  <span class="nx">alarm_description</span>   <span class="o">=</span> <span class="s2">"Network Firewall detected high-severity threat"</span>

  <span class="nx">alarm_actions</span> <span class="o">=</span> <span class="p">[</span>
    <span class="s2">"arn:aws:sns:us-east-1:181303648587:alert-critical"</span>
  <span class="p">]</span>
<span class="err">}</span>

<span class="c1"># Metric filter for cryptomining detection</span>
<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_log_metric_filter"</span> <span class="s2">"nfw_cryptomining"</span> <span class="p">{</span>
  <span class="nx">name</span>           <span class="o">=</span> <span class="s2">"nfw-cryptomining-alerts"</span>
  <span class="nx">pattern</span>        <span class="o">=</span> <span class="s2">"{ $.event.alert.signature_id &gt;= 9000020 &amp;&amp; $.event.alert.signature_id &lt;= 9000029 }"</span>
  <span class="nx">log_group_name</span> <span class="o">=</span> <span class="nx">aws_cloudwatch_log_group</span><span class="p">.</span><span class="nx">nfw_alerts</span><span class="p">.</span><span class="nx">name</span>

  <span class="nx">metric_transformation</span> <span class="p">{</span>
    <span class="nx">name</span>          <span class="o">=</span> <span class="s2">"CryptominingAlerts"</span>
    <span class="nx">namespace</span>     <span class="o">=</span> <span class="s2">"NetworkFirewall"</span>
    <span class="nx">value</span>         <span class="o">=</span> <span class="s2">"1"</span>
    <span class="nx">default_value</span> <span class="o">=</span> <span class="s2">"0"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_metric_alarm"</span> <span class="s2">"nfw_cryptomining"</span> <span class="p">{</span>
  <span class="nx">alarm_name</span>          <span class="o">=</span> <span class="s2">"network-firewall-cryptomining-detected"</span>
  <span class="nx">comparison_operator</span> <span class="o">=</span> <span class="s2">"GreaterThanThreshold"</span>
  <span class="nx">evaluation_periods</span>  <span class="o">=</span> <span class="mi">1</span>
  <span class="nx">metric_name</span>         <span class="o">=</span> <span class="s2">"CryptominingAlerts"</span>
  <span class="nx">namespace</span>           <span class="o">=</span> <span class="s2">"NetworkFirewall"</span>
  <span class="nx">period</span>              <span class="o">=</span> <span class="mi">300</span>
  <span class="nx">statistic</span>           <span class="o">=</span> <span class="s2">"Sum"</span>
  <span class="nx">threshold</span>           <span class="o">=</span> <span class="mi">0</span>
  <span class="nx">alarm_description</span>   <span class="o">=</span> <span class="s2">"Network Firewall detected cryptomining activity"</span>

  <span class="nx">alarm_actions</span> <span class="o">=</span> <span class="p">[</span>
    <span class="s2">"arn:aws:sns:us-east-1:181303648587:alert-critical"</span>
  <span class="p">]</span>
<span class="err">}</span>

<span class="c1"># Metric filter for data exfiltration patterns</span>
<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_log_metric_filter"</span> <span class="s2">"nfw_exfiltration"</span> <span class="p">{</span>
  <span class="nx">name</span>           <span class="o">=</span> <span class="s2">"nfw-exfiltration-alerts"</span>
  <span class="nx">pattern</span>        <span class="o">=</span> <span class="s2">"{ $.event.alert.signature_id &gt;= 9000010 &amp;&amp; $.event.alert.signature_id &lt;= 9000019 }"</span>
  <span class="nx">log_group_name</span> <span class="o">=</span> <span class="nx">aws_cloudwatch_log_group</span><span class="p">.</span><span class="nx">nfw_alerts</span><span class="p">.</span><span class="nx">name</span>

  <span class="nx">metric_transformation</span> <span class="p">{</span>
    <span class="nx">name</span>          <span class="o">=</span> <span class="s2">"ExfiltrationAlerts"</span>
    <span class="nx">namespace</span>     <span class="o">=</span> <span class="s2">"NetworkFirewall"</span>
    <span class="nx">value</span>         <span class="o">=</span> <span class="s2">"1"</span>
    <span class="nx">default_value</span> <span class="o">=</span> <span class="s2">"0"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_metric_alarm"</span> <span class="s2">"nfw_exfiltration"</span> <span class="p">{</span>
  <span class="nx">alarm_name</span>          <span class="o">=</span> <span class="s2">"network-firewall-exfiltration-detected"</span>
  <span class="nx">comparison_operator</span> <span class="o">=</span> <span class="s2">"GreaterThanThreshold"</span>
  <span class="nx">evaluation_periods</span>  <span class="o">=</span> <span class="mi">1</span>
  <span class="nx">metric_name</span>         <span class="o">=</span> <span class="s2">"ExfiltrationAlerts"</span>
  <span class="nx">namespace</span>           <span class="o">=</span> <span class="s2">"NetworkFirewall"</span>
  <span class="nx">period</span>              <span class="o">=</span> <span class="mi">300</span>
  <span class="nx">statistic</span>           <span class="o">=</span> <span class="s2">"Sum"</span>
  <span class="nx">threshold</span>           <span class="o">=</span> <span class="mi">0</span>
  <span class="nx">alarm_description</span>   <span class="o">=</span> <span class="s2">"Network Firewall detected possible data exfiltration"</span>

  <span class="nx">alarm_actions</span> <span class="o">=</span> <span class="p">[</span>
    <span class="s2">"arn:aws:sns:us-east-1:181303648587:alert-critical"</span>
  <span class="p">]</span>
<span class="err">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="querying-network-firewall-alerts">Querying Network Firewall Alerts</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="c"># Query recent high-severity alerts from CloudWatch Logs</span>
aws logs filter-log-events <span class="se">\</span>
  <span class="nt">--log-group-name</span> <span class="s2">"/aws/network-firewall/alerts"</span> <span class="se">\</span>
  <span class="nt">--start-time</span> <span class="si">$(</span><span class="nb">date</span> <span class="nt">-d</span> <span class="s1">'1 hour ago'</span> +%s000<span class="si">)</span> <span class="se">\</span>
  <span class="nt">--filter-pattern</span> <span class="s1">'{ $.event.alert.severity &lt;= 2 }'</span> <span class="se">\</span>
  <span class="nt">--query</span> <span class="s1">'events[*].message'</span> <span class="se">\</span>
  <span class="nt">--output</span> text | jq <span class="nt">-r</span> <span class="s1">'.event.alert | "\(.signature_id) | \(.signature) | \(.severity) | \(.src_ip) -&gt; \(.dest_ip)"'</span>

<span class="c"># Count alerts by signature over the last 24 hours</span>
aws logs start-query <span class="se">\</span>
  <span class="nt">--log-group-name</span> <span class="s2">"/aws/network-firewall/alerts"</span> <span class="se">\</span>
  <span class="nt">--start-time</span> <span class="si">$(</span><span class="nb">date</span> <span class="nt">-d</span> <span class="s1">'24 hours ago'</span> +%s<span class="si">)</span> <span class="se">\</span>
  <span class="nt">--end-time</span> <span class="si">$(</span><span class="nb">date</span> +%s<span class="si">)</span> <span class="se">\</span>
  <span class="nt">--query-string</span> <span class="s1">'stats count(*) as alert_count by event.alert.signature | sort alert_count desc | limit 20'</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="implementation-roadmap">Implementation Roadmap</h2>

<h3 id="phase-1-foundation-week-1-2">Phase 1: Foundation (Week 1-2)</h3>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Deploy Network Firewall in inspection architecture with Terraform</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Enable alert and flow logging to CloudWatch Logs</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Import AWS managed threat signature rule groups</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Validate traffic routing through firewall endpoints</li>
</ul>

<h3 id="phase-2-custom-rules-week-3-4">Phase 2: Custom Rules (Week 3-4)</h3>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Download and filter ET Open ruleset for your environment</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Create custom rule groups for malware, exploit, and policy categories</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Write environment-specific rules (IMDS protection, cloud API abuse)</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Validate all rules with the compatibility checker script</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Deploy in alert-only mode (no drop actions)</li>
</ul>

<h3 id="phase-3-automation-week-5-6">Phase 3: Automation (Week 5-6)</h3>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Deploy the Lambda rule updater with EventBridge schedule</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Set up CloudWatch metric filters and alarms for critical signatures</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Establish a baseline of normal alert volume</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Begin tuning false positives with threshold and suppression rules</li>
</ul>

<h3 id="phase-4-active-protection-week-7-8">Phase 4: Active Protection (Week 7-8)</h3>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Promote high-confidence rules from alert to drop action</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Integrate alerts with your SIEM or Security Hub</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Document runbooks for each alert category</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Schedule quarterly rule review and optimization</li>
</ul>

<h2 id="related-articles">Related Articles</h2>

<ul>
  <li><a href="/posts/aws-cloudfront-geographic-access-control/">AWS CloudFront Geographic Access Control</a> — Restrict content delivery by geography using CloudFront and WAF</li>
  <li><a href="/posts/aws-waf-cloudfront-application-protection/">AWS WAF and CloudFront Application Protection</a> — Layer 7 protection with managed and custom WAF rules</li>
  <li><a href="/posts/aws-cloud-security-best-practices-implementation-guide/">AWS Cloud Security Best Practices Implementation Guide</a> — Comprehensive security baseline for AWS environments</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>AWS Network Firewall gives you a managed Suricata engine at VPC scale. The managed rule groups provide a starting point, but the real power comes from the open-source ecosystem that Suricata was built on. The Emerging Threats Open ruleset delivers tens of thousands of community-maintained detection signatures — updated daily, fully transparent, and free. By combining managed rules for baseline coverage with custom ET Open rules for depth, and automating updates with a simple Lambda function, you build a network detection capability that rivals expensive commercial IDPS solutions.</p>

<p>The approach is straightforward: AWS provides the managed infrastructure, the open-source community provides the detection intelligence, and you maintain full control over what runs in your environment. That is the build-it-yourself advantage.</p>

<hr />

<p><strong>Want to discuss network security architecture for your AWS environment?</strong> Connect with me on <a href="https://www.linkedin.com/in/jonpricelinux/">LinkedIn</a> to continue the conversation.</p>]]></content><author><name>Jon (JR) Price</name></author><category term="AWS" /><category term="Security" /><category term="aws-network-firewall" /><category term="emerging-threats" /><category term="ids-rules" /><category term="network-security" /><category term="open-source" /><category term="suricata" /><category term="vpc-security" /><summary type="html"><![CDATA[Unlock AWS Network Firewall's full potential with open-source Suricata rules from Emerging Threats. Build advanced network threat detection beyond managed rule groups.]]></summary></entry><entry><title type="html">Security Observability with OpenTelemetry on AWS</title><link href="https://www.red-team.sh/posts/security-observability-opentelemetry-aws/" rel="alternate" type="text/html" title="Security Observability with OpenTelemetry on AWS" /><published>2026-04-29T09:00:00-07:00</published><updated>2026-04-29T09:00:00-07:00</updated><id>https://www.red-team.sh/posts/security-observability-opentelemetry-aws</id><content type="html" xml:base="https://www.red-team.sh/posts/security-observability-opentelemetry-aws/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#introduction" id="markdown-toc-introduction">Introduction</a>    <ul>
      <li><a href="#current-landscape-statistics" id="markdown-toc-current-landscape-statistics">Current Landscape Statistics</a></li>
    </ul>
  </li>
  <li><a href="#security-observability-vs-traditional-monitoring" id="markdown-toc-security-observability-vs-traditional-monitoring">Security Observability vs Traditional Monitoring</a>    <ul>
      <li><a href="#traditional-monitoring" id="markdown-toc-traditional-monitoring">Traditional Monitoring</a></li>
      <li><a href="#security-observability" id="markdown-toc-security-observability">Security Observability</a></li>
    </ul>
  </li>
  <li><a href="#aws-cloudwatch-and-x-ray-strong-but-aws-locked" id="markdown-toc-aws-cloudwatch-and-x-ray-strong-but-aws-locked">AWS CloudWatch and X-Ray: Strong but AWS-Locked</a>    <ul>
      <li><a href="#what-cloudwatch-does-well" id="markdown-toc-what-cloudwatch-does-well">What CloudWatch Does Well</a></li>
      <li><a href="#what-x-ray-does-well" id="markdown-toc-what-x-ray-does-well">What X-Ray Does Well</a></li>
      <li><a href="#where-native-falls-short" id="markdown-toc-where-native-falls-short">Where Native Falls Short</a></li>
    </ul>
  </li>
  <li><a href="#opentelemetry-traces-metrics-and-logs-unified" id="markdown-toc-opentelemetry-traces-metrics-and-logs-unified">OpenTelemetry: Traces, Metrics, and Logs Unified</a>    <ul>
      <li><a href="#the-three-pillars" id="markdown-toc-the-three-pillars">The Three Pillars</a></li>
      <li><a href="#why-otel-matters-for-security" id="markdown-toc-why-otel-matters-for-security">Why OTel Matters for Security</a></li>
      <li><a href="#otel-architecture" id="markdown-toc-otel-architecture">OTel Architecture</a></li>
    </ul>
  </li>
  <li><a href="#adot-awss-own-opentelemetry-distribution" id="markdown-toc-adot-awss-own-opentelemetry-distribution">ADOT: AWS’s Own OpenTelemetry Distribution</a>    <ul>
      <li><a href="#why-adot-over-upstream-otel" id="markdown-toc-why-adot-over-upstream-otel">Why ADOT Over Upstream OTel</a></li>
      <li><a href="#adot-collector-components" id="markdown-toc-adot-collector-components">ADOT Collector Components</a></li>
    </ul>
  </li>
  <li><a href="#security-specific-instrumentation" id="markdown-toc-security-specific-instrumentation">Security-Specific Instrumentation</a>    <ul>
      <li><a href="#authentication-events" id="markdown-toc-authentication-events">Authentication Events</a></li>
      <li><a href="#api-access-patterns" id="markdown-toc-api-access-patterns">API Access Patterns</a></li>
      <li><a href="#iam-and-authorization-events" id="markdown-toc-iam-and-authorization-events">IAM and Authorization Events</a></li>
    </ul>
  </li>
  <li><a href="#deploying-the-adot-collector-on-eks-and-lambda" id="markdown-toc-deploying-the-adot-collector-on-eks-and-lambda">Deploying the ADOT Collector on EKS and Lambda</a>    <ul>
      <li><a href="#adot-collector-configuration-for-security-telemetry" id="markdown-toc-adot-collector-configuration-for-security-telemetry">ADOT Collector Configuration for Security Telemetry</a></li>
      <li><a href="#kubernetes-deployment-for-adot-collector" id="markdown-toc-kubernetes-deployment-for-adot-collector">Kubernetes Deployment for ADOT Collector</a></li>
      <li><a href="#lambda-layer-configuration-for-adot" id="markdown-toc-lambda-layer-configuration-for-adot">Lambda Layer Configuration for ADOT</a></li>
    </ul>
  </li>
  <li><a href="#grafana-for-security-dashboards" id="markdown-toc-grafana-for-security-dashboards">Grafana for Security Dashboards</a>    <ul>
      <li><a href="#terraform-for-grafana-on-ecs-fargate" id="markdown-toc-terraform-for-grafana-on-ecs-fargate">Terraform for Grafana on ECS Fargate</a></li>
      <li><a href="#grafana-dashboard-json-for-security-metrics" id="markdown-toc-grafana-dashboard-json-for-security-metrics">Grafana Dashboard JSON for Security Metrics</a></li>
    </ul>
  </li>
  <li><a href="#prometheus-for-security-metrics-collection" id="markdown-toc-prometheus-for-security-metrics-collection">Prometheus for Security Metrics Collection</a>    <ul>
      <li><a href="#prometheus-alerting-rules-for-security-events" id="markdown-toc-prometheus-alerting-rules-for-security-events">Prometheus Alerting Rules for Security Events</a></li>
    </ul>
  </li>
  <li><a href="#comparison-cloudwatch-vs-opentelemetry--grafana" id="markdown-toc-comparison-cloudwatch-vs-opentelemetry--grafana">Comparison: CloudWatch vs OpenTelemetry + Grafana</a>    <ul>
      <li><a href="#when-to-use-each" id="markdown-toc-when-to-use-each">When to Use Each</a></li>
    </ul>
  </li>
  <li><a href="#security-use-cases" id="markdown-toc-security-use-cases">Security Use Cases</a>    <ul>
      <li><a href="#api-abuse-detection" id="markdown-toc-api-abuse-detection">API Abuse Detection</a></li>
      <li><a href="#authentication-anomaly-detection" id="markdown-toc-authentication-anomaly-detection">Authentication Anomaly Detection</a></li>
      <li><a href="#latency-based-threat-detection" id="markdown-toc-latency-based-threat-detection">Latency-Based Threat Detection</a></li>
    </ul>
  </li>
  <li><a href="#building-security-alerting-pipelines-with-otel" id="markdown-toc-building-security-alerting-pipelines-with-otel">Building Security Alerting Pipelines with OTel</a>    <ul>
      <li><a href="#connecting-grafana-alerting-to-aws-sns" id="markdown-toc-connecting-grafana-alerting-to-aws-sns">Connecting Grafana Alerting to AWS SNS</a></li>
      <li><a href="#lambda-function-for-security-hub-integration" id="markdown-toc-lambda-function-for-security-hub-integration">Lambda Function for Security Hub Integration</a></li>
    </ul>
  </li>
  <li><a href="#implementation-roadmap" id="markdown-toc-implementation-roadmap">Implementation Roadmap</a>    <ul>
      <li><a href="#phase-1-foundation-week-1-2" id="markdown-toc-phase-1-foundation-week-1-2">Phase 1: Foundation (Week 1-2)</a></li>
      <li><a href="#phase-2-security-instrumentation-week-3-4" id="markdown-toc-phase-2-security-instrumentation-week-3-4">Phase 2: Security Instrumentation (Week 3-4)</a></li>
      <li><a href="#phase-3-dashboards-and-alerting-week-5-6" id="markdown-toc-phase-3-dashboards-and-alerting-week-5-6">Phase 3: Dashboards and Alerting (Week 5-6)</a></li>
      <li><a href="#phase-4-advanced-detection-week-7-8" id="markdown-toc-phase-4-advanced-detection-week-7-8">Phase 4: Advanced Detection (Week 7-8)</a></li>
    </ul>
  </li>
  <li><a href="#related-articles" id="markdown-toc-related-articles">Related Articles</a></li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ul>

<h2 id="introduction">Introduction</h2>

<p>Security teams have an observability problem. While DevOps engineers have spent years building sophisticated monitoring stacks for uptime and performance, security telemetry remains fragmented across disconnected tools, vendor-locked dashboards, and siloed alert streams. The result is a Mean Time to Detect (MTTD) that still hovers around 204 days for breach discovery and a Mean Time to Respond (MTTR) measured in weeks rather than minutes.</p>

<p>The observability gap in security is not a tooling problem – it is an architecture problem. Traditional security monitoring treats logs, metrics, and traces as separate concerns. A WAF logs blocked requests in one system. Authentication failures appear in another. API latency anomalies surface in a third. Correlating these signals to detect a coordinated attack requires manual investigation across multiple consoles, by which point the attacker has already moved laterally.</p>

<p>OpenTelemetry changes this equation. As a vendor-neutral, CNCF-graduated observability framework, it provides a single instrumentation layer that unifies traces, metrics, and logs into correlated telemetry. AWS recognized this shift so strongly that they built their own distribution – AWS Distro for OpenTelemetry (ADOT) – and are actively deprecating the legacy X-Ray SDKs in its favor.</p>

<p>This article demonstrates how to build a security observability pipeline using ADOT as the collection layer, with the freedom to export to CloudWatch for native integration or Grafana and Prometheus for vendor-neutral dashboards. The approach follows the red-team.sh philosophy: start with AWS-native, then layer open source for maximum flexibility and zero lock-in.</p>

<h3 id="current-landscape-statistics">Current Landscape Statistics</h3>

<ul>
  <li><strong>Breach Detection Time</strong>: Organizations take an average of 204 days to identify a breach and 73 days to contain it (IBM Cost of a Data Breach 2025)</li>
  <li><strong>Observability Adoption</strong>: 85% of organizations now consider observability critical to their security posture, up from 62% in 2023</li>
  <li><strong>OpenTelemetry Adoption</strong>: OTel is the second-most active CNCF project after Kubernetes, with contributions from AWS, Google, Microsoft, and 1,000+ organizations</li>
  <li><strong>Cost Impact</strong>: Organizations with mature security observability reduce breach costs by an average of $1.76 million compared to those without</li>
  <li><strong>ADOT Transition</strong>: AWS X-Ray SDKs entered maintenance mode on February 25, 2026, making ADOT the official path forward for distributed tracing on AWS</li>
</ul>

<h2 id="security-observability-vs-traditional-monitoring">Security Observability vs Traditional Monitoring</h2>

<p>Before diving into implementation, it is important to understand what separates security observability from the monitoring you already have in place. Traditional monitoring asks “is it up?” – security observability asks “is it compromised?”</p>

<h3 id="traditional-monitoring">Traditional Monitoring</h3>

<p>Traditional monitoring focuses on availability and performance:</p>

<ul>
  <li><strong>Uptime checks</strong>: Is the service responding?</li>
  <li><strong>Resource metrics</strong>: CPU, memory, disk, network utilization</li>
  <li><strong>Error rates</strong>: HTTP 5xx counts, exception frequency</li>
  <li><strong>Latency percentiles</strong>: p50, p95, p99 response times</li>
  <li><strong>Throughput</strong>: Requests per second, transactions per minute</li>
</ul>

<p>This data tells you whether your systems are healthy. It does not tell you whether an attacker is exfiltrating data through a series of seemingly normal API calls.</p>

<h3 id="security-observability">Security Observability</h3>

<p>Security observability extends monitoring with threat-aware context:</p>

<ul>
  <li><strong>Authentication telemetry</strong>: Failed login patterns, credential stuffing velocity, impossible travel detection</li>
  <li><strong>Authorization anomalies</strong>: Privilege escalation attempts, unusual role assumptions, cross-account access patterns</li>
  <li><strong>API behavior analysis</strong>: Endpoint enumeration, parameter fuzzing, rate limit probing, unusual payload sizes</li>
  <li><strong>Data flow tracing</strong>: Which identities accessed which data, through which services, at what volume</li>
  <li><strong>Infrastructure mutation tracking</strong>: Security group changes, IAM policy modifications, encryption state changes</li>
</ul>

<p>The key difference is <strong>correlation</strong>. A single failed login is noise. Fifty failed logins from rotating IPs against the same account, followed by a successful login from a new geography, followed by an API call to list all S3 buckets – that is a signal. Security observability makes this correlation possible by unifying all three telemetry pillars (traces, metrics, logs) under a common context.</p>

<h2 id="aws-cloudwatch-and-x-ray-strong-but-aws-locked">AWS CloudWatch and X-Ray: Strong but AWS-Locked</h2>

<p>AWS provides a comprehensive native observability stack. Before reaching for open source, it is worth understanding what CloudWatch and X-Ray deliver – and where they fall short.</p>

<h3 id="what-cloudwatch-does-well">What CloudWatch Does Well</h3>

<ul>
  <li><strong>Zero-config integration</strong> with 70+ AWS services</li>
  <li><strong>Logs Insights</strong> query language for ad-hoc investigation</li>
  <li><strong>Contributor Insights</strong> for identifying top-N patterns (top IPs, top error codes)</li>
  <li><strong>Anomaly Detection</strong> using machine learning on metric streams</li>
  <li><strong>Cross-account observability</strong> through CloudWatch Organization sinks</li>
  <li><strong>Metric Math</strong> for derived security metrics (error rate ratios, authentication failure velocity)</li>
</ul>

<h3 id="what-x-ray-does-well">What X-Ray Does Well</h3>

<ul>
  <li><strong>Distributed tracing</strong> across Lambda, ECS, EKS, and API Gateway</li>
  <li><strong>Service maps</strong> that visualize request flow and identify bottlenecks</li>
  <li><strong>Trace sampling</strong> that reduces cost while maintaining visibility</li>
  <li><strong>Insights</strong> for automated anomaly detection on trace data</li>
</ul>

<h3 id="where-native-falls-short">Where Native Falls Short</h3>

<p><strong>Vendor lock-in</strong>: CloudWatch metrics, logs, and X-Ray traces use proprietary formats. Moving to another cloud or a hybrid environment means rebuilding your entire observability pipeline.</p>

<p><strong>Cost at scale</strong>: CloudWatch pricing compounds quickly. At $0.30 per custom metric per month, $0.50 per GB for log ingestion, and $0.01 per 1,000 GetMetricData API calls, a medium-sized security monitoring deployment can easily exceed $2,000/month in CloudWatch costs alone.</p>

<p><strong>Limited correlation</strong>: While CloudWatch ServiceLens attempts to unify metrics, logs, and traces, the experience remains fragmented compared to a purpose-built observability platform like Grafana.</p>

<p><strong>Dashboard limitations</strong>: CloudWatch dashboards lack the flexibility of Grafana for building complex security visualization panels with template variables, annotations, and cross-datasource correlation.</p>

<p><strong>Retention costs</strong>: Long-term metric and log retention in CloudWatch is expensive. Security compliance often requires 1-7 years of retention, which can become a significant cost driver.</p>

<h2 id="opentelemetry-traces-metrics-and-logs-unified">OpenTelemetry: Traces, Metrics, and Logs Unified</h2>

<p>OpenTelemetry (OTel) is the vendor-neutral answer to the observability fragmentation problem. It provides a single set of APIs, SDKs, and protocols for generating, collecting, and exporting telemetry data.</p>

<h3 id="the-three-pillars">The Three Pillars</h3>

<p><strong>Traces</strong> follow a request as it flows through distributed services. Each trace contains spans representing individual operations, with attributes that carry security-relevant context like user identity, source IP, and authorization decisions.</p>

<p><strong>Metrics</strong> are numerical measurements collected at regular intervals. OTel supports counters, histograms, and gauges – the building blocks for security metrics like authentication failure rates, API abuse scores, and data transfer volumes.</p>

<p><strong>Logs</strong> capture discrete events with structured attributes. OTel’s log data model standardizes log records across languages and frameworks, making it possible to correlate a log entry with the trace that generated it.</p>

<h3 id="why-otel-matters-for-security">Why OTel Matters for Security</h3>

<p>The real power of OpenTelemetry for security is <strong>context propagation</strong>. When a request enters your system, OTel generates a trace ID that follows it through every service, function, and database call. By enriching spans with security attributes (user ID, IP address, authentication method, authorization scope), you create a complete audit trail that can be queried, correlated, and alerted on.</p>

<p>This is fundamentally different from scraping CloudWatch logs after the fact. With OTel instrumentation, the security context is embedded in the telemetry at generation time, not reconstructed during investigation.</p>

<h3 id="otel-architecture">OTel Architecture</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
</pre></td><td class="rouge-code"><pre>┌─────────────────────────────────────────────────────┐
│                  Application Code                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────────────┐  │
│  │  Tracer   │  │  Meter   │  │  Logger           │  │
│  │  Provider │  │  Provider│  │  Provider         │  │
│  └─────┬────┘  └─────┬────┘  └────────┬─────────┘  │
│        │             │                │             │
│  ┌─────▼─────────────▼────────────────▼──────────┐  │
│  │            OTel SDK (Auto + Manual)            │  │
│  └──────────────────┬────────────────────────────┘  │
│                     │  OTLP Export                   │
└─────────────────────┼───────────────────────────────┘
                      │
              ┌───────▼───────┐
              │ ADOT Collector │
              │  (Receivers,   │
              │  Processors,   │
              │  Exporters)    │
              └───┬───┬───┬───┘
                  │   │   │
          ┌───────┘   │   └────────┐
          ▼           ▼            ▼
    CloudWatch   Prometheus    Jaeger
     + X-Ray     + Grafana
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="adot-awss-own-opentelemetry-distribution">ADOT: AWS’s Own OpenTelemetry Distribution</h2>

<p>AWS Distro for OpenTelemetry (ADOT) is AWS’s supported, production-ready distribution of the OpenTelemetry Collector and SDKs. It is not a fork – it is upstream OTel with AWS-specific receivers, processors, and exporters baked in.</p>

<h3 id="why-adot-over-upstream-otel">Why ADOT Over Upstream OTel</h3>

<ul>
  <li><strong>AWS-specific exporters</strong>: Native exporters for CloudWatch EMF (Embedded Metric Format), X-Ray traces, and CloudWatch Logs</li>
  <li><strong>AWS resource detection</strong>: Automatic enrichment with EC2 instance metadata, EKS cluster info, Lambda function context</li>
  <li><strong>Managed add-ons</strong>: Available as an EKS add-on, Lambda layer, and ECS sidecar with minimal configuration</li>
  <li><strong>AWS support</strong>: Covered under AWS support plans, unlike community OTel builds</li>
  <li><strong>Security patches</strong>: AWS backports security fixes and maintains release cadence aligned with upstream</li>
</ul>

<h3 id="adot-collector-components">ADOT Collector Components</h3>

<p>The ADOT Collector follows the standard OTel Collector architecture:</p>

<p><strong>Receivers</strong> ingest telemetry from applications and infrastructure:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">otlp</code> – Standard OTLP receiver for traces, metrics, and logs</li>
  <li><code class="language-plaintext highlighter-rouge">awsxray</code> – Receives X-Ray formatted segments</li>
  <li><code class="language-plaintext highlighter-rouge">prometheus</code> – Scrapes Prometheus-format metrics endpoints</li>
  <li><code class="language-plaintext highlighter-rouge">statsd</code> – Receives StatsD metrics</li>
</ul>

<p><strong>Processors</strong> transform and enrich telemetry:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">batch</code> – Batches data before export for efficiency</li>
  <li><code class="language-plaintext highlighter-rouge">resourcedetection</code> – Auto-detects AWS resource attributes (region, account, instance)</li>
  <li><code class="language-plaintext highlighter-rouge">filter</code> – Drops or keeps telemetry based on attribute conditions</li>
  <li><code class="language-plaintext highlighter-rouge">attributes</code> – Adds, modifies, or removes span/metric attributes</li>
</ul>

<p><strong>Exporters</strong> send telemetry to backends:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">awsxray</code> – Exports traces to AWS X-Ray</li>
  <li><code class="language-plaintext highlighter-rouge">awsemf</code> – Exports metrics as CloudWatch Embedded Metric Format logs</li>
  <li><code class="language-plaintext highlighter-rouge">awscloudwatchlogs</code> – Exports logs to CloudWatch Logs</li>
  <li><code class="language-plaintext highlighter-rouge">prometheusremotewrite</code> – Exports to Prometheus-compatible backends (including Amazon Managed Prometheus)</li>
  <li><code class="language-plaintext highlighter-rouge">otlp</code> – Exports to any OTLP-compatible backend (Grafana Cloud, Jaeger, etc.)</li>
</ul>

<p><img src="/assets/img/diagrams/security-observability-opentelemetry-aws.png" alt="Security Observability Architecture with ADOT" class="shadow" w="800" />
<em>ADOT Collector routing telemetry to both AWS-native and OSS backends</em></p>

<h2 id="security-specific-instrumentation">Security-Specific Instrumentation</h2>

<p>Generic observability instrumentation captures HTTP status codes and latency. Security observability requires intentional enrichment with threat-relevant attributes. Here is what to instrument and why.</p>

<h3 id="authentication-events">Authentication Events</h3>

<p>Every authentication attempt should generate a span with security attributes:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
</pre></td><td class="rouge-code"><pre><span class="kn">from</span> <span class="n">opentelemetry</span> <span class="kn">import</span> <span class="n">trace</span>
<span class="kn">from</span> <span class="n">opentelemetry.trace</span> <span class="kn">import</span> <span class="n">StatusCode</span>
<span class="kn">import</span> <span class="n">hashlib</span>

<span class="n">tracer</span> <span class="o">=</span> <span class="n">trace</span><span class="p">.</span><span class="nf">get_tracer</span><span class="p">(</span><span class="sh">"</span><span class="s">auth-service</span><span class="sh">"</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">authenticate_user</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
    <span class="k">with</span> <span class="n">tracer</span><span class="p">.</span><span class="nf">start_as_current_span</span><span class="p">(</span><span class="sh">"</span><span class="s">auth.authenticate</span><span class="sh">"</span><span class="p">)</span> <span class="k">as</span> <span class="n">span</span><span class="p">:</span>
        <span class="c1"># Security-relevant attributes
</span>        <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">auth.method</span><span class="sh">"</span><span class="p">,</span> <span class="n">request</span><span class="p">.</span><span class="n">auth_method</span><span class="p">)</span>  <span class="c1"># "password", "mfa", "sso"
</span>        <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">auth.username</span><span class="sh">"</span><span class="p">,</span> <span class="n">request</span><span class="p">.</span><span class="n">username</span><span class="p">)</span>
        <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">net.peer.ip</span><span class="sh">"</span><span class="p">,</span> <span class="n">request</span><span class="p">.</span><span class="n">client_ip</span><span class="p">)</span>
        <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">http.user_agent</span><span class="sh">"</span><span class="p">,</span> <span class="n">request</span><span class="p">.</span><span class="n">user_agent</span><span class="p">)</span>
        <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">auth.geo.country</span><span class="sh">"</span><span class="p">,</span> <span class="nf">geoip_lookup</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="n">client_ip</span><span class="p">))</span>

        <span class="k">try</span><span class="p">:</span>
            <span class="n">result</span> <span class="o">=</span> <span class="nf">verify_credentials</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
            <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">auth.result</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">success</span><span class="sh">"</span><span class="p">)</span>
            <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">auth.user_id</span><span class="sh">"</span><span class="p">,</span> <span class="n">result</span><span class="p">.</span><span class="n">user_id</span><span class="p">)</span>
            <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">auth.roles</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">roles</span><span class="p">))</span>
            <span class="n">span</span><span class="p">.</span><span class="nf">set_status</span><span class="p">(</span><span class="n">StatusCode</span><span class="p">.</span><span class="n">OK</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">result</span>

        <span class="k">except</span> <span class="n">AuthenticationError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
            <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">auth.result</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">failure</span><span class="sh">"</span><span class="p">)</span>
            <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">auth.failure_reason</span><span class="sh">"</span><span class="p">,</span> <span class="nf">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span>
            <span class="n">span</span><span class="p">.</span><span class="nf">set_status</span><span class="p">(</span><span class="n">StatusCode</span><span class="p">.</span><span class="n">ERROR</span><span class="p">,</span> <span class="nf">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span>

            <span class="c1"># Increment security metric
</span>            <span class="n">auth_failure_counter</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="p">{</span>
                <span class="sh">"</span><span class="s">auth.method</span><span class="sh">"</span><span class="p">:</span> <span class="n">request</span><span class="p">.</span><span class="n">auth_method</span><span class="p">,</span>
                <span class="sh">"</span><span class="s">auth.failure_reason</span><span class="sh">"</span><span class="p">:</span> <span class="nf">str</span><span class="p">(</span><span class="n">e</span><span class="p">),</span>
                <span class="sh">"</span><span class="s">net.peer.ip</span><span class="sh">"</span><span class="p">:</span> <span class="n">request</span><span class="p">.</span><span class="n">client_ip</span>
            <span class="p">})</span>
            <span class="k">raise</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="api-access-patterns">API Access Patterns</h3>

<p>Track API access with enough detail to detect enumeration and abuse:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
</pre></td><td class="rouge-code"><pre><span class="kn">from</span> <span class="n">opentelemetry</span> <span class="kn">import</span> <span class="n">trace</span><span class="p">,</span> <span class="n">metrics</span>

<span class="n">meter</span> <span class="o">=</span> <span class="n">metrics</span><span class="p">.</span><span class="nf">get_meter</span><span class="p">(</span><span class="sh">"</span><span class="s">api-security</span><span class="sh">"</span><span class="p">)</span>
<span class="n">api_request_counter</span> <span class="o">=</span> <span class="n">meter</span><span class="p">.</span><span class="nf">create_counter</span><span class="p">(</span>
    <span class="sh">"</span><span class="s">security.api.requests</span><span class="sh">"</span><span class="p">,</span>
    <span class="n">description</span><span class="o">=</span><span class="sh">"</span><span class="s">API requests with security context</span><span class="sh">"</span>
<span class="p">)</span>
<span class="n">api_payload_histogram</span> <span class="o">=</span> <span class="n">meter</span><span class="p">.</span><span class="nf">create_histogram</span><span class="p">(</span>
    <span class="sh">"</span><span class="s">security.api.payload_size</span><span class="sh">"</span><span class="p">,</span>
    <span class="n">description</span><span class="o">=</span><span class="sh">"</span><span class="s">Request payload sizes for anomaly detection</span><span class="sh">"</span><span class="p">,</span>
    <span class="n">unit</span><span class="o">=</span><span class="sh">"</span><span class="s">bytes</span><span class="sh">"</span>
<span class="p">)</span>

<span class="k">def</span> <span class="nf">security_middleware</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">call_next</span><span class="p">):</span>
    <span class="n">span</span> <span class="o">=</span> <span class="n">trace</span><span class="p">.</span><span class="nf">get_current_span</span><span class="p">()</span>

    <span class="c1"># Enrich with security context
</span>    <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">security.api.endpoint</span><span class="sh">"</span><span class="p">,</span> <span class="n">request</span><span class="p">.</span><span class="n">path</span><span class="p">)</span>
    <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">security.api.method</span><span class="sh">"</span><span class="p">,</span> <span class="n">request</span><span class="p">.</span><span class="n">method</span><span class="p">)</span>
    <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">security.api.authenticated</span><span class="sh">"</span><span class="p">,</span> <span class="nf">bool</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="n">user</span><span class="p">))</span>
    <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">security.api.user_id</span><span class="sh">"</span><span class="p">,</span> <span class="nf">getattr</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="n">user</span><span class="p">,</span> <span class="sh">"</span><span class="s">id</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">anonymous</span><span class="sh">"</span><span class="p">))</span>
    <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">security.api.source_ip</span><span class="sh">"</span><span class="p">,</span> <span class="n">request</span><span class="p">.</span><span class="n">client</span><span class="p">.</span><span class="n">host</span><span class="p">)</span>
    <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">security.api.request_size</span><span class="sh">"</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="n">body</span> <span class="ow">or</span> <span class="sa">b</span><span class="sh">""</span><span class="p">))</span>

    <span class="c1"># Track payload sizes for data exfiltration detection
</span>    <span class="n">api_payload_histogram</span><span class="p">.</span><span class="nf">record</span><span class="p">(</span>
        <span class="nf">len</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="n">body</span> <span class="ow">or</span> <span class="sa">b</span><span class="sh">""</span><span class="p">),</span>
        <span class="p">{</span><span class="sh">"</span><span class="s">endpoint</span><span class="sh">"</span><span class="p">:</span> <span class="n">request</span><span class="p">.</span><span class="n">path</span><span class="p">,</span> <span class="sh">"</span><span class="s">method</span><span class="sh">"</span><span class="p">:</span> <span class="n">request</span><span class="p">.</span><span class="n">method</span><span class="p">}</span>
    <span class="p">)</span>

    <span class="n">response</span> <span class="o">=</span> <span class="nf">call_next</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>

    <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">security.api.response_size</span><span class="sh">"</span><span class="p">,</span> <span class="n">response</span><span class="p">.</span><span class="n">content_length</span> <span class="ow">or</span> <span class="mi">0</span><span class="p">)</span>
    <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">security.api.status_code</span><span class="sh">"</span><span class="p">,</span> <span class="n">response</span><span class="p">.</span><span class="n">status_code</span><span class="p">)</span>

    <span class="c1"># Record security metric
</span>    <span class="n">api_request_counter</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">endpoint</span><span class="sh">"</span><span class="p">:</span> <span class="n">request</span><span class="p">.</span><span class="n">path</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">method</span><span class="sh">"</span><span class="p">:</span> <span class="n">request</span><span class="p">.</span><span class="n">method</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">status</span><span class="sh">"</span><span class="p">:</span> <span class="nf">str</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">status_code</span><span class="p">),</span>
        <span class="sh">"</span><span class="s">authenticated</span><span class="sh">"</span><span class="p">:</span> <span class="nf">str</span><span class="p">(</span><span class="nf">bool</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="n">user</span><span class="p">))</span>
    <span class="p">})</span>

    <span class="k">return</span> <span class="n">response</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="iam-and-authorization-events">IAM and Authorization Events</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre><span class="k">def</span> <span class="nf">check_authorization</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="n">resource</span><span class="p">,</span> <span class="n">action</span><span class="p">):</span>
    <span class="k">with</span> <span class="n">tracer</span><span class="p">.</span><span class="nf">start_as_current_span</span><span class="p">(</span><span class="sh">"</span><span class="s">authz.check</span><span class="sh">"</span><span class="p">)</span> <span class="k">as</span> <span class="n">span</span><span class="p">:</span>
        <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">authz.user_id</span><span class="sh">"</span><span class="p">,</span> <span class="n">user</span><span class="p">.</span><span class="nb">id</span><span class="p">)</span>
        <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">authz.resource</span><span class="sh">"</span><span class="p">,</span> <span class="n">resource</span><span class="p">)</span>
        <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">authz.action</span><span class="sh">"</span><span class="p">,</span> <span class="n">action</span><span class="p">)</span>
        <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">authz.roles</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">user</span><span class="p">.</span><span class="n">roles</span><span class="p">))</span>

        <span class="n">allowed</span> <span class="o">=</span> <span class="n">policy_engine</span><span class="p">.</span><span class="nf">evaluate</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="n">resource</span><span class="p">,</span> <span class="n">action</span><span class="p">)</span>

        <span class="n">span</span><span class="p">.</span><span class="nf">set_attribute</span><span class="p">(</span><span class="sh">"</span><span class="s">authz.decision</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">allow</span><span class="sh">"</span> <span class="k">if</span> <span class="n">allowed</span> <span class="k">else</span> <span class="sh">"</span><span class="s">deny</span><span class="sh">"</span><span class="p">)</span>

        <span class="k">if</span> <span class="ow">not</span> <span class="n">allowed</span><span class="p">:</span>
            <span class="n">authz_denial_counter</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="p">{</span>
                <span class="sh">"</span><span class="s">user_id</span><span class="sh">"</span><span class="p">:</span> <span class="n">user</span><span class="p">.</span><span class="nb">id</span><span class="p">,</span>
                <span class="sh">"</span><span class="s">resource</span><span class="sh">"</span><span class="p">:</span> <span class="n">resource</span><span class="p">,</span>
                <span class="sh">"</span><span class="s">action</span><span class="sh">"</span><span class="p">:</span> <span class="n">action</span>
            <span class="p">})</span>

        <span class="k">return</span> <span class="n">allowed</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="deploying-the-adot-collector-on-eks-and-lambda">Deploying the ADOT Collector on EKS and Lambda</h2>

<h3 id="adot-collector-configuration-for-security-telemetry">ADOT Collector Configuration for Security Telemetry</h3>

<p>The following ADOT Collector configuration receives OTLP telemetry, enriches it with AWS resource attributes, and dual-exports to both CloudWatch/X-Ray and Prometheus/Grafana:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
</pre></td><td class="rouge-code"><pre><span class="c1"># adot-collector-config.yaml</span>
<span class="na">receivers</span><span class="pi">:</span>
  <span class="na">otlp</span><span class="pi">:</span>
    <span class="na">protocols</span><span class="pi">:</span>
      <span class="na">grpc</span><span class="pi">:</span>
        <span class="na">endpoint</span><span class="pi">:</span> <span class="s">0.0.0.0:4317</span>
      <span class="na">http</span><span class="pi">:</span>
        <span class="na">endpoint</span><span class="pi">:</span> <span class="s">0.0.0.0:4318</span>

  <span class="c1"># Scrape Prometheus metrics from application security endpoints</span>
  <span class="na">prometheus</span><span class="pi">:</span>
    <span class="na">config</span><span class="pi">:</span>
      <span class="na">scrape_configs</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">job_name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">security-metrics"</span>
          <span class="na">scrape_interval</span><span class="pi">:</span> <span class="s">15s</span>
          <span class="na">kubernetes_sd_configs</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">pod</span>
          <span class="na">relabel_configs</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="na">source_labels</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">__meta_kubernetes_pod_annotation_prometheus_io_scrape</span><span class="pi">]</span>
              <span class="na">action</span><span class="pi">:</span> <span class="s">keep</span>
              <span class="na">regex</span><span class="pi">:</span> <span class="kc">true</span>
            <span class="pi">-</span> <span class="na">source_labels</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">__meta_kubernetes_pod_annotation_prometheus_io_path</span><span class="pi">]</span>
              <span class="na">action</span><span class="pi">:</span> <span class="s">replace</span>
              <span class="na">target_label</span><span class="pi">:</span> <span class="s">__metrics_path__</span>
              <span class="na">regex</span><span class="pi">:</span> <span class="s">(.+)</span>

<span class="na">processors</span><span class="pi">:</span>
  <span class="na">batch</span><span class="pi">:</span>
    <span class="na">timeout</span><span class="pi">:</span> <span class="s">10s</span>
    <span class="na">send_batch_size</span><span class="pi">:</span> <span class="m">1024</span>

  <span class="c1"># Auto-detect AWS resource attributes</span>
  <span class="na">resourcedetection</span><span class="pi">:</span>
    <span class="na">detectors</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">eks</span><span class="pi">,</span> <span class="nv">ec2</span><span class="pi">,</span> <span class="nv">env</span><span class="pi">]</span>
    <span class="na">timeout</span><span class="pi">:</span> <span class="s">5s</span>
    <span class="na">override</span><span class="pi">:</span> <span class="kc">false</span>

  <span class="c1"># Add security classification attributes</span>
  <span class="na">attributes/security</span><span class="pi">:</span>
    <span class="na">actions</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">key</span><span class="pi">:</span> <span class="s">telemetry.domain</span>
        <span class="na">value</span><span class="pi">:</span> <span class="s">security</span>
        <span class="na">action</span><span class="pi">:</span> <span class="s">upsert</span>
      <span class="pi">-</span> <span class="na">key</span><span class="pi">:</span> <span class="s">environment</span>
        <span class="na">from_attribute</span><span class="pi">:</span> <span class="s">ENVIRONMENT</span>
        <span class="na">action</span><span class="pi">:</span> <span class="s">upsert</span>

  <span class="c1"># Filter to separate security telemetry from general telemetry</span>
  <span class="na">filter/security_traces</span><span class="pi">:</span>
    <span class="na">traces</span><span class="pi">:</span>
      <span class="na">span</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s1">'</span><span class="s">attributes["security.api.endpoint"]</span><span class="nv"> </span><span class="s">!=</span><span class="nv"> </span><span class="s">nil'</span>
        <span class="pi">-</span> <span class="s1">'</span><span class="s">name</span><span class="nv"> </span><span class="s">==</span><span class="nv"> </span><span class="s">"auth.authenticate"'</span>
        <span class="pi">-</span> <span class="s1">'</span><span class="s">name</span><span class="nv"> </span><span class="s">==</span><span class="nv"> </span><span class="s">"authz.check"'</span>

  <span class="c1"># Memory limiter to prevent OOM</span>
  <span class="na">memory_limiter</span><span class="pi">:</span>
    <span class="na">check_interval</span><span class="pi">:</span> <span class="s">5s</span>
    <span class="na">limit_mib</span><span class="pi">:</span> <span class="m">512</span>
    <span class="na">spike_limit_mib</span><span class="pi">:</span> <span class="m">128</span>

<span class="na">exporters</span><span class="pi">:</span>
  <span class="c1"># AWS X-Ray for distributed tracing</span>
  <span class="na">awsxray</span><span class="pi">:</span>
    <span class="na">region</span><span class="pi">:</span> <span class="s">us-east-1</span>
    <span class="na">indexed_attributes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">auth.result"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">auth.username"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">security.api.endpoint"</span>

  <span class="c1"># CloudWatch EMF for metrics</span>
  <span class="na">awsemf</span><span class="pi">:</span>
    <span class="na">region</span><span class="pi">:</span> <span class="s">us-east-1</span>
    <span class="na">namespace</span><span class="pi">:</span> <span class="s2">"</span><span class="s">SecurityObservability"</span>
    <span class="na">log_group_name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/security/otel/metrics"</span>
    <span class="na">dimension_rollup_option</span><span class="pi">:</span> <span class="s2">"</span><span class="s">NoDimensionRollup"</span>
    <span class="na">metric_declarations</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">dimensions</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="pi">[</span><span class="s2">"</span><span class="s">auth.method"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">auth.result"</span><span class="pi">]</span>
          <span class="pi">-</span> <span class="pi">[</span><span class="s2">"</span><span class="s">security.api.endpoint"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">security.api.method"</span><span class="pi">]</span>
        <span class="na">metric_name_selectors</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="s2">"</span><span class="s">security.*"</span>

  <span class="c1"># Amazon Managed Prometheus for long-term metric storage</span>
  <span class="na">prometheusremotewrite</span><span class="pi">:</span>
    <span class="na">endpoint</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://aps-workspaces.us-east-1.amazonaws.com/workspaces/ws-XXXX/api/v1/remote_write"</span>
    <span class="na">auth</span><span class="pi">:</span>
      <span class="na">authenticator</span><span class="pi">:</span> <span class="s">sigv4auth</span>
    <span class="na">resource_to_telemetry_conversion</span><span class="pi">:</span>
      <span class="na">enabled</span><span class="pi">:</span> <span class="kc">true</span>

  <span class="c1"># OTLP export to Grafana for unified dashboards</span>
  <span class="na">otlp/grafana</span><span class="pi">:</span>
    <span class="na">endpoint</span><span class="pi">:</span> <span class="s2">"</span><span class="s">grafana-agent.monitoring.svc.cluster.local:4317"</span>
    <span class="na">tls</span><span class="pi">:</span>
      <span class="na">insecure</span><span class="pi">:</span> <span class="kc">false</span>
      <span class="na">ca_file</span><span class="pi">:</span> <span class="s">/etc/ssl/certs/ca-certificates.crt</span>

  <span class="c1"># CloudWatch Logs for security event logs</span>
  <span class="na">awscloudwatchlogs</span><span class="pi">:</span>
    <span class="na">region</span><span class="pi">:</span> <span class="s">us-east-1</span>
    <span class="na">log_group_name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/security/otel/logs"</span>
    <span class="na">log_stream_name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">security-events"</span>

<span class="na">extensions</span><span class="pi">:</span>
  <span class="na">sigv4auth</span><span class="pi">:</span>
    <span class="na">region</span><span class="pi">:</span> <span class="s">us-east-1</span>
    <span class="na">service</span><span class="pi">:</span> <span class="s">aps</span>

  <span class="na">health_check</span><span class="pi">:</span>
    <span class="na">endpoint</span><span class="pi">:</span> <span class="s">0.0.0.0:13133</span>

<span class="na">service</span><span class="pi">:</span>
  <span class="na">extensions</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">sigv4auth</span><span class="pi">,</span> <span class="nv">health_check</span><span class="pi">]</span>
  <span class="na">pipelines</span><span class="pi">:</span>
    <span class="na">traces</span><span class="pi">:</span>
      <span class="na">receivers</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">otlp</span><span class="pi">]</span>
      <span class="na">processors</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">memory_limiter</span><span class="pi">,</span> <span class="nv">resourcedetection</span><span class="pi">,</span> <span class="nv">attributes/security</span><span class="pi">,</span> <span class="nv">batch</span><span class="pi">]</span>
      <span class="na">exporters</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">awsxray</span><span class="pi">,</span> <span class="nv">otlp/grafana</span><span class="pi">]</span>

    <span class="na">metrics</span><span class="pi">:</span>
      <span class="na">receivers</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">otlp</span><span class="pi">,</span> <span class="nv">prometheus</span><span class="pi">]</span>
      <span class="na">processors</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">memory_limiter</span><span class="pi">,</span> <span class="nv">resourcedetection</span><span class="pi">,</span> <span class="nv">attributes/security</span><span class="pi">,</span> <span class="nv">batch</span><span class="pi">]</span>
      <span class="na">exporters</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">awsemf</span><span class="pi">,</span> <span class="nv">prometheusremotewrite</span><span class="pi">]</span>

    <span class="na">logs</span><span class="pi">:</span>
      <span class="na">receivers</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">otlp</span><span class="pi">]</span>
      <span class="na">processors</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">memory_limiter</span><span class="pi">,</span> <span class="nv">resourcedetection</span><span class="pi">,</span> <span class="nv">attributes/security</span><span class="pi">,</span> <span class="nv">batch</span><span class="pi">]</span>
      <span class="na">exporters</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">awscloudwatchlogs</span><span class="pi">,</span> <span class="nv">otlp/grafana</span><span class="pi">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="kubernetes-deployment-for-adot-collector">Kubernetes Deployment for ADOT Collector</h3>

<p>Deploy the ADOT Collector as a DaemonSet on EKS for node-level collection, or as a Deployment for centralized collection:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
</pre></td><td class="rouge-code"><pre><span class="c1"># adot-collector-deployment.yaml</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">adot-security-collector</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">monitoring</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">adot-security-collector</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">replicas</span><span class="pi">:</span> <span class="m">2</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">matchLabels</span><span class="pi">:</span>
      <span class="na">app</span><span class="pi">:</span> <span class="s">adot-security-collector</span>
  <span class="na">template</span><span class="pi">:</span>
    <span class="na">metadata</span><span class="pi">:</span>
      <span class="na">labels</span><span class="pi">:</span>
        <span class="na">app</span><span class="pi">:</span> <span class="s">adot-security-collector</span>
    <span class="na">spec</span><span class="pi">:</span>
      <span class="na">serviceAccountName</span><span class="pi">:</span> <span class="s">adot-collector-sa</span>
      <span class="na">containers</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">adot-collector</span>
          <span class="na">image</span><span class="pi">:</span> <span class="s">public.ecr.aws/aws-observability/aws-otel-collector:v0.47.0</span>
          <span class="na">ports</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">4317</span>  <span class="c1"># OTLP gRPC</span>
              <span class="na">name</span><span class="pi">:</span> <span class="s">otlp-grpc</span>
            <span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">4318</span>  <span class="c1"># OTLP HTTP</span>
              <span class="na">name</span><span class="pi">:</span> <span class="s">otlp-http</span>
            <span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">13133</span> <span class="c1"># Health check</span>
              <span class="na">name</span><span class="pi">:</span> <span class="s">health</span>
          <span class="na">resources</span><span class="pi">:</span>
            <span class="na">requests</span><span class="pi">:</span>
              <span class="na">cpu</span><span class="pi">:</span> <span class="s">250m</span>
              <span class="na">memory</span><span class="pi">:</span> <span class="s">512Mi</span>
            <span class="na">limits</span><span class="pi">:</span>
              <span class="na">cpu</span><span class="pi">:</span> <span class="s">1000m</span>
              <span class="na">memory</span><span class="pi">:</span> <span class="s">1Gi</span>
          <span class="na">volumeMounts</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">config</span>
              <span class="na">mountPath</span><span class="pi">:</span> <span class="s">/etc/otel</span>
          <span class="na">env</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">ENVIRONMENT</span>
              <span class="na">valueFrom</span><span class="pi">:</span>
                <span class="na">fieldRef</span><span class="pi">:</span>
                  <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">metadata.namespace</span>
          <span class="na">livenessProbe</span><span class="pi">:</span>
            <span class="na">httpGet</span><span class="pi">:</span>
              <span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
              <span class="na">port</span><span class="pi">:</span> <span class="s">health</span>
            <span class="na">initialDelaySeconds</span><span class="pi">:</span> <span class="m">15</span>
            <span class="na">periodSeconds</span><span class="pi">:</span> <span class="m">10</span>
          <span class="na">readinessProbe</span><span class="pi">:</span>
            <span class="na">httpGet</span><span class="pi">:</span>
              <span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
              <span class="na">port</span><span class="pi">:</span> <span class="s">health</span>
            <span class="na">initialDelaySeconds</span><span class="pi">:</span> <span class="m">5</span>
            <span class="na">periodSeconds</span><span class="pi">:</span> <span class="m">5</span>
      <span class="na">volumes</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">config</span>
          <span class="na">configMap</span><span class="pi">:</span>
            <span class="na">name</span><span class="pi">:</span> <span class="s">adot-security-config</span>
<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">adot-security-collector</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">monitoring</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">adot-security-collector</span>
  <span class="na">ports</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">otlp-grpc</span>
      <span class="na">port</span><span class="pi">:</span> <span class="m">4317</span>
      <span class="na">targetPort</span><span class="pi">:</span> <span class="m">4317</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">otlp-http</span>
      <span class="na">port</span><span class="pi">:</span> <span class="m">4318</span>
      <span class="na">targetPort</span><span class="pi">:</span> <span class="m">4318</span>
  <span class="na">type</span><span class="pi">:</span> <span class="s">ClusterIP</span>
<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ServiceAccount</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">adot-collector-sa</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">monitoring</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">eks.amazonaws.com/role-arn</span><span class="pi">:</span> <span class="s">arn:aws:iam::ACCOUNT_ID:role/ADOTCollectorRole</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="lambda-layer-configuration-for-adot">Lambda Layer Configuration for ADOT</h3>

<p>For Lambda-based workloads, ADOT ships as a managed Lambda layer:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
</pre></td><td class="rouge-code"><pre><span class="c1"># template.yaml (AWS SAM)</span>
<span class="na">AWSTemplateFormatVersion</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2010-09-09"</span>
<span class="na">Transform</span><span class="pi">:</span> <span class="s">AWS::Serverless-2016-10-31</span>

<span class="na">Globals</span><span class="pi">:</span>
  <span class="na">Function</span><span class="pi">:</span>
    <span class="na">Runtime</span><span class="pi">:</span> <span class="s">python3.12</span>
    <span class="na">Timeout</span><span class="pi">:</span> <span class="m">30</span>
    <span class="na">MemorySize</span><span class="pi">:</span> <span class="m">512</span>
    <span class="na">Tracing</span><span class="pi">:</span> <span class="s">Active</span>
    <span class="na">Environment</span><span class="pi">:</span>
      <span class="na">Variables</span><span class="pi">:</span>
        <span class="na">AWS_LAMBDA_EXEC_WRAPPER</span><span class="pi">:</span> <span class="s">/opt/otel-instrument</span>
        <span class="na">OPENTELEMETRY_COLLECTOR_CONFIG_FILE</span><span class="pi">:</span> <span class="s">/var/task/collector.yaml</span>
        <span class="na">OTEL_SERVICE_NAME</span><span class="pi">:</span> <span class="s">security-api</span>
        <span class="na">OTEL_RESOURCE_ATTRIBUTES</span><span class="pi">:</span> <span class="s2">"</span><span class="s">deployment.environment=production"</span>
    <span class="na">Layers</span><span class="pi">:</span>
      <span class="c1"># ADOT Lambda Layer for Python</span>
      <span class="pi">-</span> <span class="kt">!Sub</span> <span class="s2">"</span><span class="s">arn:aws:lambda:${AWS::Region}:901920570463:layer:aws-otel-python-amd64-ver-1-25-0:1"</span>

<span class="na">Resources</span><span class="pi">:</span>
  <span class="na">SecurityApiFunction</span><span class="pi">:</span>
    <span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::Serverless::Function</span>
    <span class="na">Properties</span><span class="pi">:</span>
      <span class="na">Handler</span><span class="pi">:</span> <span class="s">app.handler</span>
      <span class="na">CodeUri</span><span class="pi">:</span> <span class="s">src/</span>
      <span class="na">Description</span><span class="pi">:</span> <span class="s">Security API with ADOT instrumentation</span>
      <span class="na">Policies</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">AWSXRayDaemonWriteAccess</span>
        <span class="pi">-</span> <span class="na">Statement</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="na">Effect</span><span class="pi">:</span> <span class="s">Allow</span>
              <span class="na">Action</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="s2">"</span><span class="s">aps:RemoteWrite"</span>
              <span class="na">Resource</span><span class="pi">:</span> <span class="s2">"</span><span class="s">*"</span>
      <span class="na">Events</span><span class="pi">:</span>
        <span class="na">ApiEvent</span><span class="pi">:</span>
          <span class="na">Type</span><span class="pi">:</span> <span class="s">Api</span>
          <span class="na">Properties</span><span class="pi">:</span>
            <span class="na">Path</span><span class="pi">:</span> <span class="s">/api/{proxy+}</span>
            <span class="na">Method</span><span class="pi">:</span> <span class="s">ANY</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="grafana-for-security-dashboards">Grafana for Security Dashboards</h2>

<p>Grafana transforms raw security telemetry into actionable dashboards. Whether you run self-hosted Grafana on ECS or use Amazon Managed Grafana (AMG), the dashboard definitions are identical.</p>

<h3 id="terraform-for-grafana-on-ecs-fargate">Terraform for Grafana on ECS Fargate</h3>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
</pre></td><td class="rouge-code"><pre><span class="c1"># grafana.tf</span>
<span class="nx">resource</span> <span class="s2">"aws_ecs_cluster"</span> <span class="s2">"monitoring"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"security-monitoring"</span>

  <span class="nx">setting</span> <span class="p">{</span>
    <span class="nx">name</span>  <span class="o">=</span> <span class="s2">"containerInsights"</span>
    <span class="nx">value</span> <span class="o">=</span> <span class="s2">"enabled"</span>
  <span class="p">}</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"red-team"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"security-observability"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="s2">"production"</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"jon"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_ecs_task_definition"</span> <span class="s2">"grafana"</span> <span class="p">{</span>
  <span class="nx">family</span>                   <span class="o">=</span> <span class="s2">"grafana-security"</span>
  <span class="nx">network_mode</span>             <span class="o">=</span> <span class="s2">"awsvpc"</span>
  <span class="nx">requires_compatibilities</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"FARGATE"</span><span class="p">]</span>
  <span class="nx">cpu</span>                      <span class="o">=</span> <span class="s2">"1024"</span>
  <span class="nx">memory</span>                   <span class="o">=</span> <span class="s2">"2048"</span>
  <span class="nx">execution_role_arn</span>       <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="err">.</span><span class="nx">ecs_execution</span><span class="p">.</span><span class="nx">arn</span>
  <span class="nx">task_role_arn</span>            <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">grafana_task</span><span class="p">.</span><span class="nx">arn</span>

  <span class="nx">container_definitions</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">([</span>
    <span class="p">{</span>
      <span class="nx">name</span>  <span class="o">=</span> <span class="s2">"grafana"</span>
      <span class="nx">image</span> <span class="o">=</span> <span class="s2">"grafana/grafana-oss:12.0.0"</span>
      <span class="nx">portMappings</span> <span class="o">=</span> <span class="p">[</span>
        <span class="p">{</span>
          <span class="nx">containerPort</span> <span class="o">=</span> <span class="mi">3000</span>
          <span class="nx">protocol</span>      <span class="o">=</span> <span class="s2">"tcp"</span>
        <span class="p">}</span>
      <span class="p">]</span>
      <span class="nx">environment</span> <span class="o">=</span> <span class="p">[</span>
        <span class="p">{</span> <span class="nx">name</span> <span class="o">=</span> <span class="s2">"GF_SECURITY_ADMIN_PASSWORD"</span><span class="p">,</span> <span class="nx">value</span> <span class="o">=</span> <span class="s2">"CHANGE_ME"</span> <span class="p">},</span>
        <span class="p">{</span> <span class="nx">name</span> <span class="o">=</span> <span class="s2">"GF_INSTALL_PLUGINS"</span><span class="p">,</span> <span class="nx">value</span> <span class="o">=</span> <span class="s2">"grafana-clock-panel,grafana-piechart-panel"</span> <span class="p">},</span>
        <span class="p">{</span> <span class="nx">name</span> <span class="o">=</span> <span class="s2">"GF_AUTH_ANONYMOUS_ENABLED"</span><span class="p">,</span> <span class="nx">value</span> <span class="o">=</span> <span class="s2">"false"</span> <span class="p">},</span>
        <span class="p">{</span> <span class="nx">name</span> <span class="o">=</span> <span class="s2">"GF_SECURITY_ALLOW_EMBEDDING"</span><span class="p">,</span> <span class="nx">value</span> <span class="o">=</span> <span class="s2">"false"</span> <span class="p">},</span>
        <span class="p">{</span> <span class="nx">name</span> <span class="o">=</span> <span class="s2">"GF_SECURITY_COOKIE_SECURE"</span><span class="p">,</span> <span class="nx">value</span> <span class="o">=</span> <span class="s2">"true"</span> <span class="p">},</span>
        <span class="p">{</span> <span class="nx">name</span> <span class="o">=</span> <span class="s2">"GF_SECURITY_STRICT_TRANSPORT_SECURITY"</span><span class="p">,</span> <span class="nx">value</span> <span class="o">=</span> <span class="s2">"true"</span> <span class="p">}</span>
      <span class="p">]</span>
      <span class="nx">logConfiguration</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nx">logDriver</span> <span class="o">=</span> <span class="s2">"awslogs"</span>
        <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
          <span class="s2">"awslogs-group"</span>         <span class="p">=</span> <span class="s2">"/ecs/grafana-security"</span>
          <span class="s2">"awslogs-region"</span>        <span class="p">=</span> <span class="s2">"us-east-1"</span>
          <span class="s2">"awslogs-stream-prefix"</span> <span class="p">=</span> <span class="s2">"grafana"</span>
        <span class="p">}</span>
      <span class="p">}</span>
      <span class="nx">mountPoints</span> <span class="o">=</span> <span class="p">[</span>
        <span class="p">{</span>
          <span class="nx">sourceVolume</span>  <span class="o">=</span> <span class="s2">"grafana-data"</span>
          <span class="nx">containerPath</span> <span class="o">=</span> <span class="s2">"/var/lib/grafana"</span>
        <span class="p">}</span>
      <span class="p">]</span>
    <span class="p">}</span>
  <span class="p">])</span>

  <span class="nx">volume</span> <span class="p">{</span>
    <span class="nx">name</span> <span class="o">=</span> <span class="s2">"grafana-data"</span>
    <span class="nx">efs_volume_configuration</span> <span class="p">{</span>
      <span class="nx">file_system_id</span> <span class="o">=</span> <span class="nx">aws_efs_file_system</span><span class="p">.</span><span class="nx">grafana</span><span class="p">.</span><span class="nx">id</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"red-team"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"security-observability"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="s2">"production"</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"jon"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_ecs_service"</span> <span class="s2">"grafana"</span> <span class="p">{</span>
  <span class="nx">name</span>            <span class="o">=</span> <span class="s2">"grafana-security"</span>
  <span class="nx">cluster</span>         <span class="o">=</span> <span class="nx">aws_ecs_cluster</span><span class="p">.</span><span class="nx">monitoring</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">task_definition</span> <span class="o">=</span> <span class="nx">aws_ecs_task_definition</span><span class="p">.</span><span class="nx">grafana</span><span class="p">.</span><span class="nx">arn</span>
  <span class="nx">desired_count</span>   <span class="o">=</span> <span class="mi">1</span>
  <span class="nx">launch_type</span>     <span class="o">=</span> <span class="s2">"FARGATE"</span>

  <span class="nx">network_configuration</span> <span class="p">{</span>
    <span class="nx">subnets</span>          <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">private_subnet_ids</span>
    <span class="nx">security_groups</span>  <span class="o">=</span> <span class="p">[</span><span class="nx">aws_security_group</span><span class="p">.</span><span class="nx">grafana</span><span class="p">.</span><span class="nx">id</span><span class="p">]</span>
    <span class="nx">assign_public_ip</span> <span class="o">=</span> <span class="kc">false</span>
  <span class="p">}</span>

  <span class="nx">load_balancer</span> <span class="p">{</span>
    <span class="nx">target_group_arn</span> <span class="o">=</span> <span class="nx">aws_lb_target_group</span><span class="p">.</span><span class="nx">grafana</span><span class="p">.</span><span class="nx">arn</span>
    <span class="nx">container_name</span>   <span class="o">=</span> <span class="s2">"grafana"</span>
    <span class="nx">container_port</span>   <span class="o">=</span> <span class="mi">3000</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_efs_file_system"</span> <span class="s2">"grafana"</span> <span class="p">{</span>
  <span class="nx">creation_token</span> <span class="o">=</span> <span class="s2">"grafana-security-data"</span>
  <span class="nx">encrypted</span>      <span class="o">=</span> <span class="kc">true</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"red-team"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"security-observability"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="s2">"production"</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"jon"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security"</span>
  <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="grafana-dashboard-json-for-security-metrics">Grafana Dashboard JSON for Security Metrics</h3>

<p>The following dashboard provides a security operations overview with authentication monitoring, API abuse detection, and authorization anomaly panels:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"dashboard"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Security Observability - OTel"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"tags"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"security"</span><span class="p">,</span><span class="w"> </span><span class="s2">"opentelemetry"</span><span class="p">,</span><span class="w"> </span><span class="s2">"adot"</span><span class="p">],</span><span class="w">
    </span><span class="nl">"timezone"</span><span class="p">:</span><span class="w"> </span><span class="s2">"browser"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"panels"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Authentication Failures (15m rolling)"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"timeseries"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"gridPos"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"h"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w"> </span><span class="nl">"w"</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"> </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">},</span><span class="w">
        </span><span class="nl">"targets"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
          </span><span class="p">{</span><span class="w">
            </span><span class="nl">"expr"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sum(rate(security_auth_failures_total[15m])) by (auth_method, failure_reason)"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"legendFormat"</span><span class="p">:</span><span class="w"> </span><span class="s2">" - "</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">],</span><span class="w">
        </span><span class="nl">"fieldConfig"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"defaults"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"custom"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
              </span><span class="nl">"drawStyle"</span><span class="p">:</span><span class="w"> </span><span class="s2">"line"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"fillOpacity"</span><span class="p">:</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span><span class="w">
              </span><span class="nl">"lineWidth"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="w">
            </span><span class="p">},</span><span class="w">
            </span><span class="nl">"thresholds"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
              </span><span class="nl">"steps"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
                </span><span class="p">{</span><span class="w"> </span><span class="nl">"color"</span><span class="p">:</span><span class="w"> </span><span class="s2">"green"</span><span class="p">,</span><span class="w"> </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="w"> </span><span class="p">},</span><span class="w">
                </span><span class="p">{</span><span class="w"> </span><span class="nl">"color"</span><span class="p">:</span><span class="w"> </span><span class="s2">"yellow"</span><span class="p">,</span><span class="w"> </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="p">},</span><span class="w">
                </span><span class="p">{</span><span class="w"> </span><span class="nl">"color"</span><span class="p">:</span><span class="w"> </span><span class="s2">"red"</span><span class="p">,</span><span class="w"> </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="mi">50</span><span class="w"> </span><span class="p">}</span><span class="w">
              </span><span class="p">]</span><span class="w">
            </span><span class="p">}</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"API Request Anomalies"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"timeseries"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"gridPos"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"h"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w"> </span><span class="nl">"w"</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"> </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"> </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">},</span><span class="w">
        </span><span class="nl">"targets"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
          </span><span class="p">{</span><span class="w">
            </span><span class="nl">"expr"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sum(rate(security_api_requests_total{status=~</span><span class="se">\"</span><span class="s2">4..</span><span class="se">\"</span><span class="s2">}[5m])) by (endpoint)"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"legendFormat"</span><span class="p">:</span><span class="w"> </span><span class="s2">"4xx: "</span><span class="w">
          </span><span class="p">},</span><span class="w">
          </span><span class="p">{</span><span class="w">
            </span><span class="nl">"expr"</span><span class="p">:</span><span class="w"> </span><span class="s2">"histogram_quantile(0.99, rate(security_api_payload_size_bytes_bucket[5m]))"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"legendFormat"</span><span class="p">:</span><span class="w"> </span><span class="s2">"p99 payload size"</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">]</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Authorization Denials by Resource"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"barchart"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"gridPos"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"h"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w"> </span><span class="nl">"w"</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"> </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="p">},</span><span class="w">
        </span><span class="nl">"targets"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
          </span><span class="p">{</span><span class="w">
            </span><span class="nl">"expr"</span><span class="p">:</span><span class="w"> </span><span class="s2">"topk(10, sum(increase(security_authz_denials_total[1h])) by (resource, action))"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"legendFormat"</span><span class="p">:</span><span class="w"> </span><span class="s2">" / "</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">]</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Top Source IPs by Failed Auth"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"table"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"gridPos"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"h"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w"> </span><span class="nl">"w"</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"> </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"> </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="p">},</span><span class="w">
        </span><span class="nl">"targets"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
          </span><span class="p">{</span><span class="w">
            </span><span class="nl">"expr"</span><span class="p">:</span><span class="w"> </span><span class="s2">"topk(20, sum(increase(security_auth_failures_total[1h])) by (source_ip))"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"format"</span><span class="p">:</span><span class="w"> </span><span class="s2">"table"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"instant"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">]</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Security Event Rate (All Types)"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"stat"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"gridPos"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"h"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="nl">"w"</span><span class="p">:</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w"> </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">16</span><span class="w"> </span><span class="p">},</span><span class="w">
        </span><span class="nl">"targets"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
          </span><span class="p">{</span><span class="w">
            </span><span class="nl">"expr"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sum(rate(security_events_total[5m]))"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"legendFormat"</span><span class="p">:</span><span class="w"> </span><span class="s2">"events/sec"</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">]</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Unique Source IPs (1h)"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"stat"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"gridPos"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"h"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="nl">"w"</span><span class="p">:</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w"> </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w"> </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">16</span><span class="w"> </span><span class="p">},</span><span class="w">
        </span><span class="nl">"targets"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
          </span><span class="p">{</span><span class="w">
            </span><span class="nl">"expr"</span><span class="p">:</span><span class="w"> </span><span class="s2">"count(count by (source_ip)(security_api_requests_total))"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"legendFormat"</span><span class="p">:</span><span class="w"> </span><span class="s2">"unique IPs"</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">]</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"templating"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"list"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"environment"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"query"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"query"</span><span class="p">:</span><span class="w"> </span><span class="s2">"label_values(security_api_requests_total, environment)"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"current"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"production"</span><span class="p">,</span><span class="w"> </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"production"</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"service"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"query"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"query"</span><span class="p">:</span><span class="w"> </span><span class="s2">"label_values(security_api_requests_total, service_name)"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"time"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"from"</span><span class="p">:</span><span class="w"> </span><span class="s2">"now-6h"</span><span class="p">,</span><span class="w"> </span><span class="nl">"to"</span><span class="p">:</span><span class="w"> </span><span class="s2">"now"</span><span class="w"> </span><span class="p">},</span><span class="w">
    </span><span class="nl">"refresh"</span><span class="p">:</span><span class="w"> </span><span class="s2">"30s"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="prometheus-for-security-metrics-collection">Prometheus for Security Metrics Collection</h2>

<p>Prometheus serves as the metrics backbone for security observability. When paired with ADOT’s <code class="language-plaintext highlighter-rouge">prometheusremotewrite</code> exporter and Amazon Managed Prometheus (AMP) for storage, you get durable, queryable security metrics without managing Prometheus infrastructure.</p>

<h3 id="prometheus-alerting-rules-for-security-events">Prometheus Alerting Rules for Security Events</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
</pre></td><td class="rouge-code"><pre><span class="c1"># security-alerts.yaml</span>
<span class="na">groups</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">authentication_security</span>
    <span class="na">interval</span><span class="pi">:</span> <span class="s">30s</span>
    <span class="na">rules</span><span class="pi">:</span>
      <span class="c1"># Credential stuffing detection</span>
      <span class="pi">-</span> <span class="na">alert</span><span class="pi">:</span> <span class="s">HighAuthFailureRate</span>
        <span class="na">expr</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">sum(rate(security_auth_failures_total[5m])) &gt; 10</span>
        <span class="na">for</span><span class="pi">:</span> <span class="s">2m</span>
        <span class="na">labels</span><span class="pi">:</span>
          <span class="na">severity</span><span class="pi">:</span> <span class="s">warning</span>
          <span class="na">team</span><span class="pi">:</span> <span class="s">security</span>
        <span class="na">annotations</span><span class="pi">:</span>
          <span class="na">summary</span><span class="pi">:</span> <span class="s2">"</span><span class="s">High</span><span class="nv"> </span><span class="s">authentication</span><span class="nv"> </span><span class="s">failure</span><span class="nv"> </span><span class="s">rate</span><span class="nv"> </span><span class="s">detected"</span>
          <span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Authentication</span><span class="nv"> </span><span class="s">failures</span><span class="nv"> </span><span class="s">exceeding</span><span class="nv"> </span><span class="s">10/sec</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">2</span><span class="nv"> </span><span class="s">minutes.</span><span class="nv"> </span><span class="s">Possible</span><span class="nv"> </span><span class="s">credential</span><span class="nv"> </span><span class="s">stuffing</span><span class="nv"> </span><span class="s">attack."</span>
          <span class="na">runbook_url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://wiki.internal/runbooks/credential-stuffing"</span>

      <span class="c1"># Brute force detection per IP</span>
      <span class="pi">-</span> <span class="na">alert</span><span class="pi">:</span> <span class="s">BruteForceAttempt</span>
        <span class="na">expr</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">sum(rate(security_auth_failures_total[5m])) by (source_ip) &gt; 5</span>
        <span class="na">for</span><span class="pi">:</span> <span class="s">1m</span>
        <span class="na">labels</span><span class="pi">:</span>
          <span class="na">severity</span><span class="pi">:</span> <span class="s">critical</span>
          <span class="na">team</span><span class="pi">:</span> <span class="s">security</span>
        <span class="na">annotations</span><span class="pi">:</span>
          <span class="na">summary</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Brute</span><span class="nv"> </span><span class="s">force</span><span class="nv"> </span><span class="s">attempt</span><span class="nv"> </span><span class="s">from</span><span class="nv"> </span><span class="s">"</span>
          <span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Single</span><span class="nv"> </span><span class="s">IP</span><span class="nv">  </span><span class="s">generating</span><span class="nv"> </span><span class="s">&gt;5</span><span class="nv"> </span><span class="s">auth</span><span class="nv"> </span><span class="s">failures/sec."</span>

      <span class="c1"># Impossible travel detection</span>
      <span class="pi">-</span> <span class="na">alert</span><span class="pi">:</span> <span class="s">ImpossibleTravel</span>
        <span class="na">expr</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">count(</span>
            <span class="s">count by (user_id, geo_country)(</span>
              <span class="s">security_auth_success_total</span>
            <span class="s">)</span>
          <span class="s">) by (user_id) &gt; 2</span>
          <span class="s">and</span>
          <span class="s">sum(rate(security_auth_success_total[30m])) by (user_id) &gt; 0</span>
        <span class="na">for</span><span class="pi">:</span> <span class="s">1m</span>
        <span class="na">labels</span><span class="pi">:</span>
          <span class="na">severity</span><span class="pi">:</span> <span class="s">critical</span>
          <span class="na">team</span><span class="pi">:</span> <span class="s">security</span>
        <span class="na">annotations</span><span class="pi">:</span>
          <span class="na">summary</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Impossible</span><span class="nv"> </span><span class="s">travel</span><span class="nv"> </span><span class="s">detected</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">user</span><span class="nv"> </span><span class="s">"</span>
          <span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">User</span><span class="nv"> </span><span class="s">authenticating</span><span class="nv"> </span><span class="s">from</span><span class="nv"> </span><span class="s">multiple</span><span class="nv"> </span><span class="s">countries</span><span class="nv"> </span><span class="s">within</span><span class="nv"> </span><span class="s">30</span><span class="nv"> </span><span class="s">minutes."</span>

  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">api_security</span>
    <span class="na">interval</span><span class="pi">:</span> <span class="s">30s</span>
    <span class="na">rules</span><span class="pi">:</span>
      <span class="c1"># API enumeration detection</span>
      <span class="pi">-</span> <span class="na">alert</span><span class="pi">:</span> <span class="s">APIEnumeration</span>
        <span class="na">expr</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">count(</span>
            <span class="s">count by (source_ip, endpoint)(</span>
              <span class="s">rate(security_api_requests_total{status="404"}[5m]) &gt; 0</span>
            <span class="s">)</span>
          <span class="s">) by (source_ip) &gt; 20</span>
        <span class="na">for</span><span class="pi">:</span> <span class="s">2m</span>
        <span class="na">labels</span><span class="pi">:</span>
          <span class="na">severity</span><span class="pi">:</span> <span class="s">warning</span>
          <span class="na">team</span><span class="pi">:</span> <span class="s">security</span>
        <span class="na">annotations</span><span class="pi">:</span>
          <span class="na">summary</span><span class="pi">:</span> <span class="s2">"</span><span class="s">API</span><span class="nv"> </span><span class="s">enumeration</span><span class="nv"> </span><span class="s">detected</span><span class="nv"> </span><span class="s">from</span><span class="nv"> </span><span class="s">"</span>
          <span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">IP</span><span class="nv"> </span><span class="s">hitting</span><span class="nv"> </span><span class="s">&gt;20</span><span class="nv"> </span><span class="s">unique</span><span class="nv"> </span><span class="s">404</span><span class="nv"> </span><span class="s">endpoints</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">5</span><span class="nv"> </span><span class="s">minutes."</span>

      <span class="c1"># Data exfiltration detection</span>
      <span class="pi">-</span> <span class="na">alert</span><span class="pi">:</span> <span class="s">AnomalousDataTransfer</span>
        <span class="na">expr</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">histogram_quantile(0.99, rate(security_api_payload_size_bytes_bucket[5m]))</span>
          <span class="s">&gt; 10 * histogram_quantile(0.99, rate(security_api_payload_size_bytes_bucket[1h]))</span>
        <span class="na">for</span><span class="pi">:</span> <span class="s">5m</span>
        <span class="na">labels</span><span class="pi">:</span>
          <span class="na">severity</span><span class="pi">:</span> <span class="s">critical</span>
          <span class="na">team</span><span class="pi">:</span> <span class="s">security</span>
        <span class="na">annotations</span><span class="pi">:</span>
          <span class="na">summary</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Anomalous</span><span class="nv"> </span><span class="s">data</span><span class="nv"> </span><span class="s">transfer</span><span class="nv"> </span><span class="s">detected"</span>
          <span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Response</span><span class="nv"> </span><span class="s">payload</span><span class="nv"> </span><span class="s">sizes</span><span class="nv"> </span><span class="s">10x</span><span class="nv"> </span><span class="s">above</span><span class="nv"> </span><span class="s">normal</span><span class="nv"> </span><span class="s">p99</span><span class="nv"> </span><span class="s">baseline."</span>

  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">authorization_security</span>
    <span class="na">interval</span><span class="pi">:</span> <span class="s">30s</span>
    <span class="na">rules</span><span class="pi">:</span>
      <span class="c1"># Privilege escalation attempts</span>
      <span class="pi">-</span> <span class="na">alert</span><span class="pi">:</span> <span class="s">PrivilegeEscalationAttempt</span>
        <span class="na">expr</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">sum(rate(security_authz_denials_total{action=~"admin.*|delete.*|modify_policy.*"}[5m])) by (user_id) &gt; 3</span>
        <span class="na">for</span><span class="pi">:</span> <span class="s">1m</span>
        <span class="na">labels</span><span class="pi">:</span>
          <span class="na">severity</span><span class="pi">:</span> <span class="s">critical</span>
          <span class="na">team</span><span class="pi">:</span> <span class="s">security</span>
        <span class="na">annotations</span><span class="pi">:</span>
          <span class="na">summary</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Privilege</span><span class="nv"> </span><span class="s">escalation</span><span class="nv"> </span><span class="s">attempts</span><span class="nv"> </span><span class="s">by</span><span class="nv"> </span><span class="s">"</span>
          <span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">User</span><span class="nv"> </span><span class="s">repeatedly</span><span class="nv"> </span><span class="s">denied</span><span class="nv"> </span><span class="s">access</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">privileged</span><span class="nv"> </span><span class="s">operations."</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="comparison-cloudwatch-vs-opentelemetry--grafana">Comparison: CloudWatch vs OpenTelemetry + Grafana</h2>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>CloudWatch + X-Ray</th>
      <th>ADOT + Prometheus + Grafana</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Setup Complexity</strong></td>
      <td>Low – native integration</td>
      <td>Medium – collector deployment required</td>
    </tr>
    <tr>
      <td><strong>Vendor Lock-in</strong></td>
      <td>High – proprietary formats</td>
      <td>None – OTLP is an open standard</td>
    </tr>
    <tr>
      <td><strong>Multi-Cloud Support</strong></td>
      <td>AWS only</td>
      <td>Any cloud or on-prem</td>
    </tr>
    <tr>
      <td><strong>Cost (100 services, moderate traffic)</strong></td>
      <td>$1,500-3,000/mo</td>
      <td>$400-1,200/mo (self-managed)</td>
    </tr>
    <tr>
      <td><strong>Cost (managed)</strong></td>
      <td>Included above</td>
      <td>$800-1,800/mo (AMP + AMG)</td>
    </tr>
    <tr>
      <td><strong>Custom Metrics</strong></td>
      <td>$0.30/metric/month</td>
      <td>Free (self-hosted Prometheus)</td>
    </tr>
    <tr>
      <td><strong>Log Ingestion</strong></td>
      <td>$0.50/GB</td>
      <td>Varies by backend ($0.10-0.30/GB typical)</td>
    </tr>
    <tr>
      <td><strong>Dashboard Flexibility</strong></td>
      <td>Limited</td>
      <td>Extensive (Grafana panels, variables, annotations)</td>
    </tr>
    <tr>
      <td><strong>Alerting</strong></td>
      <td>CloudWatch Alarms (basic)</td>
      <td>Grafana Alerting + AlertManager (advanced)</td>
    </tr>
    <tr>
      <td><strong>Trace Sampling</strong></td>
      <td>Fixed rate</td>
      <td>Head/tail/probabilistic sampling</td>
    </tr>
    <tr>
      <td><strong>Data Retention</strong></td>
      <td>15 months (metrics), configurable (logs)</td>
      <td>Unlimited (your storage)</td>
    </tr>
    <tr>
      <td><strong>Correlation</strong></td>
      <td>ServiceLens (limited)</td>
      <td>Grafana Explore (traces + logs + metrics)</td>
    </tr>
    <tr>
      <td><strong>Security Compliance</strong></td>
      <td>SOC2, HIPAA, FedRAMP built-in</td>
      <td>Self-managed compliance</td>
    </tr>
    <tr>
      <td><strong>Operational Overhead</strong></td>
      <td>None – fully managed</td>
      <td>Medium – collector and backend management</td>
    </tr>
    <tr>
      <td><strong>Community Dashboards</strong></td>
      <td>Limited</td>
      <td>1,000+ community dashboards on grafana.com</td>
    </tr>
  </tbody>
</table>

<h3 id="when-to-use-each">When to Use Each</h3>

<p><strong>Choose CloudWatch + X-Ray when</strong>:</p>
<ul>
  <li>Your workloads are 100% AWS and will stay that way</li>
  <li>Your team lacks the capacity to manage observability infrastructure</li>
  <li>You need FedRAMP/HIPAA compliance out of the box</li>
  <li>Your telemetry volume is small enough that CloudWatch costs are reasonable</li>
</ul>

<p><strong>Choose ADOT + Grafana when</strong>:</p>
<ul>
  <li>You operate in multi-cloud or hybrid environments</li>
  <li>You want vendor-neutral telemetry that survives a cloud migration</li>
  <li>CloudWatch costs are growing faster than your budget</li>
  <li>You need advanced visualization, correlation, and alerting</li>
  <li>Your security team requires custom dashboards beyond what CloudWatch offers</li>
</ul>

<p><strong>The sweet spot</strong>: Use ADOT as the collection layer and export to <em>both</em>. Send traces to X-Ray for AWS console integration and simultaneously to Grafana for advanced analysis. Send metrics to both CloudWatch (for native alarms) and Prometheus (for Grafana dashboards). This dual-export approach gives you the best of both worlds at minimal additional cost.</p>

<h2 id="security-use-cases">Security Use Cases</h2>

<h3 id="api-abuse-detection">API Abuse Detection</h3>

<p>API abuse often manifests as patterns invisible to traditional monitoring. By instrumenting with OTel, you can detect:</p>

<p><strong>Credential stuffing</strong>: High-velocity authentication failures from distributed IP ranges. The authentication span attributes (<code class="language-plaintext highlighter-rouge">auth.result</code>, <code class="language-plaintext highlighter-rouge">net.peer.ip</code>, <code class="language-plaintext highlighter-rouge">auth.method</code>) feed Prometheus metrics that trigger alerts when failure rates exceed baseline.</p>

<p><strong>Endpoint enumeration</strong>: Attackers probe for undocumented API endpoints. Tracking 404 responses by source IP and correlating with the total unique endpoints hit reveals scanning behavior that per-endpoint monitoring misses.</p>

<p><strong>Rate limit evasion</strong>: Sophisticated attackers rotate through multiple API keys or source IPs to stay under per-client rate limits. By aggregating request patterns across all identifiers at the OTel Collector level, you can detect distributed abuse that no single rate limiter would catch.</p>

<h3 id="authentication-anomaly-detection">Authentication Anomaly Detection</h3>

<p>Authentication telemetry collected through OTel enables several detection patterns:</p>

<p><strong>Impossible travel</strong>: When a user authenticates from New York and then from Tokyo 30 minutes later, the geographic attributes on authentication spans make this trivially detectable. The Prometheus alert rule shown earlier implements this pattern.</p>

<p><strong>Credential reuse across accounts</strong>: By hashing and comparing authentication attempt patterns (same password hash across different usernames from the same IP), you can identify credential reuse attacks that would otherwise appear as unrelated failed logins.</p>

<p><strong>Session anomalies</strong>: Track session creation, renewal, and usage patterns. A session that suddenly starts accessing resources outside its historical pattern may indicate session hijacking.</p>

<h3 id="latency-based-threat-detection">Latency-Based Threat Detection</h3>

<p>This is where security observability truly differentiates from traditional monitoring. Certain attacks create subtle latency signatures:</p>

<p><strong>SQL injection probing</strong>: Injected SQL payloads that trigger error-path processing often exhibit different latency profiles than normal queries. By tracking database span duration distributions and alerting on shifts in the p99, you can detect injection attempts before the WAF catches the payload.</p>

<p><strong>Timing side channels</strong>: Authentication endpoints that take longer to reject valid usernames than invalid ones leak information. OTel trace data makes these timing differences visible and alertable.</p>

<p><strong>Cryptomining detection</strong>: Compromised containers running cryptominers exhibit characteristic CPU utilization patterns that diverge from their historical baseline. Prometheus metrics collected via OTel surface these anomalies.</p>

<h2 id="building-security-alerting-pipelines-with-otel">Building Security Alerting Pipelines with OTel</h2>

<p>The complete alerting pipeline connects OTel instrumentation to incident response:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
</pre></td><td class="rouge-code"><pre>Application Instrumentation (OTel SDK)
    │
    ▼
ADOT Collector (filter, enrich, batch)
    │
    ├──► Amazon Managed Prometheus (metrics storage)
    │        │
    │        ▼
    │    Prometheus AlertManager / Grafana Alerting
    │        │
    │        ├──► SNS Topic (arn:aws:sns:us-east-1:...:alert-critical)
    │        ├──► PagerDuty / Opsgenie
    │        └──► Slack #security-alerts
    │
    ├──► CloudWatch Logs (log storage)
    │        │
    │        ▼
    │    CloudWatch Alarm → Lambda → Security Hub Finding
    │
    └──► Grafana (visualization + investigation)
             │
             ▼
         Security Analyst Investigation Workflow
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="connecting-grafana-alerting-to-aws-sns">Connecting Grafana Alerting to AWS SNS</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
</pre></td><td class="rouge-code"><pre><span class="c1"># grafana-alerting-contact-points.yaml</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">contactPoints</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">orgId</span><span class="pi">:</span> <span class="m">1</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">security-critical</span>
    <span class="na">receivers</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uid</span><span class="pi">:</span> <span class="s">sns-critical</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">sns</span>
        <span class="na">settings</span><span class="pi">:</span>
          <span class="na">topic</span><span class="pi">:</span> <span class="s2">"</span><span class="s">arn:aws:sns:us-east-1:181303648587:alert-critical"</span>
          <span class="na">authProvider</span><span class="pi">:</span> <span class="s">default</span>
        <span class="na">disableResolveMessage</span><span class="pi">:</span> <span class="kc">false</span>
      <span class="pi">-</span> <span class="na">uid</span><span class="pi">:</span> <span class="s">slack-security</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">slack</span>
        <span class="na">settings</span><span class="pi">:</span>
          <span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${SLACK_WEBHOOK_URL}"</span>
          <span class="na">recipient</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#security-alerts"</span>
          <span class="na">title</span><span class="pi">:</span> <span class="pi">|</span>
            <span class="s">[] </span>
          <span class="na">text</span><span class="pi">:</span> <span class="pi">|</span>
            
            <span class="s">*Summary*: </span>
            <span class="s">*Description*: </span>
            <span class="s">*Severity*: </span>
            
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="lambda-function-for-security-hub-integration">Lambda Function for Security Hub Integration</h3>

<p>When a Prometheus alert fires, you can create a Security Hub finding to centralize security events:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
</pre></td><td class="rouge-code"><pre><span class="c1"># security_hub_finding.py
</span><span class="kn">import</span> <span class="n">boto3</span>
<span class="kn">import</span> <span class="n">json</span>
<span class="kn">from</span> <span class="n">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>

<span class="n">securityhub</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="nf">client</span><span class="p">(</span><span class="sh">"</span><span class="s">securityhub</span><span class="sh">"</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">handler</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">context</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">Convert Prometheus/Grafana alert to Security Hub finding.</span><span class="sh">"""</span>
    <span class="n">alert</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="nf">loads</span><span class="p">(</span><span class="n">event</span><span class="p">[</span><span class="sh">"</span><span class="s">body</span><span class="sh">"</span><span class="p">])</span>

    <span class="n">severity_map</span> <span class="o">=</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">critical</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">Label</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">CRITICAL</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Normalized</span><span class="sh">"</span><span class="p">:</span> <span class="mi">90</span><span class="p">},</span>
        <span class="sh">"</span><span class="s">warning</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">Label</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">HIGH</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Normalized</span><span class="sh">"</span><span class="p">:</span> <span class="mi">70</span><span class="p">},</span>
        <span class="sh">"</span><span class="s">info</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">Label</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">MEDIUM</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Normalized</span><span class="sh">"</span><span class="p">:</span> <span class="mi">40</span><span class="p">},</span>
    <span class="p">}</span>

    <span class="k">for</span> <span class="n">alert_item</span> <span class="ow">in</span> <span class="n">alert</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">alerts</span><span class="sh">"</span><span class="p">,</span> <span class="p">[]):</span>
        <span class="n">severity</span> <span class="o">=</span> <span class="n">severity_map</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span>
            <span class="n">alert_item</span><span class="p">[</span><span class="sh">"</span><span class="s">labels</span><span class="sh">"</span><span class="p">].</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">severity</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">info</span><span class="sh">"</span><span class="p">),</span>
            <span class="p">{</span><span class="sh">"</span><span class="s">Label</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">MEDIUM</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Normalized</span><span class="sh">"</span><span class="p">:</span> <span class="mi">40</span><span class="p">}</span>
        <span class="p">)</span>

        <span class="n">finding</span> <span class="o">=</span> <span class="p">{</span>
            <span class="sh">"</span><span class="s">SchemaVersion</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">2018-10-08</span><span class="sh">"</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">Id</span><span class="sh">"</span><span class="p">:</span> <span class="sa">f</span><span class="sh">"</span><span class="s">otel-security/</span><span class="si">{</span><span class="n">alert_item</span><span class="p">[</span><span class="sh">'</span><span class="s">labels</span><span class="sh">'</span><span class="p">][</span><span class="sh">'</span><span class="s">alertname</span><span class="sh">'</span><span class="p">]</span><span class="si">}</span><span class="s">/</span><span class="si">{</span><span class="n">alert_item</span><span class="p">[</span><span class="sh">'</span><span class="s">startsAt</span><span class="sh">'</span><span class="p">]</span><span class="si">}</span><span class="sh">"</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">ProductArn</span><span class="sh">"</span><span class="p">:</span> <span class="sa">f</span><span class="sh">"</span><span class="s">arn:aws:securityhub:</span><span class="si">{</span><span class="n">context</span><span class="p">.</span><span class="n">invoked_function_arn</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="sh">'</span><span class="si">:</span><span class="sh">'</span><span class="s">)[3]</span><span class="si">}</span><span class="s">:</span><span class="si">{</span><span class="n">context</span><span class="p">.</span><span class="n">invoked_function_arn</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="sh">'</span><span class="si">:</span><span class="sh">'</span><span class="s">)[4]</span><span class="si">}</span><span class="s">:product/</span><span class="si">{</span><span class="n">context</span><span class="p">.</span><span class="n">invoked_function_arn</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="sh">'</span><span class="si">:</span><span class="sh">'</span><span class="s">)[4]</span><span class="si">}</span><span class="s">/default</span><span class="sh">"</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">GeneratorId</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">opentelemetry-security-observability</span><span class="sh">"</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">AwsAccountId</span><span class="sh">"</span><span class="p">:</span> <span class="n">context</span><span class="p">.</span><span class="n">invoked_function_arn</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="sh">"</span><span class="s">:</span><span class="sh">"</span><span class="p">)[</span><span class="mi">4</span><span class="p">],</span>
            <span class="sh">"</span><span class="s">Types</span><span class="sh">"</span><span class="p">:</span> <span class="p">[</span><span class="sh">"</span><span class="s">Software and Configuration Checks/Vulnerabilities</span><span class="sh">"</span><span class="p">],</span>
            <span class="sh">"</span><span class="s">CreatedAt</span><span class="sh">"</span><span class="p">:</span> <span class="n">alert_item</span><span class="p">[</span><span class="sh">"</span><span class="s">startsAt</span><span class="sh">"</span><span class="p">],</span>
            <span class="sh">"</span><span class="s">UpdatedAt</span><span class="sh">"</span><span class="p">:</span> <span class="n">datetime</span><span class="p">.</span><span class="nf">utcnow</span><span class="p">().</span><span class="nf">isoformat</span><span class="p">()</span> <span class="o">+</span> <span class="sh">"</span><span class="s">Z</span><span class="sh">"</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">Severity</span><span class="sh">"</span><span class="p">:</span> <span class="n">severity</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">Title</span><span class="sh">"</span><span class="p">:</span> <span class="n">alert_item</span><span class="p">[</span><span class="sh">"</span><span class="s">annotations</span><span class="sh">"</span><span class="p">].</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">summary</span><span class="sh">"</span><span class="p">,</span> <span class="n">alert_item</span><span class="p">[</span><span class="sh">"</span><span class="s">labels</span><span class="sh">"</span><span class="p">][</span><span class="sh">"</span><span class="s">alertname</span><span class="sh">"</span><span class="p">]),</span>
            <span class="sh">"</span><span class="s">Description</span><span class="sh">"</span><span class="p">:</span> <span class="n">alert_item</span><span class="p">[</span><span class="sh">"</span><span class="s">annotations</span><span class="sh">"</span><span class="p">].</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">description</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Security alert from OTel pipeline</span><span class="sh">"</span><span class="p">),</span>
            <span class="sh">"</span><span class="s">Resources</span><span class="sh">"</span><span class="p">:</span> <span class="p">[</span>
                <span class="p">{</span>
                    <span class="sh">"</span><span class="s">Type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">Other</span><span class="sh">"</span><span class="p">,</span>
                    <span class="sh">"</span><span class="s">Id</span><span class="sh">"</span><span class="p">:</span> <span class="n">alert_item</span><span class="p">[</span><span class="sh">"</span><span class="s">labels</span><span class="sh">"</span><span class="p">].</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">service_name</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">unknown</span><span class="sh">"</span><span class="p">),</span>
                    <span class="sh">"</span><span class="s">Region</span><span class="sh">"</span><span class="p">:</span> <span class="n">context</span><span class="p">.</span><span class="n">invoked_function_arn</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="sh">"</span><span class="s">:</span><span class="sh">"</span><span class="p">)[</span><span class="mi">3</span><span class="p">]</span>
                <span class="p">}</span>
            <span class="p">],</span>
        <span class="p">}</span>

        <span class="n">securityhub</span><span class="p">.</span><span class="nf">batch_import_findings</span><span class="p">(</span><span class="n">Findings</span><span class="o">=</span><span class="p">[</span><span class="n">finding</span><span class="p">])</span>

    <span class="k">return</span> <span class="p">{</span><span class="sh">"</span><span class="s">statusCode</span><span class="sh">"</span><span class="p">:</span> <span class="mi">200</span><span class="p">,</span> <span class="sh">"</span><span class="s">body</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">Findings imported</span><span class="sh">"</span><span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="implementation-roadmap">Implementation Roadmap</h2>

<h3 id="phase-1-foundation-week-1-2">Phase 1: Foundation (Week 1-2)</h3>
<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Deploy ADOT Collector on EKS as a centralized Deployment</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Configure dual-export to CloudWatch/X-Ray and Amazon Managed Prometheus</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Instrument authentication endpoints with security span attributes</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Verify telemetry flow end-to-end</li>
</ul>

<h3 id="phase-2-security-instrumentation-week-3-4">Phase 2: Security Instrumentation (Week 3-4)</h3>
<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Add OTel instrumentation to all API gateways and authentication services</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Implement authorization event tracking</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Deploy ADOT Lambda layer for serverless workloads</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Create baseline security metrics (auth failure rate, API error rate, payload sizes)</li>
</ul>

<h3 id="phase-3-dashboards-and-alerting-week-5-6">Phase 3: Dashboards and Alerting (Week 5-6)</h3>
<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Deploy Grafana on ECS Fargate (or enable Amazon Managed Grafana)</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Import security dashboard JSON configurations</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Configure Prometheus alerting rules for authentication and API security</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Connect Grafana Alerting to SNS and Slack for incident notification</li>
</ul>

<h3 id="phase-4-advanced-detection-week-7-8">Phase 4: Advanced Detection (Week 7-8)</h3>
<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Implement impossible travel detection rules</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Add data exfiltration monitoring via payload size anomaly detection</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Create Security Hub integration for centralized finding management</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Build investigation workflows in Grafana Explore for trace-to-log correlation</li>
</ul>

<h2 id="related-articles">Related Articles</h2>

<ul>
  <li><a href="/posts/real-time-ids-using-guardduty/">Real-Time Intrusion Detection Using AWS GuardDuty</a> – Pair GuardDuty findings with OTel traces for context-rich threat detection</li>
  <li><a href="/posts/aws-lambda-security-automated-threat-detection-2025/">AWS Lambda Security: Automated Threat Detection</a> – Extend Lambda security with ADOT instrumentation for deeper observability</li>
  <li><a href="/posts/aws-cloud-security-best-practices-implementation-guide/">AWS Cloud Security Best Practices Implementation Guide</a> – Foundation security controls that feed into your observability pipeline</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>Security observability is not optional – it is the difference between detecting a breach in hours versus months. OpenTelemetry provides the vendor-neutral foundation, ADOT gives you production-ready AWS integration, and Grafana delivers the visualization layer your security team actually wants to use.</p>

<p>The approach outlined in this article – ADOT as the universal collector, dual-exporting to both AWS-native services and open source backends – gives you the best of both worlds. You keep CloudWatch integration for native AWS alarms and compliance. You gain Grafana for the advanced dashboards, correlation, and alerting that security operations demand. And you maintain the freedom to move your telemetry pipeline to any cloud or on-prem environment without rewriting a single line of instrumentation.</p>

<p>Start with authentication instrumentation. It is the highest-signal security telemetry you can collect, and the ADOT Collector makes it trivial to route to multiple backends. From there, expand to API behavior tracking, authorization monitoring, and the advanced detection patterns covered in this guide.</p>

<p>The observability gap in security is closing. OpenTelemetry is the open standard making it happen, and AWS is all in with ADOT. The question is not whether to adopt it – it is how quickly you can get your security telemetry flowing.</p>

<hr />

<p><em>For hands-on guidance implementing security observability with OpenTelemetry on AWS, connect with me on <a href="https://www.linkedin.com/in/jonpricelinux/">LinkedIn</a>. I regularly share DevSecOps strategies, AWS security patterns, and open source tooling recommendations.</em></p>]]></content><author><name>Jon (JR) Price</name></author><category term="AWS" /><category term="Security" /><category term="adot" /><category term="cloudwatch" /><category term="grafana" /><category term="open-source" /><category term="opentelemetry" /><category term="prometheus" /><category term="security-observability" /><category term="vendor-neutral" /><summary type="html"><![CDATA[Build vendor-neutral security observability with OpenTelemetry and AWS ADOT. Compare CloudWatch with Grafana for security monitoring dashboards.]]></summary></entry><entry><title type="html">Container Image Signing and Verification: Cosign and Sigstore on AWS</title><link href="https://www.red-team.sh/posts/container-signing-cosign-sigstore-aws/" rel="alternate" type="text/html" title="Container Image Signing and Verification: Cosign and Sigstore on AWS" /><published>2026-04-22T09:00:00-07:00</published><updated>2026-04-22T09:00:00-07:00</updated><id>https://www.red-team.sh/posts/container-signing-cosign-sigstore-aws</id><content type="html" xml:base="https://www.red-team.sh/posts/container-signing-cosign-sigstore-aws/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#introduction-why-container-signing-matters" id="markdown-toc-introduction-why-container-signing-matters">Introduction: Why Container Signing Matters</a></li>
  <li><a href="#the-sigstore-ecosystem" id="markdown-toc-the-sigstore-ecosystem">The Sigstore Ecosystem</a>    <ul>
      <li><a href="#cosign-the-signing-tool" id="markdown-toc-cosign-the-signing-tool">Cosign: The Signing Tool</a></li>
      <li><a href="#fulcio-the-certificate-authority" id="markdown-toc-fulcio-the-certificate-authority">Fulcio: The Certificate Authority</a></li>
      <li><a href="#rekor-the-transparency-log" id="markdown-toc-rekor-the-transparency-log">Rekor: The Transparency Log</a></li>
    </ul>
  </li>
  <li><a href="#how-keyless-signing-works" id="markdown-toc-how-keyless-signing-works">How Keyless Signing Works</a></li>
  <li><a href="#aws-signer-the-managed-alternative" id="markdown-toc-aws-signer-the-managed-alternative">AWS Signer: The Managed Alternative</a>    <ul>
      <li><a href="#setting-up-aws-signer" id="markdown-toc-setting-up-aws-signer">Setting Up AWS Signer</a></li>
      <li><a href="#how-it-works" id="markdown-toc-how-it-works">How It Works</a></li>
      <li><a href="#limitations" id="markdown-toc-limitations">Limitations</a></li>
    </ul>
  </li>
  <li><a href="#cosign-on-aws-signing-images-in-ecr" id="markdown-toc-cosign-on-aws-signing-images-in-ecr">Cosign on AWS: Signing Images in ECR</a>    <ul>
      <li><a href="#prerequisites" id="markdown-toc-prerequisites">Prerequisites</a></li>
      <li><a href="#keyless-signing-recommended" id="markdown-toc-keyless-signing-recommended">Keyless Signing (Recommended)</a></li>
      <li><a href="#key-pair-signing-with-aws-kms" id="markdown-toc-key-pair-signing-with-aws-kms">Key-Pair Signing with AWS KMS</a></li>
      <li><a href="#ecr-terraform-configuration" id="markdown-toc-ecr-terraform-configuration">ECR Terraform Configuration</a></li>
    </ul>
  </li>
  <li><a href="#codebuild-integration-automated-signing-in-cicd" id="markdown-toc-codebuild-integration-automated-signing-in-cicd">CodeBuild Integration: Automated Signing in CI/CD</a>    <ul>
      <li><a href="#codebuild-buildspec" id="markdown-toc-codebuild-buildspec">CodeBuild Buildspec</a></li>
      <li><a href="#github-actions-with-keyless-signing" id="markdown-toc-github-actions-with-keyless-signing">GitHub Actions with Keyless Signing</a></li>
    </ul>
  </li>
  <li><a href="#verification-in-eks-kyverno-admission-policies" id="markdown-toc-verification-in-eks-kyverno-admission-policies">Verification in EKS: Kyverno Admission Policies</a>    <ul>
      <li><a href="#installing-kyverno-on-eks" id="markdown-toc-installing-kyverno-on-eks">Installing Kyverno on EKS</a></li>
      <li><a href="#kyverno-policy-require-cosign-signatures-keyless" id="markdown-toc-kyverno-policy-require-cosign-signatures-keyless">Kyverno Policy: Require Cosign Signatures (Keyless)</a></li>
      <li><a href="#kyverno-policy-require-signatures-with-kms-key" id="markdown-toc-kyverno-policy-require-signatures-with-kms-key">Kyverno Policy: Require Signatures with KMS Key</a></li>
      <li><a href="#testing-the-policy" id="markdown-toc-testing-the-policy">Testing the Policy</a></li>
    </ul>
  </li>
  <li><a href="#comparison-cosign-vs-aws-signer-vs-notation" id="markdown-toc-comparison-cosign-vs-aws-signer-vs-notation">Comparison: Cosign vs AWS Signer vs Notation</a>    <ul>
      <li><a href="#when-to-use-each" id="markdown-toc-when-to-use-each">When to Use Each</a></li>
    </ul>
  </li>
  <li><a href="#attestations-beyond-signatures" id="markdown-toc-attestations-beyond-signatures">Attestations: Beyond Signatures</a>    <ul>
      <li><a href="#sbom-attestation" id="markdown-toc-sbom-attestation">SBOM Attestation</a></li>
      <li><a href="#vulnerability-scan-attestation" id="markdown-toc-vulnerability-scan-attestation">Vulnerability Scan Attestation</a></li>
      <li><a href="#slsa-provenance-attestation" id="markdown-toc-slsa-provenance-attestation">SLSA Provenance Attestation</a></li>
    </ul>
  </li>
  <li><a href="#complete-pipeline-build-sign-attest-deploy" id="markdown-toc-complete-pipeline-build-sign-attest-deploy">Complete Pipeline: Build, Sign, Attest, Deploy</a>    <ul>
      <li><a href="#pipeline-stages" id="markdown-toc-pipeline-stages">Pipeline Stages</a></li>
      <li><a href="#implementation-roadmap" id="markdown-toc-implementation-roadmap">Implementation Roadmap</a>        <ul>
          <li><a href="#phase-1-foundation-weeks-1-2" id="markdown-toc-phase-1-foundation-weeks-1-2">Phase 1: Foundation (Weeks 1-2)</a></li>
          <li><a href="#phase-2-keyless-and-attestations-weeks-3-4" id="markdown-toc-phase-2-keyless-and-attestations-weeks-3-4">Phase 2: Keyless and Attestations (Weeks 3-4)</a></li>
          <li><a href="#phase-3-enforcement-weeks-5-6" id="markdown-toc-phase-3-enforcement-weeks-5-6">Phase 3: Enforcement (Weeks 5-6)</a></li>
          <li><a href="#phase-4-advanced-ongoing" id="markdown-toc-phase-4-advanced-ongoing">Phase 4: Advanced (Ongoing)</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#related-articles" id="markdown-toc-related-articles">Related Articles</a></li>
  <li><a href="#additional-resources" id="markdown-toc-additional-resources">Additional Resources</a>    <ul>
      <li><a href="#official-documentation" id="markdown-toc-official-documentation">Official Documentation</a></li>
      <li><a href="#tools-and-frameworks" id="markdown-toc-tools-and-frameworks">Tools and Frameworks</a></li>
      <li><a href="#industry-resources" id="markdown-toc-industry-resources">Industry Resources</a></li>
    </ul>
  </li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ul>

<h2 id="introduction-why-container-signing-matters">Introduction: Why Container Signing Matters</h2>

<p>Every container image your cluster pulls is a trust decision. When your EKS node downloads <code class="language-plaintext highlighter-rouge">myapp:v2.1.0</code> from ECR, how does it know that image was built by your CI pipeline and not injected by an attacker who compromised your registry credentials? Without cryptographic signatures, the answer is: it does not.</p>

<p>The container supply chain has become one of the most targeted attack surfaces in cloud-native environments. The container security market reached <strong>$3.05 billion in 2025</strong> and is projected to hit <strong>$9.01 billion by 2030</strong>, driven by organizations realizing that unsigned images are an open door for supply chain attacks. Chainguard’s <strong>$356 million Series D</strong> at a $3.5 billion valuation underscores just how seriously the industry is taking this problem.</p>

<p>Recent incidents paint a stark picture:</p>

<ul>
  <li><strong>78% of organizations</strong> rely on third-party container images with no verification of provenance</li>
  <li>Researchers demonstrated successful supply chain attacks against <strong>92% of popular ML frameworks</strong> through poisoned container images</li>
  <li>The average cost of a supply chain compromise has reached <strong>$6.8 million per incident</strong></li>
</ul>

<p>The solution is cryptographic signing and verification: every image gets a signature proving who built it, when, and from what source. At deployment time, admission controllers reject anything unsigned. This is zero-trust applied to your artifact pipeline.</p>

<p>AWS provides its own signing service (AWS Signer with managed ECR signing), but the open-source Sigstore ecosystem, led by <strong>Cosign</strong>, has become the industry standard. Kubernetes itself uses Sigstore to sign its releases. The key advantage: <strong>keyless signing</strong> with Fulcio eliminates the entire problem of key management, while Rekor transparency logs provide a public, tamper-evident audit trail.</p>

<p>This guide walks through implementing container image signing on AWS using both approaches, with a clear recommendation: <strong>lead with Cosign for portability and keyless signing, use AWS Signer when you need managed simplicity within a pure-AWS environment</strong>.</p>

<p>Container signing is a core control in the <a href="/ai-supply-chain-security/">AI supply chain security pillar</a>, especially when ML training, inference, and batch jobs depend on container images that must be verified before deployment.</p>

<h2 id="the-sigstore-ecosystem">The Sigstore Ecosystem</h2>

<p>Sigstore is an open-source project under the Linux Foundation that provides free, easy-to-use tools for signing, verifying, and protecting software artifacts. It consists of three core components that work together.</p>

<h3 id="cosign-the-signing-tool">Cosign: The Signing Tool</h3>

<p>Cosign is the command-line tool that signs container images and other OCI artifacts. It supports multiple signing modes:</p>

<ul>
  <li><strong>Keyless signing</strong> via Fulcio (recommended) – no keys to manage</li>
  <li><strong>Key-pair signing</strong> with local or KMS-managed keys (AWS KMS, GCP KMS, Azure Key Vault, HashiCorp Vault)</li>
  <li><strong>Hardware key signing</strong> with PKCS#11 tokens and Yubikeys</li>
</ul>

<p>Cosign stores signatures as OCI artifacts alongside the image in the same registry. This means ECR, Docker Hub, GitHub Container Registry, and any OCI-compliant registry work out of the box with no additional infrastructure.</p>

<p><strong>Cosign v3</strong>, the latest major release, introduces the standardized bundle format (<code class="language-plaintext highlighter-rouge">--new-bundle-format</code>) that packages all signed material and verification data into a single artifact. The upcoming <strong>Cosign v4</strong> will simplify the CLI further by removing legacy flags.</p>

<h3 id="fulcio-the-certificate-authority">Fulcio: The Certificate Authority</h3>

<p>Fulcio is a free, public certificate authority that issues <strong>short-lived code signing certificates</strong> (valid for 10 minutes) bound to an OpenID Connect (OIDC) identity. Instead of managing long-lived signing keys, you authenticate with an identity provider (GitHub, Google, Microsoft) and Fulcio issues a certificate that proves <em>you</em> (or your CI system) performed the signing.</p>

<p>This eliminates the single biggest operational burden in code signing: key lifecycle management. No key generation, no key rotation, no key revocation, no key storage – just identity.</p>

<h3 id="rekor-the-transparency-log">Rekor: The Transparency Log</h3>

<p>Rekor is an immutable, append-only transparency log that records every signing event. When Cosign signs an image, it uploads the signature and certificate to Rekor, creating a tamper-evident record. This serves three purposes:</p>

<ol>
  <li><strong>Auditability</strong> – you can verify when any artifact was signed and by whom</li>
  <li><strong>Compromise detection</strong> – if Fulcio is compromised and issues rogue certificates, the transparency log reveals it</li>
  <li><strong>Offline verification</strong> – verifiers can check that a signature was logged at a time when the certificate was valid, even after the certificate expires</li>
</ol>

<p>Together, these three components create a “keyless” signing workflow that is more secure than traditional key-based signing because it eliminates the key management attack surface entirely.</p>

<h2 id="how-keyless-signing-works">How Keyless Signing Works</h2>

<p>Understanding the keyless signing flow is critical for trusting it. Here is what happens when you run <code class="language-plaintext highlighter-rouge">cosign sign</code> in keyless mode:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre>Developer/CI ──OIDC──&gt; Identity Provider (GitHub, Google)
     │                        │
     │                   OIDC Token
     │                        │
     ▼                        ▼
  Cosign ──certificate req──&gt; Fulcio (issues 10-min cert)
     │                        │
     │                  Short-lived cert
     │                        │
     ▼                        ▼
  Sign image ──signature──&gt; Rekor (transparency log)
     │
     ▼
  Push signature to OCI Registry (alongside image)
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Step by step:</p>

<ol>
  <li><strong>OIDC authentication</strong> – Cosign triggers an OIDC flow. In CI (GitHub Actions, GitLab CI), this uses the platform’s built-in OIDC token. Interactively, it opens a browser for Google/GitHub/Microsoft login.</li>
  <li><strong>Ephemeral key generation</strong> – Cosign generates an ephemeral key pair <em>in memory</em>. The private key never touches disk and is never sent to any Sigstore service.</li>
  <li><strong>Certificate request</strong> – Cosign sends the public key and OIDC token to Fulcio. Fulcio validates the token, issues a short-lived X.509 certificate binding the public key to the OIDC identity (e.g., <code class="language-plaintext highlighter-rouge">github-actions@my-org</code>), and publishes the certificate to a certificate transparency log.</li>
  <li><strong>Signing</strong> – Cosign signs the image digest with the ephemeral private key.</li>
  <li><strong>Transparency logging</strong> – The signature and certificate are uploaded to Rekor, creating an immutable record.</li>
  <li><strong>Registry upload</strong> – The signature is pushed to the OCI registry as a referrer artifact alongside the signed image.</li>
  <li><strong>Key destruction</strong> – The ephemeral private key is discarded from memory.</li>
</ol>

<p>At verification time, <code class="language-plaintext highlighter-rouge">cosign verify</code> checks: (a) the signature is valid for the image digest, (b) the certificate was issued by Fulcio, (c) the signing event was logged in Rekor at a time when the certificate was valid, and (d) the OIDC identity matches what you expect.</p>

<h2 id="aws-signer-the-managed-alternative">AWS Signer: The Managed Alternative</h2>

<p>AWS launched <strong>managed container image signing</strong> for ECR in late 2025. This is a fully managed service where ECR automatically signs images as they are pushed, using AWS Signer for the cryptographic operations.</p>

<h3 id="setting-up-aws-signer">Setting Up AWS Signer</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="c"># Create a signing profile</span>
aws signer put-signing-profile <span class="se">\</span>
    <span class="nt">--profile-name</span> my-container-profile <span class="se">\</span>
    <span class="nt">--platform-id</span> Notation-OCI-SHA384-ECDSA

<span class="c"># Create an ECR signing rule (managed signing)</span>
aws ecr put-registry-signing-configuration <span class="se">\</span>
    <span class="nt">--signing-configuration</span> <span class="se">\</span>
        <span class="s2">"signingProfiles=[{profileName=my-container-profile}]"</span> <span class="se">\</span>
    <span class="nt">--signing-rules</span> <span class="se">\</span>
        <span class="s2">"repositorySelectionCriteria=ALL"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="how-it-works">How It Works</h3>

<p>Once configured, ECR automatically signs every image pushed to repositories matching the signing rule. AWS Signer handles:</p>

<ul>
  <li>Key material generation and secure storage (backed by AWS KMS)</li>
  <li>Certificate lifecycle management including rotation</li>
  <li>Signing operations logged through CloudTrail</li>
</ul>

<h3 id="limitations">Limitations</h3>

<p>AWS Signer is convenient but comes with significant trade-offs:</p>

<ul>
  <li><strong>AWS lock-in</strong> – signatures are in Notation format, not Cosign format. Verification requires AWS-specific tooling.</li>
  <li><strong>No keyless signing</strong> – AWS manages the keys, but keys still exist. You trade self-managed keys for AWS-managed keys.</li>
  <li><strong>No transparency log</strong> – there is no public, tamper-evident record of signing events. CloudTrail provides audit logging but within your AWS account, not a public ledger.</li>
  <li><strong>No SBOM attestation support</strong> – AWS Signer signs images but does not support arbitrary attestations (SBOMs, vulnerability scan results, SLSA provenance).</li>
  <li><strong>Limited ecosystem integration</strong> – Kubernetes admission controllers like Kyverno have first-class Cosign support; AWS Signer integration requires additional configuration.</li>
</ul>

<h2 id="cosign-on-aws-signing-images-in-ecr">Cosign on AWS: Signing Images in ECR</h2>

<p>Cosign works natively with ECR. Here is the practical setup.</p>

<h3 id="prerequisites">Prerequisites</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="c"># Install Cosign</span>
brew <span class="nb">install </span>cosign
<span class="c"># or</span>
go <span class="nb">install </span>github.com/sigstore/cosign/v2/cmd/cosign@latest

<span class="c"># Verify installation</span>
cosign version

<span class="c"># Authenticate to ECR</span>
aws ecr get-login-password <span class="nt">--region</span> us-east-1 | <span class="se">\</span>
    docker login <span class="nt">--username</span> AWS <span class="se">\</span>
    <span class="nt">--password-stdin</span> 111122223333.dkr.ecr.us-east-1.amazonaws.com
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="keyless-signing-recommended">Keyless Signing (Recommended)</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
</pre></td><td class="rouge-code"><pre><span class="c"># Build and push your image</span>
docker build <span class="nt">-t</span> myapp:v2.1.0 <span class="nb">.</span>
docker tag myapp:v2.1.0 <span class="se">\</span>
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp:v2.1.0
docker push <span class="se">\</span>
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp:v2.1.0

<span class="c"># Sign with keyless (Fulcio + Rekor)</span>
<span class="c"># In CI, this uses the OIDC token automatically</span>
<span class="c"># Interactively, it opens a browser for authentication</span>
<span class="nv">COSIGN_EXPERIMENTAL</span><span class="o">=</span>1 cosign sign <span class="se">\</span>
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp:v2.1.0

<span class="c"># Verify the signature</span>
cosign verify <span class="se">\</span>
    <span class="nt">--certificate-oidc-issuer</span> https://token.actions.githubusercontent.com <span class="se">\</span>
    <span class="nt">--certificate-identity-regexp</span> <span class="s2">"https://github.com/my-org/.*"</span> <span class="se">\</span>
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp:v2.1.0
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="key-pair-signing-with-aws-kms">Key-Pair Signing with AWS KMS</h3>

<p>For organizations that require key-based signing with centralized key management:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="c"># Generate a Cosign key pair backed by AWS KMS</span>
cosign generate-key-pair <span class="se">\</span>
    <span class="nt">--kms</span> awskms:///arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab

<span class="c"># Sign using the KMS key</span>
cosign sign <span class="nt">--key</span> <span class="se">\</span>
    awskms:///arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab <span class="se">\</span>
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp:v2.1.0

<span class="c"># Verify using the KMS key</span>
cosign verify <span class="nt">--key</span> <span class="se">\</span>
    awskms:///arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab <span class="se">\</span>
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp:v2.1.0
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="ecr-terraform-configuration">ECR Terraform Configuration</h3>

<p>Set up your ECR repositories with proper configuration for signed images:</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
</pre></td><td class="rouge-code"><pre><span class="nx">resource</span> <span class="s2">"aws_ecr_repository"</span> <span class="s2">"app"</span> <span class="p">{</span>
  <span class="nx">name</span>                 <span class="o">=</span> <span class="s2">"myapp"</span>
  <span class="nx">image_tag_mutability</span> <span class="o">=</span> <span class="s2">"IMMUTABLE"</span>  <span class="c1"># Critical: prevent tag overwrites</span>

  <span class="nx">image_scanning_configuration</span> <span class="p">{</span>
    <span class="nx">scan_on_push</span> <span class="o">=</span> <span class="kc">true</span>
  <span class="p">}</span>

  <span class="nx">encryption_configuration</span> <span class="p">{</span>
    <span class="nx">encryption_type</span> <span class="o">=</span> <span class="s2">"KMS"</span>
    <span class="nx">kms_key</span>         <span class="o">=</span> <span class="nx">aws_kms_key</span><span class="p">.</span><span class="nx">ecr_signing</span><span class="p">.</span><span class="nx">arn</span>
  <span class="p">}</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"internal"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"myapp"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="s2">"prod"</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"platform-team"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"engineering"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_ecr_lifecycle_policy"</span> <span class="s2">"app"</span> <span class="p">{</span>
  <span class="nx">repository</span> <span class="o">=</span> <span class="nx">aws_ecr_repository</span><span class="p">.</span><span class="nx">app</span><span class="p">.</span><span class="nx">name</span>

  <span class="nx">policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">rules</span> <span class="o">=</span> <span class="p">[{</span>
      <span class="nx">rulePriority</span> <span class="o">=</span> <span class="mi">1</span>
      <span class="nx">description</span>  <span class="o">=</span> <span class="s2">"Keep last 30 signed images"</span>
      <span class="nx">selection</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nx">tagStatus</span>   <span class="o">=</span> <span class="s2">"any"</span>
        <span class="nx">countType</span>   <span class="o">=</span> <span class="s2">"imageCountMoreThan"</span>
        <span class="nx">countNumber</span> <span class="o">=</span> <span class="mi">30</span>
      <span class="p">}</span>
      <span class="nx">action</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nx">type</span> <span class="o">=</span> <span class="s2">"expire"</span>
      <span class="p">}</span>
    <span class="p">}]</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="c1"># KMS key for ECR encryption and Cosign signing</span>
<span class="nx">resource</span> <span class="s2">"aws_kms_key"</span> <span class="s2">"ecr_signing"</span> <span class="p">{</span>
  <span class="nx">description</span>             <span class="o">=</span> <span class="s2">"KMS key for ECR image encryption and Cosign signing"</span>
  <span class="nx">deletion_window_in_days</span> <span class="o">=</span> <span class="mi">30</span>
  <span class="nx">enable_key_rotation</span>     <span class="o">=</span> <span class="kc">true</span>

  <span class="nx">policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">Version</span> <span class="o">=</span> <span class="s2">"2012-10-17"</span>
    <span class="nx">Statement</span> <span class="o">=</span> <span class="p">[</span>
      <span class="p">{</span>
        <span class="nx">Sid</span>    <span class="o">=</span> <span class="s2">"AllowKeyAdministration"</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Principal</span> <span class="o">=</span> <span class="p">{</span>
          <span class="nx">AWS</span> <span class="o">=</span> <span class="s2">"arn:aws:iam::111122223333:root"</span>
        <span class="p">}</span>
        <span class="nx">Action</span>   <span class="o">=</span> <span class="s2">"kms:*"</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="s2">"*"</span>
      <span class="p">},</span>
      <span class="p">{</span>
        <span class="nx">Sid</span>    <span class="o">=</span> <span class="s2">"AllowCodeBuildSigning"</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Principal</span> <span class="o">=</span> <span class="p">{</span>
          <span class="nx">AWS</span> <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">codebuild_signing</span><span class="p">.</span><span class="nx">arn</span>
        <span class="p">}</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"kms:Sign"</span><span class="p">,</span>
          <span class="s2">"kms:Verify"</span><span class="p">,</span>
          <span class="s2">"kms:GetPublicKey"</span><span class="p">,</span>
          <span class="s2">"kms:DescribeKey"</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="s2">"*"</span>
      <span class="p">}</span>
    <span class="p">]</span>
  <span class="p">})</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"internal"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"ecr-signing"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="s2">"prod"</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"platform-team"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"engineering"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_kms_alias"</span> <span class="s2">"ecr_signing"</span> <span class="p">{</span>
  <span class="nx">name</span>          <span class="o">=</span> <span class="s2">"alias/ecr-signing"</span>
  <span class="nx">target_key_id</span> <span class="o">=</span> <span class="nx">aws_kms_key</span><span class="p">.</span><span class="nx">ecr_signing</span><span class="p">.</span><span class="nx">key_id</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="codebuild-integration-automated-signing-in-cicd">CodeBuild Integration: Automated Signing in CI/CD</h2>

<p>The real value of container signing is when it runs automatically in your CI/CD pipeline. Here is a complete CodeBuild buildspec that builds, signs, and attests container images.</p>

<h3 id="codebuild-buildspec">CodeBuild Buildspec</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
</pre></td><td class="rouge-code"><pre><span class="c1"># buildspec.yml</span>
<span class="na">version</span><span class="pi">:</span> <span class="m">0.2</span>

<span class="na">env</span><span class="pi">:</span>
  <span class="na">variables</span><span class="pi">:</span>
    <span class="na">AWS_ACCOUNT_ID</span><span class="pi">:</span> <span class="s2">"</span><span class="s">111122223333"</span>
    <span class="na">AWS_DEFAULT_REGION</span><span class="pi">:</span> <span class="s2">"</span><span class="s">us-east-1"</span>
    <span class="na">IMAGE_REPO_NAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">myapp"</span>
    <span class="na">COSIGN_VERSION</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2.4.1"</span>
  <span class="na">parameter-store</span><span class="pi">:</span>
    <span class="c1"># Store the KMS key ARN in SSM Parameter Store</span>
    <span class="na">KMS_KEY_ARN</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/myapp/prod/cosign-kms-key-arn"</span>

<span class="na">phases</span><span class="pi">:</span>
  <span class="na">install</span><span class="pi">:</span>
    <span class="na">commands</span><span class="pi">:</span>
      <span class="c1"># Install Cosign</span>
      <span class="pi">-</span> <span class="s">curl -LO "https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign-linux-amd64"</span>
      <span class="pi">-</span> <span class="s">chmod +x cosign-linux-amd64</span>
      <span class="pi">-</span> <span class="s">mv cosign-linux-amd64 /usr/local/bin/cosign</span>
      <span class="pi">-</span> <span class="s">cosign version</span>

  <span class="na">pre_build</span><span class="pi">:</span>
    <span class="na">commands</span><span class="pi">:</span>
      <span class="c1"># Authenticate to ECR</span>
      <span class="pi">-</span> <span class="s">aws ecr get-login-password --region $AWS_DEFAULT_REGION |</span>
          <span class="s">docker login --username AWS</span>
          <span class="s">--password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com</span>
      <span class="c1"># Set image tag from commit SHA for immutability</span>
      <span class="pi">-</span> <span class="s">IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)</span>
      <span class="pi">-</span> <span class="s">IMAGE_URI="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME"</span>
      <span class="pi">-</span> <span class="s">FULL_IMAGE="$IMAGE_URI:$IMAGE_TAG"</span>

  <span class="na">build</span><span class="pi">:</span>
    <span class="na">commands</span><span class="pi">:</span>
      <span class="c1"># Build the container image</span>
      <span class="pi">-</span> <span class="s">docker build -t $FULL_IMAGE .</span>
      <span class="c1"># Run security scan before signing</span>
      <span class="pi">-</span> <span class="s">docker run --rm -v /var/run/docker.sock:/var/run/docker.sock</span>
          <span class="s">aquasec/trivy:latest image --exit-code </span><span class="m">1</span>
          <span class="s">--severity CRITICAL,HIGH $FULL_IMAGE</span>
      <span class="c1"># Push to ECR</span>
      <span class="pi">-</span> <span class="s">docker push $FULL_IMAGE</span>
      <span class="c1"># Get the image digest for signing (never sign tags, sign digests)</span>
      <span class="pi">-</span> <span class="s">IMAGE_DIGEST=$(docker inspect --format='' $FULL_IMAGE)</span>

  <span class="na">post_build</span><span class="pi">:</span>
    <span class="na">commands</span><span class="pi">:</span>
      <span class="c1"># Sign the image with AWS KMS key</span>
      <span class="pi">-</span> <span class="s">cosign sign --key "awskms:///$KMS_KEY_ARN"</span>
          <span class="s">--yes $IMAGE_DIGEST</span>
      <span class="c1"># Generate and attach SBOM attestation</span>
      <span class="pi">-</span> <span class="s">docker run --rm -v /var/run/docker.sock:/var/run/docker.sock</span>
          <span class="s">anchore/syft:latest $FULL_IMAGE -o spdx-json &gt; sbom.spdx.json</span>
      <span class="pi">-</span> <span class="s">cosign attest --key "awskms:///$KMS_KEY_ARN"</span>
          <span class="s">--predicate sbom.spdx.json</span>
          <span class="s">--type spdxjson</span>
          <span class="s">--yes $IMAGE_DIGEST</span>
      <span class="c1"># Attach vulnerability scan results as attestation</span>
      <span class="pi">-</span> <span class="s">docker run --rm -v /var/run/docker.sock:/var/run/docker.sock</span>
          <span class="s">aquasec/trivy:latest image --format cosign-vuln</span>
          <span class="s">$FULL_IMAGE &gt; vuln-report.json</span>
      <span class="pi">-</span> <span class="s">cosign attest --key "awskms:///$KMS_KEY_ARN"</span>
          <span class="s">--predicate vuln-report.json</span>
          <span class="s">--type vuln</span>
          <span class="s">--yes $IMAGE_DIGEST</span>
      <span class="c1"># Verify the signature</span>
      <span class="pi">-</span> <span class="s">cosign verify --key "awskms:///$KMS_KEY_ARN" $IMAGE_DIGEST</span>
      <span class="pi">-</span> <span class="s">echo "Image signed and verified successfully"</span>
      <span class="pi">-</span> <span class="s">echo "SIGNED_IMAGE=$IMAGE_DIGEST" &gt;&gt; $CODEBUILD_SRC_DIR/env.txt</span>

<span class="na">artifacts</span><span class="pi">:</span>
  <span class="na">files</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">env.txt</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="github-actions-with-keyless-signing">GitHub Actions with Keyless Signing</h3>

<p>For teams using GitHub Actions, keyless signing is even simpler because GitHub provides a built-in OIDC token:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
</pre></td><td class="rouge-code"><pre><span class="c1"># .github/workflows/build-sign.yml</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Build, Sign, and Push Container Image</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">push</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">main</span><span class="pi">]</span>

<span class="na">permissions</span><span class="pi">:</span>
  <span class="na">contents</span><span class="pi">:</span> <span class="s">read</span>
  <span class="na">id-token</span><span class="pi">:</span> <span class="s">write</span>      <span class="c1"># Required for OIDC keyless signing</span>
  <span class="na">packages</span><span class="pi">:</span> <span class="s">write</span>

<span class="na">env</span><span class="pi">:</span>
  <span class="na">AWS_REGION</span><span class="pi">:</span> <span class="s">us-east-1</span>
  <span class="na">ECR_REPOSITORY</span><span class="pi">:</span> <span class="s">myapp</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build-sign-push</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Configure AWS credentials</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">aws-actions/configure-aws-credentials@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">role-to-assume</span><span class="pi">:</span> <span class="s">arn:aws:iam::111122223333:role/github-actions-ecr</span>
          <span class="na">aws-region</span><span class="pi">:</span> <span class="s">$</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Login to Amazon ECR</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">login-ecr</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">aws-actions/amazon-ecr-login@v2</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install Cosign</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">sigstore/cosign-installer@v3</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build and push image</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">build</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">REGISTRY</span><span class="pi">:</span> <span class="s">$</span>
          <span class="na">IMAGE_TAG</span><span class="pi">:</span> <span class="s">$</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">docker build -t $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .</span>
          <span class="s">docker push $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG</span>
          <span class="s">DIGEST=$(docker inspect --format='' \</span>
              <span class="s">$REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG)</span>
          <span class="s">echo "digest=$DIGEST" &gt;&gt; $GITHUB_OUTPUT</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Sign image with Cosign (keyless)</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">DIGEST</span><span class="pi">:</span> <span class="s">$</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">cosign sign --yes $DIGEST</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Verify signature</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">DIGEST</span><span class="pi">:</span> <span class="s">$</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">cosign verify \</span>
              <span class="s">--certificate-oidc-issuer https://token.actions.githubusercontent.com \</span>
              <span class="s">--certificate-identity \</span>
              <span class="s">"https://github.com/$/.github/workflows/build-sign.yml@refs/heads/main" \</span>
              <span class="s">$DIGEST</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Generate and attest SBOM</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">DIGEST</span><span class="pi">:</span> <span class="s">$</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s"># Generate SBOM with Syft</span>
          <span class="s">curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s</span>
          <span class="s">syft $DIGEST -o spdx-json &gt; sbom.spdx.json</span>

          <span class="s"># Attest SBOM to the image (keyless)</span>
          <span class="s">cosign attest --yes \</span>
              <span class="s">--predicate sbom.spdx.json \</span>
              <span class="s">--type spdxjson \</span>
              <span class="s">$DIGEST</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This workflow signs the image using GitHub’s OIDC identity. The signature proves that the image was built by this specific GitHub Actions workflow in this specific repository. No keys to manage, rotate, or protect.</p>

<h2 id="verification-in-eks-kyverno-admission-policies">Verification in EKS: Kyverno Admission Policies</h2>

<p>Signing images is only half the equation. The other half is <strong>rejecting unsigned images at deployment time</strong>. Kyverno is a Kubernetes-native policy engine that integrates directly with Cosign for image verification.</p>

<h3 id="installing-kyverno-on-eks">Installing Kyverno on EKS</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="c"># Add the Kyverno Helm repo</span>
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update

<span class="c"># Install Kyverno with admission controller</span>
helm <span class="nb">install </span>kyverno kyverno/kyverno <span class="se">\</span>
    <span class="nt">--namespace</span> kyverno <span class="se">\</span>
    <span class="nt">--create-namespace</span> <span class="se">\</span>
    <span class="nt">--set</span> <span class="nv">replicaCount</span><span class="o">=</span>3 <span class="se">\</span>
    <span class="nt">--set</span> resources.limits.memory<span class="o">=</span>512Mi
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="kyverno-policy-require-cosign-signatures-keyless">Kyverno Policy: Require Cosign Signatures (Keyless)</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
</pre></td><td class="rouge-code"><pre><span class="c1"># kyverno-verify-images.yaml</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kyverno.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterPolicy</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">verify-container-signatures</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">policies.kyverno.io/title</span><span class="pi">:</span> <span class="s">Verify Container Image Signatures</span>
    <span class="na">policies.kyverno.io/description</span><span class="pi">:</span> <span class="pi">&gt;-</span>
      <span class="s">Require all container images to be signed by Cosign with</span>
      <span class="s">a valid keyless signature from our CI/CD pipeline.</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">validationFailureAction</span><span class="pi">:</span> <span class="s">Enforce</span>  <span class="c1"># Block unsigned images</span>
  <span class="na">background</span><span class="pi">:</span> <span class="kc">false</span>
  <span class="na">webhookTimeoutSeconds</span><span class="pi">:</span> <span class="m">30</span>
  <span class="na">rules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">verify-cosign-keyless</span>
      <span class="na">match</span><span class="pi">:</span>
        <span class="na">any</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">resources</span><span class="pi">:</span>
              <span class="na">kinds</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="s">Pod</span>
              <span class="na">namespaces</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="s">production</span>
                <span class="pi">-</span> <span class="s">staging</span>
      <span class="na">verifyImages</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">imageReferences</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="s2">"</span><span class="s">111122223333.dkr.ecr.us-east-1.amazonaws.com/*"</span>
          <span class="na">attestors</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="na">entries</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="na">keyless</span><span class="pi">:</span>
                    <span class="na">issuer</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://token.actions.githubusercontent.com"</span>
                    <span class="na">subject</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://github.com/my-org/*"</span>
                    <span class="na">rekor</span><span class="pi">:</span>
                      <span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://rekor.sigstore.dev"</span>
          <span class="na">attestations</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">spdxjson</span>
              <span class="na">conditions</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="na">all</span><span class="pi">:</span>
                    <span class="pi">-</span> <span class="na">key</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>
                      <span class="na">operator</span><span class="pi">:</span> <span class="s">NotEquals</span>
                      <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="kyverno-policy-require-signatures-with-kms-key">Kyverno Policy: Require Signatures with KMS Key</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
</pre></td><td class="rouge-code"><pre><span class="c1"># kyverno-verify-kms.yaml</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kyverno.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterPolicy</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">verify-kms-signatures</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">validationFailureAction</span><span class="pi">:</span> <span class="s">Enforce</span>
  <span class="na">background</span><span class="pi">:</span> <span class="kc">false</span>
  <span class="na">rules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">verify-cosign-kms</span>
      <span class="na">match</span><span class="pi">:</span>
        <span class="na">any</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">resources</span><span class="pi">:</span>
              <span class="na">kinds</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="s">Pod</span>
              <span class="na">namespaces</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="s">production</span>
      <span class="na">verifyImages</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">imageReferences</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="s2">"</span><span class="s">111122223333.dkr.ecr.us-east-1.amazonaws.com/*"</span>
          <span class="na">attestors</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="na">entries</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="na">keys</span><span class="pi">:</span>
                    <span class="na">kms</span><span class="pi">:</span> <span class="s2">"</span><span class="s">awskms:///arn:aws:kms:us-east-1:111122223333:key/1234abcd"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="testing-the-policy">Testing the Policy</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="c"># Apply the policy</span>
kubectl apply <span class="nt">-f</span> kyverno-verify-images.yaml

<span class="c"># This should succeed (signed image)</span>
kubectl run signed-app <span class="se">\</span>
    <span class="nt">--image</span><span class="o">=</span>111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp@sha256:abc123... <span class="se">\</span>
    <span class="nt">-n</span> production

<span class="c"># This should be REJECTED (unsigned image from Docker Hub)</span>
kubectl run unsigned-app <span class="se">\</span>
    <span class="nt">--image</span><span class="o">=</span>nginx:latest <span class="se">\</span>
    <span class="nt">-n</span> production
<span class="c"># Error: admission webhook "mutate.kyverno.svc-fail" denied the request:</span>
<span class="c"># image verification failed for nginx:latest</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="comparison-cosign-vs-aws-signer-vs-notation">Comparison: Cosign vs AWS Signer vs Notation</h2>

<p>Choosing the right tool depends on your environment and requirements. Here is a detailed comparison:</p>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>Cosign (Sigstore)</th>
      <th>AWS Signer (Managed ECR)</th>
      <th>Notation (CNCF)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Keyless signing</strong></td>
      <td>Yes (Fulcio + OIDC)</td>
      <td>No (AWS-managed keys)</td>
      <td>No (key/cert required)</td>
    </tr>
    <tr>
      <td><strong>Transparency log</strong></td>
      <td>Yes (Rekor, public)</td>
      <td>No (CloudTrail only)</td>
      <td>No (optional, self-hosted)</td>
    </tr>
    <tr>
      <td><strong>Key management</strong></td>
      <td>Optional (KMS support)</td>
      <td>Fully managed</td>
      <td>Self-managed or KMS</td>
    </tr>
    <tr>
      <td><strong>SBOM attestations</strong></td>
      <td>Yes (native)</td>
      <td>No</td>
      <td>Yes (via plugins)</td>
    </tr>
    <tr>
      <td><strong>SLSA provenance</strong></td>
      <td>Yes (native)</td>
      <td>No</td>
      <td>Yes (via plugins)</td>
    </tr>
    <tr>
      <td><strong>Kubernetes integration</strong></td>
      <td>Kyverno, OPA, Connaisseur</td>
      <td>Requires custom webhook</td>
      <td>Ratify (CNCF)</td>
    </tr>
    <tr>
      <td><strong>Multi-cloud portable</strong></td>
      <td>Yes</td>
      <td>AWS only</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td><strong>Signature format</strong></td>
      <td>Cosign (OCI 1.1 referrers)</td>
      <td>Notation (OCI 1.1)</td>
      <td>Notation (OCI 1.1)</td>
    </tr>
    <tr>
      <td><strong>CI/CD integration</strong></td>
      <td>GitHub, GitLab, CircleCI, CodeBuild</td>
      <td>CodeBuild, CodePipeline</td>
      <td>GitHub, GitLab</td>
    </tr>
    <tr>
      <td><strong>Setup complexity</strong></td>
      <td>Low (single binary)</td>
      <td>Very low (API call)</td>
      <td>Medium (plugins required)</td>
    </tr>
    <tr>
      <td><strong>Cost</strong></td>
      <td>Free (public Sigstore)</td>
      <td>Free (included with ECR)</td>
      <td>Free (self-hosted infra)</td>
    </tr>
    <tr>
      <td><strong>Maturity</strong></td>
      <td>GA, used by Kubernetes project</td>
      <td>GA (Nov 2025)</td>
      <td>GA (v1.3.0, Feb 2025)</td>
    </tr>
    <tr>
      <td><strong>Community adoption</strong></td>
      <td>Industry standard</td>
      <td>AWS ecosystem</td>
      <td>Growing (CNCF incubating)</td>
    </tr>
  </tbody>
</table>

<h3 id="when-to-use-each">When to Use Each</h3>

<p><strong>Choose Cosign when:</strong></p>
<ul>
  <li>You need keyless signing (no key management)</li>
  <li>You operate across multiple clouds or registries</li>
  <li>You want transparency logs for audit and compliance</li>
  <li>You need SBOM/provenance attestations</li>
  <li>You are deploying to Kubernetes with Kyverno or OPA</li>
</ul>

<p><strong>Choose AWS Signer when:</strong></p>
<ul>
  <li>You are fully committed to the AWS ecosystem</li>
  <li>You want zero operational overhead for signing</li>
  <li>Your compliance requirements are satisfied by CloudTrail auditing</li>
  <li>You do not need multi-cloud portability</li>
</ul>

<p><strong>Choose Notation when:</strong></p>
<ul>
  <li>You need OCI-standard signatures without Sigstore dependency</li>
  <li>You are using Azure (ACR has native Notation support)</li>
  <li>You want plugin extensibility for custom signature formats</li>
</ul>

<h2 id="attestations-beyond-signatures">Attestations: Beyond Signatures</h2>

<p>Signatures prove <em>who</em> built an image. <strong>Attestations</strong> prove <em>what</em> went into it and <em>how</em> it was built. Cosign supports attaching arbitrary attestations to images in the in-toto format.</p>

<h3 id="sbom-attestation">SBOM Attestation</h3>

<p>Attach a Software Bill of Materials so consumers know exactly what dependencies are in the image:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre></td><td class="rouge-code"><pre><span class="c"># Generate SBOM with Syft</span>
syft 111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp@sha256:abc123 <span class="se">\</span>
    <span class="nt">-o</span> spdx-json <span class="o">&gt;</span> sbom.spdx.json

<span class="c"># Attest the SBOM to the image</span>
cosign attest <span class="nt">--yes</span> <span class="se">\</span>
    <span class="nt">--predicate</span> sbom.spdx.json <span class="se">\</span>
    <span class="nt">--type</span> spdxjson <span class="se">\</span>
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp@sha256:abc123

<span class="c"># Verify the SBOM attestation</span>
cosign verify-attestation <span class="se">\</span>
    <span class="nt">--certificate-oidc-issuer</span> https://token.actions.githubusercontent.com <span class="se">\</span>
    <span class="nt">--certificate-identity-regexp</span> <span class="s2">"https://github.com/my-org/.*"</span> <span class="se">\</span>
    <span class="nt">--type</span> spdxjson <span class="se">\</span>
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp@sha256:abc123
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="vulnerability-scan-attestation">Vulnerability Scan Attestation</h3>

<p>Attest that the image passed a vulnerability scan at build time:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="c"># Generate Trivy vulnerability report in Cosign format</span>
trivy image <span class="nt">--format</span> cosign-vuln <span class="se">\</span>
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp@sha256:abc123 <span class="se">\</span>
    <span class="o">&gt;</span> vuln-report.json

<span class="c"># Attest the vulnerability scan results</span>
cosign attest <span class="nt">--yes</span> <span class="se">\</span>
    <span class="nt">--predicate</span> vuln-report.json <span class="se">\</span>
    <span class="nt">--type</span> vuln <span class="se">\</span>
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp@sha256:abc123
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="slsa-provenance-attestation">SLSA Provenance Attestation</h3>

<p>SLSA (Supply-chain Levels for Software Artifacts) provenance documents the build process:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
</pre></td><td class="rouge-code"><pre><span class="c"># Generate SLSA provenance (typically done by the build system)</span>
<span class="nb">cat</span> <span class="o">&gt;</span> provenance.json <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
{
  "buildType": "https://github.com/my-org/myapp-build@v1",
  "builder": {
    "id": "https://github.com/my-org/myapp/actions/runs/12345"
  },
  "invocation": {
    "configSource": {
      "uri": "git+https://github.com/my-org/myapp@refs/heads/main",
      "digest": { "sha1": "abc123def456" },
      "entryPoint": ".github/workflows/build-sign.yml"
    }
  },
  "materials": [
    {
      "uri": "git+https://github.com/my-org/myapp",
      "digest": { "sha256": "abc123..." }
    }
  ]
}
</span><span class="no">EOF

</span><span class="c"># Attest provenance</span>
cosign attest <span class="nt">--yes</span> <span class="se">\</span>
    <span class="nt">--predicate</span> provenance.json <span class="se">\</span>
    <span class="nt">--type</span> slsaprovenance <span class="se">\</span>
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp@sha256:abc123
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="complete-pipeline-build-sign-attest-deploy">Complete Pipeline: Build, Sign, Attest, Deploy</h2>

<p>Here is the complete end-to-end flow that ties everything together. This represents a zero-trust artifact pipeline where every image must be signed and attested before it can run in production.</p>

<p><img src="/assets/img/container-signing-cosign-sigstore-aws.png" alt="Container Image Signing and Verification Pipeline" class="shadow" w="800" />
<em>End-to-end container signing pipeline: Build in CodeBuild, sign with Cosign via Fulcio/Rekor, push to ECR, verify with Kyverno, deploy to EKS</em></p>

<h3 id="pipeline-stages">Pipeline Stages</h3>

<p><strong>Stage 1: Build and Scan</strong></p>
<ul>
  <li>CodeBuild (or GitHub Actions) builds the container image from source</li>
  <li>Trivy scans for vulnerabilities; CRITICAL/HIGH findings fail the build</li>
  <li>Syft generates an SBOM in SPDX format</li>
</ul>

<p><strong>Stage 2: Sign and Attest</strong></p>
<ul>
  <li>Cosign signs the image digest (keyless via OIDC or KMS-backed key)</li>
  <li>Signature is recorded in Rekor transparency log</li>
  <li>SBOM, vulnerability scan, and SLSA provenance are attached as attestations</li>
</ul>

<p><strong>Stage 3: Push to Registry</strong></p>
<ul>
  <li>Signed image and all attestation artifacts are pushed to ECR</li>
  <li>ECR immutable tags prevent overwriting signed images with unsigned ones</li>
</ul>

<p><strong>Stage 4: Verify at Admission</strong></p>
<ul>
  <li>Deployment targets EKS with Kyverno admission controller</li>
  <li>Kyverno verifies Cosign signature against expected OIDC issuer/subject</li>
  <li>Kyverno optionally verifies SBOM and vulnerability attestations</li>
  <li>Unsigned or unverified images are rejected with a clear error message</li>
</ul>

<p><strong>Stage 5: Runtime Monitoring</strong></p>
<ul>
  <li>Falco or GuardDuty monitors for container runtime anomalies</li>
  <li>Drift detection alerts if running container content differs from signed image</li>
</ul>

<h3 id="implementation-roadmap">Implementation Roadmap</h3>

<h4 id="phase-1-foundation-weeks-1-2">Phase 1: Foundation (Weeks 1-2)</h4>
<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Install Cosign in CI/CD environment (CodeBuild or GitHub Actions)</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Configure ECR repositories with immutable tags</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Implement key-pair signing with AWS KMS as a starting point</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Sign images in non-production pipelines first</li>
</ul>

<h4 id="phase-2-keyless-and-attestations-weeks-3-4">Phase 2: Keyless and Attestations (Weeks 3-4)</h4>
<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Migrate from KMS signing to keyless signing with Fulcio</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Add SBOM generation and attestation to the build pipeline</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Add vulnerability scan attestation</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Deploy Kyverno to EKS in Audit mode (log but do not block)</li>
</ul>

<h4 id="phase-3-enforcement-weeks-5-6">Phase 3: Enforcement (Weeks 5-6)</h4>
<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Switch Kyverno to Enforce mode in staging</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Validate all production images are signed before enabling enforcement</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Switch Kyverno to Enforce mode in production</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Set up alerting for signature verification failures</li>
</ul>

<h4 id="phase-4-advanced-ongoing">Phase 4: Advanced (Ongoing)</h4>
<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Add SLSA provenance attestations</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Implement Rekor monitoring for unexpected signing events</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Extend signing to Helm charts and other OCI artifacts</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Evaluate Cosign policy-controller for additional policy capabilities</li>
</ul>

<h2 id="related-articles">Related Articles</h2>

<ul>
  <li><a href="/posts/ai-supply-chain-security-model-poisoning-defense/">AI Supply Chain Security: Model Poisoning Defense</a></li>
  <li><a href="/posts/aws-devsecops-pipeline-security-automation-implementation/">AWS DevSecOps Pipeline Security Automation</a></li>
  <li><a href="/posts/policy-as-code-opa-kyverno-eks-security/">OPA vs Kyverno on EKS: Policy-as-Code Security</a></li>
  <li><a href="/posts/implementing-zero-trust-on-aws/">Implementing Zero Trust on AWS</a></li>
  <li><a href="/posts/security-for-container-based-envs/">Security for Container-Based Environments</a></li>
</ul>

<h2 id="additional-resources">Additional Resources</h2>

<h3 id="official-documentation">Official Documentation</h3>
<ul>
  <li><a href="https://docs.sigstore.dev/">Sigstore Documentation</a> – Cosign, Fulcio, and Rekor guides</li>
  <li><a href="https://github.com/sigstore/cosign">Cosign GitHub Repository</a> – source code and releases</li>
  <li><a href="https://docs.aws.amazon.com/AmazonECR/latest/userguide/managed-signing.html">AWS ECR Managed Signing</a> – AWS documentation</li>
  <li><a href="https://kyverno.io/docs/policy-types/image-validating-policy/">Kyverno Image Verification</a> – admission policy docs</li>
  <li><a href="https://notaryproject.dev/">Notation Project (CNCF)</a> – alternative signing tool</li>
</ul>

<h3 id="tools-and-frameworks">Tools and Frameworks</h3>
<ul>
  <li><a href="https://github.com/anchore/syft">Syft</a> – SBOM generation for container images</li>
  <li><a href="https://github.com/aquasecurity/trivy">Trivy</a> – vulnerability scanner with Cosign integration</li>
  <li><a href="https://slsa.dev/">SLSA Framework</a> – Supply-chain Levels for Software Artifacts</li>
  <li><a href="https://www.chainguard.dev/">Chainguard Images</a> – pre-signed, minimal container base images</li>
</ul>

<h3 id="industry-resources">Industry Resources</h3>
<ul>
  <li><a href="https://openssf.org/blog/2024/02/16/scaling-up-supply-chain-security-implementing-sigstore-for-seamless-container-image-signing/">OpenSSF: Scaling Up Supply Chain Security with Sigstore</a></li>
  <li><a href="https://www.cncf.io/blog/2025/12/15/kubernetes-security-2025-stable-features-and-2026-preview/">CNCF: Kubernetes Security 2025 Stable Features</a></li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>Container image signing is no longer optional. With supply chain attacks increasing in frequency and sophistication, cryptographic verification of every artifact in your pipeline is a baseline security requirement.</p>

<p>The Sigstore ecosystem, with Cosign at its center, provides the most flexible and secure approach to container signing available today. Keyless signing with Fulcio eliminates the operational burden of key management. Rekor transparency logs provide tamper-evident audit trails that no proprietary solution can match. And with Kyverno enforcement in EKS, you can guarantee that only verified images run in your clusters.</p>

<p>AWS Signer with managed ECR signing is a solid choice for teams that want zero operational overhead within a pure-AWS environment. But for teams that value portability, transparency, and the ability to attach rich attestations (SBOMs, vulnerability scans, SLSA provenance), Cosign is the clear winner.</p>

<p>Start with signing. Add attestations. Enforce verification. That is how you zero-trust your artifact pipeline.</p>

<p>For personalized guidance on implementing container signing and supply chain security in your AWS environment, <a href="https://www.linkedin.com/in/jonpricelinux/">connect with Jon Price on LinkedIn</a>.</p>]]></content><author><name>Jon (JR) Price</name></author><category term="AWS" /><category term="Security" /><category term="container-signing" /><category term="cosign" /><category term="ecr" /><category term="image-verification" /><category term="open-source" /><category term="sigstore" /><category term="supply-chain-security" /><category term="zero-trust" /><summary type="html"><![CDATA[Implement zero-trust container artifact pipelines with Cosign keyless signing on AWS ECR. Compare with AWS Signer and implement verification in EKS.]]></summary></entry><entry><title type="html">Event-Driven Security Automation: EventBridge + Step Functions with Open-Source Threat Intel</title><link href="https://www.red-team.sh/posts/event-driven-security-automation-eventbridge-misp/" rel="alternate" type="text/html" title="Event-Driven Security Automation: EventBridge + Step Functions with Open-Source Threat Intel" /><published>2026-04-15T09:00:00-07:00</published><updated>2026-04-15T09:00:00-07:00</updated><id>https://www.red-team.sh/posts/event-driven-security-automation-eventbridge-misp</id><content type="html" xml:base="https://www.red-team.sh/posts/event-driven-security-automation-eventbridge-misp/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#introduction" id="markdown-toc-introduction">Introduction</a></li>
  <li><a href="#event-driven-security-architecture-overview" id="markdown-toc-event-driven-security-architecture-overview">Event-Driven Security Architecture Overview</a></li>
  <li><a href="#aws-eventbridge-for-security-events" id="markdown-toc-aws-eventbridge-for-security-events">AWS EventBridge for Security Events</a>    <ul>
      <li><a href="#creating-a-dedicated-security-event-bus" id="markdown-toc-creating-a-dedicated-security-event-bus">Creating a Dedicated Security Event Bus</a></li>
      <li><a href="#eventbridge-rule-patterns-for-security-events" id="markdown-toc-eventbridge-rule-patterns-for-security-events">EventBridge Rule Patterns for Security Events</a></li>
      <li><a href="#cross-account-event-forwarding" id="markdown-toc-cross-account-event-forwarding">Cross-Account Event Forwarding</a></li>
    </ul>
  </li>
  <li><a href="#step-functions-for-orchestrated-incident-response" id="markdown-toc-step-functions-for-orchestrated-incident-response">Step Functions for Orchestrated Incident Response</a>    <ul>
      <li><a href="#state-machine-design-for-security-incidents" id="markdown-toc-state-machine-design-for-security-incidents">State Machine Design for Security Incidents</a></li>
    </ul>
  </li>
  <li><a href="#lambda-functions-for-enrichment-and-response" id="markdown-toc-lambda-functions-for-enrichment-and-response">Lambda Functions for Enrichment and Response</a>    <ul>
      <li><a href="#misp-enrichment-function" id="markdown-toc-misp-enrichment-function">MISP Enrichment Function</a></li>
      <li><a href="#opencti-enrichment-function" id="markdown-toc-opencti-enrichment-function">OpenCTI Enrichment Function</a></li>
      <li><a href="#automated-ip-blocking-function" id="markdown-toc-automated-ip-blocking-function">Automated IP Blocking Function</a></li>
    </ul>
  </li>
  <li><a href="#misp-threat-intelligence-integration" id="markdown-toc-misp-threat-intelligence-integration">MISP Threat Intelligence Integration</a>    <ul>
      <li><a href="#deploying-misp-on-aws" id="markdown-toc-deploying-misp-on-aws">Deploying MISP on AWS</a></li>
      <li><a href="#misp-feed-integration-script" id="markdown-toc-misp-feed-integration-script">MISP Feed Integration Script</a></li>
    </ul>
  </li>
  <li><a href="#opencti-as-threat-intel-management-layer" id="markdown-toc-opencti-as-threat-intel-management-layer">OpenCTI as Threat Intel Management Layer</a></li>
  <li><a href="#sigma-rules-detection-as-code-that-works-anywhere" id="markdown-toc-sigma-rules-detection-as-code-that-works-anywhere">Sigma Rules: Detection-as-Code That Works Anywhere</a>    <ul>
      <li><a href="#sigma-rule-examples-for-aws-cloudtrail" id="markdown-toc-sigma-rule-examples-for-aws-cloudtrail">Sigma Rule Examples for AWS CloudTrail</a></li>
      <li><a href="#sigma-rule-evaluator-lambda" id="markdown-toc-sigma-rule-evaluator-lambda">Sigma Rule Evaluator Lambda</a></li>
    </ul>
  </li>
  <li><a href="#complete-pipeline-end-to-end-automated-response" id="markdown-toc-complete-pipeline-end-to-end-automated-response">Complete Pipeline: End-to-End Automated Response</a>    <ul>
      <li><a href="#scenario-compromised-iam-credentials" id="markdown-toc-scenario-compromised-iam-credentials">Scenario: Compromised IAM Credentials</a></li>
      <li><a href="#scenario-unauthorized-api-reconnaissance" id="markdown-toc-scenario-unauthorized-api-reconnaissance">Scenario: Unauthorized API Reconnaissance</a></li>
      <li><a href="#scenario-s3-bucket-policy-made-public" id="markdown-toc-scenario-s3-bucket-policy-made-public">Scenario: S3 Bucket Policy Made Public</a></li>
    </ul>
  </li>
  <li><a href="#terraform-deploying-the-complete-pipeline" id="markdown-toc-terraform-deploying-the-complete-pipeline">Terraform: Deploying the Complete Pipeline</a></li>
  <li><a href="#security-lake-integration-for-long-term-analytics" id="markdown-toc-security-lake-integration-for-long-term-analytics">Security Lake Integration for Long-Term Analytics</a>    <ul>
      <li><a href="#writing-automation-results-to-security-lake" id="markdown-toc-writing-automation-results-to-security-lake">Writing Automation Results to Security Lake</a></li>
      <li><a href="#querying-security-lake-with-athena" id="markdown-toc-querying-security-lake-with-athena">Querying Security Lake with Athena</a></li>
    </ul>
  </li>
  <li><a href="#monitoring-and-tuning-your-automation" id="markdown-toc-monitoring-and-tuning-your-automation">Monitoring and Tuning Your Automation</a>    <ul>
      <li><a href="#key-metrics-to-track" id="markdown-toc-key-metrics-to-track">Key Metrics to Track</a></li>
      <li><a href="#cloudwatch-dashboard" id="markdown-toc-cloudwatch-dashboard">CloudWatch Dashboard</a></li>
      <li><a href="#tuning-your-sigma-rules" id="markdown-toc-tuning-your-sigma-rules">Tuning Your Sigma Rules</a></li>
      <li><a href="#misp-feed-maintenance" id="markdown-toc-misp-feed-maintenance">MISP Feed Maintenance</a></li>
    </ul>
  </li>
  <li><a href="#implementation-roadmap" id="markdown-toc-implementation-roadmap">Implementation Roadmap</a></li>
  <li><a href="#related-articles" id="markdown-toc-related-articles">Related Articles</a></li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ul>

<h2 id="introduction">Introduction</h2>

<p>The gap between threat detection and effective response remains one of the most dangerous vulnerabilities in cloud security. According to industry data, the average mean time to respond (MTTR) to a cloud security incident still hovers around 277 days, giving adversaries a massive window to move laterally, exfiltrate data, and establish persistence. Organizations experienced an average of 1,925 attacks per week in early 2025, with cloud intrusion attempts jumping 75% year-over-year. The message is clear: manual incident response cannot keep pace.</p>

<p>Event-driven security automation changes the equation. Instead of waiting for a human analyst to triage an alert, review context, and decide on a response, you build pipelines that react in seconds. AWS EventBridge captures security events as they happen. Step Functions orchestrate multi-step response workflows with built-in error handling and human approval gates. And open-source threat intelligence platforms like MISP and OpenCTI enrich every alert with real-world context – is this IP part of a known botnet? Does this behavior match a documented APT campaign?</p>

<p>This guide walks you through building a complete event-driven security automation pipeline on AWS, enriched by open-source threat intelligence. You will deploy EventBridge rules that catch GuardDuty findings, Security Hub alerts, and CloudTrail anomalies. You will wire them into Step Functions state machines that orchestrate enrichment, evaluation, and response. And you will integrate MISP, OpenCTI, and Sigma rules so your automation is powered by community-driven intelligence rather than locked into a single vendor.</p>

<p>The approach follows the red-team.sh philosophy: build it yourself with open source for maximum freedom and flexibility. AWS-native services provide the event backbone and orchestration. Open-source tools provide the intelligence and detection logic that you own, control, and can take anywhere.</p>

<h2 id="event-driven-security-architecture-overview">Event-Driven Security Architecture Overview</h2>

<p><img src="/assets/img/diagrams/event-driven-security-automation-aws.png" alt="Event-Driven Security Automation Architecture" class="shadow" w="800" />
<em>Event-driven security automation pipeline: security sources to EventBridge to Step Functions with MISP enrichment and automated response</em></p>

<p>The architecture follows a straightforward event-driven pattern with four stages:</p>

<p><strong>1. Event Sources</strong> – AWS security services generate structured findings and events. GuardDuty produces threat findings. Security Hub aggregates and normalizes findings from multiple services. CloudTrail logs every API call. AWS Config tracks resource configuration changes.</p>

<p><strong>2. Event Bus</strong> – Amazon EventBridge acts as the central nervous system. Rules match specific event patterns and route them to the appropriate response workflow. A dedicated custom event bus isolates security events from application events.</p>

<p><strong>3. Orchestration</strong> – AWS Step Functions state machines coordinate the response. Each workflow follows a consistent pattern: classify the event, enrich it with threat intelligence, evaluate it against detection rules, decide on a response action, execute that action, and log everything.</p>

<p><strong>4. Intelligence and Response</strong> – Lambda functions query MISP and OpenCTI for threat context, evaluate Sigma rules against the event data, and execute response actions like WAF updates, security group modifications, or IAM policy revocations. All events flow to Security Lake in OCSF format for long-term analytics.</p>

<p>This separation of concerns makes the system maintainable. You can update detection rules without touching response logic. You can add new event sources without modifying existing workflows. You can swap enrichment providers without rebuilding the pipeline.</p>

<h2 id="aws-eventbridge-for-security-events">AWS EventBridge for Security Events</h2>

<p>EventBridge is the foundation of event-driven security on AWS. Every AWS security service publishes events to the default event bus. Your job is to write rules that match the events you care about and route them to your automation.</p>

<h3 id="creating-a-dedicated-security-event-bus">Creating a Dedicated Security Event Bus</h3>

<p>Isolating security events on a custom event bus prevents interference with application event processing and provides a clear boundary for IAM policies:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"security-automation-bus"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Dedicated event bus for security automation workflows"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Tags"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w"> </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Application"</span><span class="p">,</span><span class="w"> </span><span class="nl">"Value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"security-automation"</span><span class="w"> </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w"> </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Environment"</span><span class="p">,</span><span class="w"> </span><span class="nl">"Value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"production"</span><span class="w"> </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w"> </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Owner"</span><span class="p">,</span><span class="w"> </span><span class="nl">"Value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"security-team"</span><span class="w"> </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="eventbridge-rule-patterns-for-security-events">EventBridge Rule Patterns for Security Events</h3>

<p>The power of EventBridge lies in its pattern matching. You define JSON patterns that filter events based on any field in the event payload. Here are essential security event patterns:</p>

<p><strong>GuardDuty High-Severity Findings:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"aws.guardduty"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"detail-type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"GuardDuty Finding"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"detail"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"severity"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w"> </span><span class="nl">"numeric"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"&gt;="</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">]</span><span class="w"> </span><span class="p">}],</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
      </span><span class="nl">"prefix"</span><span class="p">:</span><span class="w"> </span><span class="s2">"UnauthorizedAccess"</span><span class="w">
    </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"prefix"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Recon"</span><span class="w">
    </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"prefix"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Trojan"</span><span class="w">
    </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"prefix"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CryptoCurrency"</span><span class="w">
    </span><span class="p">}]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Security Hub Critical and High Findings:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"aws.securityhub"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"detail-type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"Security Hub Findings - Imported"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"detail"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"findings"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Severity"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"Label"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"CRITICAL"</span><span class="p">,</span><span class="w"> </span><span class="s2">"HIGH"</span><span class="p">]</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"Workflow"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"Status"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"NEW"</span><span class="p">]</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"RecordState"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ACTIVE"</span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>CloudTrail Suspicious API Calls:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"aws.cloudtrail"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"detail-type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"AWS API Call via CloudTrail"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"detail"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"eventName"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="s2">"StopLogging"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"DeleteTrail"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"UpdateTrail"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"DisableKey"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"DeleteFlowLogs"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"DeleteDetector"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"DisableGuardDuty"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"PutBucketPolicy"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"PutBucketAcl"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"AuthorizeSecurityGroupIngress"</span><span class="w">
    </span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>AWS Config Non-Compliant Resource Changes:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"aws.config"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"detail-type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"Config Rules Compliance Change"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"detail"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"messageType"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ComplianceChangeNotification"</span><span class="p">],</span><span class="w">
    </span><span class="nl">"newEvaluationResult"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"complianceType"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"NON_COMPLIANT"</span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="cross-account-event-forwarding">Cross-Account Event Forwarding</h3>

<p>In multi-account AWS Organizations setups, forward security events from member accounts to a central security account:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"Version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2012-10-17"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Statement"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
    </span><span class="nl">"Sid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AllowSecurityAccountPutEvents"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"Effect"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Allow"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"Principal"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"AWS"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:iam::SECURITY_ACCOUNT_ID:root"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"Action"</span><span class="p">:</span><span class="w"> </span><span class="s2">"events:PutEvents"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:events:us-east-1:SECURITY_ACCOUNT_ID:event-bus/security-automation-bus"</span><span class="w">
  </span><span class="p">}]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="step-functions-for-orchestrated-incident-response">Step Functions for Orchestrated Incident Response</h2>

<p>EventBridge gets events to the right place. Step Functions decide what happens next. The state machine pattern is ideal for security workflows because it provides built-in retry logic, error handling, parallel execution, human approval gates, and a complete audit trail of every decision.</p>

<h3 id="state-machine-design-for-security-incidents">State Machine Design for Security Incidents</h3>

<p>The following ASL (Amazon States Language) definition implements a complete incident response workflow. It classifies the incoming event, enriches it with threat intelligence, evaluates it against Sigma rules, decides on the response severity, executes the appropriate actions, and logs everything to Security Lake:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"Comment"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Security Incident Response Orchestrator"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"StartAt"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ClassifyEvent"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"States"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"ClassifyEvent"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Task"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:lambda:us-east-1:ACCOUNT_ID:function:classify-security-event"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"ResultPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$.classification"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Next"</span><span class="p">:</span><span class="w"> </span><span class="s2">"EnrichParallel"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Retry"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
        </span><span class="nl">"ErrorEquals"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"States.TaskFailed"</span><span class="p">],</span><span class="w">
        </span><span class="nl">"IntervalSeconds"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
        </span><span class="nl">"MaxAttempts"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
        </span><span class="nl">"BackoffRate"</span><span class="p">:</span><span class="w"> </span><span class="mf">2.0</span><span class="w">
      </span><span class="p">}],</span><span class="w">
      </span><span class="nl">"Catch"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
        </span><span class="nl">"ErrorEquals"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"States.ALL"</span><span class="p">],</span><span class="w">
        </span><span class="nl">"Next"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LogFailure"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"ResultPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$.error"</span><span class="w">
      </span><span class="p">}]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"EnrichParallel"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Parallel"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"ResultPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$.enrichment"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Next"</span><span class="p">:</span><span class="w"> </span><span class="s2">"EvaluateSigmaRules"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Branches"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"StartAt"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MISPEnrichment"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"States"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"MISPEnrichment"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
              </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Task"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:lambda:us-east-1:ACCOUNT_ID:function:misp-enrichment"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"End"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
              </span><span class="nl">"Retry"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
                </span><span class="nl">"ErrorEquals"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"States.TaskFailed"</span><span class="p">],</span><span class="w">
                </span><span class="nl">"IntervalSeconds"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
                </span><span class="nl">"MaxAttempts"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
                </span><span class="nl">"BackoffRate"</span><span class="p">:</span><span class="w"> </span><span class="mf">2.0</span><span class="w">
              </span><span class="p">}]</span><span class="w">
            </span><span class="p">}</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"StartAt"</span><span class="p">:</span><span class="w"> </span><span class="s2">"OpenCTIEnrichment"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"States"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"OpenCTIEnrichment"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
              </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Task"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:lambda:us-east-1:ACCOUNT_ID:function:opencti-enrichment"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"End"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
              </span><span class="nl">"Retry"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
                </span><span class="nl">"ErrorEquals"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"States.TaskFailed"</span><span class="p">],</span><span class="w">
                </span><span class="nl">"IntervalSeconds"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
                </span><span class="nl">"MaxAttempts"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
                </span><span class="nl">"BackoffRate"</span><span class="p">:</span><span class="w"> </span><span class="mf">2.0</span><span class="w">
              </span><span class="p">}]</span><span class="w">
            </span><span class="p">}</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"Catch"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
        </span><span class="nl">"ErrorEquals"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"States.ALL"</span><span class="p">],</span><span class="w">
        </span><span class="nl">"Next"</span><span class="p">:</span><span class="w"> </span><span class="s2">"EvaluateSigmaRules"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"ResultPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$.enrichmentError"</span><span class="w">
      </span><span class="p">}]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"EvaluateSigmaRules"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Task"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:lambda:us-east-1:ACCOUNT_ID:function:sigma-rule-evaluator"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"ResultPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$.sigmaResults"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Next"</span><span class="p">:</span><span class="w"> </span><span class="s2">"DetermineResponseLevel"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"DetermineResponseLevel"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Choice"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Choices"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"And"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
            </span><span class="p">{</span><span class="w"> </span><span class="nl">"Variable"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$.classification.severity"</span><span class="p">,</span><span class="w"> </span><span class="nl">"StringEquals"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CRITICAL"</span><span class="w"> </span><span class="p">},</span><span class="w">
            </span><span class="p">{</span><span class="w"> </span><span class="nl">"Variable"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$.enrichment[0].iocMatch"</span><span class="p">,</span><span class="w"> </span><span class="nl">"BooleanEquals"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="p">}</span><span class="w">
          </span><span class="p">],</span><span class="w">
          </span><span class="nl">"Next"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AutomatedCriticalResponse"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"Variable"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$.classification.severity"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"StringEquals"</span><span class="p">:</span><span class="w"> </span><span class="s2">"HIGH"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"Next"</span><span class="p">:</span><span class="w"> </span><span class="s2">"HumanApprovalRequired"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"Default"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LogAndMonitor"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"AutomatedCriticalResponse"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Parallel"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Next"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LogToSecurityLake"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Branches"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"StartAt"</span><span class="p">:</span><span class="w"> </span><span class="s2">"BlockIP"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"States"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"BlockIP"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
              </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Task"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:lambda:us-east-1:ACCOUNT_ID:function:block-ip-waf"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"End"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
            </span><span class="p">}</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"StartAt"</span><span class="p">:</span><span class="w"> </span><span class="s2">"IsolateResource"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"States"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"IsolateResource"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
              </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Task"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:lambda:us-east-1:ACCOUNT_ID:function:isolate-resource"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"End"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
            </span><span class="p">}</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"StartAt"</span><span class="p">:</span><span class="w"> </span><span class="s2">"NotifySecurityTeam"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"States"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"NotifySecurityTeam"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
              </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Task"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:lambda:us-east-1:ACCOUNT_ID:function:notify-security-team"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"End"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
            </span><span class="p">}</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"HumanApprovalRequired"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Task"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:states:::sqs:sendMessage.waitForTaskToken"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Parameters"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"QueueUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://sqs.us-east-1.amazonaws.com/ACCOUNT_ID/security-approval-queue"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"MessageBody"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"taskToken.$"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$$.Task.Token"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"incident.$"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Security incident requires human approval before automated response"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"TimeoutSeconds"</span><span class="p">:</span><span class="w"> </span><span class="mi">3600</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Next"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AutomatedCriticalResponse"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Catch"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
        </span><span class="nl">"ErrorEquals"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"States.Timeout"</span><span class="p">],</span><span class="w">
        </span><span class="nl">"Next"</span><span class="p">:</span><span class="w"> </span><span class="s2">"EscalateTimeout"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"ResultPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$.timeoutError"</span><span class="w">
      </span><span class="p">}]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"EscalateTimeout"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Task"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:lambda:us-east-1:ACCOUNT_ID:function:escalate-timeout"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Next"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LogToSecurityLake"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"LogAndMonitor"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Task"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:lambda:us-east-1:ACCOUNT_ID:function:log-and-monitor"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Next"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LogToSecurityLake"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"LogToSecurityLake"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Task"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:lambda:us-east-1:ACCOUNT_ID:function:log-to-security-lake"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"End"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"LogFailure"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Task"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:lambda:us-east-1:ACCOUNT_ID:function:log-failure"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"End"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>The key design decisions in this state machine:</p>

<ul>
  <li><strong>Parallel enrichment</strong> – MISP and OpenCTI are queried simultaneously, cutting enrichment time in half.</li>
  <li><strong>Graceful degradation</strong> – If enrichment fails, the workflow continues with what it has rather than stopping entirely.</li>
  <li><strong>Tiered response</strong> – Critical events with confirmed IOC matches get automated response. High-severity events without confirmed IOCs require human approval. Everything else is logged and monitored.</li>
  <li><strong>Approval timeout</strong> – If no human responds within an hour, the workflow escalates rather than letting the incident sit.</li>
</ul>

<h2 id="lambda-functions-for-enrichment-and-response">Lambda Functions for Enrichment and Response</h2>

<p>Each Lambda function in the pipeline has a single responsibility. This makes them independently testable, deployable, and replaceable.</p>

<h3 id="misp-enrichment-function">MISP Enrichment Function</h3>

<p>This Lambda function takes security event indicators (IP addresses, domains, file hashes) and queries your MISP instance for matching threat intelligence:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="n">json</span>
<span class="kn">import</span> <span class="n">os</span>
<span class="kn">import</span> <span class="n">logging</span>
<span class="kn">from</span> <span class="n">typing</span> <span class="kn">import</span> <span class="n">Any</span>
<span class="kn">from</span> <span class="n">urllib.parse</span> <span class="kn">import</span> <span class="n">urljoin</span>

<span class="kn">import</span> <span class="n">boto3</span>
<span class="kn">import</span> <span class="n">urllib3</span>

<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="p">.</span><span class="nf">getLogger</span><span class="p">()</span>
<span class="n">logger</span><span class="p">.</span><span class="nf">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="p">.</span><span class="n">INFO</span><span class="p">)</span>

<span class="n">http</span> <span class="o">=</span> <span class="n">urllib3</span><span class="p">.</span><span class="nc">PoolManager</span><span class="p">()</span>

<span class="n">MISP_URL</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">[</span><span class="sh">"</span><span class="s">MISP_URL</span><span class="sh">"</span><span class="p">]</span>
<span class="n">MISP_API_KEY_PARAM</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">[</span><span class="sh">"</span><span class="s">MISP_API_KEY_PARAM</span><span class="sh">"</span><span class="p">]</span>

<span class="n">ssm</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="nf">client</span><span class="p">(</span><span class="sh">"</span><span class="s">ssm</span><span class="sh">"</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">get_misp_api_key</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Retrieve MISP API key from SSM Parameter Store.</span><span class="sh">"""</span>
    <span class="n">response</span> <span class="o">=</span> <span class="n">ssm</span><span class="p">.</span><span class="nf">get_parameter</span><span class="p">(</span>
        <span class="n">Name</span><span class="o">=</span><span class="n">MISP_API_KEY_PARAM</span><span class="p">,</span>
        <span class="n">WithDecryption</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="k">return</span> <span class="n">response</span><span class="p">[</span><span class="sh">"</span><span class="s">Parameter</span><span class="sh">"</span><span class="p">][</span><span class="sh">"</span><span class="s">Value</span><span class="sh">"</span><span class="p">]</span>


<span class="k">def</span> <span class="nf">extract_indicators</span><span class="p">(</span><span class="n">event</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]]:</span>
    <span class="sh">"""</span><span class="s">Extract IOCs from security event for MISP lookup.</span><span class="sh">"""</span>
    <span class="n">indicators</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">ip_addresses</span><span class="sh">"</span><span class="p">:</span> <span class="p">[],</span>
        <span class="sh">"</span><span class="s">domains</span><span class="sh">"</span><span class="p">:</span> <span class="p">[],</span>
        <span class="sh">"</span><span class="s">file_hashes</span><span class="sh">"</span><span class="p">:</span> <span class="p">[],</span>
    <span class="p">}</span>

    <span class="n">detail</span> <span class="o">=</span> <span class="n">event</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">detail</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>

    <span class="c1"># GuardDuty finding indicators
</span>    <span class="k">if</span> <span class="sh">"</span><span class="s">service</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">detail</span><span class="p">:</span>
        <span class="n">action</span> <span class="o">=</span> <span class="n">detail</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">service</span><span class="sh">"</span><span class="p">,</span> <span class="p">{}).</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">action</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>

        <span class="c1"># Network connection IPs
</span>        <span class="n">network_info</span> <span class="o">=</span> <span class="n">action</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">networkConnectionAction</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
        <span class="n">remote_ip</span> <span class="o">=</span> <span class="p">(</span>
            <span class="n">network_info</span>
            <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">remoteIpDetails</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
            <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">ipAddressV4</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">)</span>
        <span class="p">)</span>
        <span class="k">if</span> <span class="n">remote_ip</span><span class="p">:</span>
            <span class="n">indicators</span><span class="p">[</span><span class="sh">"</span><span class="s">ip_addresses</span><span class="sh">"</span><span class="p">].</span><span class="nf">append</span><span class="p">(</span><span class="n">remote_ip</span><span class="p">)</span>

        <span class="c1"># DNS query domains
</span>        <span class="n">dns_action</span> <span class="o">=</span> <span class="n">action</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">dnsRequestAction</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
        <span class="n">domain</span> <span class="o">=</span> <span class="n">dns_action</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">domain</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">domain</span><span class="p">:</span>
            <span class="n">indicators</span><span class="p">[</span><span class="sh">"</span><span class="s">domains</span><span class="sh">"</span><span class="p">].</span><span class="nf">append</span><span class="p">(</span><span class="n">domain</span><span class="p">)</span>

    <span class="c1"># CloudTrail source IP
</span>    <span class="n">source_ip</span> <span class="o">=</span> <span class="n">detail</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">sourceIPAddress</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">source_ip</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">source_ip</span><span class="p">.</span><span class="nf">endswith</span><span class="p">(</span><span class="sh">"</span><span class="s">.amazonaws.com</span><span class="sh">"</span><span class="p">):</span>
        <span class="n">indicators</span><span class="p">[</span><span class="sh">"</span><span class="s">ip_addresses</span><span class="sh">"</span><span class="p">].</span><span class="nf">append</span><span class="p">(</span><span class="n">source_ip</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">indicators</span>


<span class="k">def</span> <span class="nf">search_misp</span><span class="p">(</span><span class="n">api_key</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">indicator_type</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Search MISP for a specific indicator.</span><span class="sh">"""</span>
    <span class="n">url</span> <span class="o">=</span> <span class="nf">urljoin</span><span class="p">(</span><span class="n">MISP_URL</span><span class="p">,</span> <span class="sh">"</span><span class="s">/attributes/restSearch</span><span class="sh">"</span><span class="p">)</span>
    <span class="n">headers</span> <span class="o">=</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">Authorization</span><span class="sh">"</span><span class="p">:</span> <span class="n">api_key</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">Content-Type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">application/json</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">Accept</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">application/json</span><span class="sh">"</span><span class="p">,</span>
    <span class="p">}</span>

    <span class="n">type_mapping</span> <span class="o">=</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">ip_addresses</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">ip-dst</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">domains</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">domain</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">file_hashes</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">md5</span><span class="sh">"</span><span class="p">,</span>
    <span class="p">}</span>

    <span class="n">payload</span> <span class="o">=</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">returnFormat</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">json</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="n">type_mapping</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">indicator_type</span><span class="p">,</span> <span class="sh">"</span><span class="s">text</span><span class="sh">"</span><span class="p">),</span>
        <span class="sh">"</span><span class="s">value</span><span class="sh">"</span><span class="p">:</span> <span class="n">value</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">enforceWarninglist</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">includeCorrelations</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">limit</span><span class="sh">"</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span>
    <span class="p">}</span>

    <span class="n">response</span> <span class="o">=</span> <span class="n">http</span><span class="p">.</span><span class="nf">request</span><span class="p">(</span>
        <span class="sh">"</span><span class="s">POST</span><span class="sh">"</span><span class="p">,</span>
        <span class="n">url</span><span class="p">,</span>
        <span class="n">body</span><span class="o">=</span><span class="n">json</span><span class="p">.</span><span class="nf">dumps</span><span class="p">(</span><span class="n">payload</span><span class="p">).</span><span class="nf">encode</span><span class="p">(</span><span class="sh">"</span><span class="s">utf-8</span><span class="sh">"</span><span class="p">),</span>
        <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span>
        <span class="n">timeout</span><span class="o">=</span><span class="mf">10.0</span><span class="p">,</span>
    <span class="p">)</span>

    <span class="k">if</span> <span class="n">response</span><span class="p">.</span><span class="n">status</span> <span class="o">==</span> <span class="mi">200</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">json</span><span class="p">.</span><span class="nf">loads</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">data</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="sh">"</span><span class="s">utf-8</span><span class="sh">"</span><span class="p">))</span>
    <span class="n">logger</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="sh">"</span><span class="s">MISP search failed: %s %s</span><span class="sh">"</span><span class="p">,</span> <span class="n">response</span><span class="p">.</span><span class="n">status</span><span class="p">,</span> <span class="n">response</span><span class="p">.</span><span class="n">data</span><span class="p">)</span>
    <span class="k">return</span> <span class="p">{</span><span class="sh">"</span><span class="s">response</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">Attribute</span><span class="sh">"</span><span class="p">:</span> <span class="p">[]}}</span>


<span class="k">def</span> <span class="nf">handler</span><span class="p">(</span><span class="n">event</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">],</span> <span class="n">context</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
    <span class="sh">"""</span><span class="s">Lambda handler for MISP threat intelligence enrichment.</span><span class="sh">"""</span>
    <span class="n">api_key</span> <span class="o">=</span> <span class="nf">get_misp_api_key</span><span class="p">()</span>
    <span class="n">indicators</span> <span class="o">=</span> <span class="nf">extract_indicators</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>

    <span class="n">enrichment_results</span> <span class="o">=</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">iocMatch</span><span class="sh">"</span><span class="p">:</span> <span class="bp">False</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">matchedIndicators</span><span class="sh">"</span><span class="p">:</span> <span class="p">[],</span>
        <span class="sh">"</span><span class="s">threatLevel</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">unknown</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">relatedEvents</span><span class="sh">"</span><span class="p">:</span> <span class="p">[],</span>
        <span class="sh">"</span><span class="s">tags</span><span class="sh">"</span><span class="p">:</span> <span class="p">[],</span>
    <span class="p">}</span>

    <span class="k">for</span> <span class="n">indicator_type</span><span class="p">,</span> <span class="n">values</span> <span class="ow">in</span> <span class="n">indicators</span><span class="p">.</span><span class="nf">items</span><span class="p">():</span>
        <span class="k">for</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">values</span><span class="p">:</span>
            <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sh">"</span><span class="s">Searching MISP for %s: %s</span><span class="sh">"</span><span class="p">,</span> <span class="n">indicator_type</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
            <span class="n">result</span> <span class="o">=</span> <span class="nf">search_misp</span><span class="p">(</span><span class="n">api_key</span><span class="p">,</span> <span class="n">indicator_type</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>

            <span class="n">attributes</span> <span class="o">=</span> <span class="p">(</span>
                <span class="n">result</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">response</span><span class="sh">"</span><span class="p">,</span> <span class="p">{}).</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Attribute</span><span class="sh">"</span><span class="p">,</span> <span class="p">[])</span>
            <span class="p">)</span>

            <span class="k">if</span> <span class="n">attributes</span><span class="p">:</span>
                <span class="n">enrichment_results</span><span class="p">[</span><span class="sh">"</span><span class="s">iocMatch</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="bp">True</span>
                <span class="n">enrichment_results</span><span class="p">[</span><span class="sh">"</span><span class="s">matchedIndicators</span><span class="sh">"</span><span class="p">].</span><span class="nf">append</span><span class="p">({</span>
                    <span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="n">indicator_type</span><span class="p">,</span>
                    <span class="sh">"</span><span class="s">value</span><span class="sh">"</span><span class="p">:</span> <span class="n">value</span><span class="p">,</span>
                    <span class="sh">"</span><span class="s">mispEventCount</span><span class="sh">"</span><span class="p">:</span> <span class="nf">len</span><span class="p">(</span><span class="n">attributes</span><span class="p">),</span>
                    <span class="sh">"</span><span class="s">tags</span><span class="sh">"</span><span class="p">:</span> <span class="p">[</span>
                        <span class="n">tag</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">)</span>
                        <span class="k">for</span> <span class="n">attr</span> <span class="ow">in</span> <span class="n">attributes</span><span class="p">[:</span><span class="mi">3</span><span class="p">]</span>
                        <span class="k">for</span> <span class="n">tag</span> <span class="ow">in</span> <span class="n">attr</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Tag</span><span class="sh">"</span><span class="p">,</span> <span class="p">[])</span>
                    <span class="p">],</span>
                    <span class="sh">"</span><span class="s">threatLevel</span><span class="sh">"</span><span class="p">:</span> <span class="n">attributes</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">get</span><span class="p">(</span>
                        <span class="sh">"</span><span class="s">event_threat_level_id</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">unknown</span><span class="sh">"</span>
                    <span class="p">),</span>
                <span class="p">})</span>

    <span class="c1"># Determine overall threat level from matches
</span>    <span class="k">if</span> <span class="n">enrichment_results</span><span class="p">[</span><span class="sh">"</span><span class="s">matchedIndicators</span><span class="sh">"</span><span class="p">]:</span>
        <span class="n">threat_levels</span> <span class="o">=</span> <span class="p">[</span>
            <span class="n">m</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">threatLevel</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">4</span><span class="sh">"</span><span class="p">)</span>
            <span class="k">for</span> <span class="n">m</span> <span class="ow">in</span> <span class="n">enrichment_results</span><span class="p">[</span><span class="sh">"</span><span class="s">matchedIndicators</span><span class="sh">"</span><span class="p">]</span>
        <span class="p">]</span>
        <span class="c1"># MISP threat levels: 1=High, 2=Medium, 3=Low, 4=Undefined
</span>        <span class="n">min_level</span> <span class="o">=</span> <span class="nf">min</span><span class="p">(</span><span class="nf">int</span><span class="p">(</span><span class="n">tl</span><span class="p">)</span> <span class="k">for</span> <span class="n">tl</span> <span class="ow">in</span> <span class="n">threat_levels</span> <span class="k">if</span> <span class="n">tl</span><span class="p">.</span><span class="nf">isdigit</span><span class="p">())</span>
        <span class="n">level_map</span> <span class="o">=</span> <span class="p">{</span><span class="mi">1</span><span class="p">:</span> <span class="sh">"</span><span class="s">high</span><span class="sh">"</span><span class="p">,</span> <span class="mi">2</span><span class="p">:</span> <span class="sh">"</span><span class="s">medium</span><span class="sh">"</span><span class="p">,</span> <span class="mi">3</span><span class="p">:</span> <span class="sh">"</span><span class="s">low</span><span class="sh">"</span><span class="p">,</span> <span class="mi">4</span><span class="p">:</span> <span class="sh">"</span><span class="s">undefined</span><span class="sh">"</span><span class="p">}</span>
        <span class="n">enrichment_results</span><span class="p">[</span><span class="sh">"</span><span class="s">threatLevel</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">level_map</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span>
            <span class="n">min_level</span><span class="p">,</span> <span class="sh">"</span><span class="s">unknown</span><span class="sh">"</span>
        <span class="p">)</span>

    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span>
        <span class="sh">"</span><span class="s">MISP enrichment complete. IOC match: %s</span><span class="sh">"</span><span class="p">,</span>
        <span class="n">enrichment_results</span><span class="p">[</span><span class="sh">"</span><span class="s">iocMatch</span><span class="sh">"</span><span class="p">],</span>
    <span class="p">)</span>
    <span class="k">return</span> <span class="n">enrichment_results</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="opencti-enrichment-function">OpenCTI Enrichment Function</h3>

<p>OpenCTI provides a complementary intelligence layer with STIX2-structured data and relationship mapping:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="n">json</span>
<span class="kn">import</span> <span class="n">os</span>
<span class="kn">import</span> <span class="n">logging</span>
<span class="kn">from</span> <span class="n">typing</span> <span class="kn">import</span> <span class="n">Any</span>

<span class="kn">import</span> <span class="n">boto3</span>
<span class="kn">import</span> <span class="n">urllib3</span>

<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="p">.</span><span class="nf">getLogger</span><span class="p">()</span>
<span class="n">logger</span><span class="p">.</span><span class="nf">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="p">.</span><span class="n">INFO</span><span class="p">)</span>

<span class="n">http</span> <span class="o">=</span> <span class="n">urllib3</span><span class="p">.</span><span class="nc">PoolManager</span><span class="p">()</span>

<span class="n">OPENCTI_URL</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">[</span><span class="sh">"</span><span class="s">OPENCTI_URL</span><span class="sh">"</span><span class="p">]</span>
<span class="n">OPENCTI_TOKEN_PARAM</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">[</span><span class="sh">"</span><span class="s">OPENCTI_TOKEN_PARAM</span><span class="sh">"</span><span class="p">]</span>

<span class="n">ssm</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="nf">client</span><span class="p">(</span><span class="sh">"</span><span class="s">ssm</span><span class="sh">"</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">get_opencti_token</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Retrieve OpenCTI token from SSM Parameter Store.</span><span class="sh">"""</span>
    <span class="n">response</span> <span class="o">=</span> <span class="n">ssm</span><span class="p">.</span><span class="nf">get_parameter</span><span class="p">(</span>
        <span class="n">Name</span><span class="o">=</span><span class="n">OPENCTI_TOKEN_PARAM</span><span class="p">,</span>
        <span class="n">WithDecryption</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="k">return</span> <span class="n">response</span><span class="p">[</span><span class="sh">"</span><span class="s">Parameter</span><span class="sh">"</span><span class="p">][</span><span class="sh">"</span><span class="s">Value</span><span class="sh">"</span><span class="p">]</span>


<span class="k">def</span> <span class="nf">query_opencti</span><span class="p">(</span><span class="n">token</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">observable_value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Query OpenCTI GraphQL API for observable enrichment.</span><span class="sh">"""</span>
    <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="n">OPENCTI_URL</span><span class="si">}</span><span class="s">/graphql</span><span class="sh">"</span>
    <span class="n">headers</span> <span class="o">=</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">Authorization</span><span class="sh">"</span><span class="p">:</span> <span class="sa">f</span><span class="sh">"</span><span class="s">Bearer </span><span class="si">{</span><span class="n">token</span><span class="si">}</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">Content-Type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">application/json</span><span class="sh">"</span><span class="p">,</span>
    <span class="p">}</span>

    <span class="n">query</span> <span class="o">=</span> <span class="sh">"""</span><span class="s">
    query SearchObservable($value: String!) {
      stixCyberObservables(
        search: $value
        first: 5
      ) {
        edges {
          node {
            id
            entity_type
            observable_value
            x_opencti_score
            createdBy { name }
            objectLabel { value }
            indicators {
              edges {
                node {
                  name
                  pattern
                  indicator_types
                  confidence
                  validFrom
                  validUntil
                }
              }
            }
            stixCoreRelationships {
              edges {
                node {
                  relationship_type
                  to {
</span><span class="gp">                    ...</span> <span class="n">on</span> <span class="n">AttackPattern</span> <span class="p">{</span> <span class="n">name</span> <span class="n">x_mitre_id</span> <span class="p">}</span>
                    <span class="p">...</span> <span class="n">on</span> <span class="n">IntrusionSet</span> <span class="p">{</span> <span class="n">name</span> <span class="p">}</span>
                    <span class="p">...</span> <span class="n">on</span> <span class="n">Malware</span> <span class="p">{</span> <span class="n">name</span> <span class="p">}</span>
                    <span class="p">...</span> <span class="n">on</span> <span class="n">Campaign</span> <span class="p">{</span> <span class="n">name</span> <span class="p">}</span>
                  <span class="p">}</span>
                <span class="p">}</span>
              <span class="p">}</span>
            <span class="p">}</span>
          <span class="p">}</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">}</span>
    <span class="sh">"""</span>

    <span class="n">payload</span> <span class="o">=</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">query</span><span class="sh">"</span><span class="p">:</span> <span class="n">query</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">variables</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">value</span><span class="sh">"</span><span class="p">:</span> <span class="n">observable_value</span><span class="p">},</span>
    <span class="p">}</span>

    <span class="n">response</span> <span class="o">=</span> <span class="n">http</span><span class="p">.</span><span class="nf">request</span><span class="p">(</span>
        <span class="sh">"</span><span class="s">POST</span><span class="sh">"</span><span class="p">,</span>
        <span class="n">url</span><span class="p">,</span>
        <span class="n">body</span><span class="o">=</span><span class="n">json</span><span class="p">.</span><span class="nf">dumps</span><span class="p">(</span><span class="n">payload</span><span class="p">).</span><span class="nf">encode</span><span class="p">(</span><span class="sh">"</span><span class="s">utf-8</span><span class="sh">"</span><span class="p">),</span>
        <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span>
        <span class="n">timeout</span><span class="o">=</span><span class="mf">15.0</span><span class="p">,</span>
    <span class="p">)</span>

    <span class="k">if</span> <span class="n">response</span><span class="p">.</span><span class="n">status</span> <span class="o">==</span> <span class="mi">200</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">json</span><span class="p">.</span><span class="nf">loads</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">data</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="sh">"</span><span class="s">utf-8</span><span class="sh">"</span><span class="p">))</span>
    <span class="n">logger</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="sh">"</span><span class="s">OpenCTI query failed: %s</span><span class="sh">"</span><span class="p">,</span> <span class="n">response</span><span class="p">.</span><span class="n">status</span><span class="p">)</span>
    <span class="k">return</span> <span class="p">{</span><span class="sh">"</span><span class="s">data</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">stixCyberObservables</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">edges</span><span class="sh">"</span><span class="p">:</span> <span class="p">[]}}}</span>


<span class="k">def</span> <span class="nf">handler</span><span class="p">(</span><span class="n">event</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">],</span> <span class="n">context</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
    <span class="sh">"""</span><span class="s">Lambda handler for OpenCTI threat intelligence enrichment.</span><span class="sh">"""</span>
    <span class="n">token</span> <span class="o">=</span> <span class="nf">get_opencti_token</span><span class="p">()</span>

    <span class="c1"># Extract indicators from the event (reuse same extraction logic)
</span>    <span class="n">detail</span> <span class="o">=</span> <span class="n">event</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">detail</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
    <span class="n">observables</span> <span class="o">=</span> <span class="p">[]</span>

    <span class="n">source_ip</span> <span class="o">=</span> <span class="n">detail</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">sourceIPAddress</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">source_ip</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">source_ip</span><span class="p">.</span><span class="nf">endswith</span><span class="p">(</span><span class="sh">"</span><span class="s">.amazonaws.com</span><span class="sh">"</span><span class="p">):</span>
        <span class="n">observables</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">source_ip</span><span class="p">)</span>

    <span class="n">service</span> <span class="o">=</span> <span class="n">detail</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">service</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
    <span class="n">action</span> <span class="o">=</span> <span class="n">service</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">action</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
    <span class="n">remote_ip</span> <span class="o">=</span> <span class="p">(</span>
        <span class="n">action</span>
        <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">networkConnectionAction</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
        <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">remoteIpDetails</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
        <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">ipAddressV4</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">)</span>
    <span class="p">)</span>
    <span class="k">if</span> <span class="n">remote_ip</span><span class="p">:</span>
        <span class="n">observables</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">remote_ip</span><span class="p">)</span>

    <span class="n">domain</span> <span class="o">=</span> <span class="p">(</span>
        <span class="n">action</span>
        <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">dnsRequestAction</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
        <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">domain</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">)</span>
    <span class="p">)</span>
    <span class="k">if</span> <span class="n">domain</span><span class="p">:</span>
        <span class="n">observables</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">domain</span><span class="p">)</span>

    <span class="n">enrichment</span> <span class="o">=</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">source</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">opencti</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">matchFound</span><span class="sh">"</span><span class="p">:</span> <span class="bp">False</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">observables</span><span class="sh">"</span><span class="p">:</span> <span class="p">[],</span>
        <span class="sh">"</span><span class="s">relatedThreatActors</span><span class="sh">"</span><span class="p">:</span> <span class="p">[],</span>
        <span class="sh">"</span><span class="s">attackPatterns</span><span class="sh">"</span><span class="p">:</span> <span class="p">[],</span>
        <span class="sh">"</span><span class="s">campaigns</span><span class="sh">"</span><span class="p">:</span> <span class="p">[],</span>
        <span class="sh">"</span><span class="s">confidence</span><span class="sh">"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="p">}</span>

    <span class="k">for</span> <span class="n">observable</span> <span class="ow">in</span> <span class="n">observables</span><span class="p">:</span>
        <span class="n">result</span> <span class="o">=</span> <span class="nf">query_opencti</span><span class="p">(</span><span class="n">token</span><span class="p">,</span> <span class="n">observable</span><span class="p">)</span>
        <span class="n">edges</span> <span class="o">=</span> <span class="p">(</span>
            <span class="n">result</span>
            <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">data</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
            <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">stixCyberObservables</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
            <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">edges</span><span class="sh">"</span><span class="p">,</span> <span class="p">[])</span>
        <span class="p">)</span>

        <span class="k">for</span> <span class="n">edge</span> <span class="ow">in</span> <span class="n">edges</span><span class="p">:</span>
            <span class="n">node</span> <span class="o">=</span> <span class="n">edge</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">node</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
            <span class="n">enrichment</span><span class="p">[</span><span class="sh">"</span><span class="s">matchFound</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="bp">True</span>
            <span class="n">enrichment</span><span class="p">[</span><span class="sh">"</span><span class="s">confidence</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="nf">max</span><span class="p">(</span>
                <span class="n">enrichment</span><span class="p">[</span><span class="sh">"</span><span class="s">confidence</span><span class="sh">"</span><span class="p">],</span>
                <span class="n">node</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">x_opencti_score</span><span class="sh">"</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
            <span class="p">)</span>

            <span class="c1"># Extract related threat actors and attack patterns
</span>            <span class="k">for</span> <span class="n">rel_edge</span> <span class="ow">in</span> <span class="p">(</span>
                <span class="n">node</span>
                <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">stixCoreRelationships</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
                <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">edges</span><span class="sh">"</span><span class="p">,</span> <span class="p">[])</span>
            <span class="p">):</span>
                <span class="n">rel_node</span> <span class="o">=</span> <span class="n">rel_edge</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">node</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
                <span class="n">rel_to</span> <span class="o">=</span> <span class="n">rel_node</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">to</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
                <span class="n">rel_type</span> <span class="o">=</span> <span class="n">rel_node</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">relationship_type</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">)</span>

                <span class="k">if</span> <span class="n">rel_type</span> <span class="ow">in</span> <span class="p">(</span><span class="sh">"</span><span class="s">indicates</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">uses</span><span class="sh">"</span><span class="p">):</span>
                    <span class="k">if</span> <span class="sh">"</span><span class="s">x_mitre_id</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">rel_to</span><span class="p">:</span>
                        <span class="n">enrichment</span><span class="p">[</span><span class="sh">"</span><span class="s">attackPatterns</span><span class="sh">"</span><span class="p">].</span><span class="nf">append</span><span class="p">({</span>
                            <span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">:</span> <span class="n">rel_to</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">),</span>
                            <span class="sh">"</span><span class="s">mitreId</span><span class="sh">"</span><span class="p">:</span> <span class="n">rel_to</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">x_mitre_id</span><span class="sh">"</span><span class="p">),</span>
                        <span class="p">})</span>
                    <span class="k">elif</span> <span class="n">rel_type</span> <span class="o">==</span> <span class="sh">"</span><span class="s">attributed-to</span><span class="sh">"</span><span class="p">:</span>
                        <span class="n">enrichment</span><span class="p">[</span><span class="sh">"</span><span class="s">relatedThreatActors</span><span class="sh">"</span><span class="p">].</span><span class="nf">append</span><span class="p">(</span>
                            <span class="n">rel_to</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">)</span>
                        <span class="p">)</span>

    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span>
        <span class="sh">"</span><span class="s">OpenCTI enrichment complete. Match: %s, Confidence: %d</span><span class="sh">"</span><span class="p">,</span>
        <span class="n">enrichment</span><span class="p">[</span><span class="sh">"</span><span class="s">matchFound</span><span class="sh">"</span><span class="p">],</span>
        <span class="n">enrichment</span><span class="p">[</span><span class="sh">"</span><span class="s">confidence</span><span class="sh">"</span><span class="p">],</span>
    <span class="p">)</span>
    <span class="k">return</span> <span class="n">enrichment</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="automated-ip-blocking-function">Automated IP Blocking Function</h3>

<p>When the orchestrator confirms a malicious IP through threat intel enrichment, this function updates AWS WAF and VPC Network ACLs:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="n">json</span>
<span class="kn">import</span> <span class="n">os</span>
<span class="kn">import</span> <span class="n">logging</span>
<span class="kn">from</span> <span class="n">datetime</span> <span class="kn">import</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">timezone</span>
<span class="kn">from</span> <span class="n">typing</span> <span class="kn">import</span> <span class="n">Any</span>

<span class="kn">import</span> <span class="n">boto3</span>

<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="p">.</span><span class="nf">getLogger</span><span class="p">()</span>
<span class="n">logger</span><span class="p">.</span><span class="nf">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="p">.</span><span class="n">INFO</span><span class="p">)</span>

<span class="n">waf</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="nf">client</span><span class="p">(</span><span class="sh">"</span><span class="s">wafv2</span><span class="sh">"</span><span class="p">)</span>
<span class="n">ec2</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="nf">client</span><span class="p">(</span><span class="sh">"</span><span class="s">ec2</span><span class="sh">"</span><span class="p">)</span>

<span class="n">WAF_IP_SET_ID</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">[</span><span class="sh">"</span><span class="s">WAF_BLOCK_IP_SET_ID</span><span class="sh">"</span><span class="p">]</span>
<span class="n">WAF_IP_SET_NAME</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">[</span><span class="sh">"</span><span class="s">WAF_BLOCK_IP_SET_NAME</span><span class="sh">"</span><span class="p">]</span>
<span class="n">WAF_SCOPE</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">WAF_SCOPE</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">REGIONAL</span><span class="sh">"</span><span class="p">)</span>
<span class="n">NACL_ID</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">NACL_ID</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">block_ip_waf</span><span class="p">(</span><span class="n">ip_address</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Add malicious IP to WAF IP set for blocking.</span><span class="sh">"""</span>
    <span class="c1"># Get current IP set to retrieve lock token
</span>    <span class="n">ip_set</span> <span class="o">=</span> <span class="n">waf</span><span class="p">.</span><span class="nf">get_ip_set</span><span class="p">(</span>
        <span class="n">Name</span><span class="o">=</span><span class="n">WAF_IP_SET_NAME</span><span class="p">,</span>
        <span class="n">Scope</span><span class="o">=</span><span class="n">WAF_SCOPE</span><span class="p">,</span>
        <span class="n">Id</span><span class="o">=</span><span class="n">WAF_IP_SET_ID</span><span class="p">,</span>
    <span class="p">)</span>

    <span class="n">current_addresses</span> <span class="o">=</span> <span class="n">ip_set</span><span class="p">[</span><span class="sh">"</span><span class="s">IPSet</span><span class="sh">"</span><span class="p">][</span><span class="sh">"</span><span class="s">Addresses</span><span class="sh">"</span><span class="p">]</span>
    <span class="n">cidr</span> <span class="o">=</span> <span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="n">ip_address</span><span class="si">}</span><span class="s">/32</span><span class="sh">"</span>

    <span class="k">if</span> <span class="n">cidr</span> <span class="ow">in</span> <span class="n">current_addresses</span><span class="p">:</span>
        <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sh">"</span><span class="s">IP %s already blocked in WAF</span><span class="sh">"</span><span class="p">,</span> <span class="n">ip_address</span><span class="p">)</span>
        <span class="k">return</span> <span class="p">{</span><span class="sh">"</span><span class="s">action</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">already_blocked</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">ip</span><span class="sh">"</span><span class="p">:</span> <span class="n">ip_address</span><span class="p">}</span>

    <span class="n">current_addresses</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">cidr</span><span class="p">)</span>

    <span class="n">waf</span><span class="p">.</span><span class="nf">update_ip_set</span><span class="p">(</span>
        <span class="n">Name</span><span class="o">=</span><span class="n">WAF_IP_SET_NAME</span><span class="p">,</span>
        <span class="n">Scope</span><span class="o">=</span><span class="n">WAF_SCOPE</span><span class="p">,</span>
        <span class="n">Id</span><span class="o">=</span><span class="n">WAF_IP_SET_ID</span><span class="p">,</span>
        <span class="n">Addresses</span><span class="o">=</span><span class="n">current_addresses</span><span class="p">,</span>
        <span class="n">LockToken</span><span class="o">=</span><span class="n">ip_set</span><span class="p">[</span><span class="sh">"</span><span class="s">LockToken</span><span class="sh">"</span><span class="p">],</span>
        <span class="n">Description</span><span class="o">=</span><span class="sa">f</span><span class="sh">"</span><span class="s">Auto-blocked </span><span class="si">{</span><span class="n">ip_address</span><span class="si">}</span><span class="s"> at </span><span class="sh">"</span>
                    <span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="n">datetime</span><span class="p">.</span><span class="nf">now</span><span class="p">(</span><span class="n">timezone</span><span class="p">.</span><span class="n">utc</span><span class="p">).</span><span class="nf">isoformat</span><span class="p">()</span><span class="si">}</span><span class="sh">"</span><span class="p">,</span>
    <span class="p">)</span>

    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sh">"</span><span class="s">Blocked IP %s in WAF IP set</span><span class="sh">"</span><span class="p">,</span> <span class="n">ip_address</span><span class="p">)</span>
    <span class="k">return</span> <span class="p">{</span><span class="sh">"</span><span class="s">action</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">blocked</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">ip</span><span class="sh">"</span><span class="p">:</span> <span class="n">ip_address</span><span class="p">,</span> <span class="sh">"</span><span class="s">target</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">waf</span><span class="sh">"</span><span class="p">}</span>


<span class="k">def</span> <span class="nf">block_ip_nacl</span><span class="p">(</span><span class="n">ip_address</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Add deny rule to Network ACL for the malicious IP.</span><span class="sh">"""</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">NACL_ID</span><span class="p">:</span>
        <span class="k">return</span> <span class="p">{</span><span class="sh">"</span><span class="s">action</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">skipped</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">reason</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">No NACL configured</span><span class="sh">"</span><span class="p">}</span>

    <span class="c1"># Find the lowest available rule number above 50
</span>    <span class="n">nacl</span> <span class="o">=</span> <span class="n">ec2</span><span class="p">.</span><span class="nf">describe_network_acls</span><span class="p">(</span>
        <span class="n">NetworkAclIds</span><span class="o">=</span><span class="p">[</span><span class="n">NACL_ID</span><span class="p">],</span>
    <span class="p">)</span>

    <span class="n">existing_rules</span> <span class="o">=</span> <span class="p">[</span>
        <span class="n">entry</span><span class="p">[</span><span class="sh">"</span><span class="s">RuleNumber</span><span class="sh">"</span><span class="p">]</span>
        <span class="k">for</span> <span class="n">entry</span> <span class="ow">in</span> <span class="n">nacl</span><span class="p">[</span><span class="sh">"</span><span class="s">NetworkAcls</span><span class="sh">"</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="sh">"</span><span class="s">Entries</span><span class="sh">"</span><span class="p">]</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">entry</span><span class="p">[</span><span class="sh">"</span><span class="s">Egress</span><span class="sh">"</span><span class="p">]</span>
    <span class="p">]</span>

    <span class="n">rule_number</span> <span class="o">=</span> <span class="mi">50</span>
    <span class="k">while</span> <span class="n">rule_number</span> <span class="ow">in</span> <span class="n">existing_rules</span><span class="p">:</span>
        <span class="n">rule_number</span> <span class="o">+=</span> <span class="mi">1</span>

    <span class="n">ec2</span><span class="p">.</span><span class="nf">create_network_acl_entry</span><span class="p">(</span>
        <span class="n">NetworkAclId</span><span class="o">=</span><span class="n">NACL_ID</span><span class="p">,</span>
        <span class="n">RuleNumber</span><span class="o">=</span><span class="n">rule_number</span><span class="p">,</span>
        <span class="n">Protocol</span><span class="o">=</span><span class="sh">"</span><span class="s">-1</span><span class="sh">"</span><span class="p">,</span>
        <span class="n">RuleAction</span><span class="o">=</span><span class="sh">"</span><span class="s">deny</span><span class="sh">"</span><span class="p">,</span>
        <span class="n">Egress</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span>
        <span class="n">CidrBlock</span><span class="o">=</span><span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="n">ip_address</span><span class="si">}</span><span class="s">/32</span><span class="sh">"</span><span class="p">,</span>
    <span class="p">)</span>

    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span>
        <span class="sh">"</span><span class="s">Blocked IP %s in NACL %s rule %d</span><span class="sh">"</span><span class="p">,</span>
        <span class="n">ip_address</span><span class="p">,</span> <span class="n">NACL_ID</span><span class="p">,</span> <span class="n">rule_number</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="k">return</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">action</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">blocked</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">ip</span><span class="sh">"</span><span class="p">:</span> <span class="n">ip_address</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">target</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">nacl</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">ruleNumber</span><span class="sh">"</span><span class="p">:</span> <span class="n">rule_number</span><span class="p">,</span>
    <span class="p">}</span>


<span class="k">def</span> <span class="nf">handler</span><span class="p">(</span><span class="n">event</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">],</span> <span class="n">context</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
    <span class="sh">"""</span><span class="s">Lambda handler to block malicious IPs via WAF and NACL.</span><span class="sh">"""</span>
    <span class="n">detail</span> <span class="o">=</span> <span class="n">event</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">detail</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>

    <span class="c1"># Extract IP from various event formats
</span>    <span class="n">ip_address</span> <span class="o">=</span> <span class="n">detail</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">sourceIPAddress</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">)</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">ip_address</span><span class="p">:</span>
        <span class="n">service</span> <span class="o">=</span> <span class="n">detail</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">service</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
        <span class="n">ip_address</span> <span class="o">=</span> <span class="p">(</span>
            <span class="n">service</span>
            <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">action</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
            <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">networkConnectionAction</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
            <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">remoteIpDetails</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
            <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">ipAddressV4</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">)</span>
        <span class="p">)</span>

    <span class="k">if</span> <span class="ow">not</span> <span class="n">ip_address</span><span class="p">:</span>
        <span class="k">return</span> <span class="p">{</span><span class="sh">"</span><span class="s">action</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">no_ip_found</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">event</span><span class="sh">"</span><span class="p">:</span> <span class="n">event</span><span class="p">}</span>

    <span class="n">results</span> <span class="o">=</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">timestamp</span><span class="sh">"</span><span class="p">:</span> <span class="n">datetime</span><span class="p">.</span><span class="nf">now</span><span class="p">(</span><span class="n">timezone</span><span class="p">.</span><span class="n">utc</span><span class="p">).</span><span class="nf">isoformat</span><span class="p">(),</span>
        <span class="sh">"</span><span class="s">ip</span><span class="sh">"</span><span class="p">:</span> <span class="n">ip_address</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">waf</span><span class="sh">"</span><span class="p">:</span> <span class="nf">block_ip_waf</span><span class="p">(</span><span class="n">ip_address</span><span class="p">),</span>
        <span class="sh">"</span><span class="s">nacl</span><span class="sh">"</span><span class="p">:</span> <span class="nf">block_ip_nacl</span><span class="p">(</span><span class="n">ip_address</span><span class="p">),</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">results</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="misp-threat-intelligence-integration">MISP Threat Intelligence Integration</h2>

<p>MISP (Malware Information Sharing Platform) is the backbone of community-driven threat intelligence. Running your own MISP instance on AWS gives you full control over your threat intelligence pipeline, access to dozens of community feeds, and the ability to share intelligence with trusted partners.</p>

<h3 id="deploying-misp-on-aws">Deploying MISP on AWS</h3>

<p>The most reliable way to deploy MISP on AWS is with a containerized setup on an EC2 instance. Here is a Docker Compose configuration optimized for AWS deployment:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
</pre></td><td class="rouge-code"><pre><span class="c1"># docker-compose.yml for MISP on AWS EC2</span>
<span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3.8"</span>

<span class="na">services</span><span class="pi">:</span>
  <span class="na">misp-core</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/misp/misp-docker/misp-core:latest</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">443:443"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">80:80"</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="na">MISP_BASEURL</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://misp.internal.yourdomain.com"</span>
      <span class="na">MISP_ORG</span><span class="pi">:</span> <span class="s2">"</span><span class="s">YourOrganization"</span>
      <span class="na">MISP_EMAIL</span><span class="pi">:</span> <span class="s2">"</span><span class="s">misp@yourdomain.com"</span>
      <span class="na">MYSQL_HOST</span><span class="pi">:</span> <span class="s2">"</span><span class="s">misp-db"</span>
      <span class="na">MYSQL_DATABASE</span><span class="pi">:</span> <span class="s2">"</span><span class="s">misp"</span>
      <span class="na">MYSQL_USER</span><span class="pi">:</span> <span class="s2">"</span><span class="s">misp"</span>
      <span class="na">MYSQL_PASSWORD_FILE</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/run/secrets/mysql_password"</span>
      <span class="na">REDIS_HOST</span><span class="pi">:</span> <span class="s2">"</span><span class="s">misp-redis"</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">misp-data:/var/www/MISP/app/files</span>
      <span class="pi">-</span> <span class="s">misp-config:/var/www/MISP/app/Config</span>
      <span class="pi">-</span> <span class="s">misp-certs:/etc/nginx/certs</span>
    <span class="na">depends_on</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">misp-db</span>
      <span class="pi">-</span> <span class="s">misp-redis</span>
    <span class="na">secrets</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">mysql_password</span>

  <span class="na">misp-db</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">mysql:8.0</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="na">MYSQL_DATABASE</span><span class="pi">:</span> <span class="s2">"</span><span class="s">misp"</span>
      <span class="na">MYSQL_USER</span><span class="pi">:</span> <span class="s2">"</span><span class="s">misp"</span>
      <span class="na">MYSQL_PASSWORD_FILE</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/run/secrets/mysql_password"</span>
      <span class="na">MYSQL_ROOT_PASSWORD_FILE</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/run/secrets/mysql_root_password"</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">mysql-data:/var/lib/mysql</span>
    <span class="na">secrets</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">mysql_password</span>
      <span class="pi">-</span> <span class="s">mysql_root_password</span>

  <span class="na">misp-redis</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">redis:7-alpine</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">redis-data:/data</span>

  <span class="na">misp-modules</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/misp/misp-docker/misp-modules:latest</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="na">REDIS_BACKEND</span><span class="pi">:</span> <span class="s2">"</span><span class="s">misp-redis"</span>
    <span class="na">depends_on</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">misp-redis</span>

<span class="na">volumes</span><span class="pi">:</span>
  <span class="na">misp-data</span><span class="pi">:</span>
  <span class="na">misp-config</span><span class="pi">:</span>
  <span class="na">misp-certs</span><span class="pi">:</span>
  <span class="na">mysql-data</span><span class="pi">:</span>
  <span class="na">redis-data</span><span class="pi">:</span>

<span class="na">secrets</span><span class="pi">:</span>
  <span class="na">mysql_password</span><span class="pi">:</span>
    <span class="na">file</span><span class="pi">:</span> <span class="s">./secrets/mysql_password.txt</span>
  <span class="na">mysql_root_password</span><span class="pi">:</span>
    <span class="na">file</span><span class="pi">:</span> <span class="s">./secrets/mysql_root_password.txt</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="misp-feed-integration-script">MISP Feed Integration Script</h3>

<p>Automate the ingestion of community threat intelligence feeds into your MISP instance:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
</pre></td><td class="rouge-code"><pre><span class="c1">#!/usr/bin/env python3
</span><span class="sh">"""</span><span class="s">MISP feed management and synchronization script.

Configures and enables community threat intelligence feeds
for automated ingestion into your MISP instance.
</span><span class="sh">"""</span>

<span class="kn">import</span> <span class="n">json</span>
<span class="kn">import</span> <span class="n">logging</span>
<span class="kn">import</span> <span class="n">sys</span>

<span class="kn">from</span> <span class="n">pymisp</span> <span class="kn">import</span> <span class="n">PyMISP</span>

<span class="n">logging</span><span class="p">.</span><span class="nf">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="p">.</span><span class="n">INFO</span><span class="p">)</span>
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="p">.</span><span class="nf">getLogger</span><span class="p">(</span><span class="n">__name__</span><span class="p">)</span>

<span class="c1"># Community feeds to enable
</span><span class="n">FEEDS</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span>
        <span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">CIRCL OSINT Feed</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">provider</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">CIRCL</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">url</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">https://www.circl.lu/doc/misp/feed-osint/</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">source_format</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">misp</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">enabled</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">caching_enabled</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="p">{</span>
        <span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">Botvrij.eu</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">provider</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">Botvrij</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">url</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">https://www.botvrij.eu/data/feed-osint/</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">source_format</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">misp</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">enabled</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">caching_enabled</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="p">{</span>
        <span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">abuse.ch URLhaus</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">provider</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">abuse.ch</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">url</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">https://urlhaus.abuse.ch/downloads/misp/</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">source_format</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">misp</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">enabled</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">caching_enabled</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="p">{</span>
        <span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">abuse.ch Feodo Tracker</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">provider</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">abuse.ch</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">url</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">https://feodotracker.abuse.ch/downloads/misp/</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">source_format</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">misp</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">enabled</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">caching_enabled</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="p">{</span>
        <span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">PhishTank</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">provider</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">PhishTank</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">url</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">https://data.phishtank.com/data/online-valid.csv</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">source_format</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">csv</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">enabled</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">caching_enabled</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
    <span class="p">},</span>
<span class="p">]</span>


<span class="k">def</span> <span class="nf">configure_feeds</span><span class="p">(</span><span class="n">misp_url</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">misp_key</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Configure and enable threat intelligence feeds in MISP.</span><span class="sh">"""</span>
    <span class="n">misp</span> <span class="o">=</span> <span class="nc">PyMISP</span><span class="p">(</span><span class="n">misp_url</span><span class="p">,</span> <span class="n">misp_key</span><span class="p">,</span> <span class="n">ssl</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

    <span class="n">existing_feeds</span> <span class="o">=</span> <span class="n">misp</span><span class="p">.</span><span class="nf">feeds</span><span class="p">()</span>
    <span class="n">existing_names</span> <span class="o">=</span> <span class="p">{</span>
        <span class="n">f</span><span class="p">[</span><span class="sh">"</span><span class="s">Feed</span><span class="sh">"</span><span class="p">][</span><span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">]</span>
        <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">existing_feeds</span>
        <span class="k">if</span> <span class="sh">"</span><span class="s">Feed</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">f</span>
    <span class="p">}</span>

    <span class="k">for</span> <span class="n">feed_config</span> <span class="ow">in</span> <span class="n">FEEDS</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">feed_config</span><span class="p">[</span><span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">]</span> <span class="ow">in</span> <span class="n">existing_names</span><span class="p">:</span>
            <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sh">"</span><span class="s">Feed already exists: %s</span><span class="sh">"</span><span class="p">,</span> <span class="n">feed_config</span><span class="p">[</span><span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">])</span>
            <span class="k">continue</span>

        <span class="n">feed</span> <span class="o">=</span> <span class="n">misp</span><span class="p">.</span><span class="nf">add_feed</span><span class="p">(</span>
            <span class="n">name</span><span class="o">=</span><span class="n">feed_config</span><span class="p">[</span><span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">],</span>
            <span class="n">provider</span><span class="o">=</span><span class="n">feed_config</span><span class="p">[</span><span class="sh">"</span><span class="s">provider</span><span class="sh">"</span><span class="p">],</span>
            <span class="n">url</span><span class="o">=</span><span class="n">feed_config</span><span class="p">[</span><span class="sh">"</span><span class="s">url</span><span class="sh">"</span><span class="p">],</span>
            <span class="n">source_format</span><span class="o">=</span><span class="n">feed_config</span><span class="p">[</span><span class="sh">"</span><span class="s">source_format</span><span class="sh">"</span><span class="p">],</span>
            <span class="n">enabled</span><span class="o">=</span><span class="n">feed_config</span><span class="p">[</span><span class="sh">"</span><span class="s">enabled</span><span class="sh">"</span><span class="p">],</span>
            <span class="n">caching_enabled</span><span class="o">=</span><span class="n">feed_config</span><span class="p">[</span><span class="sh">"</span><span class="s">caching_enabled</span><span class="sh">"</span><span class="p">],</span>
            <span class="n">lookup_visible</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
        <span class="p">)</span>
        <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sh">"</span><span class="s">Added feed: %s</span><span class="sh">"</span><span class="p">,</span> <span class="n">feed_config</span><span class="p">[</span><span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">])</span>

    <span class="c1"># Trigger a fetch for all enabled feeds
</span>    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sh">"</span><span class="s">Fetching all enabled feeds...</span><span class="sh">"</span><span class="p">)</span>
    <span class="n">misp</span><span class="p">.</span><span class="nf">fetch_feed</span><span class="p">(</span><span class="sh">"</span><span class="s">all</span><span class="sh">"</span><span class="p">)</span>
    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sh">"</span><span class="s">Feed fetch initiated for all enabled feeds.</span><span class="sh">"</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">main</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="k">if</span> <span class="nf">len</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">3</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Usage: </span><span class="si">{</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">}</span><span class="s"> &lt;misp_url&gt; &lt;misp_api_key&gt;</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">sys</span><span class="p">.</span><span class="nf">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>

    <span class="nf">configure_feeds</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span>


<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="sh">"</span><span class="s">__main__</span><span class="sh">"</span><span class="p">:</span>
    <span class="nf">main</span><span class="p">()</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="opencti-as-threat-intel-management-layer">OpenCTI as Threat Intel Management Layer</h2>

<p>While MISP excels at indicator sharing and community feeds, OpenCTI provides a knowledge management layer that maps relationships between threat actors, campaigns, attack patterns, and observables using the STIX2 standard. Running both together gives you the best of both worlds: MISP for rapid indicator ingestion and sharing, OpenCTI for strategic intelligence analysis and MITRE ATT&amp;CK mapping.</p>

<p>OpenCTI integrates natively with MISP through its connector architecture. The MISP connector imports events and attributes from your MISP instance, maps them to STIX2 objects, and builds relationship graphs automatically. This means your Lambda enrichment functions can query either platform depending on the intelligence need – MISP for fast IOC lookups, OpenCTI for strategic context like related threat actors and attack patterns.</p>

<p>Key capabilities that make OpenCTI valuable in this pipeline:</p>

<ul>
  <li><strong>STIX2 Native</strong> – All data is structured using the STIX2 standard, making it interoperable with any STIX-compatible tool.</li>
  <li><strong>Relationship Mapping</strong> – Automatically builds graphs connecting observables to indicators, indicators to threat actors, and threat actors to campaigns.</li>
  <li><strong>Confidence Scoring</strong> – Every piece of intelligence includes a confidence score, helping your automation make better decisions about response severity.</li>
  <li><strong>MITRE ATT&amp;CK Integration</strong> – Threat intelligence is mapped to ATT&amp;CK techniques, giving your security team immediate context about adversary behavior.</li>
  <li><strong>GraphQL API</strong> – The API is purpose-built for programmatic access, making Lambda integration straightforward.</li>
</ul>

<h2 id="sigma-rules-detection-as-code-that-works-anywhere">Sigma Rules: Detection-as-Code That Works Anywhere</h2>

<p>Sigma rules are the open-source answer to vendor-locked detection content. A Sigma rule defines a detection pattern in a YAML format that can be compiled to any SIEM query language, EventBridge pattern, CloudWatch Logs Insights query, or Athena SQL. You write the detection logic once and deploy it everywhere.</p>

<h3 id="sigma-rule-examples-for-aws-cloudtrail">Sigma Rule Examples for AWS CloudTrail</h3>

<p><strong>Detect CloudTrail Logging Disabled:</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
</pre></td><td class="rouge-code"><pre><span class="na">title</span><span class="pi">:</span> <span class="s">AWS CloudTrail Logging Disabled</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">d7b7a68c-7e4f-4d62-bf8e-6a1e3e47c5f0</span>
<span class="na">status</span><span class="pi">:</span> <span class="s">stable</span>
<span class="na">description</span><span class="pi">:</span> <span class="pi">|</span>
  <span class="s">Detects when CloudTrail logging is stopped or the trail is</span>
  <span class="s">deleted, a common technique used by attackers to cover their tracks.</span>
<span class="na">references</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">https://attack.mitre.org/techniques/T1562/008/</span>
<span class="na">author</span><span class="pi">:</span> <span class="s">red-team.sh</span>
<span class="na">date</span><span class="pi">:</span> <span class="s">2026/04/15</span>
<span class="na">tags</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">attack.defense_evasion</span>
  <span class="pi">-</span> <span class="s">attack.t1562.008</span>
<span class="na">logsource</span><span class="pi">:</span>
  <span class="na">product</span><span class="pi">:</span> <span class="s">aws</span>
  <span class="na">service</span><span class="pi">:</span> <span class="s">cloudtrail</span>
<span class="na">detection</span><span class="pi">:</span>
  <span class="na">selection</span><span class="pi">:</span>
    <span class="na">eventName</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">StopLogging</span>
      <span class="pi">-</span> <span class="s">DeleteTrail</span>
      <span class="pi">-</span> <span class="s">UpdateTrail</span>
    <span class="na">eventSource</span><span class="pi">:</span> <span class="s">cloudtrail.amazonaws.com</span>
  <span class="na">condition</span><span class="pi">:</span> <span class="s">selection</span>
<span class="na">falsepositives</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">Legitimate administrative trail maintenance</span>
  <span class="pi">-</span> <span class="s">Infrastructure as Code deployments</span>
<span class="na">level</span><span class="pi">:</span> <span class="s">high</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Detect GuardDuty Detector Disabled:</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
</pre></td><td class="rouge-code"><pre><span class="na">title</span><span class="pi">:</span> <span class="s">AWS GuardDuty Detector Disabled or Deleted</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">a3e2c8b1-9f5d-4a67-b2c1-8d4e7f6a9b3c</span>
<span class="na">status</span><span class="pi">:</span> <span class="s">stable</span>
<span class="na">description</span><span class="pi">:</span> <span class="pi">|</span>
  <span class="s">Detects attempts to disable or delete GuardDuty detectors,</span>
  <span class="s">which would blind the organization to threat detection.</span>
<span class="na">references</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">https://attack.mitre.org/techniques/T1562/001/</span>
<span class="na">author</span><span class="pi">:</span> <span class="s">red-team.sh</span>
<span class="na">date</span><span class="pi">:</span> <span class="s">2026/04/15</span>
<span class="na">tags</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">attack.defense_evasion</span>
  <span class="pi">-</span> <span class="s">attack.t1562.001</span>
<span class="na">logsource</span><span class="pi">:</span>
  <span class="na">product</span><span class="pi">:</span> <span class="s">aws</span>
  <span class="na">service</span><span class="pi">:</span> <span class="s">cloudtrail</span>
<span class="na">detection</span><span class="pi">:</span>
  <span class="na">selection</span><span class="pi">:</span>
    <span class="na">eventName</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">DeleteDetector</span>
      <span class="pi">-</span> <span class="s">UpdateDetector</span>
      <span class="pi">-</span> <span class="s">DisassociateMembers</span>
    <span class="na">eventSource</span><span class="pi">:</span> <span class="s">guardduty.amazonaws.com</span>
  <span class="na">filter_update</span><span class="pi">:</span>
    <span class="na">eventName</span><span class="pi">:</span> <span class="s">UpdateDetector</span>
    <span class="na">requestParameters.enable</span><span class="pi">:</span> <span class="kc">true</span>
  <span class="na">condition</span><span class="pi">:</span> <span class="s">selection and not filter_update</span>
<span class="na">falsepositives</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">Legitimate detector reconfiguration</span>
<span class="na">level</span><span class="pi">:</span> <span class="s">critical</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Detect Unauthorized IAM Policy Attachment:</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
</pre></td><td class="rouge-code"><pre><span class="na">title</span><span class="pi">:</span> <span class="s">Sensitive IAM Policy Attached to User or Role</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">f8d2a1c7-6b3e-4f91-a5d8-2c7e9b4f1a6d</span>
<span class="na">status</span><span class="pi">:</span> <span class="s">experimental</span>
<span class="na">description</span><span class="pi">:</span> <span class="pi">|</span>
  <span class="s">Detects when highly privileged IAM policies are attached</span>
  <span class="s">to users or roles, potentially indicating privilege escalation.</span>
<span class="na">references</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">https://attack.mitre.org/techniques/T1098/</span>
<span class="na">author</span><span class="pi">:</span> <span class="s">red-team.sh</span>
<span class="na">date</span><span class="pi">:</span> <span class="s">2026/04/15</span>
<span class="na">tags</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">attack.persistence</span>
  <span class="pi">-</span> <span class="s">attack.privilege_escalation</span>
  <span class="pi">-</span> <span class="s">attack.t1098</span>
<span class="na">logsource</span><span class="pi">:</span>
  <span class="na">product</span><span class="pi">:</span> <span class="s">aws</span>
  <span class="na">service</span><span class="pi">:</span> <span class="s">cloudtrail</span>
<span class="na">detection</span><span class="pi">:</span>
  <span class="na">selection</span><span class="pi">:</span>
    <span class="na">eventName</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">AttachUserPolicy</span>
      <span class="pi">-</span> <span class="s">AttachRolePolicy</span>
      <span class="pi">-</span> <span class="s">AttachGroupPolicy</span>
      <span class="pi">-</span> <span class="s">PutUserPolicy</span>
      <span class="pi">-</span> <span class="s">PutRolePolicy</span>
    <span class="na">eventSource</span><span class="pi">:</span> <span class="s">iam.amazonaws.com</span>
  <span class="na">sensitive_policies</span><span class="pi">:</span>
    <span class="na">requestParameters.policyArn|contains</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">AdministratorAccess"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">PowerUserAccess"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">IAMFullAccess"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">SecurityAudit"</span>
  <span class="na">condition</span><span class="pi">:</span> <span class="s">selection and sensitive_policies</span>
<span class="na">falsepositives</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">Legitimate IAM administration</span>
  <span class="pi">-</span> <span class="s">Automated role provisioning during deployments</span>
<span class="na">level</span><span class="pi">:</span> <span class="s">high</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="sigma-rule-evaluator-lambda">Sigma Rule Evaluator Lambda</h3>

<p>This function loads Sigma rules at runtime and evaluates incoming CloudTrail events against them:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="n">json</span>
<span class="kn">import</span> <span class="n">os</span>
<span class="kn">import</span> <span class="n">logging</span>
<span class="kn">from</span> <span class="n">typing</span> <span class="kn">import</span> <span class="n">Any</span>
<span class="kn">from</span> <span class="n">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>

<span class="kn">import</span> <span class="n">boto3</span>
<span class="kn">import</span> <span class="n">yaml</span>

<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="p">.</span><span class="nf">getLogger</span><span class="p">()</span>
<span class="n">logger</span><span class="p">.</span><span class="nf">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="p">.</span><span class="n">INFO</span><span class="p">)</span>

<span class="n">s3</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="nf">client</span><span class="p">(</span><span class="sh">"</span><span class="s">s3</span><span class="sh">"</span><span class="p">)</span>

<span class="n">SIGMA_RULES_BUCKET</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">[</span><span class="sh">"</span><span class="s">SIGMA_RULES_BUCKET</span><span class="sh">"</span><span class="p">]</span>
<span class="n">SIGMA_RULES_PREFIX</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">SIGMA_RULES_PREFIX</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">sigma-rules/aws/</span><span class="sh">"</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">load_sigma_rules</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
    <span class="sh">"""</span><span class="s">Load Sigma rules from S3 bucket.</span><span class="sh">"""</span>
    <span class="n">rules</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="n">paginator</span> <span class="o">=</span> <span class="n">s3</span><span class="p">.</span><span class="nf">get_paginator</span><span class="p">(</span><span class="sh">"</span><span class="s">list_objects_v2</span><span class="sh">"</span><span class="p">)</span>

    <span class="k">for</span> <span class="n">page</span> <span class="ow">in</span> <span class="n">paginator</span><span class="p">.</span><span class="nf">paginate</span><span class="p">(</span>
        <span class="n">Bucket</span><span class="o">=</span><span class="n">SIGMA_RULES_BUCKET</span><span class="p">,</span>
        <span class="n">Prefix</span><span class="o">=</span><span class="n">SIGMA_RULES_PREFIX</span><span class="p">,</span>
    <span class="p">):</span>
        <span class="k">for</span> <span class="n">obj</span> <span class="ow">in</span> <span class="n">page</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Contents</span><span class="sh">"</span><span class="p">,</span> <span class="p">[]):</span>
            <span class="k">if</span> <span class="n">obj</span><span class="p">[</span><span class="sh">"</span><span class="s">Key</span><span class="sh">"</span><span class="p">].</span><span class="nf">endswith</span><span class="p">((</span><span class="sh">"</span><span class="s">.yml</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.yaml</span><span class="sh">"</span><span class="p">)):</span>
                <span class="n">response</span> <span class="o">=</span> <span class="n">s3</span><span class="p">.</span><span class="nf">get_object</span><span class="p">(</span>
                    <span class="n">Bucket</span><span class="o">=</span><span class="n">SIGMA_RULES_BUCKET</span><span class="p">,</span>
                    <span class="n">Key</span><span class="o">=</span><span class="n">obj</span><span class="p">[</span><span class="sh">"</span><span class="s">Key</span><span class="sh">"</span><span class="p">],</span>
                <span class="p">)</span>
                <span class="n">rule_content</span> <span class="o">=</span> <span class="n">response</span><span class="p">[</span><span class="sh">"</span><span class="s">Body</span><span class="sh">"</span><span class="p">].</span><span class="nf">read</span><span class="p">().</span><span class="nf">decode</span><span class="p">(</span><span class="sh">"</span><span class="s">utf-8</span><span class="sh">"</span><span class="p">)</span>
                <span class="k">try</span><span class="p">:</span>
                    <span class="n">rule</span> <span class="o">=</span> <span class="n">yaml</span><span class="p">.</span><span class="nf">safe_load</span><span class="p">(</span><span class="n">rule_content</span><span class="p">)</span>
                    <span class="n">rule</span><span class="p">[</span><span class="sh">"</span><span class="s">_source_key</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">obj</span><span class="p">[</span><span class="sh">"</span><span class="s">Key</span><span class="sh">"</span><span class="p">]</span>
                    <span class="n">rules</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">rule</span><span class="p">)</span>
                <span class="k">except</span> <span class="n">yaml</span><span class="p">.</span><span class="n">YAMLError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
                    <span class="n">logger</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span>
                        <span class="sh">"</span><span class="s">Failed to parse rule %s: %s</span><span class="sh">"</span><span class="p">,</span> <span class="n">obj</span><span class="p">[</span><span class="sh">"</span><span class="s">Key</span><span class="sh">"</span><span class="p">],</span> <span class="n">e</span>
                    <span class="p">)</span>

    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sh">"</span><span class="s">Loaded %d Sigma rules</span><span class="sh">"</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">rules</span><span class="p">))</span>
    <span class="k">return</span> <span class="n">rules</span>


<span class="k">def</span> <span class="nf">evaluate_selection</span><span class="p">(</span>
    <span class="n">event_detail</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span> <span class="n">selection</span><span class="p">:</span> <span class="nb">dict</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Evaluate a Sigma selection block against an event.</span><span class="sh">"""</span>
    <span class="k">for</span> <span class="n">field</span><span class="p">,</span> <span class="n">expected_values</span> <span class="ow">in</span> <span class="n">selection</span><span class="p">.</span><span class="nf">items</span><span class="p">():</span>
        <span class="c1"># Handle pipe modifiers like |contains
</span>        <span class="n">field_name</span> <span class="o">=</span> <span class="n">field</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="sh">"</span><span class="s">|</span><span class="sh">"</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
        <span class="n">modifier</span> <span class="o">=</span> <span class="n">field</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="sh">"</span><span class="s">|</span><span class="sh">"</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span> <span class="k">if</span> <span class="sh">"</span><span class="s">|</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">field</span> <span class="k">else</span> <span class="bp">None</span>

        <span class="c1"># Navigate nested fields with dot notation
</span>        <span class="n">value</span> <span class="o">=</span> <span class="n">event_detail</span>
        <span class="k">for</span> <span class="n">part</span> <span class="ow">in</span> <span class="n">field_name</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="sh">"</span><span class="s">.</span><span class="sh">"</span><span class="p">):</span>
            <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">dict</span><span class="p">):</span>
                <span class="n">value</span> <span class="o">=</span> <span class="n">value</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">part</span><span class="p">)</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">value</span> <span class="o">=</span> <span class="bp">None</span>
                <span class="k">break</span>

        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">False</span>

        <span class="k">if</span> <span class="ow">not</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">expected_values</span><span class="p">,</span> <span class="nb">list</span><span class="p">):</span>
            <span class="n">expected_values</span> <span class="o">=</span> <span class="p">[</span><span class="n">expected_values</span><span class="p">]</span>

        <span class="k">if</span> <span class="n">modifier</span> <span class="o">==</span> <span class="sh">"</span><span class="s">contains</span><span class="sh">"</span><span class="p">:</span>
            <span class="k">if</span> <span class="ow">not</span> <span class="nf">any</span><span class="p">(</span>
                <span class="nf">str</span><span class="p">(</span><span class="n">ev</span><span class="p">).</span><span class="nf">lower</span><span class="p">()</span> <span class="ow">in</span> <span class="nf">str</span><span class="p">(</span><span class="n">value</span><span class="p">).</span><span class="nf">lower</span><span class="p">()</span>
                <span class="k">for</span> <span class="n">ev</span> <span class="ow">in</span> <span class="n">expected_values</span>
            <span class="p">):</span>
                <span class="k">return</span> <span class="bp">False</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">if</span> <span class="nf">str</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">[</span><span class="nf">str</span><span class="p">(</span><span class="n">ev</span><span class="p">)</span> <span class="k">for</span> <span class="n">ev</span> <span class="ow">in</span> <span class="n">expected_values</span><span class="p">]:</span>
                <span class="k">return</span> <span class="bp">False</span>

    <span class="k">return</span> <span class="bp">True</span>


<span class="k">def</span> <span class="nf">evaluate_rule</span><span class="p">(</span>
    <span class="n">rule</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span> <span class="n">event_detail</span><span class="p">:</span> <span class="nb">dict</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]</span> <span class="o">|</span> <span class="bp">None</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Evaluate a single Sigma rule against a CloudTrail event.</span><span class="sh">"""</span>
    <span class="n">detection</span> <span class="o">=</span> <span class="n">rule</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">detection</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
    <span class="n">condition</span> <span class="o">=</span> <span class="n">detection</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">condition</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">)</span>

    <span class="c1"># Extract named detection blocks
</span>    <span class="n">blocks</span> <span class="o">=</span> <span class="p">{</span>
        <span class="n">k</span><span class="p">:</span> <span class="n">v</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">detection</span><span class="p">.</span><span class="nf">items</span><span class="p">()</span>
        <span class="k">if</span> <span class="n">k</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">(</span><span class="sh">"</span><span class="s">condition</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">timeframe</span><span class="sh">"</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="c1"># Evaluate each block
</span>    <span class="n">block_results</span> <span class="o">=</span> <span class="p">{}</span>
    <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">selection</span> <span class="ow">in</span> <span class="n">blocks</span><span class="p">.</span><span class="nf">items</span><span class="p">():</span>
        <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">selection</span><span class="p">,</span> <span class="nb">dict</span><span class="p">):</span>
            <span class="n">block_results</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="nf">evaluate_selection</span><span class="p">(</span>
                <span class="n">event_detail</span><span class="p">,</span> <span class="n">selection</span>
            <span class="p">)</span>
        <span class="k">elif</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">selection</span><span class="p">,</span> <span class="nb">list</span><span class="p">):</span>
            <span class="n">block_results</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="nf">any</span><span class="p">(</span>
                <span class="nf">evaluate_selection</span><span class="p">(</span><span class="n">event_detail</span><span class="p">,</span> <span class="n">sel</span><span class="p">)</span>
                <span class="k">for</span> <span class="n">sel</span> <span class="ow">in</span> <span class="n">selection</span>
                <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">sel</span><span class="p">,</span> <span class="nb">dict</span><span class="p">)</span>
            <span class="p">)</span>

    <span class="c1"># Simple condition evaluation
</span>    <span class="n">matched</span> <span class="o">=</span> <span class="bp">False</span>
    <span class="k">if</span> <span class="sh">"</span><span class="s"> and not </span><span class="sh">"</span> <span class="ow">in</span> <span class="n">condition</span><span class="p">:</span>
        <span class="n">parts</span> <span class="o">=</span> <span class="n">condition</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="sh">"</span><span class="s"> and not </span><span class="sh">"</span><span class="p">)</span>
        <span class="n">positive</span> <span class="o">=</span> <span class="n">parts</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">strip</span><span class="p">()</span>
        <span class="n">negative</span> <span class="o">=</span> <span class="n">parts</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nf">strip</span><span class="p">()</span>
        <span class="n">matched</span> <span class="o">=</span> <span class="p">(</span>
            <span class="n">block_results</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">positive</span><span class="p">,</span> <span class="bp">False</span><span class="p">)</span>
            <span class="ow">and</span> <span class="ow">not</span> <span class="n">block_results</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">negative</span><span class="p">,</span> <span class="bp">False</span><span class="p">)</span>
        <span class="p">)</span>
    <span class="k">elif</span> <span class="sh">"</span><span class="s"> and </span><span class="sh">"</span> <span class="ow">in</span> <span class="n">condition</span><span class="p">:</span>
        <span class="n">parts</span> <span class="o">=</span> <span class="n">condition</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="sh">"</span><span class="s"> and </span><span class="sh">"</span><span class="p">)</span>
        <span class="n">matched</span> <span class="o">=</span> <span class="nf">all</span><span class="p">(</span>
            <span class="n">block_results</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="nf">strip</span><span class="p">(),</span> <span class="bp">False</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">parts</span>
        <span class="p">)</span>
    <span class="k">elif</span> <span class="sh">"</span><span class="s"> or </span><span class="sh">"</span> <span class="ow">in</span> <span class="n">condition</span><span class="p">:</span>
        <span class="n">parts</span> <span class="o">=</span> <span class="n">condition</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="sh">"</span><span class="s"> or </span><span class="sh">"</span><span class="p">)</span>
        <span class="n">matched</span> <span class="o">=</span> <span class="nf">any</span><span class="p">(</span>
            <span class="n">block_results</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="nf">strip</span><span class="p">(),</span> <span class="bp">False</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">parts</span>
        <span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">matched</span> <span class="o">=</span> <span class="n">block_results</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">condition</span><span class="p">.</span><span class="nf">strip</span><span class="p">(),</span> <span class="bp">False</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">matched</span><span class="p">:</span>
        <span class="k">return</span> <span class="p">{</span>
            <span class="sh">"</span><span class="s">ruleTitle</span><span class="sh">"</span><span class="p">:</span> <span class="n">rule</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">title</span><span class="sh">"</span><span class="p">),</span>
            <span class="sh">"</span><span class="s">ruleId</span><span class="sh">"</span><span class="p">:</span> <span class="n">rule</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">id</span><span class="sh">"</span><span class="p">),</span>
            <span class="sh">"</span><span class="s">level</span><span class="sh">"</span><span class="p">:</span> <span class="n">rule</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">level</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">medium</span><span class="sh">"</span><span class="p">),</span>
            <span class="sh">"</span><span class="s">tags</span><span class="sh">"</span><span class="p">:</span> <span class="n">rule</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">tags</span><span class="sh">"</span><span class="p">,</span> <span class="p">[]),</span>
            <span class="sh">"</span><span class="s">description</span><span class="sh">"</span><span class="p">:</span> <span class="n">rule</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">description</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">),</span>
            <span class="sh">"</span><span class="s">sourceFile</span><span class="sh">"</span><span class="p">:</span> <span class="n">rule</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">_source_key</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">),</span>
        <span class="p">}</span>
    <span class="k">return</span> <span class="bp">None</span>


<span class="k">def</span> <span class="nf">handler</span><span class="p">(</span><span class="n">event</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">],</span> <span class="n">context</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
    <span class="sh">"""</span><span class="s">Evaluate incoming event against all loaded Sigma rules.</span><span class="sh">"""</span>
    <span class="n">rules</span> <span class="o">=</span> <span class="nf">load_sigma_rules</span><span class="p">()</span>
    <span class="n">event_detail</span> <span class="o">=</span> <span class="n">event</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">detail</span><span class="sh">"</span><span class="p">,</span> <span class="n">event</span><span class="p">)</span>

    <span class="n">matches</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">rule</span> <span class="ow">in</span> <span class="n">rules</span><span class="p">:</span>
        <span class="n">result</span> <span class="o">=</span> <span class="nf">evaluate_rule</span><span class="p">(</span><span class="n">rule</span><span class="p">,</span> <span class="n">event_detail</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">result</span><span class="p">:</span>
            <span class="n">matches</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>

    <span class="n">highest_level</span> <span class="o">=</span> <span class="sh">"</span><span class="s">informational</span><span class="sh">"</span>
    <span class="n">level_order</span> <span class="o">=</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">informational</span><span class="sh">"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="sh">"</span><span class="s">low</span><span class="sh">"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="sh">"</span><span class="s">medium</span><span class="sh">"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">high</span><span class="sh">"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="sh">"</span><span class="s">critical</span><span class="sh">"</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
    <span class="p">}</span>
    <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="n">matches</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">level_order</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span>
            <span class="k">match</span><span class="p">[</span><span class="sh">"</span><span class="s">level</span><span class="sh">"</span><span class="p">],</span> <span class="mi">0</span>
        <span class="p">)</span> <span class="o">&gt;</span> <span class="n">level_order</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">highest_level</span><span class="p">,</span> <span class="mi">0</span><span class="p">):</span>
            <span class="n">highest_level</span> <span class="o">=</span> <span class="k">match</span><span class="p">[</span><span class="sh">"</span><span class="s">level</span><span class="sh">"</span><span class="p">]</span>

    <span class="k">return</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">sigmaMatches</span><span class="sh">"</span><span class="p">:</span> <span class="nf">len</span><span class="p">(</span><span class="n">matches</span><span class="p">),</span>
        <span class="sh">"</span><span class="s">highestLevel</span><span class="sh">"</span><span class="p">:</span> <span class="n">highest_level</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">matchedRules</span><span class="sh">"</span><span class="p">:</span> <span class="n">matches</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">evaluatedRuleCount</span><span class="sh">"</span><span class="p">:</span> <span class="nf">len</span><span class="p">(</span><span class="n">rules</span><span class="p">),</span>
    <span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="complete-pipeline-end-to-end-automated-response">Complete Pipeline: End-to-End Automated Response</h2>

<p>Let us walk through a concrete scenario to see how all the pieces work together.</p>

<h3 id="scenario-compromised-iam-credentials">Scenario: Compromised IAM Credentials</h3>

<ol>
  <li>
    <p><strong>Detection</strong> – An attacker uses stolen IAM credentials from an unusual geographic location. GuardDuty detects the anomaly and generates an <code class="language-plaintext highlighter-rouge">UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B</code> finding with severity 8.</p>
  </li>
  <li>
    <p><strong>Event Routing</strong> – The GuardDuty finding is published to the default EventBridge bus. Our rule matches on <code class="language-plaintext highlighter-rouge">severity &gt;= 7</code> and <code class="language-plaintext highlighter-rouge">UnauthorizedAccess</code> prefix, routing the event to the Step Functions state machine.</p>
  </li>
  <li>
    <p><strong>Classification</strong> – The classify Lambda function examines the finding type, extracts the source IP, affected IAM principal, and affected resources. It tags the event as <code class="language-plaintext highlighter-rouge">credential-compromise</code> with severity <code class="language-plaintext highlighter-rouge">CRITICAL</code>.</p>
  </li>
  <li><strong>Parallel Enrichment</strong> – Two Lambda functions execute simultaneously:
    <ul>
      <li>The MISP enrichment function searches for the source IP across all community feeds. It finds the IP listed in abuse.ch feeds as part of a known credential-stuffing botnet.</li>
      <li>The OpenCTI enrichment function queries the STIX2 knowledge base and finds the IP associated with a threat actor group known for targeting AWS environments, mapped to MITRE ATT&amp;CK technique T1078 (Valid Accounts).</li>
    </ul>
  </li>
  <li>
    <p><strong>Sigma Evaluation</strong> – The Sigma rule evaluator checks the CloudTrail event associated with the finding. It matches the “Console Login from Unusual Country” rule at <code class="language-plaintext highlighter-rouge">high</code> level.</p>
  </li>
  <li>
    <p><strong>Response Decision</strong> – The Choice state evaluates: severity is <code class="language-plaintext highlighter-rouge">CRITICAL</code> and IOC match is <code class="language-plaintext highlighter-rouge">true</code>. This triggers the automated critical response path without human approval.</p>
  </li>
  <li><strong>Automated Response</strong> – Three actions execute in parallel:
    <ul>
      <li>WAF IP set updated to block the source IP.</li>
      <li>IAM access key deactivated and session tokens revoked for the compromised user.</li>
      <li>Security team notified via SNS with full enrichment context.</li>
    </ul>
  </li>
  <li><strong>Logging</strong> – The complete incident timeline, enrichment data, and response actions are written to Security Lake in OCSF format.</li>
</ol>

<p>Total elapsed time from detection to response: under 30 seconds.</p>

<h3 id="scenario-unauthorized-api-reconnaissance">Scenario: Unauthorized API Reconnaissance</h3>

<p>An attacker begins enumerating your AWS environment by calling <code class="language-plaintext highlighter-rouge">DescribeInstances</code>, <code class="language-plaintext highlighter-rouge">ListBuckets</code>, <code class="language-plaintext highlighter-rouge">GetAccountAuthorizationDetails</code>, and other reconnaissance APIs from a newly created IAM role. CloudTrail captures every call. EventBridge matches on the suspicious API pattern. The Step Functions workflow enriches the source with MISP (no match – this is a new IP) and evaluates Sigma rules (matches “AWS API Reconnaissance Activity” at <code class="language-plaintext highlighter-rouge">medium</code> level). Because the severity is medium and there is no confirmed IOC, the workflow routes to the log-and-monitor path, creating a Security Hub finding and alerting the SOC for manual investigation.</p>

<h3 id="scenario-s3-bucket-policy-made-public">Scenario: S3 Bucket Policy Made Public</h3>

<p>AWS Config detects a non-compliant resource change when a developer accidentally makes an S3 bucket publicly accessible. The Config compliance change event hits EventBridge, triggers the state machine, and the response workflow immediately reverts the bucket policy and notifies the bucket owner. No threat intel enrichment is needed here – the response is purely policy-driven.</p>

<h2 id="terraform-deploying-the-complete-pipeline">Terraform: Deploying the Complete Pipeline</h2>

<p>Infrastructure as Code ensures your security automation is reproducible, version-controlled, and auditable. Here is the Terraform configuration for the complete pipeline:</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
</pre></td><td class="rouge-code"><pre><span class="c1"># main.tf - Event-Driven Security Automation Pipeline</span>

<span class="nx">terraform</span> <span class="p">{</span>
  <span class="nx">required_version</span> <span class="o">=</span> <span class="s2">"&gt;= 1.5"</span>
  <span class="nx">required_providers</span> <span class="p">{</span>
    <span class="nx">aws</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">source</span>  <span class="o">=</span> <span class="s2">"hashicorp/aws"</span>
      <span class="nx">version</span> <span class="o">=</span> <span class="s2">"~&gt; 5.0"</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">backend</span> <span class="s2">"s3"</span> <span class="p">{</span>
    <span class="nx">bucket</span> <span class="o">=</span> <span class="s2">"skynet-tf-state-prod"</span>
    <span class="nx">key</span>    <span class="o">=</span> <span class="s2">"security-automation/terraform.tfstate"</span>
    <span class="nx">region</span> <span class="o">=</span> <span class="s2">"us-east-1"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">provider</span> <span class="s2">"aws"</span> <span class="p">{</span>
  <span class="nx">region</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">aws_region</span>

  <span class="nx">default_tags</span> <span class="p">{</span>
    <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"security-automation"</span>
      <span class="nx">Environment</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">environment</span>
      <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"security-team"</span>
      <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security"</span>
      <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"internal"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">variable</span> <span class="s2">"aws_region"</span> <span class="p">{</span>
  <span class="nx">default</span> <span class="o">=</span> <span class="s2">"us-east-1"</span>
<span class="p">}</span>

<span class="nx">variable</span> <span class="s2">"environment"</span> <span class="p">{</span>
  <span class="nx">default</span> <span class="o">=</span> <span class="s2">"prod"</span>
<span class="p">}</span>

<span class="nx">variable</span> <span class="s2">"misp_url"</span> <span class="p">{</span>
  <span class="nx">type</span>      <span class="o">=</span> <span class="nx">string</span>
  <span class="nx">sensitive</span> <span class="o">=</span> <span class="kc">true</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># Custom EventBridge Bus</span>
<span class="c1"># -------------------------------------------------------------------</span>
<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_event_bus"</span> <span class="s2">"security"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"security-automation-bus"</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"security-automation"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># EventBridge Rules</span>
<span class="c1"># -------------------------------------------------------------------</span>
<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_event_rule"</span> <span class="s2">"guardduty_high"</span> <span class="p">{</span>
  <span class="nx">name</span>           <span class="o">=</span> <span class="s2">"guardduty-high-severity"</span>
  <span class="nx">description</span>    <span class="o">=</span> <span class="s2">"Capture high-severity GuardDuty findings"</span>
  <span class="nx">event_bus_name</span> <span class="o">=</span> <span class="s2">"default"</span>

  <span class="nx">event_pattern</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">source</span>      <span class="o">=</span> <span class="p">[</span><span class="s2">"aws.guardduty"</span><span class="p">]</span>
    <span class="nx">detail-type</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"GuardDuty Finding"</span><span class="p">]</span>
    <span class="nx">detail</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">severity</span> <span class="o">=</span> <span class="p">[{</span> <span class="nx">numeric</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"&gt;="</span><span class="p">,</span> <span class="mi">7</span><span class="p">]</span> <span class="p">}]</span>
    <span class="p">}</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_event_rule"</span> <span class="s2">"securityhub_critical"</span> <span class="p">{</span>
  <span class="nx">name</span>           <span class="o">=</span> <span class="s2">"securityhub-critical-findings"</span>
  <span class="nx">description</span>    <span class="o">=</span> <span class="s2">"Capture critical Security Hub findings"</span>
  <span class="nx">event_bus_name</span> <span class="o">=</span> <span class="s2">"default"</span>

  <span class="nx">event_pattern</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">source</span>      <span class="o">=</span> <span class="p">[</span><span class="s2">"aws.securityhub"</span><span class="p">]</span>
    <span class="nx">detail-type</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"Security Hub Findings - Imported"</span><span class="p">]</span>
    <span class="nx">detail</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">findings</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nx">Severity</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">Label</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"CRITICAL"</span><span class="p">,</span> <span class="s2">"HIGH"</span><span class="p">]</span> <span class="p">}</span>
        <span class="nx">Workflow</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">Status</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"NEW"</span><span class="p">]</span> <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_event_rule"</span> <span class="s2">"cloudtrail_suspicious"</span> <span class="p">{</span>
  <span class="nx">name</span>           <span class="o">=</span> <span class="s2">"cloudtrail-suspicious-api"</span>
  <span class="nx">description</span>    <span class="o">=</span> <span class="s2">"Capture suspicious CloudTrail API calls"</span>
  <span class="nx">event_bus_name</span> <span class="o">=</span> <span class="s2">"default"</span>

  <span class="nx">event_pattern</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">source</span>      <span class="o">=</span> <span class="p">[</span><span class="s2">"aws.cloudtrail"</span><span class="p">]</span>
    <span class="nx">detail-type</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"AWS API Call via CloudTrail"</span><span class="p">]</span>
    <span class="nx">detail</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">eventName</span> <span class="o">=</span> <span class="p">[</span>
        <span class="s2">"StopLogging"</span><span class="p">,</span> <span class="s2">"DeleteTrail"</span><span class="p">,</span> <span class="s2">"DeleteDetector"</span><span class="p">,</span>
        <span class="s2">"DisableKey"</span><span class="p">,</span> <span class="s2">"DeleteFlowLogs"</span><span class="p">,</span> <span class="s2">"PutBucketAcl"</span><span class="p">,</span>
      <span class="p">]</span>
    <span class="p">}</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># Step Functions State Machine</span>
<span class="c1"># -------------------------------------------------------------------</span>
<span class="nx">resource</span> <span class="s2">"aws_sfn_state_machine"</span> <span class="s2">"incident_response"</span> <span class="p">{</span>
  <span class="nx">name</span>     <span class="o">=</span> <span class="s2">"security-incident-response"</span>
  <span class="nx">role_arn</span> <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">step_functions</span><span class="p">.</span><span class="nx">arn</span>

  <span class="nx">definition</span> <span class="o">=</span> <span class="nx">templatefile</span><span class="p">(</span><span class="s2">"${path.module}/state-machine.asl.json"</span><span class="p">,</span> <span class="p">{</span>
    <span class="nx">account_id</span>   <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">aws_caller_identity</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">account_id</span>
    <span class="nx">region</span>       <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">aws_region</span>
    <span class="nx">approval_queue</span> <span class="o">=</span> <span class="nx">aws_sqs_queue</span><span class="p">.</span><span class="nx">approval</span><span class="p">.</span><span class="nx">url</span>
  <span class="p">})</span>

  <span class="nx">logging_configuration</span> <span class="p">{</span>
    <span class="nx">log_destination</span>        <span class="o">=</span> <span class="s2">"${aws_cloudwatch_log_group.step_functions.arn}:*"</span>
    <span class="nx">include_execution_data</span> <span class="o">=</span> <span class="kc">true</span>
    <span class="nx">level</span>                  <span class="o">=</span> <span class="s2">"ERROR"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># EventBridge Targets -&gt; Step Functions</span>
<span class="c1"># -------------------------------------------------------------------</span>
<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_event_target"</span> <span class="s2">"guardduty_to_sfn"</span> <span class="p">{</span>
  <span class="nx">rule</span>      <span class="o">=</span> <span class="nx">aws_cloudwatch_event_rule</span><span class="p">.</span><span class="nx">guardduty_high</span><span class="p">.</span><span class="nx">name</span>
  <span class="nx">target_id</span> <span class="o">=</span> <span class="s2">"guardduty-to-step-functions"</span>
  <span class="nx">arn</span>       <span class="o">=</span> <span class="nx">aws_sfn_state_machine</span><span class="p">.</span><span class="nx">incident_response</span><span class="p">.</span><span class="nx">arn</span>
  <span class="nx">role_arn</span>  <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">eventbridge_sfn</span><span class="p">.</span><span class="nx">arn</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_event_target"</span> <span class="s2">"securityhub_to_sfn"</span> <span class="p">{</span>
  <span class="nx">rule</span>      <span class="o">=</span> <span class="nx">aws_cloudwatch_event_rule</span><span class="p">.</span><span class="nx">securityhub_critical</span><span class="p">.</span><span class="nx">name</span>
  <span class="nx">target_id</span> <span class="o">=</span> <span class="s2">"securityhub-to-step-functions"</span>
  <span class="nx">arn</span>       <span class="o">=</span> <span class="nx">aws_sfn_state_machine</span><span class="p">.</span><span class="nx">incident_response</span><span class="p">.</span><span class="nx">arn</span>
  <span class="nx">role_arn</span>  <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">eventbridge_sfn</span><span class="p">.</span><span class="nx">arn</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_event_target"</span> <span class="s2">"cloudtrail_to_sfn"</span> <span class="p">{</span>
  <span class="nx">rule</span>      <span class="o">=</span> <span class="nx">aws_cloudwatch_event_rule</span><span class="p">.</span><span class="nx">cloudtrail_suspicious</span><span class="p">.</span><span class="nx">name</span>
  <span class="nx">target_id</span> <span class="o">=</span> <span class="s2">"cloudtrail-to-step-functions"</span>
  <span class="nx">arn</span>       <span class="o">=</span> <span class="nx">aws_sfn_state_machine</span><span class="p">.</span><span class="nx">incident_response</span><span class="p">.</span><span class="nx">arn</span>
  <span class="nx">role_arn</span>  <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">eventbridge_sfn</span><span class="p">.</span><span class="nx">arn</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># Lambda Functions</span>
<span class="c1"># -------------------------------------------------------------------</span>
<span class="nx">resource</span> <span class="s2">"aws_lambda_function"</span> <span class="s2">"misp_enrichment"</span> <span class="p">{</span>
  <span class="nx">function_name</span> <span class="o">=</span> <span class="s2">"misp-enrichment"</span>
  <span class="nx">role</span>          <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">lambda_enrichment</span><span class="p">.</span><span class="nx">arn</span>
  <span class="nx">handler</span>       <span class="o">=</span> <span class="s2">"misp_enrichment.handler"</span>
  <span class="nx">runtime</span>       <span class="o">=</span> <span class="s2">"python3.12"</span>
  <span class="nx">timeout</span>       <span class="o">=</span> <span class="mi">30</span>
  <span class="nx">memory_size</span>   <span class="o">=</span> <span class="mi">256</span>

  <span class="nx">filename</span>         <span class="o">=</span> <span class="nx">data</span><span class="err">.</span><span class="nx">archive_file</span><span class="p">.</span><span class="nx">misp_enrichment</span><span class="p">.</span><span class="nx">output_path</span>
  <span class="nx">source_code_hash</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">archive_file</span><span class="p">.</span><span class="nx">misp_enrichment</span><span class="p">.</span><span class="nx">output_base64sha256</span>

  <span class="nx">environment</span> <span class="p">{</span>
    <span class="nx">variables</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">MISP_URL</span>           <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">misp_url</span>
      <span class="nx">MISP_API_KEY_PARAM</span> <span class="o">=</span> <span class="s2">"/security-automation/${var.environment}/misp-api-key"</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">layers</span> <span class="o">=</span> <span class="p">[</span><span class="nx">aws_lambda_layer_version</span><span class="p">.</span><span class="nx">common_deps</span><span class="p">.</span><span class="nx">arn</span><span class="p">]</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_lambda_function"</span> <span class="s2">"opencti_enrichment"</span> <span class="p">{</span>
  <span class="nx">function_name</span> <span class="o">=</span> <span class="s2">"opencti-enrichment"</span>
  <span class="nx">role</span>          <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">lambda_enrichment</span><span class="p">.</span><span class="nx">arn</span>
  <span class="nx">handler</span>       <span class="o">=</span> <span class="s2">"opencti_enrichment.handler"</span>
  <span class="nx">runtime</span>       <span class="o">=</span> <span class="s2">"python3.12"</span>
  <span class="nx">timeout</span>       <span class="o">=</span> <span class="mi">30</span>
  <span class="nx">memory_size</span>   <span class="o">=</span> <span class="mi">256</span>

  <span class="nx">filename</span>         <span class="o">=</span> <span class="nx">data</span><span class="err">.</span><span class="nx">archive_file</span><span class="p">.</span><span class="nx">opencti_enrichment</span><span class="p">.</span><span class="nx">output_path</span>
  <span class="nx">source_code_hash</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">archive_file</span><span class="p">.</span><span class="nx">opencti_enrichment</span><span class="p">.</span><span class="nx">output_base64sha256</span>

  <span class="nx">environment</span> <span class="p">{</span>
    <span class="nx">variables</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">OPENCTI_URL</span>         <span class="o">=</span> <span class="s2">"https://opencti.internal.yourdomain.com"</span>
      <span class="nx">OPENCTI_TOKEN_PARAM</span> <span class="o">=</span> <span class="s2">"/security-automation/${var.environment}/opencti-token"</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">layers</span> <span class="o">=</span> <span class="p">[</span><span class="nx">aws_lambda_layer_version</span><span class="p">.</span><span class="nx">common_deps</span><span class="p">.</span><span class="nx">arn</span><span class="p">]</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_lambda_function"</span> <span class="s2">"sigma_evaluator"</span> <span class="p">{</span>
  <span class="nx">function_name</span> <span class="o">=</span> <span class="s2">"sigma-rule-evaluator"</span>
  <span class="nx">role</span>          <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">lambda_enrichment</span><span class="p">.</span><span class="nx">arn</span>
  <span class="nx">handler</span>       <span class="o">=</span> <span class="s2">"sigma_evaluator.handler"</span>
  <span class="nx">runtime</span>       <span class="o">=</span> <span class="s2">"python3.12"</span>
  <span class="nx">timeout</span>       <span class="o">=</span> <span class="mi">60</span>
  <span class="nx">memory_size</span>   <span class="o">=</span> <span class="mi">512</span>

  <span class="nx">filename</span>         <span class="o">=</span> <span class="nx">data</span><span class="err">.</span><span class="nx">archive_file</span><span class="p">.</span><span class="nx">sigma_evaluator</span><span class="p">.</span><span class="nx">output_path</span>
  <span class="nx">source_code_hash</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">archive_file</span><span class="p">.</span><span class="nx">sigma_evaluator</span><span class="p">.</span><span class="nx">output_base64sha256</span>

  <span class="nx">environment</span> <span class="p">{</span>
    <span class="nx">variables</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">SIGMA_RULES_BUCKET</span> <span class="o">=</span> <span class="nx">aws_s3_bucket</span><span class="p">.</span><span class="nx">sigma_rules</span><span class="p">.</span><span class="nx">id</span>
      <span class="nx">SIGMA_RULES_PREFIX</span> <span class="o">=</span> <span class="s2">"sigma-rules/aws/"</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">layers</span> <span class="o">=</span> <span class="p">[</span><span class="nx">aws_lambda_layer_version</span><span class="p">.</span><span class="nx">common_deps</span><span class="p">.</span><span class="nx">arn</span><span class="p">]</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_lambda_function"</span> <span class="s2">"block_ip"</span> <span class="p">{</span>
  <span class="nx">function_name</span> <span class="o">=</span> <span class="s2">"block-ip-waf"</span>
  <span class="nx">role</span>          <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">lambda_response</span><span class="p">.</span><span class="nx">arn</span>
  <span class="nx">handler</span>       <span class="o">=</span> <span class="s2">"block_ip.handler"</span>
  <span class="nx">runtime</span>       <span class="o">=</span> <span class="s2">"python3.12"</span>
  <span class="nx">timeout</span>       <span class="o">=</span> <span class="mi">15</span>
  <span class="nx">memory_size</span>   <span class="o">=</span> <span class="mi">128</span>

  <span class="nx">filename</span>         <span class="o">=</span> <span class="nx">data</span><span class="err">.</span><span class="nx">archive_file</span><span class="p">.</span><span class="nx">block_ip</span><span class="p">.</span><span class="nx">output_path</span>
  <span class="nx">source_code_hash</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">archive_file</span><span class="p">.</span><span class="nx">block_ip</span><span class="p">.</span><span class="nx">output_base64sha256</span>

  <span class="nx">environment</span> <span class="p">{</span>
    <span class="nx">variables</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">WAF_BLOCK_IP_SET_ID</span>   <span class="o">=</span> <span class="nx">aws_wafv2_ip_set</span><span class="p">.</span><span class="nx">blocked_ips</span><span class="p">.</span><span class="nx">id</span>
      <span class="nx">WAF_BLOCK_IP_SET_NAME</span> <span class="o">=</span> <span class="nx">aws_wafv2_ip_set</span><span class="p">.</span><span class="nx">blocked_ips</span><span class="p">.</span><span class="nx">name</span>
      <span class="nx">WAF_SCOPE</span>             <span class="o">=</span> <span class="s2">"REGIONAL"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># WAF IP Set for Automated Blocking</span>
<span class="c1"># -------------------------------------------------------------------</span>
<span class="nx">resource</span> <span class="s2">"aws_wafv2_ip_set"</span> <span class="s2">"blocked_ips"</span> <span class="p">{</span>
  <span class="nx">name</span>               <span class="o">=</span> <span class="s2">"automated-blocked-ips"</span>
  <span class="nx">scope</span>              <span class="o">=</span> <span class="s2">"REGIONAL"</span>
  <span class="nx">ip_address_version</span> <span class="o">=</span> <span class="s2">"IPV4"</span>
  <span class="nx">addresses</span>          <span class="o">=</span> <span class="p">[]</span>

  <span class="nx">description</span> <span class="o">=</span> <span class="s2">"IPs blocked by automated security response"</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># S3 Bucket for Sigma Rules</span>
<span class="c1"># -------------------------------------------------------------------</span>
<span class="nx">resource</span> <span class="s2">"aws_s3_bucket"</span> <span class="s2">"sigma_rules"</span> <span class="p">{</span>
  <span class="nx">bucket</span> <span class="o">=</span> <span class="s2">"security-automation-sigma-rules-${data.aws_caller_identity.current.account_id}"</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_s3_bucket_versioning"</span> <span class="s2">"sigma_rules"</span> <span class="p">{</span>
  <span class="nx">bucket</span> <span class="o">=</span> <span class="nx">aws_s3_bucket</span><span class="p">.</span><span class="nx">sigma_rules</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">versioning_configuration</span> <span class="p">{</span>
    <span class="nx">status</span> <span class="o">=</span> <span class="s2">"Enabled"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_s3_bucket_server_side_encryption_configuration"</span> <span class="s2">"sigma_rules"</span> <span class="p">{</span>
  <span class="nx">bucket</span> <span class="o">=</span> <span class="nx">aws_s3_bucket</span><span class="p">.</span><span class="nx">sigma_rules</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">rule</span> <span class="p">{</span>
    <span class="nx">apply_server_side_encryption_by_default</span> <span class="p">{</span>
      <span class="nx">sse_algorithm</span> <span class="o">=</span> <span class="s2">"aws:kms"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_s3_bucket_public_access_block"</span> <span class="s2">"sigma_rules"</span> <span class="p">{</span>
  <span class="nx">bucket</span> <span class="o">=</span> <span class="nx">aws_s3_bucket</span><span class="p">.</span><span class="nx">sigma_rules</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">block_public_acls</span>       <span class="o">=</span> <span class="kc">true</span>
  <span class="nx">block_public_policy</span>     <span class="o">=</span> <span class="kc">true</span>
  <span class="nx">ignore_public_acls</span>      <span class="o">=</span> <span class="kc">true</span>
  <span class="nx">restrict_public_buckets</span> <span class="o">=</span> <span class="kc">true</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># SQS Queue for Human Approval</span>
<span class="c1"># -------------------------------------------------------------------</span>
<span class="nx">resource</span> <span class="s2">"aws_sqs_queue"</span> <span class="s2">"approval"</span> <span class="p">{</span>
  <span class="nx">name</span>                       <span class="o">=</span> <span class="s2">"security-incident-approval"</span>
  <span class="nx">message_retention_seconds</span>  <span class="o">=</span> <span class="mi">86400</span>
  <span class="nx">visibility_timeout_seconds</span> <span class="o">=</span> <span class="mi">3600</span>

  <span class="nx">sqs_managed_sse_enabled</span> <span class="o">=</span> <span class="kc">true</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># CloudWatch Log Group for Step Functions</span>
<span class="c1"># -------------------------------------------------------------------</span>
<span class="nx">resource</span> <span class="s2">"aws_cloudwatch_log_group"</span> <span class="s2">"step_functions"</span> <span class="p">{</span>
  <span class="nx">name</span>              <span class="o">=</span> <span class="s2">"/aws/stepfunctions/security-incident-response"</span>
  <span class="nx">retention_in_days</span> <span class="o">=</span> <span class="mi">90</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># SNS Topic for Security Alerts</span>
<span class="c1"># -------------------------------------------------------------------</span>
<span class="nx">resource</span> <span class="s2">"aws_sns_topic"</span> <span class="s2">"security_alerts"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"security-automation-alerts"</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># IAM Roles (abbreviated for clarity)</span>
<span class="c1"># -------------------------------------------------------------------</span>
<span class="nx">data</span> <span class="s2">"aws_caller_identity"</span> <span class="s2">"current"</span> <span class="p">{}</span>

<span class="nx">resource</span> <span class="s2">"aws_iam_role"</span> <span class="s2">"step_functions"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"security-automation-step-functions"</span>

  <span class="nx">assume_role_policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">Version</span> <span class="o">=</span> <span class="s2">"2012-10-17"</span>
    <span class="nx">Statement</span> <span class="o">=</span> <span class="p">[{</span>
      <span class="nx">Action</span> <span class="o">=</span> <span class="s2">"sts:AssumeRole"</span>
      <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
      <span class="nx">Principal</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nx">Service</span> <span class="o">=</span> <span class="s2">"states.amazonaws.com"</span>
      <span class="p">}</span>
    <span class="p">}]</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_iam_role"</span> <span class="s2">"eventbridge_sfn"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"security-automation-eventbridge-sfn"</span>

  <span class="nx">assume_role_policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">Version</span> <span class="o">=</span> <span class="s2">"2012-10-17"</span>
    <span class="nx">Statement</span> <span class="o">=</span> <span class="p">[{</span>
      <span class="nx">Action</span> <span class="o">=</span> <span class="s2">"sts:AssumeRole"</span>
      <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
      <span class="nx">Principal</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nx">Service</span> <span class="o">=</span> <span class="s2">"events.amazonaws.com"</span>
      <span class="p">}</span>
    <span class="p">}]</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_iam_role_policy"</span> <span class="s2">"eventbridge_start_sfn"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"start-step-functions"</span>
  <span class="nx">role</span> <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">eventbridge_sfn</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">Version</span> <span class="o">=</span> <span class="s2">"2012-10-17"</span>
    <span class="nx">Statement</span> <span class="o">=</span> <span class="p">[{</span>
      <span class="nx">Effect</span>   <span class="o">=</span> <span class="s2">"Allow"</span>
      <span class="nx">Action</span>   <span class="o">=</span> <span class="s2">"states:StartExecution"</span>
      <span class="nx">Resource</span> <span class="o">=</span> <span class="nx">aws_sfn_state_machine</span><span class="p">.</span><span class="nx">incident_response</span><span class="p">.</span><span class="nx">arn</span>
    <span class="p">}]</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_iam_role"</span> <span class="s2">"lambda_enrichment"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"security-automation-lambda-enrichment"</span>

  <span class="nx">assume_role_policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">Version</span> <span class="o">=</span> <span class="s2">"2012-10-17"</span>
    <span class="nx">Statement</span> <span class="o">=</span> <span class="p">[{</span>
      <span class="nx">Action</span> <span class="o">=</span> <span class="s2">"sts:AssumeRole"</span>
      <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
      <span class="nx">Principal</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nx">Service</span> <span class="o">=</span> <span class="s2">"lambda.amazonaws.com"</span>
      <span class="p">}</span>
    <span class="p">}]</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_iam_role_policy"</span> <span class="s2">"lambda_enrichment"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"enrichment-permissions"</span>
  <span class="nx">role</span> <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">lambda_enrichment</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">Version</span> <span class="o">=</span> <span class="s2">"2012-10-17"</span>
    <span class="nx">Statement</span> <span class="o">=</span> <span class="p">[</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"ssm:GetParameter"</span><span class="p">,</span>
          <span class="s2">"ssm:GetParameters"</span><span class="p">,</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="s2">"arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/security-automation/*"</span>
      <span class="p">},</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"s3:GetObject"</span><span class="p">,</span>
          <span class="s2">"s3:ListBucket"</span><span class="p">,</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="p">[</span>
          <span class="nx">aws_s3_bucket</span><span class="p">.</span><span class="nx">sigma_rules</span><span class="p">.</span><span class="nx">arn</span><span class="p">,</span>
          <span class="s2">"${aws_s3_bucket.sigma_rules.arn}/*"</span><span class="p">,</span>
        <span class="p">]</span>
      <span class="p">},</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"logs:CreateLogGroup"</span><span class="p">,</span>
          <span class="s2">"logs:CreateLogStream"</span><span class="p">,</span>
          <span class="s2">"logs:PutLogEvents"</span><span class="p">,</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="s2">"arn:aws:logs:*:*:*"</span>
      <span class="p">},</span>
    <span class="p">]</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_iam_role"</span> <span class="s2">"lambda_response"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"security-automation-lambda-response"</span>

  <span class="nx">assume_role_policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">Version</span> <span class="o">=</span> <span class="s2">"2012-10-17"</span>
    <span class="nx">Statement</span> <span class="o">=</span> <span class="p">[{</span>
      <span class="nx">Action</span> <span class="o">=</span> <span class="s2">"sts:AssumeRole"</span>
      <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
      <span class="nx">Principal</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nx">Service</span> <span class="o">=</span> <span class="s2">"lambda.amazonaws.com"</span>
      <span class="p">}</span>
    <span class="p">}]</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_iam_role_policy"</span> <span class="s2">"lambda_response"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"response-permissions"</span>
  <span class="nx">role</span> <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">lambda_response</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">Version</span> <span class="o">=</span> <span class="s2">"2012-10-17"</span>
    <span class="nx">Statement</span> <span class="o">=</span> <span class="p">[</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"wafv2:GetIPSet"</span><span class="p">,</span>
          <span class="s2">"wafv2:UpdateIPSet"</span><span class="p">,</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="nx">aws_wafv2_ip_set</span><span class="p">.</span><span class="nx">blocked_ips</span><span class="p">.</span><span class="nx">arn</span>
      <span class="p">},</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"ec2:CreateNetworkAclEntry"</span><span class="p">,</span>
          <span class="s2">"ec2:DescribeNetworkAcls"</span><span class="p">,</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="s2">"*"</span>
      <span class="p">},</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"iam:UpdateAccessKey"</span><span class="p">,</span>
          <span class="s2">"iam:ListAccessKeys"</span><span class="p">,</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="s2">"arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/*"</span>
      <span class="p">},</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"sns:Publish"</span><span class="p">,</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="nx">aws_sns_topic</span><span class="p">.</span><span class="nx">security_alerts</span><span class="p">.</span><span class="nx">arn</span>
      <span class="p">},</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"logs:CreateLogGroup"</span><span class="p">,</span>
          <span class="s2">"logs:CreateLogStream"</span><span class="p">,</span>
          <span class="s2">"logs:PutLogEvents"</span><span class="p">,</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="s2">"arn:aws:logs:*:*:*"</span>
      <span class="p">},</span>
    <span class="p">]</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># Outputs</span>
<span class="c1"># -------------------------------------------------------------------</span>
<span class="nx">output</span> <span class="s2">"event_bus_arn"</span> <span class="p">{</span>
  <span class="nx">value</span> <span class="o">=</span> <span class="nx">aws_cloudwatch_event_bus</span><span class="p">.</span><span class="nx">security</span><span class="p">.</span><span class="nx">arn</span>
<span class="p">}</span>

<span class="nx">output</span> <span class="s2">"state_machine_arn"</span> <span class="p">{</span>
  <span class="nx">value</span> <span class="o">=</span> <span class="nx">aws_sfn_state_machine</span><span class="p">.</span><span class="nx">incident_response</span><span class="p">.</span><span class="nx">arn</span>
<span class="p">}</span>

<span class="nx">output</span> <span class="s2">"sigma_rules_bucket"</span> <span class="p">{</span>
  <span class="nx">value</span> <span class="o">=</span> <span class="nx">aws_s3_bucket</span><span class="p">.</span><span class="nx">sigma_rules</span><span class="p">.</span><span class="nx">id</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="security-lake-integration-for-long-term-analytics">Security Lake Integration for Long-Term Analytics</h2>

<p>AWS Security Lake normalizes all security data into the Open Cybersecurity Schema Framework (OCSF) format and stores it in Apache Parquet in S3. This gives you a unified data lake where GuardDuty findings, CloudTrail events, VPC Flow Logs, and your custom automation logs all share the same schema.</p>

<h3 id="writing-automation-results-to-security-lake">Writing Automation Results to Security Lake</h3>

<p>Your Step Functions workflow should write every incident response action to Security Lake for long-term analytics:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="n">json</span>
<span class="kn">import</span> <span class="n">os</span>
<span class="kn">import</span> <span class="n">logging</span>
<span class="kn">from</span> <span class="n">datetime</span> <span class="kn">import</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">timezone</span>
<span class="kn">from</span> <span class="n">typing</span> <span class="kn">import</span> <span class="n">Any</span>

<span class="kn">import</span> <span class="n">boto3</span>

<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="p">.</span><span class="nf">getLogger</span><span class="p">()</span>
<span class="n">logger</span><span class="p">.</span><span class="nf">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="p">.</span><span class="n">INFO</span><span class="p">)</span>

<span class="n">s3</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="nf">client</span><span class="p">(</span><span class="sh">"</span><span class="s">s3</span><span class="sh">"</span><span class="p">)</span>

<span class="n">SECURITY_LAKE_BUCKET</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">[</span><span class="sh">"</span><span class="s">SECURITY_LAKE_BUCKET</span><span class="sh">"</span><span class="p">]</span>
<span class="n">SECURITY_LAKE_PREFIX</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span>
    <span class="sh">"</span><span class="s">SECURITY_LAKE_PREFIX</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">ext/custom-security-automation/</span><span class="sh">"</span>
<span class="p">)</span>


<span class="k">def</span> <span class="nf">to_ocsf_detection_finding</span><span class="p">(</span><span class="n">event</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Convert automation result to OCSF Detection Finding format.</span><span class="sh">"""</span>
    <span class="n">now</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">.</span><span class="nf">now</span><span class="p">(</span><span class="n">timezone</span><span class="p">.</span><span class="n">utc</span><span class="p">)</span>

    <span class="k">return</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">metadata</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span>
            <span class="sh">"</span><span class="s">version</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">1.1.0</span><span class="sh">"</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">product</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span>
                <span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">Security Automation Pipeline</span><span class="sh">"</span><span class="p">,</span>
                <span class="sh">"</span><span class="s">vendor_name</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">red-team.sh</span><span class="sh">"</span><span class="p">,</span>
                <span class="sh">"</span><span class="s">feature</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">Automated Incident Response</span><span class="sh">"</span><span class="p">},</span>
            <span class="p">},</span>
            <span class="sh">"</span><span class="s">log_name</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">security-automation</span><span class="sh">"</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">logged_time</span><span class="sh">"</span><span class="p">:</span> <span class="nf">int</span><span class="p">(</span><span class="n">now</span><span class="p">.</span><span class="nf">timestamp</span><span class="p">()</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">),</span>
        <span class="p">},</span>
        <span class="sh">"</span><span class="s">class_uid</span><span class="sh">"</span><span class="p">:</span> <span class="mi">2004</span><span class="p">,</span>  <span class="c1"># Detection Finding
</span>        <span class="sh">"</span><span class="s">category_uid</span><span class="sh">"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>  <span class="c1"># Findings
</span>        <span class="sh">"</span><span class="s">type_uid</span><span class="sh">"</span><span class="p">:</span> <span class="mi">200401</span><span class="p">,</span>  <span class="c1"># Detection Finding: Create
</span>        <span class="sh">"</span><span class="s">severity_id</span><span class="sh">"</span><span class="p">:</span> <span class="nf">_severity_to_id</span><span class="p">(</span>
            <span class="n">event</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">classification</span><span class="sh">"</span><span class="p">,</span> <span class="p">{}).</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">severity</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">MEDIUM</span><span class="sh">"</span><span class="p">)</span>
        <span class="p">),</span>
        <span class="sh">"</span><span class="s">time</span><span class="sh">"</span><span class="p">:</span> <span class="nf">int</span><span class="p">(</span><span class="n">now</span><span class="p">.</span><span class="nf">timestamp</span><span class="p">()</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">),</span>
        <span class="sh">"</span><span class="s">activity_id</span><span class="sh">"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>  <span class="c1"># Create
</span>        <span class="sh">"</span><span class="s">finding_info</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span>
            <span class="sh">"</span><span class="s">title</span><span class="sh">"</span><span class="p">:</span> <span class="n">event</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span>
                <span class="sh">"</span><span class="s">classification</span><span class="sh">"</span><span class="p">,</span> <span class="p">{}</span>
            <span class="p">).</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Security Incident</span><span class="sh">"</span><span class="p">),</span>
            <span class="sh">"</span><span class="s">uid</span><span class="sh">"</span><span class="p">:</span> <span class="n">event</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span>
                <span class="sh">"</span><span class="s">detail</span><span class="sh">"</span><span class="p">,</span> <span class="p">{}</span>
            <span class="p">).</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">id</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">unknown</span><span class="sh">"</span><span class="p">),</span>
            <span class="sh">"</span><span class="s">types</span><span class="sh">"</span><span class="p">:</span> <span class="p">[</span>
                <span class="n">event</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span>
                    <span class="sh">"</span><span class="s">classification</span><span class="sh">"</span><span class="p">,</span> <span class="p">{}</span>
                <span class="p">).</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">category</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">unknown</span><span class="sh">"</span><span class="p">)</span>
            <span class="p">],</span>
        <span class="p">},</span>
        <span class="sh">"</span><span class="s">enrichments</span><span class="sh">"</span><span class="p">:</span> <span class="nf">_build_enrichments</span><span class="p">(</span><span class="n">event</span><span class="p">),</span>
        <span class="sh">"</span><span class="s">remediation</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span>
            <span class="sh">"</span><span class="s">desc</span><span class="sh">"</span><span class="p">:</span> <span class="n">json</span><span class="p">.</span><span class="nf">dumps</span><span class="p">(</span>
                <span class="n">event</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">responseActions</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
            <span class="p">),</span>
        <span class="p">},</span>
        <span class="sh">"</span><span class="s">status_id</span><span class="sh">"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>  <span class="c1"># New
</span>    <span class="p">}</span>


<span class="k">def</span> <span class="nf">_severity_to_id</span><span class="p">(</span><span class="n">severity</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="n">mapping</span> <span class="o">=</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">INFORMATIONAL</span><span class="sh">"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">LOW</span><span class="sh">"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">MEDIUM</span><span class="sh">"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">HIGH</span><span class="sh">"</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">CRITICAL</span><span class="sh">"</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">mapping</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">severity</span><span class="p">.</span><span class="nf">upper</span><span class="p">(),</span> <span class="mi">0</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">_build_enrichments</span><span class="p">(</span><span class="n">event</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
    <span class="n">enrichments</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="n">misp_data</span> <span class="o">=</span> <span class="n">event</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">enrichment</span><span class="sh">"</span><span class="p">,</span> <span class="p">[{}])[</span><span class="mi">0</span><span class="p">]</span> <span class="k">if</span> <span class="n">event</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">enrichment</span><span class="sh">"</span><span class="p">)</span> <span class="k">else</span> <span class="p">{}</span>
    <span class="k">if</span> <span class="n">misp_data</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">iocMatch</span><span class="sh">"</span><span class="p">):</span>
        <span class="n">enrichments</span><span class="p">.</span><span class="nf">append</span><span class="p">({</span>
            <span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">MISP Threat Intelligence</span><span class="sh">"</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">data</span><span class="sh">"</span><span class="p">:</span> <span class="n">json</span><span class="p">.</span><span class="nf">dumps</span><span class="p">(</span><span class="n">misp_data</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">matchedIndicators</span><span class="sh">"</span><span class="p">,</span> <span class="p">[])),</span>
            <span class="sh">"</span><span class="s">provider</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">MISP</span><span class="sh">"</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">threat_intelligence</span><span class="sh">"</span><span class="p">,</span>
        <span class="p">})</span>
    <span class="k">return</span> <span class="n">enrichments</span>


<span class="k">def</span> <span class="nf">handler</span><span class="p">(</span><span class="n">event</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">],</span> <span class="n">context</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
    <span class="sh">"""</span><span class="s">Write incident response results to Security Lake in OCSF format.</span><span class="sh">"""</span>
    <span class="n">ocsf_record</span> <span class="o">=</span> <span class="nf">to_ocsf_detection_finding</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>

    <span class="n">now</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">.</span><span class="nf">now</span><span class="p">(</span><span class="n">timezone</span><span class="p">.</span><span class="n">utc</span><span class="p">)</span>
    <span class="n">key</span> <span class="o">=</span> <span class="p">(</span>
        <span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="n">SECURITY_LAKE_PREFIX</span><span class="si">}</span><span class="sh">"</span>
        <span class="sa">f</span><span class="sh">"</span><span class="s">region=</span><span class="si">{</span><span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">'</span><span class="s">AWS_REGION</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">us-east-1</span><span class="sh">'</span><span class="p">)</span><span class="si">}</span><span class="s">/</span><span class="sh">"</span>
        <span class="sa">f</span><span class="sh">"</span><span class="s">year=</span><span class="si">{</span><span class="n">now</span><span class="p">.</span><span class="n">year</span><span class="si">}</span><span class="s">/month=</span><span class="si">{</span><span class="n">now</span><span class="p">.</span><span class="n">month</span><span class="si">:</span><span class="mi">02</span><span class="n">d</span><span class="si">}</span><span class="s">/day=</span><span class="si">{</span><span class="n">now</span><span class="p">.</span><span class="n">day</span><span class="si">:</span><span class="mi">02</span><span class="n">d</span><span class="si">}</span><span class="s">/</span><span class="sh">"</span>
        <span class="sa">f</span><span class="sh">"</span><span class="s">incident-</span><span class="si">{</span><span class="n">now</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="sh">'</span><span class="s">%Y%m%dT%H%M%SZ</span><span class="sh">'</span><span class="p">)</span><span class="si">}</span><span class="s">.json</span><span class="sh">"</span>
    <span class="p">)</span>

    <span class="n">s3</span><span class="p">.</span><span class="nf">put_object</span><span class="p">(</span>
        <span class="n">Bucket</span><span class="o">=</span><span class="n">SECURITY_LAKE_BUCKET</span><span class="p">,</span>
        <span class="n">Key</span><span class="o">=</span><span class="n">key</span><span class="p">,</span>
        <span class="n">Body</span><span class="o">=</span><span class="n">json</span><span class="p">.</span><span class="nf">dumps</span><span class="p">(</span><span class="n">ocsf_record</span><span class="p">).</span><span class="nf">encode</span><span class="p">(</span><span class="sh">"</span><span class="s">utf-8</span><span class="sh">"</span><span class="p">),</span>
        <span class="n">ContentType</span><span class="o">=</span><span class="sh">"</span><span class="s">application/json</span><span class="sh">"</span><span class="p">,</span>
        <span class="n">ServerSideEncryption</span><span class="o">=</span><span class="sh">"</span><span class="s">aws:kms</span><span class="sh">"</span><span class="p">,</span>
    <span class="p">)</span>

    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sh">"</span><span class="s">Wrote OCSF record to s3://%s/%s</span><span class="sh">"</span><span class="p">,</span> <span class="n">SECURITY_LAKE_BUCKET</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span>
    <span class="k">return</span> <span class="p">{</span><span class="sh">"</span><span class="s">logged</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span> <span class="sh">"</span><span class="s">key</span><span class="sh">"</span><span class="p">:</span> <span class="n">key</span><span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="querying-security-lake-with-athena">Querying Security Lake with Athena</h3>

<p>Once your automation data flows into Security Lake, you can query it with Athena for trend analysis and metrics:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
</pre></td><td class="rouge-code"><pre><span class="c1">-- Find all automated responses in the last 7 days</span>
<span class="k">SELECT</span>
    <span class="n">finding_info</span><span class="p">.</span><span class="n">title</span> <span class="k">AS</span> <span class="n">finding_type</span><span class="p">,</span>
    <span class="n">severity_id</span><span class="p">,</span>
    <span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="k">AS</span> <span class="n">incident_count</span><span class="p">,</span>
    <span class="k">AVG</span><span class="p">(</span>
        <span class="k">CAST</span><span class="p">(</span>
            <span class="n">json_extract_scalar</span><span class="p">(</span><span class="n">remediation</span><span class="p">.</span><span class="k">desc</span><span class="p">,</span> <span class="s1">'$.elapsed_seconds'</span><span class="p">)</span>
            <span class="k">AS</span> <span class="nb">DOUBLE</span>
        <span class="p">)</span>
    <span class="p">)</span> <span class="k">AS</span> <span class="n">avg_response_seconds</span>
<span class="k">FROM</span> <span class="n">security_lake_db</span><span class="p">.</span><span class="n">custom_security_automation</span>
<span class="k">WHERE</span> <span class="nb">time</span> <span class="o">&gt;</span> <span class="p">(</span><span class="k">CAST</span><span class="p">(</span><span class="n">now</span><span class="p">()</span> <span class="k">AS</span> <span class="nb">BIGINT</span><span class="p">)</span> <span class="o">-</span> <span class="mi">604800000</span><span class="p">)</span>
<span class="k">GROUP</span> <span class="k">BY</span> <span class="n">finding_info</span><span class="p">.</span><span class="n">title</span><span class="p">,</span> <span class="n">severity_id</span>
<span class="k">ORDER</span> <span class="k">BY</span> <span class="n">incident_count</span> <span class="k">DESC</span><span class="p">;</span>

<span class="c1">-- Identify top threat intelligence matches</span>
<span class="k">SELECT</span>
    <span class="n">json_extract_scalar</span><span class="p">(</span><span class="n">enrichment</span><span class="p">,</span> <span class="s1">'$.provider'</span><span class="p">)</span> <span class="k">AS</span> <span class="n">intel_source</span><span class="p">,</span>
    <span class="n">json_extract_scalar</span><span class="p">(</span><span class="n">enrichment</span><span class="p">,</span> <span class="s1">'$.name'</span><span class="p">)</span> <span class="k">AS</span> <span class="n">enrichment_type</span><span class="p">,</span>
    <span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="k">AS</span> <span class="n">match_count</span>
<span class="k">FROM</span> <span class="n">security_lake_db</span><span class="p">.</span><span class="n">custom_security_automation</span>
<span class="k">CROSS</span> <span class="k">JOIN</span> <span class="k">UNNEST</span><span class="p">(</span><span class="n">enrichments</span><span class="p">)</span> <span class="k">AS</span> <span class="n">t</span><span class="p">(</span><span class="n">enrichment</span><span class="p">)</span>
<span class="k">WHERE</span> <span class="nb">time</span> <span class="o">&gt;</span> <span class="p">(</span><span class="k">CAST</span><span class="p">(</span><span class="n">now</span><span class="p">()</span> <span class="k">AS</span> <span class="nb">BIGINT</span><span class="p">)</span> <span class="o">-</span> <span class="mi">2592000000</span><span class="p">)</span>
<span class="k">GROUP</span> <span class="k">BY</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span>
<span class="k">ORDER</span> <span class="k">BY</span> <span class="n">match_count</span> <span class="k">DESC</span><span class="p">;</span>

<span class="c1">-- Mean time to respond by severity</span>
<span class="k">SELECT</span>
    <span class="k">CASE</span> <span class="n">severity_id</span>
        <span class="k">WHEN</span> <span class="mi">5</span> <span class="k">THEN</span> <span class="s1">'CRITICAL'</span>
        <span class="k">WHEN</span> <span class="mi">4</span> <span class="k">THEN</span> <span class="s1">'HIGH'</span>
        <span class="k">WHEN</span> <span class="mi">3</span> <span class="k">THEN</span> <span class="s1">'MEDIUM'</span>
        <span class="k">WHEN</span> <span class="mi">2</span> <span class="k">THEN</span> <span class="s1">'LOW'</span>
        <span class="k">ELSE</span> <span class="s1">'INFO'</span>
    <span class="k">END</span> <span class="k">AS</span> <span class="n">severity</span><span class="p">,</span>
    <span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="k">AS</span> <span class="n">total_incidents</span><span class="p">,</span>
    <span class="k">AVG</span><span class="p">(</span>
        <span class="k">CAST</span><span class="p">(</span>
            <span class="n">json_extract_scalar</span><span class="p">(</span><span class="n">remediation</span><span class="p">.</span><span class="k">desc</span><span class="p">,</span> <span class="s1">'$.elapsed_seconds'</span><span class="p">)</span>
            <span class="k">AS</span> <span class="nb">DOUBLE</span>
        <span class="p">)</span>
    <span class="p">)</span> <span class="k">AS</span> <span class="n">avg_mttr_seconds</span>
<span class="k">FROM</span> <span class="n">security_lake_db</span><span class="p">.</span><span class="n">custom_security_automation</span>
<span class="k">WHERE</span> <span class="nb">time</span> <span class="o">&gt;</span> <span class="p">(</span><span class="k">CAST</span><span class="p">(</span><span class="n">now</span><span class="p">()</span> <span class="k">AS</span> <span class="nb">BIGINT</span><span class="p">)</span> <span class="o">-</span> <span class="mi">2592000000</span><span class="p">)</span>
<span class="k">GROUP</span> <span class="k">BY</span> <span class="n">severity_id</span>
<span class="k">ORDER</span> <span class="k">BY</span> <span class="n">severity_id</span> <span class="k">DESC</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="monitoring-and-tuning-your-automation">Monitoring and Tuning Your Automation</h2>

<p>Deploying automation is not the end – it is the beginning. You need to monitor the pipeline itself for health, accuracy, and performance.</p>

<h3 id="key-metrics-to-track">Key Metrics to Track</h3>

<p><strong>Pipeline Health:</strong></p>
<ul>
  <li>Step Functions execution success/failure rate</li>
  <li>Lambda function error rates and duration</li>
  <li>EventBridge rule invocation counts</li>
  <li>MISP and OpenCTI API response times and availability</li>
</ul>

<p><strong>Detection Quality:</strong></p>
<ul>
  <li>True positive rate – confirmed incidents that were correctly automated</li>
  <li>False positive rate – benign events that triggered automated response</li>
  <li>False negative rate – incidents missed by the pipeline (discovered through other means)</li>
  <li>Sigma rule match distribution – which rules are firing and how often</li>
</ul>

<p><strong>Response Effectiveness:</strong></p>
<ul>
  <li>Mean time to detect (MTTD) – time from event occurrence to pipeline trigger</li>
  <li>Mean time to respond (MTTR) – time from pipeline trigger to response completion</li>
  <li>Escalation rate – how often the pipeline requires human intervention</li>
  <li>Rollback rate – how often automated responses are reversed</li>
</ul>

<h3 id="cloudwatch-dashboard">CloudWatch Dashboard</h3>

<p>Create a CloudWatch dashboard that tracks these metrics. Set alarms for:</p>

<ul>
  <li>Step Functions execution failures exceeding 5% in any 15-minute window</li>
  <li>Lambda enrichment function timeouts (MISP or OpenCTI unavailable)</li>
  <li>EventBridge rule invocation spikes (possible event storm or attack)</li>
  <li>Approval queue depth exceeding 10 messages (human bottleneck)</li>
</ul>

<h3 id="tuning-your-sigma-rules">Tuning Your Sigma Rules</h3>

<p>Sigma rules require ongoing tuning. Review matched rules weekly:</p>

<ol>
  <li><strong>High false positive rules</strong> – tighten the detection criteria or add filter conditions for known legitimate activity.</li>
  <li><strong>Rules that never fire</strong> – verify the rule logic against actual CloudTrail event formats. The field names may have changed.</li>
  <li><strong>Missing coverage</strong> – compare your Sigma rule set against the MITRE ATT&amp;CK matrix for AWS. Identify techniques without detection coverage.</li>
  <li><strong>Severity calibration</strong> – adjust rule severity based on your environment. A rule that is <code class="language-plaintext highlighter-rouge">critical</code> in a production account might be <code class="language-plaintext highlighter-rouge">low</code> in a sandbox account.</li>
</ol>

<h3 id="misp-feed-maintenance">MISP Feed Maintenance</h3>

<p>Review your MISP feeds monthly:</p>

<ul>
  <li>Disable feeds with high false positive rates</li>
  <li>Add new community feeds as they become available</li>
  <li>Monitor feed freshness – stale feeds provide outdated intelligence</li>
  <li>Review warninglist effectiveness – ensure legitimate services (CDNs, cloud providers) are properly excluded</li>
</ul>

<h2 id="implementation-roadmap">Implementation Roadmap</h2>

<p><strong>Week 1-2: Foundation</strong></p>
<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Deploy EventBridge rules for GuardDuty, Security Hub, and CloudTrail</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Create the Step Functions state machine with basic classification and logging</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Deploy the initial set of Lambda functions (classify, log, notify)</li>
</ul>

<p><strong>Week 3-4: Enrichment</strong></p>
<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Deploy MISP on EC2 with Docker Compose</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Configure community threat intelligence feeds</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Deploy and integrate the MISP enrichment Lambda</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />(Optional) Deploy OpenCTI and connect to MISP</li>
</ul>

<p><strong>Week 5-6: Detection and Response</strong></p>
<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Upload initial Sigma rule set to S3</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Deploy the Sigma evaluator Lambda</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Implement automated response functions (IP blocking, resource isolation)</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Configure human approval workflows for high-severity events</li>
</ul>

<p><strong>Week 7-8: Analytics and Tuning</strong></p>
<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Enable Security Lake and configure custom source</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Build Athena queries and CloudWatch dashboards</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Tune Sigma rules based on initial findings</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Document runbooks for human escalation scenarios</li>
</ul>

<h2 id="related-articles">Related Articles</h2>

<ul>
  <li><a href="/posts/aws-lambda-security-automated-threat-detection-2025/">AWS Lambda Security: Building Automated Threat Detection Systems for 2025</a> – Deep dive into Lambda-based threat detection patterns that complement this EventBridge pipeline.</li>
  <li><a href="/posts/real-time-ids-using-guardduty/">Real-Time Intrusion Detection Using GuardDuty</a> – Understanding GuardDuty findings that feed into the EventBridge automation.</li>
  <li><a href="/posts/aws-devsecops-pipeline-security-automation-implementation/">AWS DevSecOps Pipeline Security Automation Implementation</a> – Securing the CI/CD pipeline that deploys your security automation infrastructure.</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>Event-driven security automation with EventBridge, Step Functions, and open-source threat intelligence gives you the fastest path from detection to response. The AWS-native event backbone handles scale and reliability. Open-source tools like MISP, OpenCTI, and Sigma rules give you freedom – freedom to choose your intelligence sources, freedom to write detection logic that works across any platform, and freedom to avoid vendor lock-in on the intelligence that matters most.</p>

<p>The pipeline we built responds to critical threats in under 30 seconds, enriches every alert with community threat intelligence, evaluates events against vendor-neutral detection rules, and logs everything in OCSF format for long-term analytics. More importantly, every component is replaceable. You can swap MISP for a commercial threat intel platform. You can replace Sigma rules with Splunk queries. You can add new event sources without touching existing workflows. The architecture supports evolution.</p>

<p>Start with the foundation: EventBridge rules catching your highest-value security events. Add Step Functions orchestration when you need multi-step workflows. Layer in MISP enrichment when you want threat context. Deploy Sigma rules when you want detection-as-code. Each layer adds value independently, and together they form a complete security orchestration platform that you own and control.</p>

<p>Build it yourself. Own your intelligence. Automate your defense.</p>

<hr />

<p><em>Jon (JR) Price is a DevSecOps engineer and cloud security architect specializing in AWS security automation. Connect with him on <a href="https://www.linkedin.com/in/jonpricelinux/">LinkedIn</a> to discuss event-driven security architectures and open-source threat intelligence.</em></p>]]></content><author><name>Jon (JR) Price</name></author><category term="AWS" /><category term="DevSecOps" /><category term="Security" /><category term="eventbridge" /><category term="misp" /><category term="open-source" /><category term="security-automation" /><category term="sigma-rules" /><category term="soar" /><category term="step-functions" /><category term="threat-intelligence" /><summary type="html"><![CDATA[Build automated security response pipelines with AWS EventBridge and Step Functions, enriched by open-source threat intelligence from MISP and Sigma rules.]]></summary></entry><entry><title type="html">Secrets Management Showdown: HashiCorp Vault vs AWS SSM and Secrets Manager</title><link href="https://www.red-team.sh/posts/secrets-management-vault-vs-aws-ssm/" rel="alternate" type="text/html" title="Secrets Management Showdown: HashiCorp Vault vs AWS SSM and Secrets Manager" /><published>2026-04-08T09:00:00-07:00</published><updated>2026-04-08T09:00:00-07:00</updated><id>https://www.red-team.sh/posts/secrets-management-vault-vs-aws-ssm</id><content type="html" xml:base="https://www.red-team.sh/posts/secrets-management-vault-vs-aws-ssm/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#introduction-secrets-sprawl-is-a-ticking-time-bomb" id="markdown-toc-introduction-secrets-sprawl-is-a-ticking-time-bomb">Introduction: Secrets Sprawl Is a Ticking Time Bomb</a></li>
  <li><a href="#aws-ssm-parameter-store-free-simple-and-sufficient-for-most-teams" id="markdown-toc-aws-ssm-parameter-store-free-simple-and-sufficient-for-most-teams">AWS SSM Parameter Store: Free, Simple, and Sufficient for Most Teams</a>    <ul>
      <li><a href="#why-ssm-parameter-store-works" id="markdown-toc-why-ssm-parameter-store-works">Why SSM Parameter Store Works</a></li>
      <li><a href="#ssm-parameter-store-in-practice" id="markdown-toc-ssm-parameter-store-in-practice">SSM Parameter Store in Practice</a></li>
      <li><a href="#limitations" id="markdown-toc-limitations">Limitations</a></li>
    </ul>
  </li>
  <li><a href="#aws-secrets-manager-rotation-integration-and-a-price-tag" id="markdown-toc-aws-secrets-manager-rotation-integration-and-a-price-tag">AWS Secrets Manager: Rotation, Integration, and a Price Tag</a>    <ul>
      <li><a href="#key-features" id="markdown-toc-key-features">Key Features</a></li>
      <li><a href="#pricing-2026" id="markdown-toc-pricing-2026">Pricing (2026)</a></li>
      <li><a href="#secrets-manager-with-terraform-and-rotation" id="markdown-toc-secrets-manager-with-terraform-and-rotation">Secrets Manager with Terraform and Rotation</a></li>
      <li><a href="#when-to-choose-secrets-manager-over-ssm" id="markdown-toc-when-to-choose-secrets-manager-over-ssm">When to Choose Secrets Manager over SSM</a></li>
    </ul>
  </li>
  <li><a href="#hashicorp-vault-dynamic-secrets-multi-cloud-and-full-audit" id="markdown-toc-hashicorp-vault-dynamic-secrets-multi-cloud-and-full-audit">HashiCorp Vault: Dynamic Secrets, Multi-Cloud, and Full Audit</a>    <ul>
      <li><a href="#core-capabilities" id="markdown-toc-core-capabilities">Core Capabilities</a></li>
      <li><a href="#vault-server-with-docker-compose" id="markdown-toc-vault-server-with-docker-compose">Vault Server with Docker Compose</a></li>
      <li><a href="#vault-policies" id="markdown-toc-vault-policies">Vault Policies</a></li>
      <li><a href="#aws-auth-backend" id="markdown-toc-aws-auth-backend">AWS Auth Backend</a></li>
      <li><a href="#dynamic-database-credentials" id="markdown-toc-dynamic-database-credentials">Dynamic Database Credentials</a></li>
      <li><a href="#kms-auto-unseal" id="markdown-toc-kms-auto-unseal">KMS Auto-Unseal</a></li>
    </ul>
  </li>
  <li><a href="#openbao-the-open-source-fork-after-vaults-license-change" id="markdown-toc-openbao-the-open-source-fork-after-vaults-license-change">OpenBao: The Open Source Fork After Vault’s License Change</a>    <ul>
      <li><a href="#what-openbao-adds-over-community-vault" id="markdown-toc-what-openbao-adds-over-community-vault">What OpenBao Adds Over Community Vault</a></li>
      <li><a href="#when-to-consider-openbao" id="markdown-toc-when-to-consider-openbao">When to Consider OpenBao</a></li>
    </ul>
  </li>
  <li><a href="#sops--age-lightweight-encryption-for-gitops" id="markdown-toc-sops--age-lightweight-encryption-for-gitops">SOPS + age: Lightweight Encryption for GitOps</a>    <ul>
      <li><a href="#how-it-works" id="markdown-toc-how-it-works">How It Works</a></li>
      <li><a href="#setup" id="markdown-toc-setup">Setup</a></li>
      <li><a href="#encrypting-secrets" id="markdown-toc-encrypting-secrets">Encrypting Secrets</a></li>
      <li><a href="#sops-with-aws-kms" id="markdown-toc-sops-with-aws-kms">SOPS with AWS KMS</a></li>
      <li><a href="#when-sops--age-makes-sense" id="markdown-toc-when-sops--age-makes-sense">When SOPS + age Makes Sense</a></li>
    </ul>
  </li>
  <li><a href="#comparison-table" id="markdown-toc-comparison-table">Comparison Table</a></li>
  <li><a href="#decision-framework-when-to-use-which" id="markdown-toc-decision-framework-when-to-use-which">Decision Framework: When to Use Which</a>    <ul>
      <li><a href="#the-hybrid-approach" id="markdown-toc-the-hybrid-approach">The Hybrid Approach</a></li>
    </ul>
  </li>
  <li><a href="#real-world-scenarios" id="markdown-toc-real-world-scenarios">Real-World Scenarios</a>    <ul>
      <li><a href="#scenario-1-database-credentials-for-a-microservices-fleet" id="markdown-toc-scenario-1-database-credentials-for-a-microservices-fleet">Scenario 1: Database Credentials for a Microservices Fleet</a></li>
      <li><a href="#scenario-2-api-keys-for-a-saas-integration" id="markdown-toc-scenario-2-api-keys-for-a-saas-integration">Scenario 2: API Keys for a SaaS Integration</a></li>
      <li><a href="#scenario-3-tls-certificates-for-internal-services" id="markdown-toc-scenario-3-tls-certificates-for-internal-services">Scenario 3: TLS Certificates for Internal Services</a></li>
    </ul>
  </li>
  <li><a href="#migration-strategies" id="markdown-toc-migration-strategies">Migration Strategies</a>    <ul>
      <li><a href="#moving-from-hardcoded-secrets-to-ssm-parameter-store" id="markdown-toc-moving-from-hardcoded-secrets-to-ssm-parameter-store">Moving from Hardcoded Secrets to SSM Parameter Store</a></li>
      <li><a href="#moving-from-ssm-parameter-store-to-vault" id="markdown-toc-moving-from-ssm-parameter-store-to-vault">Moving from SSM Parameter Store to Vault</a></li>
      <li><a href="#gradual-adoption-vault-alongside-aws-native" id="markdown-toc-gradual-adoption-vault-alongside-aws-native">Gradual Adoption: Vault Alongside AWS Native</a></li>
    </ul>
  </li>
  <li><a href="#security-hardening-checklist" id="markdown-toc-security-hardening-checklist">Security Hardening Checklist</a></li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
  <li><a href="#related-articles" id="markdown-toc-related-articles">Related Articles</a></li>
</ul>

<h2 id="introduction-secrets-sprawl-is-a-ticking-time-bomb">Introduction: Secrets Sprawl Is a Ticking Time Bomb</h2>

<p>Hardcoded credentials remain one of the most exploited attack vectors in cloud environments, and the numbers are getting worse. GitGuardian’s 2025 State of Secrets Sprawl report found <strong>23.8 million new hardcoded secrets</strong> pushed to public GitHub repositories in 2024 alone. Worse, 70% of secrets leaked in 2022 are still valid today, giving attackers prolonged access to critical systems. An analysis of 15 million public Docker images uncovered 100,000 valid secrets – including AWS keys and GitHub tokens from Fortune 500 companies.</p>

<p>The median time to remediate a leaked secret on GitHub? <strong>94 days.</strong> That is three months of exposure before anyone rotates a credential. Stolen credentials appeared in 31% of all breaches over the past decade, with an average incident cost of <strong>$3.7 million</strong>.</p>

<p>The solution is disciplined secrets management – but which tool do you reach for? If you are running on AWS, you have two native options that work out of the box: <strong>SSM Parameter Store</strong> (free) and <strong>Secrets Manager</strong> ($0.40/secret/month). If you need multi-cloud support, dynamic credentials, or fine-grained audit logging, <strong>HashiCorp Vault</strong> and its open source fork <strong>OpenBao</strong> provide capabilities that go far beyond what AWS offers natively. And for lightweight GitOps workflows, <strong>SOPS + age</strong> encrypts secrets right in your repository.</p>

<p>This article walks through each tool, compares them head to head, and gives you a decision framework so you pick the right one for your environment.</p>

<p><img src="/assets/img/secrets-management-vault-vs-aws.png" alt="Secrets Management Architecture" class="shadow" w="800" />
<em>Application secrets flow through AWS native services or Vault to backend resources</em></p>

<hr />

<h2 id="aws-ssm-parameter-store-free-simple-and-sufficient-for-most-teams">AWS SSM Parameter Store: Free, Simple, and Sufficient for Most Teams</h2>

<p>AWS Systems Manager Parameter Store is the unsung hero of secrets management on AWS. It stores configuration data and secrets as key-value pairs, encrypts them with KMS, and costs exactly nothing for standard parameters.</p>

<h3 id="why-ssm-parameter-store-works">Why SSM Parameter Store Works</h3>

<ul>
  <li><strong>Free tier</strong>: Up to 10,000 standard parameters per account per region at no charge</li>
  <li><strong>SecureString type</strong>: Encrypts values with AWS KMS (default <code class="language-plaintext highlighter-rouge">aws/ssm</code> key or your own CMK)</li>
  <li><strong>Hierarchical paths</strong>: Organize secrets like <code class="language-plaintext highlighter-rouge">/myapp/prod/database/password</code></li>
  <li><strong>IAM integration</strong>: Control access with standard IAM policies</li>
  <li><strong>Parameter policies</strong>: Set expiration dates and notification windows</li>
  <li><strong>Cross-service references</strong>: Lambda, ECS, CloudFormation, and CodePipeline all read SSM natively</li>
</ul>

<h3 id="ssm-parameter-store-in-practice">SSM Parameter Store in Practice</h3>

<p>Store a secret:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre>aws ssm put-parameter <span class="se">\</span>
  <span class="nt">--name</span> <span class="s2">"/myapp/prod/database/password"</span> <span class="se">\</span>
  <span class="nt">--value</span> <span class="s2">"s3cureP@ss!"</span> <span class="se">\</span>
  <span class="nt">--type</span> <span class="s2">"SecureString"</span> <span class="se">\</span>
  <span class="nt">--tier</span> <span class="s2">"Standard"</span> <span class="se">\</span>
  <span class="nt">--tags</span> <span class="nv">Key</span><span class="o">=</span>Application,Value<span class="o">=</span>myapp <span class="nv">Key</span><span class="o">=</span>Environment,Value<span class="o">=</span>prod
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Retrieve it in Python:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="n">boto3</span>

<span class="k">def</span> <span class="nf">get_secret</span><span class="p">(</span><span class="n">param_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Retrieve a SecureString parameter from SSM.</span><span class="sh">"""</span>
    <span class="n">ssm</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="nf">client</span><span class="p">(</span><span class="sh">"</span><span class="s">ssm</span><span class="sh">"</span><span class="p">)</span>
    <span class="n">response</span> <span class="o">=</span> <span class="n">ssm</span><span class="p">.</span><span class="nf">get_parameter</span><span class="p">(</span>
        <span class="n">Name</span><span class="o">=</span><span class="n">param_name</span><span class="p">,</span>
        <span class="n">WithDecryption</span><span class="o">=</span><span class="bp">True</span>
    <span class="p">)</span>
    <span class="k">return</span> <span class="n">response</span><span class="p">[</span><span class="sh">"</span><span class="s">Parameter</span><span class="sh">"</span><span class="p">][</span><span class="sh">"</span><span class="s">Value</span><span class="sh">"</span><span class="p">]</span>

<span class="c1"># Usage
</span><span class="n">db_password</span> <span class="o">=</span> <span class="nf">get_secret</span><span class="p">(</span><span class="sh">"</span><span class="s">/myapp/prod/database/password</span><span class="sh">"</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Retrieve multiple parameters by path:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre><span class="k">def</span> <span class="nf">get_secrets_by_path</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Retrieve all parameters under a path prefix.</span><span class="sh">"""</span>
    <span class="n">ssm</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="nf">client</span><span class="p">(</span><span class="sh">"</span><span class="s">ssm</span><span class="sh">"</span><span class="p">)</span>
    <span class="n">params</span> <span class="o">=</span> <span class="p">{}</span>
    <span class="n">paginator</span> <span class="o">=</span> <span class="n">ssm</span><span class="p">.</span><span class="nf">get_paginator</span><span class="p">(</span><span class="sh">"</span><span class="s">get_parameters_by_path</span><span class="sh">"</span><span class="p">)</span>

    <span class="k">for</span> <span class="n">page</span> <span class="ow">in</span> <span class="n">paginator</span><span class="p">.</span><span class="nf">paginate</span><span class="p">(</span>
        <span class="n">Path</span><span class="o">=</span><span class="n">path</span><span class="p">,</span>
        <span class="n">Recursive</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
        <span class="n">WithDecryption</span><span class="o">=</span><span class="bp">True</span>
    <span class="p">):</span>
        <span class="k">for</span> <span class="n">param</span> <span class="ow">in</span> <span class="n">page</span><span class="p">[</span><span class="sh">"</span><span class="s">Parameters</span><span class="sh">"</span><span class="p">]:</span>
            <span class="n">key</span> <span class="o">=</span> <span class="n">param</span><span class="p">[</span><span class="sh">"</span><span class="s">Name</span><span class="sh">"</span><span class="p">].</span><span class="nf">split</span><span class="p">(</span><span class="sh">"</span><span class="s">/</span><span class="sh">"</span><span class="p">)[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
            <span class="n">params</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">param</span><span class="p">[</span><span class="sh">"</span><span class="s">Value</span><span class="sh">"</span><span class="p">]</span>

    <span class="k">return</span> <span class="n">params</span>

<span class="c1"># Retrieve all secrets for an app environment
</span><span class="n">config</span> <span class="o">=</span> <span class="nf">get_secrets_by_path</span><span class="p">(</span><span class="sh">"</span><span class="s">/myapp/prod/</span><span class="sh">"</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="limitations">Limitations</h3>

<ul>
  <li><strong>No built-in rotation</strong>: You must build your own rotation logic or use Secrets Manager</li>
  <li><strong>No dynamic credentials</strong>: Parameters are static values; you set them, you rotate them manually</li>
  <li><strong>API throttling</strong>: Standard throughput is 40 TPS for <code class="language-plaintext highlighter-rouge">GetParameter</code>; advanced tier bumps it to 1,000 TPS ($0.05/10,000 advanced API interactions)</li>
  <li><strong>No cross-account sharing</strong>: Each account manages its own parameters (use AWS Organizations + resource policies for workarounds)</li>
  <li><strong>Limited audit</strong>: CloudTrail logs API calls, but there is no built-in audit trail for secret access patterns</li>
</ul>

<hr />

<h2 id="aws-secrets-manager-rotation-integration-and-a-price-tag">AWS Secrets Manager: Rotation, Integration, and a Price Tag</h2>

<p>Secrets Manager is the premium offering. It costs more, but it brings automatic rotation and native database integration that SSM Parameter Store does not have.</p>

<h3 id="key-features">Key Features</h3>

<ul>
  <li><strong>Automatic rotation</strong>: Lambda-based rotation for RDS, Redshift, DocumentDB, and custom secrets</li>
  <li><strong>RDS integration</strong>: Native credential rotation without application downtime</li>
  <li><strong>Cross-account sharing</strong>: Resource-based policies enable sharing secrets across accounts</li>
  <li><strong>Versioning</strong>: Automatic version tracking with staging labels (<code class="language-plaintext highlighter-rouge">AWSCURRENT</code>, <code class="language-plaintext highlighter-rouge">AWSPREVIOUS</code>)</li>
  <li><strong>Replication</strong>: Cross-region replication for disaster recovery</li>
</ul>

<h3 id="pricing-2026">Pricing (2026)</h3>

<table>
  <thead>
    <tr>
      <th>Component</th>
      <th>Cost</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Per secret per month</td>
      <td>$0.40</td>
    </tr>
    <tr>
      <td>Per 10,000 API calls</td>
      <td>$0.05</td>
    </tr>
    <tr>
      <td>Rotation Lambda</td>
      <td>Standard Lambda pricing</td>
    </tr>
    <tr>
      <td>KMS encryption</td>
      <td>$1.00/month per CMK</td>
    </tr>
  </tbody>
</table>

<p>For 100 secrets with moderate access patterns, expect roughly <strong>$40-50/month</strong>.</p>

<h3 id="secrets-manager-with-terraform-and-rotation">Secrets Manager with Terraform and Rotation</h3>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
</pre></td><td class="rouge-code"><pre><span class="c1"># Secrets Manager secret with automatic RDS rotation</span>
<span class="nx">resource</span> <span class="s2">"aws_secretsmanager_secret"</span> <span class="s2">"db_credentials"</span> <span class="p">{</span>
  <span class="nx">name</span>        <span class="o">=</span> <span class="s2">"myapp/prod/rds-credentials"</span>
  <span class="nx">description</span> <span class="o">=</span> <span class="s2">"RDS credentials for myapp production"</span>
  <span class="nx">kms_key_id</span>  <span class="o">=</span> <span class="nx">aws_kms_key</span><span class="p">.</span><span class="nx">secrets</span><span class="p">.</span><span class="nx">arn</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"myapp"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="s2">"prod"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"engineering"</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"platform-team"</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"internal"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_secretsmanager_secret_version"</span> <span class="s2">"db_credentials"</span> <span class="p">{</span>
  <span class="nx">secret_id</span> <span class="o">=</span> <span class="nx">aws_secretsmanager_secret</span><span class="p">.</span><span class="nx">db_credentials</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">secret_string</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">username</span> <span class="o">=</span> <span class="s2">"app_user"</span>
    <span class="nx">password</span> <span class="o">=</span> <span class="nx">random_password</span><span class="p">.</span><span class="nx">db</span><span class="p">.</span><span class="nx">result</span>
    <span class="nx">engine</span>   <span class="o">=</span> <span class="s2">"postgres"</span>
    <span class="nx">host</span>     <span class="o">=</span> <span class="nx">aws_db_instance</span><span class="p">.</span><span class="nx">main</span><span class="p">.</span><span class="nx">address</span>
    <span class="nx">port</span>     <span class="o">=</span> <span class="mi">5432</span>
    <span class="nx">dbname</span>   <span class="o">=</span> <span class="s2">"myapp"</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_secretsmanager_secret_rotation"</span> <span class="s2">"db_credentials"</span> <span class="p">{</span>
  <span class="nx">secret_id</span>           <span class="o">=</span> <span class="nx">aws_secretsmanager_secret</span><span class="p">.</span><span class="nx">db_credentials</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">rotation_lambda_arn</span> <span class="o">=</span> <span class="nx">aws_lambda_function</span><span class="p">.</span><span class="nx">rotation</span><span class="p">.</span><span class="nx">arn</span>

  <span class="nx">rotation_rules</span> <span class="p">{</span>
    <span class="nx">automatically_after_days</span> <span class="o">=</span> <span class="mi">30</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"random_password"</span> <span class="s2">"db"</span> <span class="p">{</span>
  <span class="nx">length</span>  <span class="o">=</span> <span class="mi">32</span>
  <span class="nx">special</span> <span class="o">=</span> <span class="kc">true</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="when-to-choose-secrets-manager-over-ssm">When to Choose Secrets Manager over SSM</h3>

<p>Pick Secrets Manager when you need:</p>

<ol>
  <li><strong>Automatic rotation</strong> – especially for RDS, Redshift, or DocumentDB credentials</li>
  <li><strong>Cross-account secret sharing</strong> – resource policies make this straightforward</li>
  <li><strong>Cross-region replication</strong> – for DR or multi-region deployments</li>
  <li><strong>Compliance requirements</strong> – some auditors specifically want a dedicated secrets management service</li>
</ol>

<p>If none of those apply, SSM Parameter Store with SecureString is probably enough.</p>

<hr />

<h2 id="hashicorp-vault-dynamic-secrets-multi-cloud-and-full-audit">HashiCorp Vault: Dynamic Secrets, Multi-Cloud, and Full Audit</h2>

<p>Vault is a different class of tool. While SSM and Secrets Manager store and retrieve static secrets, Vault generates credentials on demand, enforces fine-grained policies, and provides a complete audit log of every secret access.</p>

<h3 id="core-capabilities">Core Capabilities</h3>

<ul>
  <li><strong>Dynamic secrets</strong>: Generate short-lived database credentials, AWS IAM credentials, TLS certificates on the fly</li>
  <li><strong>Auth methods</strong>: AWS IAM, Kubernetes, OIDC, LDAP, AppRole, and more</li>
  <li><strong>Policy engine</strong>: Path-based ACL policies with templating</li>
  <li><strong>Audit logging</strong>: Every request and response logged to file, syslog, or socket</li>
  <li><strong>Secret engines</strong>: KV, database, PKI, SSH, transit encryption, TOTP, and dozens more</li>
  <li><strong>Multi-cloud</strong>: Single control plane for AWS, GCP, Azure, and on-premises</li>
</ul>

<h3 id="vault-server-with-docker-compose">Vault Server with Docker Compose</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
</pre></td><td class="rouge-code"><pre><span class="na">services</span><span class="pi">:</span>
  <span class="na">vault</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">hashicorp/vault:1.18</span>
    <span class="na">container_name</span><span class="pi">:</span> <span class="s">vault</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">8200:8200"</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="na">VAULT_ADDR</span><span class="pi">:</span> <span class="s2">"</span><span class="s">http://0.0.0.0:8200"</span>
      <span class="na">VAULT_API_ADDR</span><span class="pi">:</span> <span class="s2">"</span><span class="s">http://0.0.0.0:8200"</span>
    <span class="na">cap_add</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">IPC_LOCK</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">vault-data:/vault/data</span>
      <span class="pi">-</span> <span class="s">./vault-config:/vault/config</span>
    <span class="na">command</span><span class="pi">:</span> <span class="s">server</span>
    <span class="na">networks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">vault-network</span>
      <span class="pi">-</span> <span class="s">backend</span>
    <span class="na">labels</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">traefik.enable=true"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">traefik.http.routers.vault.entrypoints=http"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">traefik.http.routers.vault.rule=Host(`vault.local.team-skynet.io`)"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">traefik.http.routers.vault.middlewares=https-redirectscheme@file"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">traefik.http.routers.vault-secure.entrypoints=https"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">traefik.http.routers.vault-secure.rule=Host(`vault.local.team-skynet.io`)"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">traefik.http.routers.vault-secure.tls=true"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">traefik.http.routers.vault-secure.service=vault"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">traefik.docker.network=backend"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">traefik.http.services.vault.loadbalancer.server.port=8200"</span>

<span class="na">volumes</span><span class="pi">:</span>
  <span class="na">vault-data</span><span class="pi">:</span>

<span class="na">networks</span><span class="pi">:</span>
  <span class="na">vault-network</span><span class="pi">:</span>
    <span class="na">driver</span><span class="pi">:</span> <span class="s">bridge</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">vault-network</span>
  <span class="na">backend</span><span class="pi">:</span>
    <span class="na">external</span><span class="pi">:</span> <span class="kc">true</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">backend</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Vault server configuration (<code class="language-plaintext highlighter-rouge">vault-config/vault.hcl</code>):</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre><span class="nx">storage</span> <span class="s2">"raft"</span> <span class="p">{</span>
  <span class="nx">path</span>    <span class="o">=</span> <span class="s2">"/vault/data"</span>
  <span class="nx">node_id</span> <span class="o">=</span> <span class="s2">"vault-node-1"</span>
<span class="p">}</span>

<span class="nx">listener</span> <span class="s2">"tcp"</span> <span class="p">{</span>
  <span class="nx">address</span>     <span class="o">=</span> <span class="s2">"0.0.0.0:8200"</span>
  <span class="nx">tls_disable</span> <span class="o">=</span> <span class="mi">1</span>  <span class="c1"># TLS handled by Traefik</span>
<span class="p">}</span>

<span class="nx">api_addr</span>     <span class="o">=</span> <span class="s2">"http://vault:8200"</span>
<span class="nx">cluster_addr</span> <span class="o">=</span> <span class="s2">"http://vault:8201"</span>
<span class="nx">ui</span>           <span class="o">=</span> <span class="kc">true</span>

<span class="c1"># Use AWS KMS for auto-unseal in production</span>
<span class="c1"># seal "awskms" {</span>
<span class="c1">#   region     = "us-east-1"</span>
<span class="c1">#   kms_key_id = "your-kms-key-id"</span>
<span class="c1"># }</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="vault-policies">Vault Policies</h3>

<p>Policies control who can access what. They are path-based and support templating:</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="c1"># policy: app-readonly.hcl</span>
<span class="c1"># Applications can read secrets under their own path</span>
<span class="nx">path</span> <span class="s2">"secret/data//*"</span> <span class="p">{</span>
  <span class="nx">capabilities</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"read"</span><span class="p">,</span> <span class="s2">"list"</span><span class="p">]</span>
<span class="p">}</span>

<span class="c1"># Applications can request dynamic database credentials</span>
<span class="nx">path</span> <span class="s2">"database/creds/"</span> <span class="p">{</span>
  <span class="nx">capabilities</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"read"</span><span class="p">]</span>
<span class="p">}</span>

<span class="c1"># Deny access to other apps' secrets</span>
<span class="nx">path</span> <span class="s2">"secret/data/*"</span> <span class="p">{</span>
  <span class="nx">capabilities</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"deny"</span><span class="p">]</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Apply the policy:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>vault policy write app-readonly app-readonly.hcl
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="aws-auth-backend">AWS Auth Backend</h3>

<p>Let your AWS workloads authenticate to Vault using their IAM identity – no passwords needed:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
</pre></td><td class="rouge-code"><pre><span class="c"># Enable AWS auth method</span>
vault auth <span class="nb">enable </span>aws

<span class="c"># Configure the AWS auth backend</span>
vault write auth/aws/config/client <span class="se">\</span>
  <span class="nv">secret_key</span><span class="o">=</span><span class="s2">"VAULT_AWS_SECRET_KEY"</span> <span class="se">\</span>
  <span class="nv">access_key</span><span class="o">=</span><span class="s2">"VAULT_AWS_ACCESS_KEY"</span>

<span class="c"># Create a role for EC2 instances</span>
vault write auth/aws/role/app-server <span class="se">\</span>
  <span class="nv">auth_type</span><span class="o">=</span>iam <span class="se">\</span>
  <span class="nv">bound_iam_principal_arn</span><span class="o">=</span><span class="s2">"arn:aws:iam::123456789012:role/app-server-role"</span> <span class="se">\</span>
  <span class="nv">policies</span><span class="o">=</span>app-readonly <span class="se">\</span>
  <span class="nv">token_ttl</span><span class="o">=</span>1h <span class="se">\</span>
  <span class="nv">token_max_ttl</span><span class="o">=</span>4h

<span class="c"># Create a role for Lambda functions</span>
vault write auth/aws/role/lambda-worker <span class="se">\</span>
  <span class="nv">auth_type</span><span class="o">=</span>iam <span class="se">\</span>
  <span class="nv">bound_iam_principal_arn</span><span class="o">=</span><span class="s2">"arn:aws:iam::123456789012:role/lambda-execution-role"</span> <span class="se">\</span>
  <span class="nv">policies</span><span class="o">=</span>app-readonly <span class="se">\</span>
  <span class="nv">token_ttl</span><span class="o">=</span>15m <span class="se">\</span>
  <span class="nv">token_max_ttl</span><span class="o">=</span>1h
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="dynamic-database-credentials">Dynamic Database Credentials</h3>

<p>This is where Vault truly shines. Instead of storing a static database password, Vault creates a unique, short-lived credential for each request:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre><span class="c"># Enable the database secrets engine</span>
vault secrets <span class="nb">enable </span>database

<span class="c"># Configure PostgreSQL connection</span>
vault write database/config/myapp-db <span class="se">\</span>
  <span class="nv">plugin_name</span><span class="o">=</span>postgresql-database-plugin <span class="se">\</span>
  <span class="nv">allowed_roles</span><span class="o">=</span><span class="s2">"app-readonly,app-readwrite"</span> <span class="se">\</span>
  <span class="nv">connection_url</span><span class="o">=</span><span class="s2">"postgresql://:@db.example.com:5432/myapp?sslmode=require"</span> <span class="se">\</span>
  <span class="nv">username</span><span class="o">=</span><span class="s2">"vault_admin"</span> <span class="se">\</span>
  <span class="nv">password</span><span class="o">=</span><span class="s2">"vault_admin_password"</span>

<span class="c"># Create a role that generates read-only credentials</span>
vault write database/roles/app-readonly <span class="se">\</span>
  <span class="nv">db_name</span><span class="o">=</span>myapp-db <span class="se">\</span>
  <span class="nv">creation_statements</span><span class="o">=</span><span class="s2">"CREATE ROLE </span><span class="se">\"\"</span><span class="s2"> WITH LOGIN PASSWORD '' VALID UNTIL ''; </span><span class="se">\</span><span class="s2">
    GRANT SELECT ON ALL TABLES IN SCHEMA public TO </span><span class="se">\"\"</span><span class="s2">;"</span> <span class="se">\</span>
  <span class="nv">revocation_statements</span><span class="o">=</span><span class="s2">"DROP ROLE IF EXISTS </span><span class="se">\"\"</span><span class="s2">;"</span> <span class="se">\</span>
  <span class="nv">default_ttl</span><span class="o">=</span>1h <span class="se">\</span>
  <span class="nv">max_ttl</span><span class="o">=</span>24h
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Now any authenticated application can request fresh credentials:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="c"># Request dynamic credentials</span>
vault <span class="nb">read </span>database/creds/app-readonly

<span class="c"># Output:</span>
<span class="c"># Key                Value</span>
<span class="c"># ---                -----</span>
<span class="c"># lease_id           database/creds/app-readonly/abcd1234</span>
<span class="c"># lease_duration     1h</span>
<span class="c"># username           v-app-readonly-xyz789</span>
<span class="c"># password           A1b2C3d4E5f6G7h8</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The credentials are unique per request, automatically revoked after the TTL expires, and every access is logged in Vault’s audit trail. If a credential is compromised, you know exactly which service instance was affected.</p>

<h3 id="kms-auto-unseal">KMS Auto-Unseal</h3>

<p>In production, use AWS KMS to auto-unseal Vault instead of manual Shamir keys:</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="c1"># vault-config/vault.hcl</span>
<span class="nx">seal</span> <span class="s2">"awskms"</span> <span class="p">{</span>
  <span class="nx">region</span>     <span class="o">=</span> <span class="s2">"us-east-1"</span>
  <span class="nx">kms_key_id</span> <span class="o">=</span> <span class="s2">"alias/vault-unseal-key"</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Required IAM permissions for the Vault server:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"Version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2012-10-17"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Statement"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Effect"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Allow"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Action"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="s2">"kms:Encrypt"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"kms:Decrypt"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"kms:DescribeKey"</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:kms:us-east-1:123456789012:key/your-kms-key-id"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="openbao-the-open-source-fork-after-vaults-license-change">OpenBao: The Open Source Fork After Vault’s License Change</h2>

<p>In August 2023, HashiCorp changed Vault’s license from Mozilla Public License (MPL) 2.0 to the Business Source License (BSL) 1.1. The BSL permits internal and personal use, but prohibits hosting or embedding Vault to compete with HashiCorp’s commercial offerings. If you are running Vault internally, the BSL is fine. If you are building a managed service on top of Vault, it is not.</p>

<p>The community responded by forking Vault’s last MPL-licensed version into <strong>OpenBao</strong>, managed by the Linux Foundation. As of February 2026, OpenBao has released version 2.5.0, has 5,400+ GitHub stars, and has attracted corporate backing from GitLab (which plans to ship OpenBao as a built-in component).</p>

<h3 id="what-openbao-adds-over-community-vault">What OpenBao Adds Over Community Vault</h3>

<p>OpenBao includes features that were previously exclusive to Vault Enterprise:</p>

<ul>
  <li><strong>Namespaces</strong>: Multi-tenancy and workload isolation</li>
  <li><strong>Horizontal read scalability</strong>: HA standby nodes can serve read requests (added in 2.5.0)</li>
  <li><strong>MPL 2.0 license</strong>: True open source with no usage restrictions</li>
</ul>

<h3 id="when-to-consider-openbao">When to Consider OpenBao</h3>

<ul>
  <li>You want Enterprise-grade features without the Enterprise price tag</li>
  <li>You are building a platform or managed service that the BSL would restrict</li>
  <li>You want to contribute to the project without CLA restrictions</li>
  <li>You need namespaces for multi-tenant environments</li>
</ul>

<p>The trade-off is maturity. OpenBao’s plugin ecosystem and contributor base are still growing, and some Vault plugins may not be compatible yet. For teams already running Vault OSS internally under the BSL, there is no urgent reason to switch – but OpenBao is worth watching.</p>

<hr />

<h2 id="sops--age-lightweight-encryption-for-gitops">SOPS + age: Lightweight Encryption for GitOps</h2>

<p>Not every team needs a centralized secrets server. If you practice GitOps and want to store encrypted secrets alongside your Kubernetes manifests, Terraform variables, or Helm values, <strong>SOPS</strong> (Secrets OPerationS) with <strong>age</strong> encryption is a lightweight alternative.</p>

<h3 id="how-it-works">How It Works</h3>

<p>SOPS encrypts the <em>values</em> in structured files (YAML, JSON, ENV, INI) while leaving the <em>keys</em> in plaintext. This means you can review diffs, see which secrets changed, and store them in Git – without exposing the actual values.</p>

<p><strong>age</strong> is a modern encryption tool that replaces GPG with simpler key management. No key servers, no web of trust, no expired subkeys.</p>

<h3 id="setup">Setup</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="c"># Install SOPS and age</span>
brew <span class="nb">install </span>sops age

<span class="c"># Generate an age key pair</span>
age-keygen <span class="nt">-o</span> ~/.config/sops/age/keys.txt

<span class="c"># The public key looks like: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="encrypting-secrets">Encrypting Secrets</h3>

<p>Create a <code class="language-plaintext highlighter-rouge">.sops.yaml</code> configuration in your repo root:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="na">creation_rules</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">path_regex</span><span class="pi">:</span> <span class="s">.*\.secrets\.yaml$</span>
    <span class="na">age</span><span class="pi">:</span> <span class="pi">&gt;-</span>
      <span class="s">age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p,</span>
      <span class="s">age1another_team_member_public_key_here</span>
  <span class="pi">-</span> <span class="na">path_regex</span><span class="pi">:</span> <span class="s">.*\.secrets\.json$</span>
    <span class="na">age</span><span class="pi">:</span> <span class="pi">&gt;-</span>
      <span class="s">age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Create a secrets file:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="c1"># secrets.secrets.yaml (before encryption)</span>
<span class="na">database</span><span class="pi">:</span>
  <span class="na">password</span><span class="pi">:</span> <span class="s">s3cureP@ss!</span>
  <span class="na">connection_string</span><span class="pi">:</span> <span class="s">postgresql://user:s3cureP@ss!@db.example.com:5432/myapp</span>
<span class="na">api_keys</span><span class="pi">:</span>
  <span class="na">stripe</span><span class="pi">:</span> <span class="s">sk_live_abc123</span>
  <span class="na">sendgrid</span><span class="pi">:</span> <span class="s">SG.xyz789</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Encrypt it:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>sops <span class="nt">--encrypt</span> <span class="nt">--in-place</span> secrets.secrets.yaml
</pre></td></tr></tbody></table></code></pre></div></div>

<p>After encryption, the file looks like:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="na">database</span><span class="pi">:</span>
  <span class="na">password</span><span class="pi">:</span> <span class="s">ENC[AES256_GCM,data:abc123...,iv:...,tag:...,type:str]</span>
  <span class="na">connection_string</span><span class="pi">:</span> <span class="s">ENC[AES256_GCM,data:def456...,iv:...,tag:...,type:str]</span>
<span class="na">api_keys</span><span class="pi">:</span>
  <span class="na">stripe</span><span class="pi">:</span> <span class="s">ENC[AES256_GCM,data:ghi789...,iv:...,tag:...,type:str]</span>
  <span class="na">sendgrid</span><span class="pi">:</span> <span class="s">ENC[AES256_GCM,data:jkl012...,iv:...,tag:...,type:str]</span>
<span class="na">sops</span><span class="pi">:</span>
  <span class="na">age</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">recipient</span><span class="pi">:</span> <span class="s">age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p</span>
      <span class="na">enc</span><span class="pi">:</span> <span class="pi">|</span>
        <span class="s">-----BEGIN AGE ENCRYPTED FILE-----</span>
        <span class="s">...</span>
        <span class="s">-----END AGE ENCRYPTED FILE-----</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Keys are visible; values are encrypted. You can commit this safely to Git.</p>

<h3 id="sops-with-aws-kms">SOPS with AWS KMS</h3>

<p>For teams on AWS, SOPS also supports KMS as a key provider, giving you centralized key management without running Vault:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="c1"># .sops.yaml with AWS KMS</span>
<span class="na">creation_rules</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">path_regex</span><span class="pi">:</span> <span class="s">.*\.secrets\.yaml$</span>
    <span class="na">kms</span><span class="pi">:</span> <span class="s2">"</span><span class="s">arn:aws:kms:us-east-1:123456789012:key/your-kms-key-id"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="when-sops--age-makes-sense">When SOPS + age Makes Sense</h3>

<ul>
  <li>Small teams with GitOps workflows</li>
  <li>Kubernetes secrets stored alongside manifests (Flux, ArgoCD integration)</li>
  <li>Terraform variable files that need to be versioned</li>
  <li>You do not want to run or pay for a secrets management server</li>
  <li>Development and staging environments where simplicity matters</li>
</ul>

<hr />

<h2 id="comparison-table">Comparison Table</h2>

<table>
  <thead>
    <tr>
      <th>Capability</th>
      <th>SSM Parameter Store</th>
      <th>Secrets Manager</th>
      <th>Vault / OpenBao</th>
      <th>SOPS + age</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Cost</strong></td>
      <td>Free (standard)</td>
      <td>$0.40/secret/month</td>
      <td>Self-hosted (compute cost)</td>
      <td>Free</td>
    </tr>
    <tr>
      <td><strong>Dynamic secrets</strong></td>
      <td>No</td>
      <td>No</td>
      <td>Yes</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Automatic rotation</strong></td>
      <td>No</td>
      <td>Yes (Lambda-based)</td>
      <td>Yes (built-in leases)</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Multi-cloud</strong></td>
      <td>AWS only</td>
      <td>AWS only</td>
      <td>Yes</td>
      <td>Yes (client-side)</td>
    </tr>
    <tr>
      <td><strong>Audit logging</strong></td>
      <td>CloudTrail</td>
      <td>CloudTrail</td>
      <td>Built-in detailed audit</td>
      <td>Git history</td>
    </tr>
    <tr>
      <td><strong>Auth methods</strong></td>
      <td>IAM</td>
      <td>IAM</td>
      <td>IAM, K8s, OIDC, LDAP, etc.</td>
      <td>Key-based</td>
    </tr>
    <tr>
      <td><strong>Encryption</strong></td>
      <td>KMS</td>
      <td>KMS</td>
      <td>Transit engine + storage</td>
      <td>age, GPG, KMS</td>
    </tr>
    <tr>
      <td><strong>Complexity</strong></td>
      <td>Low</td>
      <td>Low</td>
      <td>High</td>
      <td>Low</td>
    </tr>
    <tr>
      <td><strong>High availability</strong></td>
      <td>Built-in (AWS managed)</td>
      <td>Built-in (AWS managed)</td>
      <td>Raft/Consul (self-managed)</td>
      <td>N/A</td>
    </tr>
    <tr>
      <td><strong>Cross-account</strong></td>
      <td>Limited</td>
      <td>Yes (resource policies)</td>
      <td>Yes (namespaces)</td>
      <td>N/A</td>
    </tr>
    <tr>
      <td><strong>GitOps friendly</strong></td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td><strong>License</strong></td>
      <td>Proprietary (AWS)</td>
      <td>Proprietary (AWS)</td>
      <td>BSL 1.1 / MPL 2.0 (OpenBao)</td>
      <td>MPL 2.0</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="decision-framework-when-to-use-which">Decision Framework: When to Use Which</h2>

<p>Use this flowchart to pick the right tool for your situation:</p>

<p><strong>Start here: Are you AWS-only with fewer than 100 secrets?</strong></p>

<ul>
  <li><strong>Yes</strong> –&gt; Do you need automatic rotation?
    <ul>
      <li><strong>Yes</strong> –&gt; <strong>AWS Secrets Manager</strong> (native rotation, low operational overhead)</li>
      <li><strong>No</strong> –&gt; <strong>SSM Parameter Store</strong> (free, simple, sufficient)</li>
    </ul>
  </li>
  <li><strong>No</strong> –&gt; Continue below</li>
</ul>

<p><strong>Do you need dynamic, short-lived credentials?</strong></p>

<ul>
  <li><strong>Yes</strong> –&gt; <strong>HashiCorp Vault</strong> or <strong>OpenBao</strong> (dynamic secrets engine is unique to Vault)</li>
  <li><strong>No</strong> –&gt; Continue below</li>
</ul>

<p><strong>Are you multi-cloud or hybrid?</strong></p>

<ul>
  <li><strong>Yes</strong> –&gt; <strong>Vault / OpenBao</strong> (single control plane across clouds)</li>
  <li><strong>No</strong> –&gt; Continue below</li>
</ul>

<p><strong>Do you practice GitOps and want secrets in version control?</strong></p>

<ul>
  <li><strong>Yes</strong> –&gt; <strong>SOPS + age</strong> (or SOPS + KMS for AWS-native key management)</li>
  <li><strong>No</strong> –&gt; <strong>SSM Parameter Store</strong> or <strong>Secrets Manager</strong> based on rotation needs</li>
</ul>

<h3 id="the-hybrid-approach">The Hybrid Approach</h3>

<p>Many mature organizations use a combination:</p>

<ul>
  <li><strong>SSM Parameter Store</strong> for application configuration and low-sensitivity secrets</li>
  <li><strong>Secrets Manager</strong> for RDS credentials that need automatic rotation</li>
  <li><strong>Vault</strong> for dynamic database credentials, PKI certificates, and multi-cloud secrets</li>
  <li><strong>SOPS</strong> for secrets that live in Git alongside infrastructure code</li>
</ul>

<p>These tools are not mutually exclusive. Vault’s AWS auth backend means it integrates seamlessly with IAM, and SOPS can use KMS as its encryption provider.</p>

<hr />

<h2 id="real-world-scenarios">Real-World Scenarios</h2>

<h3 id="scenario-1-database-credentials-for-a-microservices-fleet">Scenario 1: Database Credentials for a Microservices Fleet</h3>

<p><strong>Problem</strong>: 20 microservices need PostgreSQL access. Static credentials shared across services make it impossible to trace which service performed a questionable query.</p>

<p><strong>Solution</strong>: Vault dynamic database credentials.</p>

<p>Each service authenticates to Vault via its IAM role and receives unique, short-lived credentials:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="n">hvac</span>
<span class="kn">import</span> <span class="n">boto3</span>

<span class="k">def</span> <span class="nf">get_dynamic_db_creds</span><span class="p">(</span><span class="n">vault_addr</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">vault_role</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Authenticate to Vault with AWS IAM and get dynamic DB credentials.</span><span class="sh">"""</span>
    <span class="c1"># Get AWS credentials for Vault auth
</span>    <span class="n">session</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="nc">Session</span><span class="p">()</span>
    <span class="n">credentials</span> <span class="o">=</span> <span class="n">session</span><span class="p">.</span><span class="nf">get_credentials</span><span class="p">().</span><span class="nf">get_frozen_credentials</span><span class="p">()</span>

    <span class="n">client</span> <span class="o">=</span> <span class="n">hvac</span><span class="p">.</span><span class="nc">Client</span><span class="p">(</span><span class="n">url</span><span class="o">=</span><span class="n">vault_addr</span><span class="p">)</span>

    <span class="c1"># Authenticate with AWS IAM
</span>    <span class="n">client</span><span class="p">.</span><span class="n">auth</span><span class="p">.</span><span class="n">aws</span><span class="p">.</span><span class="nf">iam_login</span><span class="p">(</span>
        <span class="n">access_key</span><span class="o">=</span><span class="n">credentials</span><span class="p">.</span><span class="n">access_key</span><span class="p">,</span>
        <span class="n">secret_key</span><span class="o">=</span><span class="n">credentials</span><span class="p">.</span><span class="n">secret_key</span><span class="p">,</span>
        <span class="n">session_token</span><span class="o">=</span><span class="n">credentials</span><span class="p">.</span><span class="n">token</span><span class="p">,</span>
        <span class="n">role</span><span class="o">=</span><span class="n">vault_role</span>
    <span class="p">)</span>

    <span class="c1"># Request dynamic database credentials
</span>    <span class="n">creds</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="n">secrets</span><span class="p">.</span><span class="n">database</span><span class="p">.</span><span class="nf">generate_credentials</span><span class="p">(</span>
        <span class="n">name</span><span class="o">=</span><span class="sh">"</span><span class="s">app-readonly</span><span class="sh">"</span>
    <span class="p">)</span>

    <span class="k">return</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">username</span><span class="sh">"</span><span class="p">:</span> <span class="n">creds</span><span class="p">[</span><span class="sh">"</span><span class="s">data</span><span class="sh">"</span><span class="p">][</span><span class="sh">"</span><span class="s">username</span><span class="sh">"</span><span class="p">],</span>
        <span class="sh">"</span><span class="s">password</span><span class="sh">"</span><span class="p">:</span> <span class="n">creds</span><span class="p">[</span><span class="sh">"</span><span class="s">data</span><span class="sh">"</span><span class="p">][</span><span class="sh">"</span><span class="s">password</span><span class="sh">"</span><span class="p">],</span>
        <span class="sh">"</span><span class="s">lease_id</span><span class="sh">"</span><span class="p">:</span> <span class="n">creds</span><span class="p">[</span><span class="sh">"</span><span class="s">lease_id</span><span class="sh">"</span><span class="p">],</span>
        <span class="sh">"</span><span class="s">lease_duration</span><span class="sh">"</span><span class="p">:</span> <span class="n">creds</span><span class="p">[</span><span class="sh">"</span><span class="s">lease_duration</span><span class="sh">"</span><span class="p">]</span>
    <span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Result</strong>: Every database connection is traceable to a specific service instance. Credentials expire automatically. If a service is compromised, revoke its Vault lease and the credentials are immediately invalid.</p>

<h3 id="scenario-2-api-keys-for-a-saas-integration">Scenario 2: API Keys for a SaaS Integration</h3>

<p><strong>Problem</strong>: Third-party API keys (Stripe, SendGrid, Datadog) need to be available to Lambda functions.</p>

<p><strong>Solution</strong>: SSM Parameter Store with SecureString.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="c"># Store API keys</span>
aws ssm put-parameter <span class="se">\</span>
  <span class="nt">--name</span> <span class="s2">"/myapp/prod/stripe/api-key"</span> <span class="se">\</span>
  <span class="nt">--value</span> <span class="s2">"sk_live_abc123"</span> <span class="se">\</span>
  <span class="nt">--type</span> <span class="s2">"SecureString"</span> <span class="se">\</span>
  <span class="nt">--tier</span> <span class="s2">"Standard"</span>

aws ssm put-parameter <span class="se">\</span>
  <span class="nt">--name</span> <span class="s2">"/myapp/prod/sendgrid/api-key"</span> <span class="se">\</span>
  <span class="nt">--value</span> <span class="s2">"SG.xyz789"</span> <span class="se">\</span>
  <span class="nt">--type</span> <span class="s2">"SecureString"</span> <span class="se">\</span>
  <span class="nt">--tier</span> <span class="s2">"Standard"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Reference them in Lambda via CloudFormation:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="na">MyFunction</span><span class="pi">:</span>
  <span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::Serverless::Function</span>
  <span class="na">Properties</span><span class="pi">:</span>
    <span class="na">Environment</span><span class="pi">:</span>
      <span class="na">Variables</span><span class="pi">:</span>
        <span class="na">STRIPE_API_KEY</span><span class="pi">:</span> <span class="kt">!Sub</span> <span class="s2">"</span><span class="s">"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Result</strong>: Free, simple, and integrated. No servers to manage. IAM controls who can access which parameters.</p>

<h3 id="scenario-3-tls-certificates-for-internal-services">Scenario 3: TLS Certificates for Internal Services</h3>

<p><strong>Problem</strong>: Internal microservices need mutual TLS (mTLS) certificates that rotate frequently.</p>

<p><strong>Solution</strong>: Vault PKI secrets engine.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="c"># Enable PKI and configure a root CA</span>
vault secrets <span class="nb">enable </span>pki
vault secrets tune <span class="nt">-max-lease-ttl</span><span class="o">=</span>87600h pki

vault write pki/root/generate/internal <span class="se">\</span>
  <span class="nv">common_name</span><span class="o">=</span><span class="s2">"internal.example.com"</span> <span class="se">\</span>
  <span class="nv">ttl</span><span class="o">=</span>87600h

<span class="c"># Enable an intermediate CA</span>
vault secrets <span class="nb">enable</span> <span class="nt">-path</span><span class="o">=</span>pki_int pki
vault secrets tune <span class="nt">-max-lease-ttl</span><span class="o">=</span>43800h pki_int

<span class="c"># Create a role for issuing certificates</span>
vault write pki_int/roles/internal-service <span class="se">\</span>
  <span class="nv">allowed_domains</span><span class="o">=</span><span class="s2">"internal.example.com"</span> <span class="se">\</span>
  <span class="nv">allow_subdomains</span><span class="o">=</span><span class="nb">true</span> <span class="se">\</span>
  <span class="nv">max_ttl</span><span class="o">=</span>72h
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Services request certificates dynamically:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>vault write pki_int/issue/internal-service <span class="se">\</span>
  <span class="nv">common_name</span><span class="o">=</span><span class="s2">"myservice.internal.example.com"</span> <span class="se">\</span>
  <span class="nv">ttl</span><span class="o">=</span>24h
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Result</strong>: Short-lived certificates, automatic rotation, and a full audit trail. No certificate sprawl, no expired certs causing outages.</p>

<hr />

<h2 id="migration-strategies">Migration Strategies</h2>

<h3 id="moving-from-hardcoded-secrets-to-ssm-parameter-store">Moving from Hardcoded Secrets to SSM Parameter Store</h3>

<ol>
  <li><strong>Audit</strong>: Scan your codebase for hardcoded secrets using tools like <code class="language-plaintext highlighter-rouge">gitleaks</code> or <code class="language-plaintext highlighter-rouge">trufflehog</code></li>
  <li><strong>Inventory</strong>: Document every secret, its current location, and which services use it</li>
  <li><strong>Store</strong>: Create SSM parameters following the <code class="language-plaintext highlighter-rouge">/{app}/{env}/{secret}</code> naming convention</li>
  <li><strong>Update</strong>: Modify application code to read from SSM at startup or runtime</li>
  <li><strong>Remove</strong>: Delete hardcoded secrets from source code and rotate all compromised values</li>
  <li><strong>Verify</strong>: Confirm applications function correctly with SSM-sourced secrets</li>
  <li><strong>Enforce</strong>: Add pre-commit hooks with <code class="language-plaintext highlighter-rouge">gitleaks</code> to prevent future leaks</li>
</ol>

<h3 id="moving-from-ssm-parameter-store-to-vault">Moving from SSM Parameter Store to Vault</h3>

<ol>
  <li><strong>Deploy Vault</strong>: Set up Vault with Raft storage and KMS auto-unseal</li>
  <li><strong>Configure auth</strong>: Enable the AWS auth backend and create roles for your workloads</li>
  <li><strong>Migrate static secrets</strong>: Export from SSM and import into Vault’s KV engine</li>
  <li><strong>Enable dynamic secrets</strong>: Configure database and other secret engines</li>
  <li><strong>Update applications</strong>: Replace SSM SDK calls with Vault client calls</li>
  <li><strong>Parallel run</strong>: Keep SSM as a fallback while validating Vault</li>
  <li><strong>Cut over</strong>: Remove SSM dependencies once Vault is proven stable</li>
</ol>

<h3 id="gradual-adoption-vault-alongside-aws-native">Gradual Adoption: Vault Alongside AWS Native</h3>

<p>You do not have to migrate everything at once. A practical approach:</p>

<ul>
  <li><strong>Phase 1</strong>: Keep API keys and static config in SSM Parameter Store</li>
  <li><strong>Phase 2</strong>: Move database credentials to Vault dynamic secrets</li>
  <li><strong>Phase 3</strong>: Add Vault PKI for internal TLS certificates</li>
  <li><strong>Phase 4</strong>: Migrate remaining secrets as teams gain Vault operational experience</li>
</ul>

<hr />

<h2 id="security-hardening-checklist">Security Hardening Checklist</h2>

<p>Regardless of which tool you choose, follow these baseline practices:</p>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Encrypt at rest</strong>: KMS for AWS services, Vault’s storage encryption, age for files</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Encrypt in transit</strong>: TLS everywhere, including between Vault and its storage backend</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Least privilege</strong>: IAM policies scoped to specific parameter paths or Vault policies scoped to specific secret paths</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Audit logging</strong>: Enable CloudTrail for AWS services, Vault audit backend for Vault</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Rotation</strong>: Automate credential rotation – 30 days for static, hours for dynamic</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>No hardcoded secrets</strong>: Pre-commit hooks with <code class="language-plaintext highlighter-rouge">gitleaks</code> or <code class="language-plaintext highlighter-rouge">trufflehog</code></li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Disaster recovery</strong>: Back up Vault’s Raft snapshots, replicate Secrets Manager cross-region</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Access reviews</strong>: Quarterly review of who and what can access each secret</li>
</ul>

<hr />

<h2 id="conclusion">Conclusion</h2>

<p>There is no single “best” secrets management tool. The right choice depends on your environment, your team’s operational maturity, and your specific requirements:</p>

<ul>
  <li><strong>SSM Parameter Store</strong> is the right starting point for most AWS-native teams. It is free, integrated, and handles 80% of use cases.</li>
  <li><strong>Secrets Manager</strong> is worth the $0.40/secret/month when you need automatic rotation, especially for RDS credentials.</li>
  <li><strong>Vault</strong> (or <strong>OpenBao</strong>) is the answer when you need dynamic secrets, multi-cloud support, PKI, or detailed audit logging. It is more complex to operate, but the security model is superior.</li>
  <li><strong>SOPS + age</strong> fills a niche for GitOps workflows where secrets need to live in version control.</li>
</ul>

<p>Start with AWS native. Adopt Vault when you outgrow it. Use SOPS for what lives in Git. The tools complement each other – build the stack that fits your threat model and operational capacity.</p>

<hr />

<h2 id="related-articles">Related Articles</h2>

<ul>
  <li><a href="/posts/data-protection-on-aws-encryption-and-secrets-management/">Data Protection on AWS: Encryption and Secrets Management</a></li>
  <li><a href="/posts/aws-cloud-security-best-practices-implementation-guide/">AWS Cloud Security Best Practices Implementation Guide</a></li>
  <li><a href="/posts/aws-iam-zero-trust-identity-network-deep-dive/">AWS IAM Zero Trust: Identity and Network Deep Dive</a></li>
</ul>

<hr />

<p><strong>Have questions about secrets management architecture?</strong> Connect with me on <a href="https://www.linkedin.com/in/jonpricelinux/">LinkedIn</a> to discuss your specific environment and requirements.</p>]]></content><author><name>Jon (JR) Price</name></author><category term="AWS" /><category term="DevSecOps" /><category term="Security" /><category term="aws-secrets-manager" /><category term="hashicorp-vault" /><category term="kms" /><category term="open-source" /><category term="secrets-management" /><category term="sops" /><category term="ssm-parameter-store" /><summary type="html"><![CDATA[Compare HashiCorp Vault with AWS SSM Parameter Store and Secrets Manager for secrets management. Decision framework for choosing the right tool.]]></summary></entry><entry><title type="html">IaC Security Scanning: Checkov and tfsec vs AWS-Native Controls</title><link href="https://www.red-team.sh/posts/iac-security-scanning-checkov-tfsec-aws/" rel="alternate" type="text/html" title="IaC Security Scanning: Checkov and tfsec vs AWS-Native Controls" /><published>2026-04-01T09:00:00-07:00</published><updated>2026-04-01T09:00:00-07:00</updated><id>https://www.red-team.sh/posts/iac-security-scanning-checkov-tfsec-aws</id><content type="html" xml:base="https://www.red-team.sh/posts/iac-security-scanning-checkov-tfsec-aws/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#introduction" id="markdown-toc-introduction">Introduction</a></li>
  <li><a href="#the-shift-left-security-model-for-infrastructure" id="markdown-toc-the-shift-left-security-model-for-infrastructure">The Shift-Left Security Model for Infrastructure</a>    <ul>
      <li><a href="#what-shift-left-means-for-iac" id="markdown-toc-what-shift-left-means-for-iac">What Shift-Left Means for IaC</a></li>
      <li><a href="#why-oss-scanners-lead-the-shift-left-movement" id="markdown-toc-why-oss-scanners-lead-the-shift-left-movement">Why OSS Scanners Lead the Shift-Left Movement</a></li>
    </ul>
  </li>
  <li><a href="#aws-cloudformation-guard-capabilities-and-limitations" id="markdown-toc-aws-cloudformation-guard-capabilities-and-limitations">AWS CloudFormation Guard: Capabilities and Limitations</a>    <ul>
      <li><a href="#what-is-cloudformation-guard" id="markdown-toc-what-is-cloudformation-guard">What Is CloudFormation Guard?</a></li>
      <li><a href="#guard-rule-syntax" id="markdown-toc-guard-rule-syntax">Guard Rule Syntax</a></li>
      <li><a href="#running-guard-locally" id="markdown-toc-running-guard-locally">Running Guard Locally</a></li>
      <li><a href="#guard-rules-registry" id="markdown-toc-guard-rules-registry">Guard Rules Registry</a></li>
      <li><a href="#limitations" id="markdown-toc-limitations">Limitations</a></li>
    </ul>
  </li>
  <li><a href="#aws-config-rules-post-deployment-compliance" id="markdown-toc-aws-config-rules-post-deployment-compliance">AWS Config Rules: Post-Deployment Compliance</a>    <ul>
      <li><a href="#how-config-rules-work" id="markdown-toc-how-config-rules-work">How Config Rules Work</a></li>
      <li><a href="#config-rules-with-cloudformation-guard" id="markdown-toc-config-rules-with-cloudformation-guard">Config Rules with CloudFormation Guard</a></li>
      <li><a href="#limitations-of-config-rules" id="markdown-toc-limitations-of-config-rules">Limitations of Config Rules</a></li>
    </ul>
  </li>
  <li><a href="#checkov-deep-dive" id="markdown-toc-checkov-deep-dive">Checkov Deep Dive</a>    <ul>
      <li><a href="#overview" id="markdown-toc-overview">Overview</a></li>
      <li><a href="#key-differentiators" id="markdown-toc-key-differentiators">Key Differentiators</a></li>
      <li><a href="#running-checkov" id="markdown-toc-running-checkov">Running Checkov</a></li>
      <li><a href="#example-scanning-terraform-with-intentional-misconfigurations" id="markdown-toc-example-scanning-terraform-with-intentional-misconfigurations">Example: Scanning Terraform with Intentional Misconfigurations</a></li>
      <li><a href="#writing-custom-checkov-checks-in-python" id="markdown-toc-writing-custom-checkov-checks-in-python">Writing Custom Checkov Checks in Python</a></li>
      <li><a href="#writing-custom-checkov-checks-in-yaml" id="markdown-toc-writing-custom-checkov-checks-in-yaml">Writing Custom Checkov Checks in YAML</a></li>
    </ul>
  </li>
  <li><a href="#tfsec-and-trivy-iac-scanning" id="markdown-toc-tfsec-and-trivy-iac-scanning">tfsec and Trivy IaC Scanning</a>    <ul>
      <li><a href="#the-tfsec-to-trivy-transition" id="markdown-toc-the-tfsec-to-trivy-transition">The tfsec to Trivy Transition</a></li>
      <li><a href="#running-trivy-for-iac-scanning" id="markdown-toc-running-trivy-for-iac-scanning">Running Trivy for IaC Scanning</a></li>
      <li><a href="#example-trivy-output" id="markdown-toc-example-trivy-output">Example Trivy Output</a></li>
      <li><a href="#custom-rego-policies-for-trivy" id="markdown-toc-custom-rego-policies-for-trivy">Custom Rego Policies for Trivy</a></li>
    </ul>
  </li>
  <li><a href="#comparison-table" id="markdown-toc-comparison-table">Comparison Table</a></li>
  <li><a href="#pre-commit-hook-integration" id="markdown-toc-pre-commit-hook-integration">Pre-Commit Hook Integration</a>    <ul>
      <li><a href="#pre-commit-configuration" id="markdown-toc-pre-commit-configuration">Pre-Commit Configuration</a></li>
      <li><a href="#installing-and-running-pre-commit" id="markdown-toc-installing-and-running-pre-commit">Installing and Running Pre-Commit</a></li>
    </ul>
  </li>
  <li><a href="#cicd-pipeline-integration" id="markdown-toc-cicd-pipeline-integration">CI/CD Pipeline Integration</a>    <ul>
      <li><a href="#github-actions-workflow" id="markdown-toc-github-actions-workflow">GitHub Actions Workflow</a></li>
      <li><a href="#aws-codebuild-buildspec" id="markdown-toc-aws-codebuild-buildspec">AWS CodeBuild Buildspec</a></li>
    </ul>
  </li>
  <li><a href="#custom-policy-authoring" id="markdown-toc-custom-policy-authoring">Custom Policy Authoring</a>    <ul>
      <li><a href="#checkov-python-or-yaml" id="markdown-toc-checkov-python-or-yaml">Checkov: Python or YAML</a></li>
      <li><a href="#trivy-rego-opa" id="markdown-toc-trivy-rego-opa">Trivy: Rego (OPA)</a></li>
      <li><a href="#cloudformation-guard-guard-dsl" id="markdown-toc-cloudformation-guard-guard-dsl">CloudFormation Guard: Guard DSL</a></li>
    </ul>
  </li>
  <li><a href="#defense-in-depth-the-complete-scanning-architecture" id="markdown-toc-defense-in-depth-the-complete-scanning-architecture">Defense-in-Depth: The Complete Scanning Architecture</a>    <ul>
      <li><a href="#layer-1-pre-commit-developer-machine" id="markdown-toc-layer-1-pre-commit-developer-machine">Layer 1: Pre-Commit (Developer Machine)</a></li>
      <li><a href="#layer-2-cicd-pipeline-automated-gate" id="markdown-toc-layer-2-cicd-pipeline-automated-gate">Layer 2: CI/CD Pipeline (Automated Gate)</a></li>
      <li><a href="#layer-3-post-deploy-continuous-compliance" id="markdown-toc-layer-3-post-deploy-continuous-compliance">Layer 3: Post-Deploy (Continuous Compliance)</a></li>
      <li><a href="#putting-it-together" id="markdown-toc-putting-it-together">Putting It Together</a></li>
      <li><a href="#coverage-matrix" id="markdown-toc-coverage-matrix">Coverage Matrix</a></li>
    </ul>
  </li>
  <li><a href="#getting-started-recommended-implementation-path" id="markdown-toc-getting-started-recommended-implementation-path">Getting Started: Recommended Implementation Path</a></li>
  <li><a href="#related-articles" id="markdown-toc-related-articles">Related Articles</a></li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ul>

<h2 id="introduction">Introduction</h2>

<p>Infrastructure misconfigurations remain the single largest attack surface in cloud environments. In 2025, roughly 23% of all cloud security incidents originated from misconfigured resources, with 55% of cloud breaches tracing back to configuration drift or oversight. The average cost per misconfiguration incident now exceeds $4.3 million, and Gartner continues to forecast that through 2025 and beyond, 99% of cloud security failures will be the customer’s fault — primarily due to misconfigurations.</p>

<p>The good news: policy-as-code, automation, and continuous scanning can prevent up to 75% of these issues before deployment ever happens. That is what shift-left IaC security scanning delivers. By catching insecure defaults, overly permissive IAM policies, unencrypted storage, and exposed network configurations at the code level, you eliminate entire classes of vulnerabilities before they reach production.</p>

<p>This guide compares the open-source IaC security scanners — Checkov and tfsec (now part of Trivy) — with AWS-native controls including CloudFormation Guard and AWS Config Rules. The takeaway: run both OSS and AWS-native tools for defense-in-depth coverage.</p>

<p><img src="/assets/img/iac-security-scanning-checkov-tfsec.png" alt="IaC Security Scanning Defense in Depth" class="shadow" w="800" />
<em>IaC security scanning defense-in-depth: pre-commit, CI/CD, and post-deploy continuous compliance</em></p>

<h2 id="the-shift-left-security-model-for-infrastructure">The Shift-Left Security Model for Infrastructure</h2>

<h3 id="what-shift-left-means-for-iac">What Shift-Left Means for IaC</h3>

<p>Traditional infrastructure security operates reactively: deploy resources, then scan for misconfigurations after the fact. Shift-left inverts this model by embedding security validation directly into the development workflow:</p>

<ol>
  <li><strong>Pre-commit</strong> — Scan IaC files on the developer’s machine before code is pushed</li>
  <li><strong>CI/CD pipeline</strong> — Automated security gates that block merges or deployments on policy violations</li>
  <li><strong>Post-deployment</strong> — Continuous compliance monitoring to catch drift and runtime changes</li>
</ol>

<p>Each layer catches what the previous one missed. Pre-commit hooks provide fast developer feedback. CI/CD gates enforce organizational policy. Post-deployment monitoring handles manual console changes and configuration drift.</p>

<h3 id="why-oss-scanners-lead-the-shift-left-movement">Why OSS Scanners Lead the Shift-Left Movement</h3>

<p>Open-source IaC scanners have a structural advantage over vendor-specific tools: community-driven rule sets grow faster and cover more frameworks. Checkov ships with over 2,500 built-in policies. Trivy (the successor to tfsec) integrates IaC scanning alongside container and dependency scanning in a single binary. These tools are cloud-agnostic, free, and extensible.</p>

<p>AWS-native tools like CloudFormation Guard and AWS Config Rules are essential complements, but they operate within the AWS ecosystem and require you to author or adopt rules from a smaller registry. The winning strategy is to run OSS scanners for breadth and AWS-native tools for depth on AWS-specific compliance.</p>

<h2 id="aws-cloudformation-guard-capabilities-and-limitations">AWS CloudFormation Guard: Capabilities and Limitations</h2>

<h3 id="what-is-cloudformation-guard">What Is CloudFormation Guard?</h3>

<p><a href="https://github.com/aws-cloudformation/cloudformation-guard">AWS CloudFormation Guard</a> (cfn-guard) is an open-source, general-purpose policy-as-code evaluation tool. Despite the name, it validates any JSON or YAML structured data — not just CloudFormation templates. You can use it against Terraform plan JSON output, Kubernetes manifests, or any structured configuration.</p>

<p>Guard uses a purpose-built domain-specific language (DSL) for writing rules. Guard 3.0 introduced stateful rules through built-in functions, advanced regex support, structured JSON/YAML output, and shell auto-completions.</p>

<h3 id="guard-rule-syntax">Guard Rule Syntax</h3>

<p>Guard rules are declarative and readable. Here is an example that enforces S3 bucket encryption:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre><span class="c1"># s3_encryption.guard</span>
<span class="n">let</span> <span class="n">s3_buckets</span> <span class="o">=</span> <span class="no">Resources</span><span class="p">.</span><span class="nf">*</span><span class="p">[</span> <span class="no">Type</span> <span class="o">==</span> <span class="s1">'AWS::S3::Bucket'</span> <span class="p">]</span>

<span class="n">rule</span> <span class="n">s3_bucket_encryption_enabled</span> <span class="k">when</span> <span class="o">%</span><span class="n">s3_buckets</span> <span class="o">!</span><span class="n">empty</span> <span class="p">{</span>
    <span class="o">%</span><span class="n">s3_buckets</span><span class="o">.</span><span class="no">Properties</span><span class="o">.</span><span class="no">BucketEncryption</span> <span class="n">exists</span>
    <span class="o">%</span><span class="n">s3_buckets</span><span class="o">.</span><span class="no">Properties</span><span class="o">.</span><span class="no">BucketEncryption</span><span class="o">.</span><span class="no">ServerSideEncryptionConfiguration</span><span class="p">[</span><span class="o">*</span><span class="p">]</span> <span class="p">{</span>
        <span class="no">ServerSideEncryptionByDefault</span><span class="o">.</span><span class="no">SSEAlgorithm</span> <span class="k">in</span> <span class="p">[</span><span class="s2">"aws:kms"</span><span class="p">,</span> <span class="s2">"AES256"</span><span class="p">]</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="n">rule</span> <span class="n">s3_bucket_public_access_blocked</span> <span class="k">when</span> <span class="o">%</span><span class="n">s3_buckets</span> <span class="o">!</span><span class="n">empty</span> <span class="p">{</span>
    <span class="o">%</span><span class="n">s3_buckets</span><span class="o">.</span><span class="no">Properties</span><span class="o">.</span><span class="no">PublicAccessBlockConfiguration</span> <span class="n">exists</span>
    <span class="o">%</span><span class="n">s3_buckets</span><span class="o">.</span><span class="no">Properties</span><span class="o">.</span><span class="no">PublicAccessBlockConfiguration</span> <span class="p">{</span>
        <span class="no">BlockPublicAcls</span> <span class="o">==</span> <span class="kp">true</span>
        <span class="no">BlockPublicPolicy</span> <span class="o">==</span> <span class="kp">true</span>
        <span class="no">IgnorePublicAcls</span> <span class="o">==</span> <span class="kp">true</span>
        <span class="no">RestrictPublicBuckets</span> <span class="o">==</span> <span class="kp">true</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="running-guard-locally">Running Guard Locally</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="c"># Install cfn-guard</span>
brew <span class="nb">install </span>cloudformation-guard

<span class="c"># Validate a CloudFormation template</span>
cfn-guard validate <span class="se">\</span>
  <span class="nt">--data</span> template.yaml <span class="se">\</span>
  <span class="nt">--rules</span> rules/s3_encryption.guard <span class="se">\</span>
  <span class="nt">--output-format</span> json

<span class="c"># Validate Terraform plan output</span>
terraform plan <span class="nt">-out</span><span class="o">=</span>tfplan
terraform show <span class="nt">-json</span> tfplan <span class="o">&gt;</span> tfplan.json
cfn-guard validate <span class="se">\</span>
  <span class="nt">--data</span> tfplan.json <span class="se">\</span>
  <span class="nt">--rules</span> rules/terraform_rules.guard
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="guard-rules-registry">Guard Rules Registry</h3>

<p>AWS maintains the <a href="https://github.com/aws-cloudformation/aws-guard-rules-registry">Guard Rules Registry</a>, an open-source repository of rule sets mapped to compliance frameworks including CIS AWS Foundations Benchmark, NIST 800-53, PCI DSS, and HIPAA. This registry gives you a head start, but the total rule count is significantly smaller than what OSS scanners provide.</p>

<h3 id="limitations">Limitations</h3>

<ul>
  <li><strong>DSL learning curve</strong>: Guard’s rule language is purpose-built and unfamiliar to most teams</li>
  <li><strong>Smaller rule ecosystem</strong>: The community-contributed rule set is a fraction of Checkov’s 2,500+ policies</li>
  <li><strong>CloudFormation-centric</strong>: While it can parse any JSON/YAML, the tooling and documentation focus on CloudFormation</li>
  <li><strong>No graph-based analysis</strong>: Guard evaluates resources individually and does not analyze cross-resource relationships</li>
</ul>

<h2 id="aws-config-rules-post-deployment-compliance">AWS Config Rules: Post-Deployment Compliance</h2>

<p>AWS Config Rules provide continuous compliance monitoring for deployed resources. They operate as the final safety net in a defense-in-depth strategy.</p>

<h3 id="how-config-rules-work">How Config Rules Work</h3>

<p>AWS Config continuously records resource configurations and evaluates them against rules. Rules can be AWS-managed (pre-built by AWS) or custom (Lambda-backed or Guard-based).</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre></td><td class="rouge-code"><pre><span class="c"># Example: Enable the s3-bucket-server-side-encryption-enabled managed rule</span>
aws configservice put-config-rule <span class="nt">--config-rule</span> <span class="s1">'{
  "ConfigRuleName": "s3-bucket-encryption",
  "Source": {
    "Owner": "AWS",
    "SourceIdentifier": "S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED"
  },
  "Scope": {
    "ComplianceResourceTypes": ["AWS::S3::Bucket"]
  }
}'</span>

<span class="c"># Check compliance status</span>
aws configservice get-compliance-details-by-config-rule <span class="se">\</span>
  <span class="nt">--config-rule-name</span> s3-bucket-encryption <span class="se">\</span>
  <span class="nt">--compliance-types</span> NON_COMPLIANT
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="config-rules-with-cloudformation-guard">Config Rules with CloudFormation Guard</h3>

<p>You can now use Guard rules directly as AWS Config custom rules, bridging pre-deployment and post-deployment scanning with the same policy language:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
</pre></td><td class="rouge-code"><pre><span class="c1"># CloudFormation template for a Guard-based Config rule</span>
<span class="na">AWSTemplateFormatVersion</span><span class="pi">:</span> <span class="s1">'</span><span class="s">2010-09-09'</span>
<span class="na">Resources</span><span class="pi">:</span>
  <span class="na">S3EncryptionGuardRule</span><span class="pi">:</span>
    <span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::Config::ConfigRule</span>
    <span class="na">Properties</span><span class="pi">:</span>
      <span class="na">ConfigRuleName</span><span class="pi">:</span> <span class="s">s3-encryption-guard</span>
      <span class="na">Source</span><span class="pi">:</span>
        <span class="na">Owner</span><span class="pi">:</span> <span class="s">CUSTOM_POLICY</span>
        <span class="na">SourceDetails</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">EventSource</span><span class="pi">:</span> <span class="s">aws.config</span>
            <span class="na">MessageType</span><span class="pi">:</span> <span class="s">ConfigurationItemChangeNotification</span>
        <span class="na">CustomPolicyDetails</span><span class="pi">:</span>
          <span class="na">PolicyRuntime</span><span class="pi">:</span> <span class="s">guard-2.x.x</span>
          <span class="na">PolicyText</span><span class="pi">:</span> <span class="pi">|</span>
            <span class="s">rule s3_encrypted {</span>
              <span class="s">resourceType == "AWS::S3::Bucket"</span>
              <span class="s">configuration.ServerSideEncryptionConfiguration exists</span>
            <span class="s">}</span>
      <span class="na">Scope</span><span class="pi">:</span>
        <span class="na">ComplianceResourceTypes</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="s">AWS::S3::Bucket</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="limitations-of-config-rules">Limitations of Config Rules</h3>

<ul>
  <li><strong>Post-deployment only</strong>: Config Rules evaluate after resources exist, not before</li>
  <li><strong>Cost</strong>: Each rule evaluation costs $0.001 per evaluation ($0.10/month per active rule with daily recording)</li>
  <li><strong>Latency</strong>: Evaluation can take minutes after a configuration change</li>
  <li><strong>Remediation complexity</strong>: Auto-remediation requires additional Lambda functions or SSM Automation</li>
</ul>

<p>Config Rules are essential for detecting drift and unauthorized manual changes, but they should not be your first line of defense.</p>

<h2 id="checkov-deep-dive">Checkov Deep Dive</h2>

<h3 id="overview">Overview</h3>

<p><a href="https://github.com/bridgecrewio/checkov">Checkov</a> is the most comprehensive open-source IaC scanner available. Maintained by Palo Alto Networks (Bridgecrew), it ships with over 2,500 built-in policies covering AWS, Azure, GCP, Kubernetes, and general security best practices.</p>

<h3 id="key-differentiators">Key Differentiators</h3>

<ul>
  <li><strong>Graph-based scanning</strong>: Checkov parses IaC files into a graph representation and evaluates cross-resource relationships. For example, it checks that an S3 bucket referenced by a CloudFront distribution has the correct origin access policy.</li>
  <li><strong>Framework breadth</strong>: Supports Terraform, CloudFormation, Kubernetes, Helm, Dockerfiles, ARM templates, Serverless Framework, Bicep, and more.</li>
  <li><strong>Compliance mapping</strong>: Built-in mapping to CIS, SOC2, HIPAA, PCI DSS, NIST 800-53, and ISO 27001.</li>
  <li><strong>Custom check authoring</strong>: Write custom checks in Python or YAML.</li>
  <li><strong>SCA integration</strong>: Scans open-source dependencies for vulnerabilities alongside IaC checks.</li>
</ul>

<h3 id="running-checkov">Running Checkov</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
</pre></td><td class="rouge-code"><pre><span class="c"># Install</span>
pip <span class="nb">install </span>checkov

<span class="c"># Scan a Terraform directory</span>
checkov <span class="nt">-d</span> ./terraform/ <span class="nt">--framework</span> terraform

<span class="c"># Scan with specific compliance framework</span>
checkov <span class="nt">-d</span> ./terraform/ <span class="nt">--check</span> CIS_AWS

<span class="c"># Scan CloudFormation templates</span>
checkov <span class="nt">-f</span> cloudformation-template.yaml <span class="nt">--framework</span> cloudformation

<span class="c"># Output in JUnit XML for CI/CD integration</span>
checkov <span class="nt">-d</span> ./terraform/ <span class="nt">-o</span> junitxml <span class="o">&gt;</span> checkov-results.xml

<span class="c"># Skip specific checks</span>
checkov <span class="nt">-d</span> ./terraform/ <span class="nt">--skip-check</span> CKV_AWS_18,CKV_AWS_19

<span class="c"># Scan with custom external checks</span>
checkov <span class="nt">-d</span> ./terraform/ <span class="nt">--external-checks-dir</span> ./custom-checks/
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="example-scanning-terraform-with-intentional-misconfigurations">Example: Scanning Terraform with Intentional Misconfigurations</h3>

<p>Here is a Terraform file with several common security issues:</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
</pre></td><td class="rouge-code"><pre><span class="c1"># main.tf - Intentionally insecure for demonstration</span>
<span class="nx">resource</span> <span class="s2">"aws_s3_bucket"</span> <span class="s2">"data"</span> <span class="p">{</span>
  <span class="nx">bucket</span> <span class="o">=</span> <span class="s2">"my-data-bucket"</span>
  <span class="c1"># Missing: encryption, versioning, public access block, logging</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_security_group"</span> <span class="s2">"web"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"web-sg"</span>

  <span class="nx">ingress</span> <span class="p">{</span>
    <span class="nx">from_port</span>   <span class="o">=</span> <span class="mi">0</span>
    <span class="nx">to_port</span>     <span class="o">=</span> <span class="mi">0</span>
    <span class="nx">protocol</span>    <span class="o">=</span> <span class="s2">"-1"</span>
    <span class="nx">cidr_blocks</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"0.0.0.0/0"</span><span class="p">]</span>  <span class="c1"># Open to the world</span>
  <span class="p">}</span>

  <span class="nx">egress</span> <span class="p">{</span>
    <span class="nx">from_port</span>   <span class="o">=</span> <span class="mi">0</span>
    <span class="nx">to_port</span>     <span class="o">=</span> <span class="mi">0</span>
    <span class="nx">protocol</span>    <span class="o">=</span> <span class="s2">"-1"</span>
    <span class="nx">cidr_blocks</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"0.0.0.0/0"</span><span class="p">]</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_db_instance"</span> <span class="s2">"main"</span> <span class="p">{</span>
  <span class="nx">engine</span>               <span class="o">=</span> <span class="s2">"mysql"</span>
  <span class="nx">instance_class</span>       <span class="o">=</span> <span class="s2">"db.t3.micro"</span>
  <span class="nx">allocated_storage</span>    <span class="o">=</span> <span class="mi">20</span>
  <span class="nx">publicly_accessible</span>  <span class="o">=</span> <span class="kc">true</span>     <span class="c1"># Database exposed to internet</span>
  <span class="nx">storage_encrypted</span>    <span class="o">=</span> <span class="kc">false</span>    <span class="c1"># Unencrypted storage</span>
  <span class="nx">skip_final_snapshot</span>  <span class="o">=</span> <span class="kc">true</span>
<span class="err">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Running Checkov against this file:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span>checkov <span class="nt">-f</span> main.tf

       _               _
   ___| |__   ___  ___| | _______   __
  / __| <span class="s1">'_ \ / _ \/ __| |/ / _ \ \ / /
 | (__| | | |  __/ (__|   &lt; (_) \ V /
  \___|_| |_|\___|\___|_|\_\___/ \_/

By Prisma Cloud | version: 3.2.x

terraform scan results:

Passed checks: 0, Failed checks: 12, Skipped checks: 0

Check: CKV_AWS_145: "Ensure S3 bucket is encrypted with KMS"
  FAILED for resource: aws_s3_bucket.data
  File: /main.tf:2-5

Check: CKV_AWS_18: "Ensure S3 bucket has access logging enabled"
  FAILED for resource: aws_s3_bucket.data
  File: /main.tf:2-5

Check: CKV_AWS_21: "Ensure S3 bucket has versioning enabled"
  FAILED for resource: aws_s3_bucket.data
  File: /main.tf:2-5

Check: CKV_AWS_260: "Ensure no security group allows ingress from 0.0.0.0/0 to all ports"
  FAILED for resource: aws_security_group.web
  File: /main.tf:7-21

Check: CKV_AWS_16: "Ensure RDS instance is encrypted at rest"
  FAILED for resource: aws_db_instance.main
  File: /main.tf:23-31

Check: CKV_AWS_17: "Ensure RDS instance is not publicly accessible"
  FAILED for resource: aws_db_instance.main
  File: /main.tf:23-31
...
</span></pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="writing-custom-checkov-checks-in-python">Writing Custom Checkov Checks in Python</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
</pre></td><td class="rouge-code"><pre><span class="c1"># custom_checks/s3_naming_convention.py
</span><span class="kn">from</span> <span class="n">checkov.terraform.checks.resource.base_resource_check</span> <span class="kn">import</span> <span class="n">BaseResourceCheck</span>
<span class="kn">from</span> <span class="n">checkov.common.models.enums</span> <span class="kn">import</span> <span class="n">CheckCategories</span><span class="p">,</span> <span class="n">CheckResult</span>


<span class="k">class</span> <span class="nc">S3NamingConvention</span><span class="p">(</span><span class="n">BaseResourceCheck</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">Ensure S3 buckets follow organizational naming convention.</span><span class="sh">"""</span>

    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="n">name</span> <span class="o">=</span> <span class="sh">"</span><span class="s">Ensure S3 bucket follows naming convention: {env}-{app}-{purpose}</span><span class="sh">"</span>
        <span class="nb">id</span> <span class="o">=</span> <span class="sh">"</span><span class="s">CKV_CUSTOM_1</span><span class="sh">"</span>
        <span class="n">supported_resources</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">aws_s3_bucket</span><span class="sh">"</span><span class="p">]</span>
        <span class="n">categories</span> <span class="o">=</span> <span class="p">[</span><span class="n">CheckCategories</span><span class="p">.</span><span class="n">CONVENTION</span><span class="p">]</span>
        <span class="nf">super</span><span class="p">().</span><span class="nf">__init__</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">,</span> <span class="nb">id</span><span class="o">=</span><span class="nb">id</span><span class="p">,</span>
                         <span class="n">categories</span><span class="o">=</span><span class="n">categories</span><span class="p">,</span>
                         <span class="n">supported_resources</span><span class="o">=</span><span class="n">supported_resources</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">scan_resource_conf</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">conf</span><span class="p">):</span>
        <span class="n">bucket_name</span> <span class="o">=</span> <span class="n">conf</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">bucket</span><span class="sh">"</span><span class="p">,</span> <span class="p">[</span><span class="sh">""</span><span class="p">])[</span><span class="mi">0</span><span class="p">]</span>
        <span class="c1"># Enforce pattern: env-app-purpose
</span>        <span class="kn">import</span> <span class="n">re</span>
        <span class="n">pattern</span> <span class="o">=</span> <span class="sa">r</span><span class="sh">"</span><span class="s">^(dev|staging|prod)-[a-z]+-[a-z]+$</span><span class="sh">"</span>
        <span class="k">if</span> <span class="n">re</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">bucket_name</span><span class="p">):</span>
            <span class="k">return</span> <span class="n">CheckResult</span><span class="p">.</span><span class="n">PASSED</span>
        <span class="k">return</span> <span class="n">CheckResult</span><span class="p">.</span><span class="n">FAILED</span>


<span class="n">check</span> <span class="o">=</span> <span class="nc">S3NamingConvention</span><span class="p">()</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="writing-custom-checkov-checks-in-yaml">Writing Custom Checkov Checks in YAML</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
</pre></td><td class="rouge-code"><pre><span class="c1"># custom_checks/s3_tags_required.yaml</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">id</span><span class="pi">:</span> <span class="s2">"</span><span class="s">CKV_CUSTOM_2"</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Ensure</span><span class="nv"> </span><span class="s">S3</span><span class="nv"> </span><span class="s">buckets</span><span class="nv"> </span><span class="s">have</span><span class="nv"> </span><span class="s">required</span><span class="nv"> </span><span class="s">tags"</span>
  <span class="na">category</span><span class="pi">:</span> <span class="s2">"</span><span class="s">GENERAL_SECURITY"</span>
<span class="na">definition</span><span class="pi">:</span>
  <span class="na">and</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">cond_type</span><span class="pi">:</span> <span class="s2">"</span><span class="s">attribute"</span>
      <span class="na">resource_types</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s2">"</span><span class="s">aws_s3_bucket"</span>
      <span class="na">attribute</span><span class="pi">:</span> <span class="s2">"</span><span class="s">tags.Environment"</span>
      <span class="na">operator</span><span class="pi">:</span> <span class="s2">"</span><span class="s">exists"</span>
    <span class="pi">-</span> <span class="na">cond_type</span><span class="pi">:</span> <span class="s2">"</span><span class="s">attribute"</span>
      <span class="na">resource_types</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s2">"</span><span class="s">aws_s3_bucket"</span>
      <span class="na">attribute</span><span class="pi">:</span> <span class="s2">"</span><span class="s">tags.Owner"</span>
      <span class="na">operator</span><span class="pi">:</span> <span class="s2">"</span><span class="s">exists"</span>
    <span class="pi">-</span> <span class="na">cond_type</span><span class="pi">:</span> <span class="s2">"</span><span class="s">attribute"</span>
      <span class="na">resource_types</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s2">"</span><span class="s">aws_s3_bucket"</span>
      <span class="na">attribute</span><span class="pi">:</span> <span class="s2">"</span><span class="s">tags.Application"</span>
      <span class="na">operator</span><span class="pi">:</span> <span class="s2">"</span><span class="s">exists"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="tfsec-and-trivy-iac-scanning">tfsec and Trivy IaC Scanning</h2>

<h3 id="the-tfsec-to-trivy-transition">The tfsec to Trivy Transition</h3>

<p>tfsec was one of the first purpose-built Terraform security scanners, acquired by Aqua Security in 2021. In 2023, Aqua merged tfsec’s scanning engine into <a href="https://github.com/aquasecurity/trivy">Trivy</a>, their unified security scanner. While tfsec remains available as a standalone CLI, all new development happens in Trivy. The community is encouraged to migrate.</p>

<p>The migration path is straightforward: Trivy includes every security check available in tfsec, plus additional capabilities for scanning containers, dependencies, SBOM generation, and Kubernetes cluster scanning — all in a single binary.</p>

<h3 id="running-trivy-for-iac-scanning">Running Trivy for IaC Scanning</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
</pre></td><td class="rouge-code"><pre><span class="c"># Install Trivy</span>
brew <span class="nb">install </span>trivy

<span class="c"># Scan a Terraform directory</span>
trivy config ./terraform/

<span class="c"># Scan with specific severity threshold</span>
trivy config <span class="nt">--severity</span> HIGH,CRITICAL ./terraform/

<span class="c"># Scan CloudFormation templates</span>
trivy config ./cloudformation/

<span class="c"># Scan Kubernetes manifests</span>
trivy config ./k8s-manifests/

<span class="c"># Output in SARIF format for GitHub Security tab</span>
trivy config <span class="nt">--format</span> sarif <span class="nt">--output</span> trivy-results.sarif ./terraform/

<span class="c"># Scan with custom Rego policies</span>
trivy config <span class="nt">--policy</span> ./custom-policies/ ./terraform/
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="example-trivy-output">Example Trivy Output</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span>trivy config ./terraform/

2026-04-01T10:00:00.000Z  INFO  Misconfiguration scanning is enabled

main.tf <span class="o">(</span>terraform<span class="o">)</span>
<span class="o">===================</span>
Tests: 24 <span class="o">(</span>SUCCESSES: 12, FAILURES: 12, EXCEPTIONS: 0<span class="o">)</span>
Failures: 12 <span class="o">(</span>HIGH: 6, CRITICAL: 3, MEDIUM: 2, LOW: 1<span class="o">)</span>

CRITICAL: S3 bucket does not have encryption enabled
════════════════════════════════════════════════════
S3 Buckets should be encrypted to protect data at rest.

See https://avd.aquasec.com/misconfig/avd-aws-0088

 ──────────────────────────────────────────────
  main.tf:2-5
 ──────────────────────────────────────────────
   2 │ resource <span class="s2">"aws_s3_bucket"</span> <span class="s2">"data"</span> <span class="o">{</span>
   3 │   bucket <span class="o">=</span> <span class="s2">"my-data-bucket"</span>
   4 │ <span class="o">}</span>
 ──────────────────────────────────────────────

HIGH: Security group rule allows ingress from 0.0.0.0/0
════════════════════════════════════════════════════════
Opening to the world is rarely a good idea.

See https://avd.aquasec.com/misconfig/avd-aws-0107
...
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="custom-rego-policies-for-trivy">Custom Rego Policies for Trivy</h3>

<p>Trivy uses <a href="https://www.openpolicyagent.org/">Open Policy Agent (OPA) Rego</a> for custom policies, which is more widely adopted than Checkov’s Python checks:</p>

<div class="language-rego highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
</pre></td><td class="rouge-code"><pre><span class="c1"># custom-policies/s3_lifecycle.rego</span>
<span class="ow">package</span> <span class="n">user</span><span class="p">.</span><span class="n">terraform</span><span class="p">.</span><span class="n">s3</span>

<span class="ow">import</span> <span class="n">rego</span><span class="p">.</span><span class="n">v1</span>

<span class="n">__rego_metadata__</span> <span class="o">:=</span> <span class="p">{</span>
    <span class="s2">"id"</span><span class="o">:</span> <span class="s2">"CUSTOM-001"</span><span class="p">,</span>
    <span class="s2">"title"</span><span class="o">:</span> <span class="s2">"S3 buckets must have lifecycle policies"</span><span class="p">,</span>
    <span class="s2">"severity"</span><span class="o">:</span> <span class="s2">"MEDIUM"</span><span class="p">,</span>
    <span class="s2">"type"</span><span class="o">:</span> <span class="s2">"Terraform Security Check"</span><span class="p">,</span>
<span class="p">}</span>

<span class="n">__rego_input__</span> <span class="o">:=</span> <span class="p">{</span>
    <span class="s2">"combine"</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
    <span class="s2">"selector"</span><span class="o">:</span> <span class="p">[{</span><span class="s2">"type"</span><span class="o">:</span> <span class="s2">"defsec"</span><span class="p">,</span> <span class="s2">"subtypes"</span><span class="o">:</span> <span class="p">[{</span><span class="s2">"service"</span><span class="o">:</span> <span class="s2">"s3"</span><span class="p">,</span> <span class="s2">"provider"</span><span class="o">:</span> <span class="s2">"aws"</span><span class="p">}]}],</span>
<span class="p">}</span>

<span class="n">deny</span> <span class="n">contains</span> <span class="n">res</span> <span class="n">if</span> <span class="p">{</span>
    <span class="n">bucket</span> <span class="o">:=</span> <span class="n">input</span><span class="p">.</span><span class="n">aws</span><span class="p">.</span><span class="n">s3</span><span class="p">.</span><span class="n">buckets</span><span class="p">[</span><span class="n">_</span><span class="p">]</span>
    <span class="n">count</span><span class="p">(</span><span class="n">bucket</span><span class="p">.</span><span class="n">lifecycleconfig</span><span class="p">.</span><span class="n">rules</span><span class="p">)</span> <span class="o">==</span> <span class="m">0</span>
    <span class="n">res</span> <span class="o">:=</span> <span class="n">result</span><span class="p">.</span><span class="n">new</span><span class="p">(</span>
        <span class="s2">"S3 bucket does not have a lifecycle policy configured"</span><span class="p">,</span>
        <span class="n">bucket</span><span class="p">,</span>
    <span class="p">)</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="comparison-table">Comparison Table</h2>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>Checkov</th>
      <th>Trivy (tfsec)</th>
      <th>CloudFormation Guard</th>
      <th>AWS Config Rules</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Built-in Rules</strong></td>
      <td>2,500+</td>
      <td>1,500+</td>
      <td>~350 (registry)</td>
      <td>~300 managed</td>
    </tr>
    <tr>
      <td><strong>Terraform</strong></td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Via JSON plan</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>CloudFormation</strong></td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes (native)</td>
      <td>N/A (post-deploy)</td>
    </tr>
    <tr>
      <td><strong>Kubernetes</strong></td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes (JSON/YAML)</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Helm Charts</strong></td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Dockerfiles</strong></td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>ARM Templates</strong></td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Graph Analysis</strong></td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Custom Rules</strong></td>
      <td>Python + YAML</td>
      <td>Rego (OPA)</td>
      <td>Guard DSL</td>
      <td>Lambda + Guard</td>
    </tr>
    <tr>
      <td><strong>SCA/Dependencies</strong></td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Compliance Frameworks</strong></td>
      <td>CIS, SOC2, HIPAA, PCI, NIST, ISO 27001</td>
      <td>CIS, PCI, HIPAA</td>
      <td>CIS, NIST, PCI, HIPAA</td>
      <td>CIS, PCI (limited)</td>
    </tr>
    <tr>
      <td><strong>CI/CD Integration</strong></td>
      <td>Native</td>
      <td>Native</td>
      <td>CLI-based</td>
      <td>Post-deploy only</td>
    </tr>
    <tr>
      <td><strong>Cost</strong></td>
      <td>Free (OSS)</td>
      <td>Free (OSS)</td>
      <td>Free (OSS)</td>
      <td>$0.001/evaluation</td>
    </tr>
    <tr>
      <td><strong>Multi-Cloud</strong></td>
      <td>AWS, Azure, GCP</td>
      <td>AWS, Azure, GCP</td>
      <td>AWS-focused</td>
      <td>AWS only</td>
    </tr>
    <tr>
      <td><strong>SARIF Output</strong></td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No (JSON/YAML)</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>IDE Plugins</strong></td>
      <td>VS Code, JetBrains</td>
      <td>VS Code</td>
      <td>No</td>
      <td>No</td>
    </tr>
  </tbody>
</table>

<h2 id="pre-commit-hook-integration">Pre-Commit Hook Integration</h2>

<p>Pre-commit hooks provide the fastest feedback loop. Developers catch issues before code leaves their machine.</p>

<h3 id="pre-commit-configuration">Pre-Commit Configuration</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
</pre></td><td class="rouge-code"><pre><span class="c1"># .pre-commit-config.yaml</span>
<span class="na">repos</span><span class="pi">:</span>
  <span class="c1"># Checkov</span>
  <span class="pi">-</span> <span class="na">repo</span><span class="pi">:</span> <span class="s">https://github.com/bridgecrewio/checkov</span>
    <span class="na">rev</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3.2.0'</span>
    <span class="na">hooks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">checkov</span>
        <span class="na">args</span><span class="pi">:</span> <span class="pi">[</span>
          <span class="s1">'</span><span class="s">--framework'</span><span class="pi">,</span> <span class="s1">'</span><span class="s">terraform'</span><span class="pi">,</span>
          <span class="s1">'</span><span class="s">--skip-check'</span><span class="pi">,</span> <span class="s1">'</span><span class="s">CKV_AWS_18'</span><span class="pi">,</span>  <span class="c1"># Skip if logging is handled elsewhere</span>
          <span class="s1">'</span><span class="s">--quiet'</span><span class="pi">,</span>
          <span class="s1">'</span><span class="s">--compact'</span>
        <span class="pi">]</span>

  <span class="c1"># Trivy IaC scanning</span>
  <span class="pi">-</span> <span class="na">repo</span><span class="pi">:</span> <span class="s">https://github.com/antonbabenko/pre-commit-terraform</span>
    <span class="na">rev</span><span class="pi">:</span> <span class="s">v1.96.0</span>
    <span class="na">hooks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">terraform_trivy</span>
        <span class="na">args</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="s">--args=--severity=HIGH,CRITICAL</span>
          <span class="pi">-</span> <span class="s">--args=--skip-dirs=modules/deprecated</span>

  <span class="c1"># CloudFormation Guard</span>
  <span class="pi">-</span> <span class="na">repo</span><span class="pi">:</span> <span class="s">local</span>
    <span class="na">hooks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">cfn-guard</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">CloudFormation Guard</span>
        <span class="na">entry</span><span class="pi">:</span> <span class="s">cfn-guard validate --data</span>
        <span class="na">language</span><span class="pi">:</span> <span class="s">system</span>
        <span class="na">files</span><span class="pi">:</span> <span class="s1">'</span><span class="s">\.ya?ml$'</span>
        <span class="na">args</span><span class="pi">:</span> <span class="pi">[</span><span class="s1">'</span><span class="s">--rules'</span><span class="pi">,</span> <span class="s1">'</span><span class="s">guard-rules/'</span><span class="pi">]</span>
        <span class="na">types</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">file</span><span class="pi">]</span>

  <span class="c1"># Terraform formatting and validation</span>
  <span class="pi">-</span> <span class="na">repo</span><span class="pi">:</span> <span class="s">https://github.com/antonbabenko/pre-commit-terraform</span>
    <span class="na">rev</span><span class="pi">:</span> <span class="s">v1.96.0</span>
    <span class="na">hooks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">terraform_fmt</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">terraform_validate</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">terraform_tflint</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="installing-and-running-pre-commit">Installing and Running Pre-Commit</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="c"># Install pre-commit</span>
pip <span class="nb">install </span>pre-commit

<span class="c"># Install hooks in your repository</span>
pre-commit <span class="nb">install</span>

<span class="c"># Run against all files (initial scan)</span>
pre-commit run <span class="nt">--all-files</span>

<span class="c"># Run specific hook</span>
pre-commit run checkov <span class="nt">--all-files</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="cicd-pipeline-integration">CI/CD Pipeline Integration</h2>

<h3 id="github-actions-workflow">GitHub Actions Workflow</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
</pre></td><td class="rouge-code"><pre><span class="c1"># .github/workflows/iac-security.yml</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">IaC Security Scanning</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="na">paths</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s1">'</span><span class="s">terraform/**'</span>
      <span class="pi">-</span> <span class="s1">'</span><span class="s">cloudformation/**'</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">checkov</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Checkov IaC Scan</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Run Checkov</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">bridgecrewio/checkov-action@v12</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">directory</span><span class="pi">:</span> <span class="s">terraform/</span>
          <span class="na">framework</span><span class="pi">:</span> <span class="s">terraform</span>
          <span class="na">output_format</span><span class="pi">:</span> <span class="s">cli,sarif</span>
          <span class="na">output_file_path</span><span class="pi">:</span> <span class="s">console,checkov-results.sarif</span>
          <span class="na">soft_fail</span><span class="pi">:</span> <span class="kc">false</span>
          <span class="na">skip_check</span><span class="pi">:</span> <span class="s">CKV_AWS_18</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Upload SARIF</span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">always()</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">github/codeql-action/upload-sarif@v3</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">sarif_file</span><span class="pi">:</span> <span class="s">checkov-results.sarif</span>

  <span class="na">trivy</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Trivy IaC Scan</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Run Trivy IaC scan</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">aquasecurity/trivy-action@master</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">scan-type</span><span class="pi">:</span> <span class="s1">'</span><span class="s">config'</span>
          <span class="na">scan-ref</span><span class="pi">:</span> <span class="s1">'</span><span class="s">terraform/'</span>
          <span class="na">severity</span><span class="pi">:</span> <span class="s1">'</span><span class="s">HIGH,CRITICAL'</span>
          <span class="na">format</span><span class="pi">:</span> <span class="s1">'</span><span class="s">sarif'</span>
          <span class="na">output</span><span class="pi">:</span> <span class="s1">'</span><span class="s">trivy-results.sarif'</span>
          <span class="na">exit-code</span><span class="pi">:</span> <span class="s1">'</span><span class="s">1'</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Upload SARIF</span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">always()</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">github/codeql-action/upload-sarif@v3</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">sarif_file</span><span class="pi">:</span> <span class="s">trivy-results.sarif</span>

  <span class="na">cfn-guard</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">CloudFormation Guard</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install cfn-guard</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">curl --proto '=https' --tlsv1.2 -sSf \</span>
            <span class="s">https://raw.githubusercontent.com/aws-cloudformation/cloudformation-guard/main/install-guard.sh \</span>
            <span class="s">| sh</span>
          <span class="s">export PATH=~/.guard/bin:$PATH</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Validate CloudFormation templates</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">cfn-guard validate \</span>
            <span class="s">--data cloudformation/ \</span>
            <span class="s">--rules guard-rules/ \</span>
            <span class="s">--output-format json \</span>
            <span class="s">--structured</span>

  <span class="na">security-gate</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Security Gate</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">checkov</span><span class="pi">,</span> <span class="nv">trivy</span><span class="pi">,</span> <span class="nv">cfn-guard</span><span class="pi">]</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">All security checks passed</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">echo "All IaC security scans passed. Safe to deploy."</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="aws-codebuild-buildspec">AWS CodeBuild Buildspec</h3>

<p>For teams using AWS-native CI/CD, here is a CodeBuild buildspec that integrates Checkov and cfn-guard:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
</pre></td><td class="rouge-code"><pre><span class="c1"># buildspec-security.yml</span>
<span class="na">version</span><span class="pi">:</span> <span class="m">0.2</span>

<span class="na">env</span><span class="pi">:</span>
  <span class="na">variables</span><span class="pi">:</span>
    <span class="na">CHECKOV_SKIP</span><span class="pi">:</span> <span class="s2">"</span><span class="s">CKV_AWS_18"</span>

<span class="na">phases</span><span class="pi">:</span>
  <span class="na">install</span><span class="pi">:</span>
    <span class="na">runtime-versions</span><span class="pi">:</span>
      <span class="na">python</span><span class="pi">:</span> <span class="m">3.12</span>
    <span class="na">commands</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">pip install checkov</span>
      <span class="pi">-</span> <span class="s">curl --proto '=https' --tlsv1.2 -sSf</span>
          <span class="s">https://raw.githubusercontent.com/aws-cloudformation/cloudformation-guard/main/install-guard.sh</span>
          <span class="s">| sh</span>
      <span class="pi">-</span> <span class="s">export PATH=~/.guard/bin:$PATH</span>
      <span class="pi">-</span> <span class="s">curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh</span>
          <span class="s">| sh -s -- -b /usr/local/bin</span>

  <span class="na">pre_build</span><span class="pi">:</span>
    <span class="na">commands</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">echo "Running IaC security scans..."</span>

      <span class="c1"># Checkov scan</span>
      <span class="pi">-</span> <span class="pi">|</span>
        <span class="s">checkov -d ./terraform/ \</span>
          <span class="s">--framework terraform \</span>
          <span class="s">--skip-check $CHECKOV_SKIP \</span>
          <span class="s">--output cli \</span>
          <span class="s">--output junitxml \</span>
          <span class="s">--output-file-path console,checkov-results.xml \</span>
          <span class="s">--compact</span>

      <span class="c1"># Trivy IaC scan</span>
      <span class="pi">-</span> <span class="pi">|</span>
        <span class="s">trivy config ./terraform/ \</span>
          <span class="s">--severity HIGH,CRITICAL \</span>
          <span class="s">--exit-code 1 \</span>
          <span class="s">--format json \</span>
          <span class="s">--output trivy-results.json</span>

      <span class="c1"># CloudFormation Guard validation</span>
      <span class="pi">-</span> <span class="pi">|</span>
        <span class="s">cfn-guard validate \</span>
          <span class="s">--data ./cloudformation/ \</span>
          <span class="s">--rules ./guard-rules/ \</span>
          <span class="s">--output-format json &gt; guard-results.json</span>

  <span class="na">build</span><span class="pi">:</span>
    <span class="na">commands</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">echo "Security scans passed. Proceeding with deployment..."</span>
      <span class="pi">-</span> <span class="s">terraform init</span>
      <span class="pi">-</span> <span class="s">terraform plan -out=tfplan</span>

<span class="na">reports</span><span class="pi">:</span>
  <span class="na">checkov-report</span><span class="pi">:</span>
    <span class="na">files</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">checkov-results.xml</span>
    <span class="na">file-format</span><span class="pi">:</span> <span class="s">JUNITXML</span>

<span class="na">artifacts</span><span class="pi">:</span>
  <span class="na">files</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">checkov-results.xml</span>
    <span class="pi">-</span> <span class="s">trivy-results.json</span>
    <span class="pi">-</span> <span class="s">guard-results.json</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">security-scan-results</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="custom-policy-authoring">Custom Policy Authoring</h2>

<p>Each tool takes a different approach to custom policies. Choose based on your team’s existing skills.</p>

<h3 id="checkov-python-or-yaml">Checkov: Python or YAML</h3>

<p>Python checks give you full programmatic control:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
</pre></td><td class="rouge-code"><pre><span class="c1"># checks/rds_deletion_protection.py
</span><span class="kn">from</span> <span class="n">checkov.terraform.checks.resource.base_resource_check</span> <span class="kn">import</span> <span class="n">BaseResourceCheck</span>
<span class="kn">from</span> <span class="n">checkov.common.models.enums</span> <span class="kn">import</span> <span class="n">CheckCategories</span><span class="p">,</span> <span class="n">CheckResult</span>


<span class="k">class</span> <span class="nc">RDSDeletionProtection</span><span class="p">(</span><span class="n">BaseResourceCheck</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">Ensure RDS instances have deletion protection enabled in production.</span><span class="sh">"""</span>

    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="n">name</span> <span class="o">=</span> <span class="sh">"</span><span class="s">Ensure RDS has deletion protection enabled</span><span class="sh">"</span>
        <span class="nb">id</span> <span class="o">=</span> <span class="sh">"</span><span class="s">CKV_CUSTOM_10</span><span class="sh">"</span>
        <span class="n">supported_resources</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">aws_db_instance</span><span class="sh">"</span><span class="p">]</span>
        <span class="n">categories</span> <span class="o">=</span> <span class="p">[</span><span class="n">CheckCategories</span><span class="p">.</span><span class="n">BACKUP_AND_RECOVERY</span><span class="p">]</span>
        <span class="nf">super</span><span class="p">().</span><span class="nf">__init__</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">,</span> <span class="nb">id</span><span class="o">=</span><span class="nb">id</span><span class="p">,</span>
                         <span class="n">categories</span><span class="o">=</span><span class="n">categories</span><span class="p">,</span>
                         <span class="n">supported_resources</span><span class="o">=</span><span class="n">supported_resources</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">scan_resource_conf</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">conf</span><span class="p">):</span>
        <span class="n">deletion_protection</span> <span class="o">=</span> <span class="n">conf</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">deletion_protection</span><span class="sh">"</span><span class="p">,</span> <span class="p">[</span><span class="bp">False</span><span class="p">])</span>
        <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">deletion_protection</span><span class="p">,</span> <span class="nb">list</span><span class="p">):</span>
            <span class="n">deletion_protection</span> <span class="o">=</span> <span class="n">deletion_protection</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
        <span class="k">if</span> <span class="n">deletion_protection</span> <span class="ow">is</span> <span class="bp">True</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">CheckResult</span><span class="p">.</span><span class="n">PASSED</span>
        <span class="k">return</span> <span class="n">CheckResult</span><span class="p">.</span><span class="n">FAILED</span>


<span class="n">check</span> <span class="o">=</span> <span class="nc">RDSDeletionProtection</span><span class="p">()</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="trivy-rego-opa">Trivy: Rego (OPA)</h3>

<p>Rego is the standard policy language across the cloud-native ecosystem:</p>

<div class="language-rego highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
</pre></td><td class="rouge-code"><pre><span class="c1"># policies/rds_multi_az.rego</span>
<span class="ow">package</span> <span class="n">user</span><span class="p">.</span><span class="n">terraform</span><span class="p">.</span><span class="n">rds</span>

<span class="ow">import</span> <span class="n">rego</span><span class="p">.</span><span class="n">v1</span>

<span class="n">__rego_metadata__</span> <span class="o">:=</span> <span class="p">{</span>
    <span class="s2">"id"</span><span class="o">:</span> <span class="s2">"CUSTOM-010"</span><span class="p">,</span>
    <span class="s2">"title"</span><span class="o">:</span> <span class="s2">"RDS instances should use Multi-AZ for production"</span><span class="p">,</span>
    <span class="s2">"severity"</span><span class="o">:</span> <span class="s2">"HIGH"</span><span class="p">,</span>
    <span class="s2">"type"</span><span class="o">:</span> <span class="s2">"Terraform Security Check"</span><span class="p">,</span>
<span class="p">}</span>

<span class="n">__rego_input__</span> <span class="o">:=</span> <span class="p">{</span>
    <span class="s2">"combine"</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
    <span class="s2">"selector"</span><span class="o">:</span> <span class="p">[{</span><span class="s2">"type"</span><span class="o">:</span> <span class="s2">"defsec"</span><span class="p">,</span> <span class="s2">"subtypes"</span><span class="o">:</span> <span class="p">[{</span><span class="s2">"service"</span><span class="o">:</span> <span class="s2">"rds"</span><span class="p">,</span> <span class="s2">"provider"</span><span class="o">:</span> <span class="s2">"aws"</span><span class="p">}]}],</span>
<span class="p">}</span>

<span class="n">deny</span> <span class="n">contains</span> <span class="n">res</span> <span class="n">if</span> <span class="p">{</span>
    <span class="n">instance</span> <span class="o">:=</span> <span class="n">input</span><span class="p">.</span><span class="n">aws</span><span class="p">.</span><span class="n">rds</span><span class="p">.</span><span class="n">instances</span><span class="p">[</span><span class="n">_</span><span class="p">]</span>
    <span class="ow">not</span> <span class="n">instance</span><span class="p">.</span><span class="n">multiaz</span><span class="p">.</span><span class="n">value</span>
    <span class="n">res</span> <span class="o">:=</span> <span class="n">result</span><span class="p">.</span><span class="n">new</span><span class="p">(</span>
        <span class="s2">"RDS instance does not have Multi-AZ enabled"</span><span class="p">,</span>
        <span class="n">instance</span><span class="p">,</span>
    <span class="p">)</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="cloudformation-guard-guard-dsl">CloudFormation Guard: Guard DSL</h3>

<p>Guard DSL is declarative and purpose-built for infrastructure validation:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
</pre></td><td class="rouge-code"><pre><span class="c1"># rules/rds_security.guard</span>
<span class="n">let</span> <span class="n">rds_instances</span> <span class="o">=</span> <span class="no">Resources</span><span class="p">.</span><span class="nf">*</span><span class="p">[</span> <span class="no">Type</span> <span class="o">==</span> <span class="s1">'AWS::RDS::DBInstance'</span> <span class="p">]</span>

<span class="n">rule</span> <span class="n">rds_encryption_required</span> <span class="k">when</span> <span class="o">%</span><span class="n">rds_instances</span> <span class="o">!</span><span class="n">empty</span> <span class="p">{</span>
    <span class="o">%</span><span class="n">rds_instances</span><span class="o">.</span><span class="no">Properties</span><span class="o">.</span><span class="no">StorageEncrypted</span> <span class="o">==</span> <span class="kp">true</span>
        <span class="o">&lt;&lt;</span> <span class="no">RDS</span> <span class="n">instances</span> <span class="n">must</span> <span class="n">have</span> <span class="n">storage</span> <span class="n">encryption</span> <span class="n">enabled</span> <span class="o">&gt;&gt;</span>
<span class="p">}</span>

<span class="n">rule</span> <span class="n">rds_no_public_access</span> <span class="k">when</span> <span class="o">%</span><span class="n">rds_instances</span> <span class="o">!</span><span class="n">empty</span> <span class="p">{</span>
    <span class="o">%</span><span class="n">rds_instances</span><span class="o">.</span><span class="no">Properties</span><span class="o">.</span><span class="no">PubliclyAccessible</span> <span class="o">==</span> <span class="kp">false</span>
        <span class="o">&lt;&lt;</span> <span class="no">RDS</span> <span class="n">instances</span> <span class="n">must</span> <span class="ow">not</span> <span class="n">be</span> <span class="n">publicly</span> <span class="n">accessible</span> <span class="o">&gt;&gt;</span>
<span class="p">}</span>

<span class="n">rule</span> <span class="n">rds_backup_retention</span> <span class="k">when</span> <span class="o">%</span><span class="n">rds_instances</span> <span class="o">!</span><span class="n">empty</span> <span class="p">{</span>
    <span class="o">%</span><span class="n">rds_instances</span><span class="o">.</span><span class="no">Properties</span><span class="o">.</span><span class="no">BackupRetentionPeriod</span> <span class="o">&gt;=</span> <span class="mi">7</span>
        <span class="o">&lt;&lt;</span> <span class="no">RDS</span> <span class="n">backup</span> <span class="n">retention</span> <span class="n">must</span> <span class="n">be</span> <span class="n">at</span> <span class="n">least</span> <span class="mi">7</span> <span class="n">days</span> <span class="o">&gt;&gt;</span>
<span class="p">}</span>

<span class="n">rule</span> <span class="n">rds_multi_az_production</span> <span class="k">when</span> <span class="o">%</span><span class="n">rds_instances</span> <span class="o">!</span><span class="n">empty</span> <span class="p">{</span>
    <span class="o">%</span><span class="n">rds_instances</span><span class="o">.</span><span class="no">Properties</span> <span class="p">{</span>
        <span class="k">when</span> <span class="no">Tags</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="o">.</span><span class="no">Key</span> <span class="o">==</span> <span class="s2">"Environment"</span> <span class="p">{</span>
            <span class="k">when</span> <span class="no">Tags</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="o">.</span><span class="no">Value</span> <span class="o">==</span> <span class="s2">"production"</span> <span class="p">{</span>
                <span class="no">MultiAZ</span> <span class="o">==</span> <span class="kp">true</span>
                    <span class="o">&lt;&lt;</span> <span class="no">Production</span> <span class="no">RDS</span> <span class="n">instances</span> <span class="n">must</span> <span class="n">use</span> <span class="no">Multi</span><span class="o">-</span><span class="no">AZ</span> <span class="o">&gt;&gt;</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="defense-in-depth-the-complete-scanning-architecture">Defense-in-Depth: The Complete Scanning Architecture</h2>

<p>The strongest IaC security posture layers all three scanning stages. Here is how to architect the full pipeline:</p>

<h3 id="layer-1-pre-commit-developer-machine">Layer 1: Pre-Commit (Developer Machine)</h3>

<ul>
  <li><strong>Tools</strong>: Checkov, Trivy, cfn-guard</li>
  <li><strong>Purpose</strong>: Immediate feedback before code is committed</li>
  <li><strong>Behavior</strong>: Block commit on CRITICAL/HIGH findings</li>
  <li><strong>Speed</strong>: Must complete in under 30 seconds for developer adoption</li>
</ul>

<h3 id="layer-2-cicd-pipeline-automated-gate">Layer 2: CI/CD Pipeline (Automated Gate)</h3>

<ul>
  <li><strong>Tools</strong>: Checkov (full scan), Trivy (full scan), cfn-guard (CloudFormation validation)</li>
  <li><strong>Purpose</strong>: Enforce organizational policy on every pull request</li>
  <li><strong>Behavior</strong>: Block merge/deploy on any policy violation</li>
  <li><strong>Integration</strong>: SARIF output to GitHub Security tab, JUnit XML to build reports</li>
  <li><strong>Speed</strong>: Full scan in 2-5 minutes is acceptable</li>
</ul>

<h3 id="layer-3-post-deploy-continuous-compliance">Layer 3: Post-Deploy (Continuous Compliance)</h3>

<ul>
  <li><strong>Tools</strong>: AWS Config Rules (managed + custom Guard rules), Security Hub</li>
  <li><strong>Purpose</strong>: Detect configuration drift and manual console changes</li>
  <li><strong>Behavior</strong>: Alert or auto-remediate non-compliant resources</li>
  <li><strong>Integration</strong>: Security Hub aggregates findings from Config, GuardDuty, Inspector</li>
  <li><strong>Frequency</strong>: Daily recording for cost optimization, event-based for critical resources</li>
</ul>

<h3 id="putting-it-together">Putting It Together</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
</pre></td><td class="rouge-code"><pre><span class="c"># Developer workflow:</span>
<span class="c"># 1. Write Terraform code</span>
<span class="c"># 2. Pre-commit hooks catch issues immediately</span>
git commit <span class="nt">-m</span> <span class="s2">"Add S3 bucket for data pipeline"</span>
<span class="c"># checkov runs -&gt; finds missing encryption -&gt; blocks commit</span>

<span class="c"># 3. Fix and recommit</span>
git commit <span class="nt">-m</span> <span class="s2">"Add encrypted S3 bucket for data pipeline"</span>
<span class="c"># checkov passes -&gt; trivy passes -&gt; commit succeeds</span>

<span class="c"># 4. Push and open PR</span>
git push origin feature/data-pipeline
<span class="c"># GitHub Actions runs full Checkov + Trivy + cfn-guard scans</span>
<span class="c"># Security gate must pass before merge is allowed</span>

<span class="c"># 5. Merge and deploy</span>
<span class="c"># Terraform applies through CodePipeline/GitHub Actions</span>
<span class="c"># AWS Config Rules begin evaluating the new resources</span>
<span class="c"># Security Hub aggregates compliance status</span>

<span class="c"># 6. Ongoing monitoring</span>
<span class="c"># Someone modifies the bucket via console -&gt; Config detects drift</span>
<span class="c"># Config Rule marks resource NON_COMPLIANT</span>
<span class="c"># Security Hub alert fires -&gt; SNS notification -&gt; team investigates</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="coverage-matrix">Coverage Matrix</h3>

<table>
  <thead>
    <tr>
      <th>Misconfiguration Type</th>
      <th style="text-align: center">Pre-Commit</th>
      <th style="text-align: center">CI/CD</th>
      <th style="text-align: center">Post-Deploy</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Unencrypted storage</td>
      <td style="text-align: center">Checkov, Trivy</td>
      <td style="text-align: center">Checkov, Trivy</td>
      <td style="text-align: center">Config Rules</td>
    </tr>
    <tr>
      <td>Open security groups</td>
      <td style="text-align: center">Checkov, Trivy</td>
      <td style="text-align: center">Checkov, Trivy</td>
      <td style="text-align: center">Config Rules</td>
    </tr>
    <tr>
      <td>Public RDS instances</td>
      <td style="text-align: center">Checkov, Trivy</td>
      <td style="text-align: center">Checkov, Trivy</td>
      <td style="text-align: center">Config Rules</td>
    </tr>
    <tr>
      <td>Missing tags</td>
      <td style="text-align: center">Checkov, Trivy</td>
      <td style="text-align: center">Checkov, Trivy</td>
      <td style="text-align: center">Config Rules</td>
    </tr>
    <tr>
      <td>IAM over-permissions</td>
      <td style="text-align: center">Checkov (graph)</td>
      <td style="text-align: center">Checkov (graph)</td>
      <td style="text-align: center">IAM Access Analyzer</td>
    </tr>
    <tr>
      <td>Cross-resource issues</td>
      <td style="text-align: center">Checkov (graph)</td>
      <td style="text-align: center">Checkov (graph)</td>
      <td style="text-align: center">Security Hub</td>
    </tr>
    <tr>
      <td>Configuration drift</td>
      <td style="text-align: center">N/A</td>
      <td style="text-align: center">N/A</td>
      <td style="text-align: center">Config Rules</td>
    </tr>
    <tr>
      <td>Manual console changes</td>
      <td style="text-align: center">N/A</td>
      <td style="text-align: center">N/A</td>
      <td style="text-align: center">Config Rules</td>
    </tr>
    <tr>
      <td>Compliance violations</td>
      <td style="text-align: center">Checkov, cfn-guard</td>
      <td style="text-align: center">Checkov, cfn-guard</td>
      <td style="text-align: center">Config Rules</td>
    </tr>
  </tbody>
</table>

<h2 id="getting-started-recommended-implementation-path">Getting Started: Recommended Implementation Path</h2>

<ol>
  <li><strong>Week 1</strong>: Install Checkov and Trivy locally. Run against your existing IaC to baseline findings.</li>
  <li><strong>Week 2</strong>: Add pre-commit hooks with Checkov. Start with <code class="language-plaintext highlighter-rouge">--soft-fail</code> to avoid blocking developers immediately.</li>
  <li><strong>Week 3</strong>: Add Checkov and Trivy to your CI/CD pipeline as non-blocking checks. Review findings and suppress false positives.</li>
  <li><strong>Week 4</strong>: Switch CI/CD checks to blocking (fail the build on HIGH/CRITICAL). Add cfn-guard for CloudFormation validation.</li>
  <li><strong>Month 2</strong>: Enable AWS Config Rules for your top 10 compliance requirements. Integrate with Security Hub.</li>
  <li><strong>Month 3</strong>: Write custom checks for organizational policies (naming conventions, tagging, required configurations). Remove <code class="language-plaintext highlighter-rouge">--soft-fail</code> from pre-commit.</li>
</ol>

<h2 id="related-articles">Related Articles</h2>

<ul>
  <li><a href="/posts/aws-devsecops-pipeline-security-automation-implementation/">AWS DevSecOps Pipeline Security: Complete Automation Implementation Guide</a></li>
  <li><a href="/posts/policy-as-code-opa-kyverno-eks-security/">EKS Policy-as-Code: OPA vs Kyverno Kubernetes Security</a></li>
  <li><a href="/posts/aws-cloud-security-best-practices-implementation-guide/">AWS Cloud Security Best Practices Implementation Guide</a></li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>IaC security scanning is not a single-tool problem. AWS CloudFormation Guard provides native policy-as-code validation with a growing rules registry. AWS Config Rules deliver continuous post-deployment compliance monitoring. But open-source scanners like Checkov (2,500+ policies) and Trivy (the successor to tfsec with 1,500+ checks) offer unmatched breadth, multi-cloud portability, and community-driven rule velocity.</p>

<p>The defense-in-depth approach — pre-commit hooks, CI/CD security gates, and post-deployment Config Rules — catches misconfigurations at every stage. Start with OSS scanners for breadth, layer in AWS-native controls for depth, and write custom policies for your organization’s specific requirements.</p>

<p>The IaC security market is projected to reach $8.76 billion by 2033 because organizations are recognizing that catching a misconfiguration in code costs orders of magnitude less than remediating a breach in production. Shift left, scan early, scan often, and automate everything.</p>

<hr />

<p><em>For more on building secure DevSecOps pipelines and implementing cloud security automation, connect with me on <a href="https://www.linkedin.com/in/jonpricelinux/">LinkedIn</a>.</em></p>]]></content><author><name>Jon (JR) Price</name></author><category term="AWS" /><category term="DevSecOps" /><category term="Security" /><category term="checkov" /><category term="cloudformation-guard" /><category term="iac-security" /><category term="open-source" /><category term="shift-left-security" /><category term="terraform" /><category term="tfsec" /><category term="trivy" /><summary type="html"><![CDATA[Compare open-source IaC security scanners Checkov and tfsec with AWS CloudFormation Guard. Shift-left security scanning for Terraform and CloudFormation.]]></summary></entry><entry><title type="html">Building a Multi-Cloud SIEM: Wazuh on AWS vs Security Hub</title><link href="https://www.red-team.sh/posts/wazuh-vs-security-hub-multi-cloud-siem/" rel="alternate" type="text/html" title="Building a Multi-Cloud SIEM: Wazuh on AWS vs Security Hub" /><published>2026-03-25T09:00:00-07:00</published><updated>2026-03-25T09:00:00-07:00</updated><id>https://www.red-team.sh/posts/wazuh-vs-security-hub-multi-cloud-siem</id><content type="html" xml:base="https://www.red-team.sh/posts/wazuh-vs-security-hub-multi-cloud-siem/"><![CDATA[<p><img src="/assets/img/wazuh-vs-security-hub-siem.png" alt="Wazuh on AWS vs Security Hub Architecture" class="shadow" w="800" />
<em>Wazuh SIEM deployed on AWS alongside Security Hub for unified multi-cloud security monitoring</em></p>

<ul id="markdown-toc">
  <li><a href="#why-your-siem-strategy-needs-to-evolve" id="markdown-toc-why-your-siem-strategy-needs-to-evolve">Why Your SIEM Strategy Needs to Evolve</a></li>
  <li><a href="#aws-security-hub-the-aws-native-baseline" id="markdown-toc-aws-security-hub-the-aws-native-baseline">AWS Security Hub: The AWS-Native Baseline</a>    <ul>
      <li><a href="#what-security-hub-does-well" id="markdown-toc-what-security-hub-does-well">What Security Hub Does Well</a></li>
      <li><a href="#setting-up-security-hub-with-cloudformation" id="markdown-toc-setting-up-security-hub-with-cloudformation">Setting Up Security Hub with CloudFormation</a></li>
      <li><a href="#security-hub-pricing-2026" id="markdown-toc-security-hub-pricing-2026">Security Hub Pricing (2026)</a></li>
    </ul>
  </li>
  <li><a href="#where-security-hub-falls-short" id="markdown-toc-where-security-hub-falls-short">Where Security Hub Falls Short</a></li>
  <li><a href="#wazuh-the-open-source-siem-that-sees-everything" id="markdown-toc-wazuh-the-open-source-siem-that-sees-everything">Wazuh: The Open-Source SIEM That Sees Everything</a>    <ul>
      <li><a href="#from-ossec-to-wazuh" id="markdown-toc-from-ossec-to-wazuh">From OSSEC to Wazuh</a></li>
      <li><a href="#wazuh-architecture" id="markdown-toc-wazuh-architecture">Wazuh Architecture</a></li>
      <li><a href="#what-wazuh-monitors" id="markdown-toc-what-wazuh-monitors">What Wazuh Monitors</a></li>
    </ul>
  </li>
  <li><a href="#deploying-wazuh-on-aws" id="markdown-toc-deploying-wazuh-on-aws">Deploying Wazuh on AWS</a>    <ul>
      <li><a href="#option-1-docker-compose-on-ec2-recommended-for-getting-started" id="markdown-toc-option-1-docker-compose-on-ec2-recommended-for-getting-started">Option 1: Docker Compose on EC2 (Recommended for Getting Started)</a></li>
      <li><a href="#option-2-terraform-for-production-infrastructure" id="markdown-toc-option-2-terraform-for-production-infrastructure">Option 2: Terraform for Production Infrastructure</a></li>
      <li><a href="#option-3-aws-marketplace-ami" id="markdown-toc-option-3-aws-marketplace-ami">Option 3: AWS Marketplace AMI</a></li>
    </ul>
  </li>
  <li><a href="#configuring-wazuh-agents-for-aws-instances" id="markdown-toc-configuring-wazuh-agents-for-aws-instances">Configuring Wazuh Agents for AWS Instances</a></li>
  <li><a href="#feeding-security-hub-findings-into-wazuh" id="markdown-toc-feeding-security-hub-findings-into-wazuh">Feeding Security Hub Findings into Wazuh</a>    <ul>
      <li><a href="#python-integration-script" id="markdown-toc-python-integration-script">Python Integration Script</a></li>
      <li><a href="#custom-wazuh-rules-for-aws-threats" id="markdown-toc-custom-wazuh-rules-for-aws-threats">Custom Wazuh Rules for AWS Threats</a></li>
    </ul>
  </li>
  <li><a href="#wazuh-vs-security-hub-complete-comparison" id="markdown-toc-wazuh-vs-security-hub-complete-comparison">Wazuh vs Security Hub: Complete Comparison</a></li>
  <li><a href="#real-monitoring-scenarios" id="markdown-toc-real-monitoring-scenarios">Real Monitoring Scenarios</a>    <ul>
      <li><a href="#scenario-1-detecting-lateral-movement" id="markdown-toc-scenario-1-detecting-lateral-movement">Scenario 1: Detecting Lateral Movement</a></li>
      <li><a href="#scenario-2-compliance-violation-detection" id="markdown-toc-scenario-2-compliance-violation-detection">Scenario 2: Compliance Violation Detection</a></li>
      <li><a href="#scenario-3-ransomware-precursor-activity" id="markdown-toc-scenario-3-ransomware-precursor-activity">Scenario 3: Ransomware Precursor Activity</a></li>
    </ul>
  </li>
  <li><a href="#cost-analysis" id="markdown-toc-cost-analysis">Cost Analysis</a>    <ul>
      <li><a href="#security-hub-costs-50-aws-accounts-500-instances" id="markdown-toc-security-hub-costs-50-aws-accounts-500-instances">Security Hub Costs (50 AWS Accounts, 500 Instances)</a></li>
      <li><a href="#self-hosted-wazuh-on-aws-500-agents" id="markdown-toc-self-hosted-wazuh-on-aws-500-agents">Self-Hosted Wazuh on AWS (500 Agents)</a></li>
      <li><a href="#hybrid-approach-recommended" id="markdown-toc-hybrid-approach-recommended">Hybrid Approach (Recommended)</a></li>
    </ul>
  </li>
  <li><a href="#best-practices-for-hybrid-deployment" id="markdown-toc-best-practices-for-hybrid-deployment">Best Practices for Hybrid Deployment</a></li>
  <li><a href="#implementation-roadmap" id="markdown-toc-implementation-roadmap">Implementation Roadmap</a></li>
  <li><a href="#related-articles" id="markdown-toc-related-articles">Related Articles</a></li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ul>

<h2 id="why-your-siem-strategy-needs-to-evolve">Why Your SIEM Strategy Needs to Evolve</h2>

<p>The SIEM market generated USD 10.78 billion in revenue in 2025 and is forecast to reach USD 19.13 billion by 2030. That growth is not driven by hype — it is driven by the reality that 87% of organizations now operate in multi-cloud environments, and 61% experienced at least one cloud security incident in 2024. The average enterprise uses 2.1 public cloud providers, yet most security tools only see one slice of that infrastructure.</p>

<p>AWS Security Hub is a strong service for consolidating AWS-native security findings. But if your environment includes on-premises servers, endpoints, containers running outside AWS, or workloads on Azure and GCP, Security Hub leaves you blind. That is where Wazuh comes in — a fully open-source SIEM that sees everything, deployed on AWS infrastructure for the best of both worlds.</p>

<p>This guide breaks down both options, shows you how to deploy Wazuh on AWS, and demonstrates how to feed Security Hub findings into Wazuh for a unified security view across your entire estate.</p>

<h2 id="aws-security-hub-the-aws-native-baseline">AWS Security Hub: The AWS-Native Baseline</h2>

<h3 id="what-security-hub-does-well">What Security Hub Does Well</h3>

<p>AWS Security Hub is a cloud security posture management (CSPM) service that aggregates, organizes, and prioritizes security findings from across your AWS environment. At re:Invent 2025, AWS re-imagined Security Hub to unify GuardDuty, Inspector, and Macie into a single experience with near real-time risk analytics.</p>

<p><strong>Core Capabilities:</strong></p>

<ul>
  <li><strong>Automated Compliance Checks</strong>: Runs continuous evaluations against CIS AWS Foundations, PCI DSS, NIST 800-53, and AWS Foundational Security Best Practices</li>
  <li><strong>Finding Aggregation</strong>: Correlates signals from GuardDuty, Inspector, Macie, and 80+ third-party integrations</li>
  <li><strong>Attack Path Visualization</strong>: Automatically maps how adversaries could chain threats, vulnerabilities, and misconfigurations</li>
  <li><strong>Risk Prioritization</strong>: Scores findings based on exploitability, resource criticality, and blast radius</li>
  <li><strong>OCSF Schema</strong>: Findings formatted in Open Cybersecurity Schema Framework for interoperability</li>
  <li><strong>Incident Management</strong>: Direct integration with Jira and ServiceNow for ticket creation</li>
</ul>

<h3 id="setting-up-security-hub-with-cloudformation">Setting Up Security Hub with CloudFormation</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
</pre></td><td class="rouge-code"><pre><span class="na">AWSTemplateFormatVersion</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2010-09-09"</span>
<span class="na">Description</span><span class="pi">:</span> <span class="s">Security Hub with GuardDuty and Inspector integration</span>

<span class="na">Resources</span><span class="pi">:</span>
  <span class="na">SecurityHub</span><span class="pi">:</span>
    <span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::SecurityHub::Hub</span>
    <span class="na">Properties</span><span class="pi">:</span>
      <span class="na">Tags</span><span class="pi">:</span>
        <span class="na">Customer</span><span class="pi">:</span> <span class="s">red-team-sh</span>
        <span class="na">Application</span><span class="pi">:</span> <span class="s">security-monitoring</span>
        <span class="na">Environment</span><span class="pi">:</span> <span class="s">production</span>
        <span class="na">Owner</span><span class="pi">:</span> <span class="s">security-team</span>
        <span class="na">Costcenter</span><span class="pi">:</span> <span class="s">security-ops</span>

  <span class="na">SecurityHubStandard</span><span class="pi">:</span>
    <span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::SecurityHub::Standard</span>
    <span class="na">DependsOn</span><span class="pi">:</span> <span class="s">SecurityHub</span>
    <span class="na">Properties</span><span class="pi">:</span>
      <span class="na">StandardsArn</span><span class="pi">:</span> <span class="kt">!Sub</span> <span class="s2">"</span><span class="s">arn:aws:securityhub:${AWS::Region}::standards/aws-foundational-security-best-practices/v/1.0.0"</span>

  <span class="na">SecurityHubCISStandard</span><span class="pi">:</span>
    <span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::SecurityHub::Standard</span>
    <span class="na">DependsOn</span><span class="pi">:</span> <span class="s">SecurityHub</span>
    <span class="na">Properties</span><span class="pi">:</span>
      <span class="na">StandardsArn</span><span class="pi">:</span> <span class="kt">!Sub</span> <span class="s2">"</span><span class="s">arn:aws:securityhub:${AWS::Region}::standards/cis-aws-foundations-benchmark/v/1.4.0"</span>

  <span class="na">GuardDutyDetector</span><span class="pi">:</span>
    <span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::GuardDuty::Detector</span>
    <span class="na">Properties</span><span class="pi">:</span>
      <span class="na">Enable</span><span class="pi">:</span> <span class="kc">true</span>
      <span class="na">DataSources</span><span class="pi">:</span>
        <span class="na">S3Logs</span><span class="pi">:</span>
          <span class="na">Enable</span><span class="pi">:</span> <span class="kc">true</span>
        <span class="na">Kubernetes</span><span class="pi">:</span>
          <span class="na">AuditLogs</span><span class="pi">:</span>
            <span class="na">Enable</span><span class="pi">:</span> <span class="kc">true</span>
        <span class="na">MalwareProtection</span><span class="pi">:</span>
          <span class="na">ScanEc2InstanceWithFindings</span><span class="pi">:</span>
            <span class="na">EbsVolumes</span><span class="pi">:</span> <span class="kc">true</span>
      <span class="na">Tags</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">Key</span><span class="pi">:</span> <span class="s">Customer</span>
          <span class="na">Value</span><span class="pi">:</span> <span class="s">red-team-sh</span>
        <span class="pi">-</span> <span class="na">Key</span><span class="pi">:</span> <span class="s">Application</span>
          <span class="na">Value</span><span class="pi">:</span> <span class="s">security-monitoring</span>

  <span class="na">InspectorEnablement</span><span class="pi">:</span>
    <span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::Inspector2::Filter</span>
    <span class="na">Properties</span><span class="pi">:</span>
      <span class="na">FilterAction</span><span class="pi">:</span> <span class="s">NONE</span>
      <span class="na">Name</span><span class="pi">:</span> <span class="s">baseline-filter</span>
      <span class="na">Description</span><span class="pi">:</span> <span class="s">Baseline Inspector filter for Security Hub integration</span>
      <span class="na">FilterCriteria</span><span class="pi">:</span>
        <span class="na">ResourceType</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">Comparison</span><span class="pi">:</span> <span class="s">EQUALS</span>
            <span class="na">Value</span><span class="pi">:</span> <span class="s">AWS_EC2_INSTANCE</span>
          <span class="pi">-</span> <span class="na">Comparison</span><span class="pi">:</span> <span class="s">EQUALS</span>
            <span class="na">Value</span><span class="pi">:</span> <span class="s">AWS_ECR_CONTAINER_IMAGE</span>
          <span class="pi">-</span> <span class="na">Comparison</span><span class="pi">:</span> <span class="s">EQUALS</span>
            <span class="na">Value</span><span class="pi">:</span> <span class="s">AWS_LAMBDA_FUNCTION</span>

<span class="na">Outputs</span><span class="pi">:</span>
  <span class="na">SecurityHubArn</span><span class="pi">:</span>
    <span class="na">Description</span><span class="pi">:</span> <span class="s">Security Hub ARN</span>
    <span class="na">Value</span><span class="pi">:</span> <span class="kt">!Ref</span> <span class="s">SecurityHub</span>
  <span class="na">GuardDutyDetectorId</span><span class="pi">:</span>
    <span class="na">Description</span><span class="pi">:</span> <span class="s">GuardDuty Detector ID</span>
    <span class="na">Value</span><span class="pi">:</span> <span class="kt">!Ref</span> <span class="s">GuardDutyDetector</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="security-hub-pricing-2026">Security Hub Pricing (2026)</h3>

<p>Security Hub uses pay-as-you-go pricing based on security checks and finding ingestion events:</p>

<table>
  <thead>
    <tr>
      <th>Component</th>
      <th>Cost</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Security checks</td>
      <td>$0.0010 per check per month (first 100K), $0.0008 thereafter</td>
    </tr>
    <tr>
      <td>Finding ingestion events</td>
      <td>$0.00003 per event per month</td>
    </tr>
    <tr>
      <td>GuardDuty (CloudTrail)</td>
      <td>$4.00 per million events (first 5B)</td>
    </tr>
    <tr>
      <td>GuardDuty (VPC Flow Logs)</td>
      <td>$1.00 per million events (first 5B)</td>
    </tr>
    <tr>
      <td>Inspector (EC2 scanning)</td>
      <td>$0.60 per instance per month</td>
    </tr>
  </tbody>
</table>

<p>For a typical 50-account organization with 500 instances, expect <strong>$500-$2,000/month</strong> depending on event volume and enabled integrations.</p>

<h2 id="where-security-hub-falls-short">Where Security Hub Falls Short</h2>

<p>Security Hub is excellent at what it does, but it has structural limitations that matter for real-world security operations:</p>

<p><strong>1. AWS-Only Visibility</strong></p>

<p>Security Hub cannot ingest events from on-premises servers, Azure VMs, GCP instances, or employee endpoints. If an attacker compromises a developer laptop and pivots into your AWS environment, Security Hub only sees the AWS side of the attack chain.</p>

<p><strong>2. No Endpoint Monitoring</strong></p>

<p>There is no file integrity monitoring (FIM), no rootkit detection, no process monitoring. Security Hub tells you about misconfigurations and known threats — it does not watch what is actually happening on your hosts.</p>

<p><strong>3. Limited Custom Detection Rules</strong></p>

<p>You can create custom insights and automation rules, but Security Hub is not designed for writing complex correlation rules across disparate log sources. Its detection logic is primarily driven by the findings that upstream services (GuardDuty, Inspector) generate.</p>

<p><strong>4. No Active Response</strong></p>

<p>Security Hub can trigger Lambda functions through EventBridge, but it has no built-in active response capability like blocking an IP, quarantining a file, or killing a process on an endpoint.</p>

<p><strong>5. Vendor Lock-In</strong></p>

<p>Your entire security detection capability is tied to AWS. If you move workloads to another provider, or need to maintain visibility during a cloud migration, your SIEM goes with it.</p>

<h2 id="wazuh-the-open-source-siem-that-sees-everything">Wazuh: The Open-Source SIEM That Sees Everything</h2>

<h3 id="from-ossec-to-wazuh">From OSSEC to Wazuh</h3>

<p>Wazuh was forked from OSSEC in 2015 to address the limitations of the aging host-based intrusion detection system that had been in maintenance mode with minimal development. Since then, Wazuh has accumulated over 40,000 commits — more than 30,000 ahead of OSSEC — and evolved into a full SIEM/XDR platform.</p>

<p>The latest release (4.14.1, November 2025) includes IAM role support for VPC flow logs, eBPF-based file integrity monitoring, ARM architecture support, CISA-sourced vulnerability prioritization, and an IT Hygiene dashboard for centralized asset visibility.</p>

<h3 id="wazuh-architecture">Wazuh Architecture</h3>

<p>Wazuh consists of four core components that can be distributed across multiple nodes for high availability and scale:</p>

<p><strong>Wazuh Manager</strong> — The central processing engine that receives events from agents, applies decoders and rules, triggers alerts, and manages agent configurations. In a cluster deployment, a master node handles configuration distribution while worker nodes process agent events.</p>

<p><strong>Wazuh Agent</strong> — A lightweight agent (under 50MB memory footprint) deployed on monitored endpoints. Supports Linux, Windows, macOS, Solaris, AIX, and HP-UX. Agents collect logs, monitor file integrity, detect rootkits, inventory software, and assess vulnerabilities locally before forwarding events.</p>

<p><strong>Wazuh Indexer</strong> — A fork of OpenSearch that stores alerts, events, and monitoring data. Provides full-text search, analytics, and the data layer for dashboards. Supports index lifecycle management and snapshot-based backups.</p>

<p><strong>Wazuh Dashboard</strong> — A fork of OpenSearch Dashboards providing visualization, threat hunting, compliance reporting, and agent management through a web interface.</p>

<h3 id="what-wazuh-monitors">What Wazuh Monitors</h3>

<table>
  <thead>
    <tr>
      <th>Capability</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Log Analysis</strong></td>
      <td>Collects and analyzes logs from OS, applications, and cloud services</td>
    </tr>
    <tr>
      <td><strong>File Integrity Monitoring</strong></td>
      <td>Detects changes to critical files using eBPF (Linux) or audit subsystem</td>
    </tr>
    <tr>
      <td><strong>Vulnerability Detection</strong></td>
      <td>Scans installed packages against CVE databases with CISA prioritization</td>
    </tr>
    <tr>
      <td><strong>Rootkit Detection</strong></td>
      <td>Scans for rootkits, trojans, and hidden processes</td>
    </tr>
    <tr>
      <td><strong>Security Configuration Assessment</strong></td>
      <td>Evaluates system configurations against CIS benchmarks</td>
    </tr>
    <tr>
      <td><strong>Active Response</strong></td>
      <td>Automatically blocks IPs, quarantines files, or runs custom scripts</td>
    </tr>
    <tr>
      <td><strong>Compliance Reporting</strong></td>
      <td>Built-in dashboards for PCI DSS, HIPAA, GDPR, NIST 800-53, TSC</td>
    </tr>
    <tr>
      <td><strong>Cloud Security</strong></td>
      <td>Native modules for AWS (CloudTrail, VPC, WAF, Config), Azure, and GCP</td>
    </tr>
    <tr>
      <td><strong>Container Security</strong></td>
      <td>Docker runtime monitoring, Kubernetes audit log analysis</td>
    </tr>
    <tr>
      <td><strong>Inventory</strong></td>
      <td>Hardware, software, network interfaces, open ports, running processes</td>
    </tr>
  </tbody>
</table>

<h2 id="deploying-wazuh-on-aws">Deploying Wazuh on AWS</h2>

<p>There are three primary deployment options for running Wazuh on AWS. Each has tradeoffs around control, cost, and operational overhead.</p>

<h3 id="option-1-docker-compose-on-ec2-recommended-for-getting-started">Option 1: Docker Compose on EC2 (Recommended for Getting Started)</h3>

<p>The fastest path to a production-capable Wazuh deployment. Use a single EC2 instance for smaller environments (up to 100 agents) or distribute components across multiple instances for scale.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
</pre></td><td class="rouge-code"><pre><span class="c1"># docker-compose.yml - Wazuh All-in-One on EC2</span>
<span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3.8"</span>

<span class="na">services</span><span class="pi">:</span>
  <span class="na">wazuh.manager</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">wazuh/wazuh-manager:4.14.1</span>
    <span class="na">hostname</span><span class="pi">:</span> <span class="s">wazuh.manager</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>
    <span class="na">ulimits</span><span class="pi">:</span>
      <span class="na">memlock</span><span class="pi">:</span>
        <span class="na">soft</span><span class="pi">:</span> <span class="s">-1</span>
        <span class="na">hard</span><span class="pi">:</span> <span class="s">-1</span>
      <span class="na">nofile</span><span class="pi">:</span>
        <span class="na">soft</span><span class="pi">:</span> <span class="m">655360</span>
        <span class="na">hard</span><span class="pi">:</span> <span class="m">655360</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">1514:1514"</span>       <span class="c1"># Agent communication</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">1515:1515"</span>       <span class="c1"># Agent enrollment</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">514:514/udp"</span>     <span class="c1"># Syslog collection</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">55000:55000"</span>     <span class="c1"># Wazuh API</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="na">INDEXER_URL</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://wazuh.indexer:9200"</span>
      <span class="na">INDEXER_USERNAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">admin"</span>
      <span class="na">INDEXER_PASSWORD</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${WAZUH_INDEXER_PASSWORD}"</span>
      <span class="na">FILEBEAT_SSL_VERIFICATION_MODE</span><span class="pi">:</span> <span class="s2">"</span><span class="s">full"</span>
      <span class="na">SSL_CERTIFICATE_AUTHORITIES</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/etc/ssl/root-ca.pem"</span>
      <span class="na">SSL_CERTIFICATE</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/etc/ssl/filebeat.pem"</span>
      <span class="na">SSL_KEY</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/etc/ssl/filebeat.key"</span>
      <span class="na">API_USERNAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">wazuh-wui"</span>
      <span class="na">API_PASSWORD</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${WAZUH_API_PASSWORD}"</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">wazuh_api_configuration:/var/ossec/api/configuration</span>
      <span class="pi">-</span> <span class="s">wazuh_etc:/var/ossec/etc</span>
      <span class="pi">-</span> <span class="s">wazuh_logs:/var/ossec/logs</span>
      <span class="pi">-</span> <span class="s">wazuh_queue:/var/ossec/queue</span>
      <span class="pi">-</span> <span class="s">wazuh_var_multigroups:/var/ossec/var/multigroups</span>
      <span class="pi">-</span> <span class="s">wazuh_integrations:/var/ossec/integrations</span>
      <span class="pi">-</span> <span class="s">wazuh_active_response:/var/ossec/active-response/bin</span>
      <span class="pi">-</span> <span class="s">wazuh_agentless:/var/ossec/agentless</span>
      <span class="pi">-</span> <span class="s">wazuh_wodles:/var/ossec/wodles</span>
      <span class="pi">-</span> <span class="s">filebeat_etc:/etc/filebeat</span>
      <span class="pi">-</span> <span class="s">filebeat_var:/var/lib/filebeat</span>
    <span class="na">networks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">wazuh-net</span>

  <span class="na">wazuh.indexer</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">wazuh/wazuh-indexer:4.14.1</span>
    <span class="na">hostname</span><span class="pi">:</span> <span class="s">wazuh.indexer</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>
    <span class="na">ulimits</span><span class="pi">:</span>
      <span class="na">memlock</span><span class="pi">:</span>
        <span class="na">soft</span><span class="pi">:</span> <span class="s">-1</span>
        <span class="na">hard</span><span class="pi">:</span> <span class="s">-1</span>
      <span class="na">nofile</span><span class="pi">:</span>
        <span class="na">soft</span><span class="pi">:</span> <span class="m">65536</span>
        <span class="na">hard</span><span class="pi">:</span> <span class="m">65536</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">9200:9200"</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="na">OPENSEARCH_JAVA_OPTS</span><span class="pi">:</span> <span class="s2">"</span><span class="s">-Xms2g</span><span class="nv"> </span><span class="s">-Xmx2g"</span>
      <span class="na">bootstrap.memory_lock</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
      <span class="na">discovery.type</span><span class="pi">:</span> <span class="s2">"</span><span class="s">single-node"</span>
      <span class="na">plugins.security.ssl.http.pemcert_filepath</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/usr/share/wazuh-indexer/certs/wazuh.indexer.pem"</span>
      <span class="na">plugins.security.ssl.http.pemkey_filepath</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/usr/share/wazuh-indexer/certs/wazuh.indexer-key.pem"</span>
      <span class="na">plugins.security.ssl.http.pemtrustedcas_filepath</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/usr/share/wazuh-indexer/certs/root-ca.pem"</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">wazuh_indexer_data:/var/lib/wazuh-indexer</span>
    <span class="na">networks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">wazuh-net</span>

  <span class="na">wazuh.dashboard</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">wazuh/wazuh-dashboard:4.14.1</span>
    <span class="na">hostname</span><span class="pi">:</span> <span class="s">wazuh.dashboard</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">443:5601"</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="na">INDEXER_USERNAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">admin"</span>
      <span class="na">INDEXER_PASSWORD</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${WAZUH_INDEXER_PASSWORD}"</span>
      <span class="na">WAZUH_API_URL</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://wazuh.manager"</span>
      <span class="na">DASHBOARD_USERNAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">kibanaserver"</span>
      <span class="na">DASHBOARD_PASSWORD</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${WAZUH_DASHBOARD_PASSWORD}"</span>
      <span class="na">API_USERNAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">wazuh-wui"</span>
      <span class="na">API_PASSWORD</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${WAZUH_API_PASSWORD}"</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">dashboard_certs:/usr/share/wazuh-dashboard/certs</span>
    <span class="na">depends_on</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">wazuh.indexer</span>
    <span class="na">networks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">wazuh-net</span>

<span class="na">volumes</span><span class="pi">:</span>
  <span class="na">wazuh_api_configuration</span><span class="pi">:</span>
  <span class="na">wazuh_etc</span><span class="pi">:</span>
  <span class="na">wazuh_logs</span><span class="pi">:</span>
  <span class="na">wazuh_queue</span><span class="pi">:</span>
  <span class="na">wazuh_var_multigroups</span><span class="pi">:</span>
  <span class="na">wazuh_integrations</span><span class="pi">:</span>
  <span class="na">wazuh_active_response</span><span class="pi">:</span>
  <span class="na">wazuh_agentless</span><span class="pi">:</span>
  <span class="na">wazuh_wodles</span><span class="pi">:</span>
  <span class="na">filebeat_etc</span><span class="pi">:</span>
  <span class="na">filebeat_var</span><span class="pi">:</span>
  <span class="na">wazuh_indexer_data</span><span class="pi">:</span>
  <span class="na">dashboard_certs</span><span class="pi">:</span>

<span class="na">networks</span><span class="pi">:</span>
  <span class="na">wazuh-net</span><span class="pi">:</span>
    <span class="na">driver</span><span class="pi">:</span> <span class="s">bridge</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="option-2-terraform-for-production-infrastructure">Option 2: Terraform for Production Infrastructure</h3>

<p>For production deployments, use Terraform to provision the underlying infrastructure with proper networking, load balancing, and security controls.</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
</pre></td><td class="rouge-code"><pre><span class="c1"># main.tf - Wazuh Production Infrastructure on AWS</span>

<span class="nx">terraform</span> <span class="p">{</span>
  <span class="nx">required_version</span> <span class="o">=</span> <span class="s2">"&gt;= 1.5"</span>
  <span class="nx">required_providers</span> <span class="p">{</span>
    <span class="nx">aws</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">source</span>  <span class="o">=</span> <span class="s2">"hashicorp/aws"</span>
      <span class="nx">version</span> <span class="o">=</span> <span class="s2">"~&gt; 5.0"</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="nx">backend</span> <span class="s2">"s3"</span> <span class="p">{</span>
    <span class="nx">bucket</span> <span class="o">=</span> <span class="s2">"skynet-tf-state-prod"</span>
    <span class="nx">key</span>    <span class="o">=</span> <span class="s2">"wazuh/terraform.tfstate"</span>
    <span class="nx">region</span> <span class="o">=</span> <span class="s2">"us-east-1"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">provider</span> <span class="s2">"aws"</span> <span class="p">{</span>
  <span class="nx">region</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">aws_region</span>
<span class="p">}</span>

<span class="nx">variable</span> <span class="s2">"aws_region"</span> <span class="p">{</span>
  <span class="nx">default</span> <span class="o">=</span> <span class="s2">"us-east-1"</span>
<span class="p">}</span>

<span class="nx">variable</span> <span class="s2">"environment"</span> <span class="p">{</span>
  <span class="nx">default</span> <span class="o">=</span> <span class="s2">"production"</span>
<span class="p">}</span>

<span class="nx">variable</span> <span class="s2">"wazuh_instance_type"</span> <span class="p">{</span>
  <span class="nx">default</span>     <span class="o">=</span> <span class="s2">"c5a.xlarge"</span>
  <span class="nx">description</span> <span class="o">=</span> <span class="s2">"Instance type for Wazuh manager - c5a.xlarge recommended"</span>
<span class="p">}</span>

<span class="nx">variable</span> <span class="s2">"indexer_instance_type"</span> <span class="p">{</span>
  <span class="nx">default</span>     <span class="o">=</span> <span class="s2">"r5.xlarge"</span>
  <span class="nx">description</span> <span class="o">=</span> <span class="s2">"Instance type for Wazuh indexer - memory optimized"</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># VPC and Networking</span>
<span class="c1"># -------------------------------------------------------------------</span>

<span class="nx">resource</span> <span class="s2">"aws_vpc"</span> <span class="s2">"wazuh"</span> <span class="p">{</span>
  <span class="nx">cidr_block</span>           <span class="o">=</span> <span class="s2">"10.10.0.0/16"</span>
  <span class="nx">enable_dns_hostnames</span> <span class="o">=</span> <span class="kc">true</span>
  <span class="nx">enable_dns_support</span>   <span class="o">=</span> <span class="kc">true</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span>        <span class="o">=</span> <span class="s2">"wazuh-vpc"</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"red-team-sh"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"wazuh-siem"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">environment</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"security-team"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security-ops"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_subnet"</span> <span class="s2">"wazuh_public_a"</span> <span class="p">{</span>
  <span class="nx">vpc_id</span>                  <span class="o">=</span> <span class="nx">aws_vpc</span><span class="p">.</span><span class="nx">wazuh</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">cidr_block</span>              <span class="o">=</span> <span class="s2">"10.10.1.0/24"</span>
  <span class="nx">availability_zone</span>       <span class="o">=</span> <span class="s2">"${var.aws_region}a"</span>
  <span class="nx">map_public_ip_on_launch</span> <span class="o">=</span> <span class="kc">false</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span>        <span class="o">=</span> <span class="s2">"wazuh-public-a"</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"red-team-sh"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"wazuh-siem"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">environment</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"security-team"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security-ops"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_subnet"</span> <span class="s2">"wazuh_public_b"</span> <span class="p">{</span>
  <span class="nx">vpc_id</span>                  <span class="o">=</span> <span class="nx">aws_vpc</span><span class="p">.</span><span class="nx">wazuh</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">cidr_block</span>              <span class="o">=</span> <span class="s2">"10.10.2.0/24"</span>
  <span class="nx">availability_zone</span>       <span class="o">=</span> <span class="s2">"${var.aws_region}b"</span>
  <span class="nx">map_public_ip_on_launch</span> <span class="o">=</span> <span class="kc">false</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span>        <span class="o">=</span> <span class="s2">"wazuh-public-b"</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"red-team-sh"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"wazuh-siem"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">environment</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"security-team"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security-ops"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_internet_gateway"</span> <span class="s2">"wazuh"</span> <span class="p">{</span>
  <span class="nx">vpc_id</span> <span class="o">=</span> <span class="nx">aws_vpc</span><span class="p">.</span><span class="nx">wazuh</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span>        <span class="o">=</span> <span class="s2">"wazuh-igw"</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"red-team-sh"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"wazuh-siem"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">environment</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"security-team"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security-ops"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_route_table"</span> <span class="s2">"wazuh_public"</span> <span class="p">{</span>
  <span class="nx">vpc_id</span> <span class="o">=</span> <span class="nx">aws_vpc</span><span class="p">.</span><span class="nx">wazuh</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">route</span> <span class="p">{</span>
    <span class="nx">cidr_block</span> <span class="o">=</span> <span class="s2">"0.0.0.0/0"</span>
    <span class="nx">gateway_id</span> <span class="o">=</span> <span class="nx">aws_internet_gateway</span><span class="p">.</span><span class="nx">wazuh</span><span class="p">.</span><span class="nx">id</span>
  <span class="p">}</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span>        <span class="o">=</span> <span class="s2">"wazuh-public-rt"</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"red-team-sh"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"wazuh-siem"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">environment</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"security-team"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security-ops"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_route_table_association"</span> <span class="s2">"wazuh_public_a"</span> <span class="p">{</span>
  <span class="nx">subnet_id</span>      <span class="o">=</span> <span class="nx">aws_subnet</span><span class="p">.</span><span class="nx">wazuh_public_a</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">route_table_id</span> <span class="o">=</span> <span class="nx">aws_route_table</span><span class="p">.</span><span class="nx">wazuh_public</span><span class="p">.</span><span class="nx">id</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_route_table_association"</span> <span class="s2">"wazuh_public_b"</span> <span class="p">{</span>
  <span class="nx">subnet_id</span>      <span class="o">=</span> <span class="nx">aws_subnet</span><span class="p">.</span><span class="nx">wazuh_public_b</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">route_table_id</span> <span class="o">=</span> <span class="nx">aws_route_table</span><span class="p">.</span><span class="nx">wazuh_public</span><span class="p">.</span><span class="nx">id</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># Security Groups</span>
<span class="c1"># -------------------------------------------------------------------</span>

<span class="nx">resource</span> <span class="s2">"aws_security_group"</span> <span class="s2">"wazuh_alb"</span> <span class="p">{</span>
  <span class="nx">name_prefix</span> <span class="o">=</span> <span class="s2">"wazuh-alb-"</span>
  <span class="nx">vpc_id</span>      <span class="o">=</span> <span class="nx">aws_vpc</span><span class="p">.</span><span class="nx">wazuh</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">ingress</span> <span class="p">{</span>
    <span class="nx">description</span> <span class="o">=</span> <span class="s2">"HTTPS from anywhere"</span>
    <span class="nx">from_port</span>   <span class="o">=</span> <span class="mi">443</span>
    <span class="nx">to_port</span>     <span class="o">=</span> <span class="mi">443</span>
    <span class="nx">protocol</span>    <span class="o">=</span> <span class="s2">"tcp"</span>
    <span class="nx">cidr_blocks</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"0.0.0.0/0"</span><span class="p">]</span>
  <span class="p">}</span>

  <span class="nx">egress</span> <span class="p">{</span>
    <span class="nx">from_port</span>   <span class="o">=</span> <span class="mi">0</span>
    <span class="nx">to_port</span>     <span class="o">=</span> <span class="mi">0</span>
    <span class="nx">protocol</span>    <span class="o">=</span> <span class="s2">"-1"</span>
    <span class="nx">cidr_blocks</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"0.0.0.0/0"</span><span class="p">]</span>
  <span class="p">}</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span>        <span class="o">=</span> <span class="s2">"wazuh-alb-sg"</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"red-team-sh"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"wazuh-siem"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">environment</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"security-team"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security-ops"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_security_group"</span> <span class="s2">"wazuh_manager"</span> <span class="p">{</span>
  <span class="nx">name_prefix</span> <span class="o">=</span> <span class="s2">"wazuh-manager-"</span>
  <span class="nx">vpc_id</span>      <span class="o">=</span> <span class="nx">aws_vpc</span><span class="p">.</span><span class="nx">wazuh</span><span class="p">.</span><span class="nx">id</span>

  <span class="c1"># Agent communication</span>
  <span class="nx">ingress</span> <span class="p">{</span>
    <span class="nx">description</span> <span class="o">=</span> <span class="s2">"Wazuh agent communication"</span>
    <span class="nx">from_port</span>   <span class="o">=</span> <span class="mi">1514</span>
    <span class="nx">to_port</span>     <span class="o">=</span> <span class="mi">1514</span>
    <span class="nx">protocol</span>    <span class="o">=</span> <span class="s2">"tcp"</span>
    <span class="nx">cidr_blocks</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"10.0.0.0/8"</span><span class="p">,</span> <span class="s2">"172.16.0.0/12"</span><span class="p">,</span> <span class="s2">"192.168.0.0/16"</span><span class="p">]</span>
  <span class="p">}</span>

  <span class="c1"># Agent enrollment</span>
  <span class="nx">ingress</span> <span class="p">{</span>
    <span class="nx">description</span> <span class="o">=</span> <span class="s2">"Wazuh agent enrollment"</span>
    <span class="nx">from_port</span>   <span class="o">=</span> <span class="mi">1515</span>
    <span class="nx">to_port</span>     <span class="o">=</span> <span class="mi">1515</span>
    <span class="nx">protocol</span>    <span class="o">=</span> <span class="s2">"tcp"</span>
    <span class="nx">cidr_blocks</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"10.0.0.0/8"</span><span class="p">,</span> <span class="s2">"172.16.0.0/12"</span><span class="p">,</span> <span class="s2">"192.168.0.0/16"</span><span class="p">]</span>
  <span class="p">}</span>

  <span class="c1"># Syslog</span>
  <span class="nx">ingress</span> <span class="p">{</span>
    <span class="nx">description</span> <span class="o">=</span> <span class="s2">"Syslog UDP"</span>
    <span class="nx">from_port</span>   <span class="o">=</span> <span class="mi">514</span>
    <span class="nx">to_port</span>     <span class="o">=</span> <span class="mi">514</span>
    <span class="nx">protocol</span>    <span class="o">=</span> <span class="s2">"udp"</span>
    <span class="nx">cidr_blocks</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"10.0.0.0/8"</span><span class="p">]</span>
  <span class="p">}</span>

  <span class="c1"># API from ALB</span>
  <span class="nx">ingress</span> <span class="p">{</span>
    <span class="nx">description</span>     <span class="o">=</span> <span class="s2">"Wazuh API from ALB"</span>
    <span class="nx">from_port</span>       <span class="o">=</span> <span class="mi">55000</span>
    <span class="nx">to_port</span>         <span class="o">=</span> <span class="mi">55000</span>
    <span class="nx">protocol</span>        <span class="o">=</span> <span class="s2">"tcp"</span>
    <span class="nx">security_groups</span> <span class="o">=</span> <span class="p">[</span><span class="nx">aws_security_group</span><span class="p">.</span><span class="nx">wazuh_alb</span><span class="p">.</span><span class="nx">id</span><span class="p">]</span>
  <span class="p">}</span>

  <span class="c1"># Dashboard from ALB</span>
  <span class="nx">ingress</span> <span class="p">{</span>
    <span class="nx">description</span>     <span class="o">=</span> <span class="s2">"Dashboard from ALB"</span>
    <span class="nx">from_port</span>       <span class="o">=</span> <span class="mi">5601</span>
    <span class="nx">to_port</span>         <span class="o">=</span> <span class="mi">5601</span>
    <span class="nx">protocol</span>        <span class="o">=</span> <span class="s2">"tcp"</span>
    <span class="nx">security_groups</span> <span class="o">=</span> <span class="p">[</span><span class="nx">aws_security_group</span><span class="p">.</span><span class="nx">wazuh_alb</span><span class="p">.</span><span class="nx">id</span><span class="p">]</span>
  <span class="p">}</span>

  <span class="nx">egress</span> <span class="p">{</span>
    <span class="nx">from_port</span>   <span class="o">=</span> <span class="mi">0</span>
    <span class="nx">to_port</span>     <span class="o">=</span> <span class="mi">0</span>
    <span class="nx">protocol</span>    <span class="o">=</span> <span class="s2">"-1"</span>
    <span class="nx">cidr_blocks</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"0.0.0.0/0"</span><span class="p">]</span>
  <span class="p">}</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span>        <span class="o">=</span> <span class="s2">"wazuh-manager-sg"</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"red-team-sh"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"wazuh-siem"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">environment</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"security-team"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security-ops"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># ALB for Dashboard Access</span>
<span class="c1"># -------------------------------------------------------------------</span>

<span class="nx">resource</span> <span class="s2">"aws_lb"</span> <span class="s2">"wazuh"</span> <span class="p">{</span>
  <span class="nx">name</span>               <span class="o">=</span> <span class="s2">"wazuh-dashboard-alb"</span>
  <span class="nx">internal</span>           <span class="o">=</span> <span class="kc">false</span>
  <span class="nx">load_balancer_type</span> <span class="o">=</span> <span class="s2">"application"</span>
  <span class="nx">security_groups</span>    <span class="o">=</span> <span class="p">[</span><span class="nx">aws_security_group</span><span class="p">.</span><span class="nx">wazuh_alb</span><span class="p">.</span><span class="nx">id</span><span class="p">]</span>
  <span class="nx">subnets</span>            <span class="o">=</span> <span class="p">[</span><span class="nx">aws_subnet</span><span class="p">.</span><span class="nx">wazuh_public_a</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="nx">aws_subnet</span><span class="p">.</span><span class="nx">wazuh_public_b</span><span class="p">.</span><span class="nx">id</span><span class="p">]</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span>        <span class="o">=</span> <span class="s2">"wazuh-alb"</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"red-team-sh"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"wazuh-siem"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">environment</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"security-team"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security-ops"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_lb_target_group"</span> <span class="s2">"wazuh_dashboard"</span> <span class="p">{</span>
  <span class="nx">name</span>     <span class="o">=</span> <span class="s2">"wazuh-dashboard-tg"</span>
  <span class="nx">port</span>     <span class="o">=</span> <span class="mi">5601</span>
  <span class="nx">protocol</span> <span class="o">=</span> <span class="s2">"HTTPS"</span>
  <span class="nx">vpc_id</span>   <span class="o">=</span> <span class="nx">aws_vpc</span><span class="p">.</span><span class="nx">wazuh</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">health_check</span> <span class="p">{</span>
    <span class="nx">path</span>                <span class="o">=</span> <span class="s2">"/api/status"</span>
    <span class="nx">protocol</span>            <span class="o">=</span> <span class="s2">"HTTPS"</span>
    <span class="nx">healthy_threshold</span>   <span class="o">=</span> <span class="mi">2</span>
    <span class="nx">unhealthy_threshold</span> <span class="o">=</span> <span class="mi">5</span>
    <span class="nx">timeout</span>             <span class="o">=</span> <span class="mi">10</span>
    <span class="nx">interval</span>            <span class="o">=</span> <span class="mi">30</span>
  <span class="err">}</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"red-team-sh"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"wazuh-siem"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">environment</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"security-team"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security-ops"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># EC2 Instances</span>
<span class="c1"># -------------------------------------------------------------------</span>

<span class="nx">data</span> <span class="s2">"aws_ami"</span> <span class="s2">"wazuh"</span> <span class="p">{</span>
  <span class="nx">most_recent</span> <span class="o">=</span> <span class="kc">true</span>
  <span class="nx">owners</span>      <span class="o">=</span> <span class="p">[</span><span class="s2">"aws-marketplace"</span><span class="p">]</span>

  <span class="nx">filter</span> <span class="p">{</span>
    <span class="nx">name</span>   <span class="o">=</span> <span class="s2">"name"</span>
    <span class="nx">values</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"wazuh-*"</span><span class="p">]</span>
  <span class="p">}</span>

  <span class="nx">filter</span> <span class="p">{</span>
    <span class="nx">name</span>   <span class="o">=</span> <span class="s2">"architecture"</span>
    <span class="nx">values</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"x86_64"</span><span class="p">]</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_instance"</span> <span class="s2">"wazuh_manager"</span> <span class="p">{</span>
  <span class="nx">ami</span>                    <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">aws_ami</span><span class="p">.</span><span class="nx">wazuh</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">instance_type</span>          <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">wazuh_instance_type</span>
  <span class="nx">subnet_id</span>              <span class="o">=</span> <span class="nx">aws_subnet</span><span class="p">.</span><span class="nx">wazuh_public_a</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">vpc_security_group_ids</span> <span class="o">=</span> <span class="p">[</span><span class="nx">aws_security_group</span><span class="p">.</span><span class="nx">wazuh_manager</span><span class="p">.</span><span class="nx">id</span><span class="p">]</span>
  <span class="nx">key_name</span>               <span class="o">=</span> <span class="s2">"wazuh-key"</span>

  <span class="nx">root_block_device</span> <span class="p">{</span>
    <span class="nx">volume_size</span> <span class="o">=</span> <span class="mi">100</span>
    <span class="nx">volume_type</span> <span class="o">=</span> <span class="s2">"gp3"</span>
    <span class="nx">iops</span>        <span class="o">=</span> <span class="mi">3000</span>
    <span class="nx">throughput</span>  <span class="o">=</span> <span class="mi">125</span>
    <span class="nx">encrypted</span>   <span class="o">=</span> <span class="kc">true</span>
  <span class="p">}</span>

  <span class="c1"># Additional EBS for data</span>
  <span class="nx">ebs_block_device</span> <span class="p">{</span>
    <span class="nx">device_name</span> <span class="o">=</span> <span class="s2">"/dev/sdb"</span>
    <span class="nx">volume_size</span> <span class="o">=</span> <span class="mi">500</span>
    <span class="nx">volume_type</span> <span class="o">=</span> <span class="s2">"gp3"</span>
    <span class="nx">iops</span>        <span class="o">=</span> <span class="mi">6000</span>
    <span class="nx">throughput</span>  <span class="o">=</span> <span class="mi">250</span>
    <span class="nx">encrypted</span>   <span class="o">=</span> <span class="kc">true</span>
  <span class="err">}</span>

  <span class="nx">iam_instance_profile</span> <span class="o">=</span> <span class="nx">aws_iam_instance_profile</span><span class="p">.</span><span class="nx">wazuh_manager</span><span class="p">.</span><span class="nx">name</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Name</span>        <span class="o">=</span> <span class="s2">"wazuh-manager"</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"red-team-sh"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"wazuh-siem"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">environment</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"security-team"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security-ops"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># IAM Role for Wazuh AWS Integration</span>
<span class="c1"># -------------------------------------------------------------------</span>

<span class="nx">resource</span> <span class="s2">"aws_iam_role"</span> <span class="s2">"wazuh_manager"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"wazuh-manager-role"</span>

  <span class="nx">assume_role_policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">Version</span> <span class="o">=</span> <span class="s2">"2012-10-17"</span>
    <span class="nx">Statement</span> <span class="o">=</span> <span class="p">[</span>
      <span class="p">{</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="s2">"sts:AssumeRole"</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Principal</span> <span class="o">=</span> <span class="p">{</span>
          <span class="nx">Service</span> <span class="o">=</span> <span class="s2">"ec2.amazonaws.com"</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">]</span>
  <span class="p">})</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"red-team-sh"</span>
    <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"wazuh-siem"</span>
    <span class="nx">Environment</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">environment</span>
    <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"security-team"</span>
    <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security-ops"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_iam_role_policy"</span> <span class="s2">"wazuh_aws_integration"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"wazuh-aws-integration"</span>
  <span class="nx">role</span> <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">wazuh_manager</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">Version</span> <span class="o">=</span> <span class="s2">"2012-10-17"</span>
    <span class="nx">Statement</span> <span class="o">=</span> <span class="p">[</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"cloudtrail:LookupEvents"</span><span class="p">,</span>
          <span class="s2">"cloudtrail:GetTrailStatus"</span><span class="p">,</span>
          <span class="s2">"s3:GetObject"</span><span class="p">,</span>
          <span class="s2">"s3:ListBucket"</span><span class="p">,</span>
          <span class="s2">"logs:GetLogEvents"</span><span class="p">,</span>
          <span class="s2">"logs:DescribeLogGroups"</span><span class="p">,</span>
          <span class="s2">"logs:DescribeLogStreams"</span><span class="p">,</span>
          <span class="s2">"guardduty:GetFindings"</span><span class="p">,</span>
          <span class="s2">"guardduty:ListFindings"</span><span class="p">,</span>
          <span class="s2">"guardduty:ListDetectors"</span><span class="p">,</span>
          <span class="s2">"securityhub:GetFindings"</span><span class="p">,</span>
          <span class="s2">"securityhub:BatchImportFindings"</span><span class="p">,</span>
          <span class="s2">"inspector2:ListFindings"</span><span class="p">,</span>
          <span class="s2">"ec2:DescribeInstances"</span><span class="p">,</span>
          <span class="s2">"ec2:DescribeFlowLogs"</span><span class="p">,</span>
          <span class="s2">"iam:GetAccountSummary"</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="s2">"*"</span>
      <span class="p">}</span>
    <span class="p">]</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_iam_instance_profile"</span> <span class="s2">"wazuh_manager"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"wazuh-manager-profile"</span>
  <span class="nx">role</span> <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">wazuh_manager</span><span class="p">.</span><span class="nx">name</span>
<span class="p">}</span>

<span class="c1"># -------------------------------------------------------------------</span>
<span class="c1"># Outputs</span>
<span class="c1"># -------------------------------------------------------------------</span>

<span class="nx">output</span> <span class="s2">"wazuh_manager_private_ip"</span> <span class="p">{</span>
  <span class="nx">value</span>       <span class="o">=</span> <span class="nx">aws_instance</span><span class="p">.</span><span class="nx">wazuh_manager</span><span class="p">.</span><span class="nx">private_ip</span>
  <span class="nx">description</span> <span class="o">=</span> <span class="s2">"Private IP of Wazuh Manager"</span>
<span class="p">}</span>

<span class="nx">output</span> <span class="s2">"wazuh_alb_dns"</span> <span class="p">{</span>
  <span class="nx">value</span>       <span class="o">=</span> <span class="nx">aws_lb</span><span class="p">.</span><span class="nx">wazuh</span><span class="p">.</span><span class="nx">dns_name</span>
  <span class="nx">description</span> <span class="o">=</span> <span class="s2">"ALB DNS name for Wazuh Dashboard"</span>
<span class="p">}</span>

<span class="nx">output</span> <span class="s2">"wazuh_manager_id"</span> <span class="p">{</span>
  <span class="nx">value</span>       <span class="o">=</span> <span class="nx">aws_instance</span><span class="p">.</span><span class="nx">wazuh_manager</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">description</span> <span class="o">=</span> <span class="s2">"Instance ID of Wazuh Manager"</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="option-3-aws-marketplace-ami">Option 3: AWS Marketplace AMI</h3>

<p>Wazuh provides a pre-built AMI through the AWS Marketplace that deploys all central components on Amazon Linux 2023. The recommended instance type is <code class="language-plaintext highlighter-rouge">c5a.xlarge</code>. This is the fastest path for evaluation but gives you less control over component placement and scaling.</p>

<p>Search the AWS Marketplace for <strong>“Wazuh All-In-One Deployment”</strong> and launch with the default security group settings.</p>

<h2 id="configuring-wazuh-agents-for-aws-instances">Configuring Wazuh Agents for AWS Instances</h2>

<p>Once your Wazuh manager is running, deploy agents to your monitored systems. Here is the agent configuration optimized for AWS EC2 instances:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
</pre></td><td class="rouge-code"><pre><span class="c">&lt;!-- /var/ossec/etc/ossec.conf - Wazuh Agent Configuration --&gt;</span>
<span class="nt">&lt;ossec_config&gt;</span>

  <span class="c">&lt;!-- Manager connection --&gt;</span>
  <span class="nt">&lt;client&gt;</span>
    <span class="nt">&lt;server&gt;</span>
      <span class="nt">&lt;address&gt;</span>WAZUH_MANAGER_IP<span class="nt">&lt;/address&gt;</span>
      <span class="nt">&lt;port&gt;</span>1514<span class="nt">&lt;/port&gt;</span>
      <span class="nt">&lt;protocol&gt;</span>tcp<span class="nt">&lt;/protocol&gt;</span>
    <span class="nt">&lt;/server&gt;</span>
    <span class="nt">&lt;enrollment&gt;</span>
      <span class="nt">&lt;enabled&gt;</span>yes<span class="nt">&lt;/enabled&gt;</span>
      <span class="nt">&lt;manager_address&gt;</span>WAZUH_MANAGER_IP<span class="nt">&lt;/manager_address&gt;</span>
      <span class="nt">&lt;port&gt;</span>1515<span class="nt">&lt;/port&gt;</span>
      <span class="nt">&lt;agent_name&gt;</span>aws-ec2-$(hostname)<span class="nt">&lt;/agent_name&gt;</span>
      <span class="nt">&lt;groups&gt;</span>aws,ec2,production<span class="nt">&lt;/groups&gt;</span>
    <span class="nt">&lt;/enrollment&gt;</span>
  <span class="nt">&lt;/client&gt;</span>

  <span class="c">&lt;!-- File Integrity Monitoring --&gt;</span>
  <span class="nt">&lt;syscheck&gt;</span>
    <span class="nt">&lt;disabled&gt;</span>no<span class="nt">&lt;/disabled&gt;</span>
    <span class="nt">&lt;frequency&gt;</span>43200<span class="nt">&lt;/frequency&gt;</span>
    <span class="nt">&lt;scan_on_start&gt;</span>yes<span class="nt">&lt;/scan_on_start&gt;</span>
    <span class="nt">&lt;alert_new_files&gt;</span>yes<span class="nt">&lt;/alert_new_files&gt;</span>

    <span class="c">&lt;!-- Critical system directories --&gt;</span>
    <span class="nt">&lt;directories</span> <span class="na">check_all=</span><span class="s">"yes"</span> <span class="na">realtime=</span><span class="s">"yes"</span><span class="nt">&gt;</span>/etc<span class="nt">&lt;/directories&gt;</span>
    <span class="nt">&lt;directories</span> <span class="na">check_all=</span><span class="s">"yes"</span> <span class="na">realtime=</span><span class="s">"yes"</span><span class="nt">&gt;</span>/usr/bin<span class="nt">&lt;/directories&gt;</span>
    <span class="nt">&lt;directories</span> <span class="na">check_all=</span><span class="s">"yes"</span> <span class="na">realtime=</span><span class="s">"yes"</span><span class="nt">&gt;</span>/usr/sbin<span class="nt">&lt;/directories&gt;</span>
    <span class="nt">&lt;directories</span> <span class="na">check_all=</span><span class="s">"yes"</span> <span class="na">realtime=</span><span class="s">"yes"</span><span class="nt">&gt;</span>/boot<span class="nt">&lt;/directories&gt;</span>

    <span class="c">&lt;!-- AWS-specific paths --&gt;</span>
    <span class="nt">&lt;directories</span> <span class="na">check_all=</span><span class="s">"yes"</span> <span class="na">realtime=</span><span class="s">"yes"</span><span class="nt">&gt;</span>/root/.aws<span class="nt">&lt;/directories&gt;</span>
    <span class="nt">&lt;directories</span> <span class="na">check_all=</span><span class="s">"yes"</span><span class="nt">&gt;</span>/var/log/cloud-init.log<span class="nt">&lt;/directories&gt;</span>

    <span class="c">&lt;!-- Ignore noisy paths --&gt;</span>
    <span class="nt">&lt;ignore&gt;</span>/etc/mtab<span class="nt">&lt;/ignore&gt;</span>
    <span class="nt">&lt;ignore&gt;</span>/etc/hosts.deny<span class="nt">&lt;/ignore&gt;</span>
    <span class="nt">&lt;ignore&gt;</span>/etc/adjtime<span class="nt">&lt;/ignore&gt;</span>
    <span class="nt">&lt;ignore</span> <span class="na">type=</span><span class="s">"sregex"</span><span class="nt">&gt;</span>.log$|.swp$<span class="nt">&lt;/ignore&gt;</span>
  <span class="nt">&lt;/syscheck&gt;</span>

  <span class="c">&lt;!-- Rootkit detection --&gt;</span>
  <span class="nt">&lt;rootcheck&gt;</span>
    <span class="nt">&lt;disabled&gt;</span>no<span class="nt">&lt;/disabled&gt;</span>
    <span class="nt">&lt;check_files&gt;</span>yes<span class="nt">&lt;/check_files&gt;</span>
    <span class="nt">&lt;check_trojans&gt;</span>yes<span class="nt">&lt;/check_trojans&gt;</span>
    <span class="nt">&lt;check_dev&gt;</span>yes<span class="nt">&lt;/check_dev&gt;</span>
    <span class="nt">&lt;check_sys&gt;</span>yes<span class="nt">&lt;/check_sys&gt;</span>
    <span class="nt">&lt;check_pids&gt;</span>yes<span class="nt">&lt;/check_pids&gt;</span>
    <span class="nt">&lt;check_ports&gt;</span>yes<span class="nt">&lt;/check_ports&gt;</span>
    <span class="nt">&lt;check_if&gt;</span>yes<span class="nt">&lt;/check_if&gt;</span>
    <span class="nt">&lt;frequency&gt;</span>43200<span class="nt">&lt;/frequency&gt;</span>
  <span class="nt">&lt;/rootcheck&gt;</span>

  <span class="c">&lt;!-- Vulnerability detection --&gt;</span>
  <span class="nt">&lt;wodle</span> <span class="na">name=</span><span class="s">"syscollector"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;disabled&gt;</span>no<span class="nt">&lt;/disabled&gt;</span>
    <span class="nt">&lt;interval&gt;</span>1h<span class="nt">&lt;/interval&gt;</span>
    <span class="nt">&lt;scan_on_start&gt;</span>yes<span class="nt">&lt;/scan_on_start&gt;</span>
    <span class="nt">&lt;hardware&gt;</span>yes<span class="nt">&lt;/hardware&gt;</span>
    <span class="nt">&lt;os&gt;</span>yes<span class="nt">&lt;/os&gt;</span>
    <span class="nt">&lt;network&gt;</span>yes<span class="nt">&lt;/network&gt;</span>
    <span class="nt">&lt;packages&gt;</span>yes<span class="nt">&lt;/packages&gt;</span>
    <span class="nt">&lt;ports</span> <span class="na">all=</span><span class="s">"no"</span><span class="nt">&gt;</span>yes<span class="nt">&lt;/ports&gt;</span>
    <span class="nt">&lt;processes&gt;</span>yes<span class="nt">&lt;/processes&gt;</span>
  <span class="nt">&lt;/wodle&gt;</span>

  <span class="c">&lt;!-- Security Configuration Assessment --&gt;</span>
  <span class="nt">&lt;sca&gt;</span>
    <span class="nt">&lt;enabled&gt;</span>yes<span class="nt">&lt;/enabled&gt;</span>
    <span class="nt">&lt;scan_on_start&gt;</span>yes<span class="nt">&lt;/scan_on_start&gt;</span>
    <span class="nt">&lt;interval&gt;</span>12h<span class="nt">&lt;/interval&gt;</span>
  <span class="nt">&lt;/sca&gt;</span>

  <span class="c">&lt;!-- Log collection --&gt;</span>
  <span class="nt">&lt;localfile&gt;</span>
    <span class="nt">&lt;log_format&gt;</span>syslog<span class="nt">&lt;/log_format&gt;</span>
    <span class="nt">&lt;location&gt;</span>/var/log/messages<span class="nt">&lt;/location&gt;</span>
  <span class="nt">&lt;/localfile&gt;</span>

  <span class="nt">&lt;localfile&gt;</span>
    <span class="nt">&lt;log_format&gt;</span>syslog<span class="nt">&lt;/log_format&gt;</span>
    <span class="nt">&lt;location&gt;</span>/var/log/secure<span class="nt">&lt;/location&gt;</span>
  <span class="nt">&lt;/localfile&gt;</span>

  <span class="nt">&lt;localfile&gt;</span>
    <span class="nt">&lt;log_format&gt;</span>audit<span class="nt">&lt;/log_format&gt;</span>
    <span class="nt">&lt;location&gt;</span>/var/log/audit/audit.log<span class="nt">&lt;/location&gt;</span>
  <span class="nt">&lt;/localfile&gt;</span>

  <span class="c">&lt;!-- CloudWatch agent logs if present --&gt;</span>
  <span class="nt">&lt;localfile&gt;</span>
    <span class="nt">&lt;log_format&gt;</span>json<span class="nt">&lt;/log_format&gt;</span>
    <span class="nt">&lt;location&gt;</span>/opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.json<span class="nt">&lt;/location&gt;</span>
  <span class="nt">&lt;/localfile&gt;</span>

<span class="nt">&lt;/ossec_config&gt;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Deploy agents at scale using SSM Run Command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="c"># Install Wazuh agent on all tagged EC2 instances via SSM</span>
aws ssm send-command <span class="se">\</span>
  <span class="nt">--document-name</span> <span class="s2">"AWS-RunShellScript"</span> <span class="se">\</span>
  <span class="nt">--targets</span> <span class="s2">"Key=tag:Application,Values=production"</span> <span class="se">\</span>
  <span class="nt">--parameters</span> <span class="s1">'{
    "commands": [
      "curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | gpg --no-default-keyring --keyring gnupg-ring:/usr/share/keyrings/wazuh.gpg --import &amp;&amp; chmod 644 /usr/share/keyrings/wazuh.gpg",
      "echo \"deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main\" | tee /etc/apt/sources.list.d/wazuh.list",
      "apt-get update &amp;&amp; WAZUH_MANAGER=\"10.10.1.50\" WAZUH_AGENT_GROUP=\"aws,production\" apt-get install -y wazuh-agent",
      "systemctl daemon-reload &amp;&amp; systemctl enable wazuh-agent &amp;&amp; systemctl start wazuh-agent"
    ]
  }'</span> <span class="se">\</span>
  <span class="nt">--comment</span> <span class="s2">"Deploy Wazuh agent to production instances"</span> <span class="se">\</span>
  <span class="nt">--timeout-seconds</span> 300 <span class="se">\</span>
  <span class="nt">--region</span> us-east-1
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="feeding-security-hub-findings-into-wazuh">Feeding Security Hub Findings into Wazuh</h2>

<p>This is where the real power of the hybrid approach emerges. Instead of choosing one or the other, feed Security Hub findings into Wazuh so you get AWS-native detection alongside your cross-platform SIEM.</p>

<h3 id="python-integration-script">Python Integration Script</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
</pre></td><td class="rouge-code"><pre><span class="c1">#!/usr/bin/env python3
</span><span class="sh">"""</span><span class="s">
Security Hub to Wazuh Integration
Pulls Security Hub findings and forwards them to Wazuh manager
via the Wazuh API for unified correlation and alerting.
</span><span class="sh">"""</span>

<span class="kn">import</span> <span class="n">json</span>
<span class="kn">import</span> <span class="n">logging</span>
<span class="kn">import</span> <span class="n">time</span>
<span class="kn">from</span> <span class="n">datetime</span> <span class="kn">import</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">timedelta</span><span class="p">,</span> <span class="n">timezone</span>

<span class="kn">import</span> <span class="n">boto3</span>
<span class="kn">import</span> <span class="n">requests</span>
<span class="kn">import</span> <span class="n">urllib3</span>

<span class="c1"># Suppress SSL warnings for self-signed Wazuh certs
</span><span class="n">urllib3</span><span class="p">.</span><span class="nf">disable_warnings</span><span class="p">(</span><span class="n">urllib3</span><span class="p">.</span><span class="n">exceptions</span><span class="p">.</span><span class="n">InsecureRequestWarning</span><span class="p">)</span>

<span class="n">logging</span><span class="p">.</span><span class="nf">basicConfig</span><span class="p">(</span>
    <span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="p">.</span><span class="n">INFO</span><span class="p">,</span>
    <span class="nb">format</span><span class="o">=</span><span class="sh">"</span><span class="s">%(asctime)s - %(name)s - %(levelname)s - %(message)s</span><span class="sh">"</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="p">.</span><span class="nf">getLogger</span><span class="p">(</span><span class="sh">"</span><span class="s">securityhub-wazuh</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># Configuration
</span><span class="n">WAZUH_API_URL</span> <span class="o">=</span> <span class="sh">"</span><span class="s">https://localhost:55000</span><span class="sh">"</span>
<span class="n">WAZUH_API_USER</span> <span class="o">=</span> <span class="sh">"</span><span class="s">wazuh-wui</span><span class="sh">"</span>
<span class="n">WAZUH_API_PASSWORD</span> <span class="o">=</span> <span class="sh">"</span><span class="s">CHANGE_ME</span><span class="sh">"</span>  <span class="c1"># Use SSM Parameter Store in production
</span><span class="n">POLL_INTERVAL_SECONDS</span> <span class="o">=</span> <span class="mi">300</span>  <span class="c1"># 5 minutes
</span><span class="n">SEVERITY_THRESHOLD</span> <span class="o">=</span> <span class="mi">40</span>  <span class="c1"># Only forward MEDIUM+ findings (0-100 scale)
</span>

<span class="k">def</span> <span class="nf">get_wazuh_token</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Authenticate with Wazuh API and return JWT token.</span><span class="sh">"""</span>
    <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="nf">post</span><span class="p">(</span>
        <span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="n">WAZUH_API_URL</span><span class="si">}</span><span class="s">/security/user/authenticate</span><span class="sh">"</span><span class="p">,</span>
        <span class="n">auth</span><span class="o">=</span><span class="p">(</span><span class="n">WAZUH_API_USER</span><span class="p">,</span> <span class="n">WAZUH_API_PASSWORD</span><span class="p">),</span>
        <span class="n">verify</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span>
        <span class="n">timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="n">response</span><span class="p">.</span><span class="nf">raise_for_status</span><span class="p">()</span>
    <span class="k">return</span> <span class="n">response</span><span class="p">.</span><span class="nf">json</span><span class="p">()[</span><span class="sh">"</span><span class="s">data</span><span class="sh">"</span><span class="p">][</span><span class="sh">"</span><span class="s">token</span><span class="sh">"</span><span class="p">]</span>


<span class="k">def</span> <span class="nf">get_security_hub_findings</span><span class="p">(</span>
    <span class="n">hub_client</span><span class="p">:</span> <span class="n">boto3</span><span class="p">.</span><span class="n">client</span><span class="p">,</span>
    <span class="n">since</span><span class="p">:</span> <span class="n">datetime</span><span class="p">,</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
    <span class="sh">"""</span><span class="s">Fetch Security Hub findings updated since the given timestamp.</span><span class="sh">"""</span>
    <span class="n">findings</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="n">paginator</span> <span class="o">=</span> <span class="n">hub_client</span><span class="p">.</span><span class="nf">get_paginator</span><span class="p">(</span><span class="sh">"</span><span class="s">get_findings</span><span class="sh">"</span><span class="p">)</span>

    <span class="n">filters</span> <span class="o">=</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">UpdatedAt</span><span class="sh">"</span><span class="p">:</span> <span class="p">[</span>
            <span class="p">{</span>
                <span class="sh">"</span><span class="s">Start</span><span class="sh">"</span><span class="p">:</span> <span class="n">since</span><span class="p">.</span><span class="nf">isoformat</span><span class="p">(),</span>
                <span class="sh">"</span><span class="s">End</span><span class="sh">"</span><span class="p">:</span> <span class="n">datetime</span><span class="p">.</span><span class="nf">now</span><span class="p">(</span><span class="n">timezone</span><span class="p">.</span><span class="n">utc</span><span class="p">).</span><span class="nf">isoformat</span><span class="p">(),</span>
            <span class="p">}</span>
        <span class="p">],</span>
        <span class="sh">"</span><span class="s">SeverityNormalized</span><span class="sh">"</span><span class="p">:</span> <span class="p">[</span>
            <span class="p">{</span><span class="sh">"</span><span class="s">Gte</span><span class="sh">"</span><span class="p">:</span> <span class="n">SEVERITY_THRESHOLD</span><span class="p">}</span>
        <span class="p">],</span>
        <span class="sh">"</span><span class="s">RecordState</span><span class="sh">"</span><span class="p">:</span> <span class="p">[{</span><span class="sh">"</span><span class="s">Value</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">ACTIVE</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Comparison</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">EQUALS</span><span class="sh">"</span><span class="p">}],</span>
        <span class="sh">"</span><span class="s">WorkflowStatus</span><span class="sh">"</span><span class="p">:</span> <span class="p">[{</span><span class="sh">"</span><span class="s">Value</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">NEW</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Comparison</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">EQUALS</span><span class="sh">"</span><span class="p">}],</span>
    <span class="p">}</span>

    <span class="k">for</span> <span class="n">page</span> <span class="ow">in</span> <span class="n">paginator</span><span class="p">.</span><span class="nf">paginate</span><span class="p">(</span><span class="n">Filters</span><span class="o">=</span><span class="n">filters</span><span class="p">,</span> <span class="n">MaxResults</span><span class="o">=</span><span class="mi">100</span><span class="p">):</span>
        <span class="n">findings</span><span class="p">.</span><span class="nf">extend</span><span class="p">(</span><span class="n">page</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Findings</span><span class="sh">"</span><span class="p">,</span> <span class="p">[]))</span>

    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sh">"</span><span class="s">Retrieved %d findings from Security Hub</span><span class="sh">"</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">findings</span><span class="p">))</span>
    <span class="k">return</span> <span class="n">findings</span>


<span class="k">def</span> <span class="nf">map_severity</span><span class="p">(</span><span class="n">normalized_severity</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Map Security Hub normalized severity (0-100) to Wazuh level (1-15).</span><span class="sh">"""</span>
    <span class="k">if</span> <span class="n">normalized_severity</span> <span class="o">&gt;=</span> <span class="mi">90</span><span class="p">:</span>
        <span class="k">return</span> <span class="mi">15</span>  <span class="c1"># Critical
</span>    <span class="k">if</span> <span class="n">normalized_severity</span> <span class="o">&gt;=</span> <span class="mi">70</span><span class="p">:</span>
        <span class="k">return</span> <span class="mi">12</span>  <span class="c1"># High
</span>    <span class="k">if</span> <span class="n">normalized_severity</span> <span class="o">&gt;=</span> <span class="mi">40</span><span class="p">:</span>
        <span class="k">return</span> <span class="mi">8</span>   <span class="c1"># Medium
</span>    <span class="k">if</span> <span class="n">normalized_severity</span> <span class="o">&gt;=</span> <span class="mi">20</span><span class="p">:</span>
        <span class="k">return</span> <span class="mi">5</span>   <span class="c1"># Low
</span>    <span class="k">return</span> <span class="mi">3</span>       <span class="c1"># Informational
</span>

<span class="k">def</span> <span class="nf">forward_to_wazuh</span><span class="p">(</span><span class="n">token</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">findings</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Forward Security Hub findings to Wazuh via API.</span><span class="sh">"""</span>
    <span class="n">headers</span> <span class="o">=</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">Authorization</span><span class="sh">"</span><span class="p">:</span> <span class="sa">f</span><span class="sh">"</span><span class="s">Bearer </span><span class="si">{</span><span class="n">token</span><span class="si">}</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">Content-Type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">application/json</span><span class="sh">"</span><span class="p">,</span>
    <span class="p">}</span>

    <span class="n">forwarded</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">for</span> <span class="n">finding</span> <span class="ow">in</span> <span class="n">findings</span><span class="p">:</span>
        <span class="n">severity</span> <span class="o">=</span> <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Severity</span><span class="sh">"</span><span class="p">,</span> <span class="p">{}).</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Normalized</span><span class="sh">"</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
        <span class="n">wazuh_level</span> <span class="o">=</span> <span class="nf">map_severity</span><span class="p">(</span><span class="n">severity</span><span class="p">)</span>

        <span class="c1"># Build Wazuh-compatible event
</span>        <span class="n">event</span> <span class="o">=</span> <span class="p">{</span>
            <span class="sh">"</span><span class="s">event</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span>
                <span class="sh">"</span><span class="s">source</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">aws-security-hub</span><span class="sh">"</span><span class="p">,</span>
                <span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">security-hub-finding</span><span class="sh">"</span><span class="p">,</span>
                <span class="sh">"</span><span class="s">severity</span><span class="sh">"</span><span class="p">:</span> <span class="n">wazuh_level</span><span class="p">,</span>
                <span class="sh">"</span><span class="s">description</span><span class="sh">"</span><span class="p">:</span> <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Description</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">),</span>
                <span class="sh">"</span><span class="s">title</span><span class="sh">"</span><span class="p">:</span> <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Title</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">),</span>
                <span class="sh">"</span><span class="s">id</span><span class="sh">"</span><span class="p">:</span> <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Id</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">),</span>
                <span class="sh">"</span><span class="s">product</span><span class="sh">"</span><span class="p">:</span> <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">ProductName</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">SecurityHub</span><span class="sh">"</span><span class="p">),</span>
                <span class="sh">"</span><span class="s">account_id</span><span class="sh">"</span><span class="p">:</span> <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">AwsAccountId</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">),</span>
                <span class="sh">"</span><span class="s">region</span><span class="sh">"</span><span class="p">:</span> <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Region</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">),</span>
                <span class="sh">"</span><span class="s">resource_type</span><span class="sh">"</span><span class="p">:</span> <span class="p">(</span>
                    <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Resources</span><span class="sh">"</span><span class="p">,</span> <span class="p">[{}])[</span><span class="mi">0</span><span class="p">].</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Type</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Unknown</span><span class="sh">"</span><span class="p">)</span>
                <span class="p">),</span>
                <span class="sh">"</span><span class="s">resource_id</span><span class="sh">"</span><span class="p">:</span> <span class="p">(</span>
                    <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Resources</span><span class="sh">"</span><span class="p">,</span> <span class="p">[{}])[</span><span class="mi">0</span><span class="p">].</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Id</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Unknown</span><span class="sh">"</span><span class="p">)</span>
                <span class="p">),</span>
                <span class="sh">"</span><span class="s">compliance_status</span><span class="sh">"</span><span class="p">:</span> <span class="p">(</span>
                    <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Compliance</span><span class="sh">"</span><span class="p">,</span> <span class="p">{}).</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Status</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">UNKNOWN</span><span class="sh">"</span><span class="p">)</span>
                <span class="p">),</span>
                <span class="sh">"</span><span class="s">recommendation</span><span class="sh">"</span><span class="p">:</span> <span class="p">(</span>
                    <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Remediation</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
                    <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Recommendation</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
                    <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Text</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">)</span>
                <span class="p">),</span>
                <span class="sh">"</span><span class="s">first_observed</span><span class="sh">"</span><span class="p">:</span> <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">FirstObservedAt</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">),</span>
                <span class="sh">"</span><span class="s">last_observed</span><span class="sh">"</span><span class="p">:</span> <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">LastObservedAt</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">),</span>
                <span class="sh">"</span><span class="s">generator_id</span><span class="sh">"</span><span class="p">:</span> <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">GeneratorId</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">),</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="k">try</span><span class="p">:</span>
            <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="nf">post</span><span class="p">(</span>
                <span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="n">WAZUH_API_URL</span><span class="si">}</span><span class="s">/events</span><span class="sh">"</span><span class="p">,</span>
                <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span>
                <span class="n">json</span><span class="o">=</span><span class="n">event</span><span class="p">,</span>
                <span class="n">verify</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span>
                <span class="n">timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span>
            <span class="p">)</span>
            <span class="k">if</span> <span class="n">response</span><span class="p">.</span><span class="n">status_code</span> <span class="ow">in</span> <span class="p">(</span><span class="mi">200</span><span class="p">,</span> <span class="mi">201</span><span class="p">):</span>
                <span class="n">forwarded</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">logger</span><span class="p">.</span><span class="nf">warning</span><span class="p">(</span>
                    <span class="sh">"</span><span class="s">Failed to forward finding %s: %s</span><span class="sh">"</span><span class="p">,</span>
                    <span class="n">finding</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">Id</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">unknown</span><span class="sh">"</span><span class="p">),</span>
                    <span class="n">response</span><span class="p">.</span><span class="n">text</span><span class="p">,</span>
                <span class="p">)</span>
        <span class="k">except</span> <span class="n">requests</span><span class="p">.</span><span class="n">RequestException</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
            <span class="n">logger</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="sh">"</span><span class="s">Error forwarding finding: %s</span><span class="sh">"</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">forwarded</span>


<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="sh">"""</span><span class="s">Main polling loop.</span><span class="sh">"""</span>
    <span class="n">hub_client</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="nf">client</span><span class="p">(</span><span class="sh">"</span><span class="s">securityhub</span><span class="sh">"</span><span class="p">)</span>
    <span class="n">last_poll</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">.</span><span class="nf">now</span><span class="p">(</span><span class="n">timezone</span><span class="p">.</span><span class="n">utc</span><span class="p">)</span> <span class="o">-</span> <span class="nf">timedelta</span><span class="p">(</span><span class="n">minutes</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>

    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sh">"</span><span class="s">Starting Security Hub to Wazuh integration</span><span class="sh">"</span><span class="p">)</span>
    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sh">"</span><span class="s">Poll interval: %ds, Severity threshold: %d</span><span class="sh">"</span><span class="p">,</span>
                <span class="n">POLL_INTERVAL_SECONDS</span><span class="p">,</span> <span class="n">SEVERITY_THRESHOLD</span><span class="p">)</span>

    <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">token</span> <span class="o">=</span> <span class="nf">get_wazuh_token</span><span class="p">()</span>
            <span class="n">findings</span> <span class="o">=</span> <span class="nf">get_security_hub_findings</span><span class="p">(</span><span class="n">hub_client</span><span class="p">,</span> <span class="n">last_poll</span><span class="p">)</span>

            <span class="k">if</span> <span class="n">findings</span><span class="p">:</span>
                <span class="n">forwarded</span> <span class="o">=</span> <span class="nf">forward_to_wazuh</span><span class="p">(</span><span class="n">token</span><span class="p">,</span> <span class="n">findings</span><span class="p">)</span>
                <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span>
                    <span class="sh">"</span><span class="s">Forwarded %d/%d findings to Wazuh</span><span class="sh">"</span><span class="p">,</span>
                    <span class="n">forwarded</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">findings</span><span class="p">),</span>
                <span class="p">)</span>

            <span class="n">last_poll</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">.</span><span class="nf">now</span><span class="p">(</span><span class="n">timezone</span><span class="p">.</span><span class="n">utc</span><span class="p">)</span>
        <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
            <span class="n">logger</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="sh">"</span><span class="s">Integration error: %s</span><span class="sh">"</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>

        <span class="n">time</span><span class="p">.</span><span class="nf">sleep</span><span class="p">(</span><span class="n">POLL_INTERVAL_SECONDS</span><span class="p">)</span>


<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="sh">"</span><span class="s">__main__</span><span class="sh">"</span><span class="p">:</span>
    <span class="nf">main</span><span class="p">()</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="custom-wazuh-rules-for-aws-threats">Custom Wazuh Rules for AWS Threats</h3>

<p>Add these rules to your Wazuh manager to detect AWS-specific attack patterns, including events forwarded from Security Hub:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
</pre></td><td class="rouge-code"><pre><span class="c">&lt;!-- /var/ossec/etc/rules/aws_custom_rules.xml --&gt;</span>
<span class="nt">&lt;group</span> <span class="na">name=</span><span class="s">"aws,security-hub,custom"</span><span class="nt">&gt;</span>

  <span class="c">&lt;!-- Security Hub finding ingestion --&gt;</span>
  <span class="nt">&lt;rule</span> <span class="na">id=</span><span class="s">"100100"</span> <span class="na">level=</span><span class="s">"0"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;decoded_as&gt;</span>json<span class="nt">&lt;/decoded_as&gt;</span>
    <span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">"event.source"</span><span class="nt">&gt;</span>aws-security-hub<span class="nt">&lt;/field&gt;</span>
    <span class="nt">&lt;description&gt;</span>AWS Security Hub finding received.<span class="nt">&lt;/description&gt;</span>
    <span class="nt">&lt;group&gt;</span>aws,security-hub<span class="nt">&lt;/group&gt;</span>
  <span class="nt">&lt;/rule&gt;</span>

  <span class="c">&lt;!-- Critical Security Hub findings --&gt;</span>
  <span class="nt">&lt;rule</span> <span class="na">id=</span><span class="s">"100101"</span> <span class="na">level=</span><span class="s">"14"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;if_sid&gt;</span>100100<span class="nt">&lt;/if_sid&gt;</span>
    <span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">"event.severity"</span><span class="nt">&gt;</span>^1[345]$<span class="nt">&lt;/field&gt;</span>
    <span class="nt">&lt;description&gt;</span>CRITICAL: AWS Security Hub finding - $(event.title)<span class="nt">&lt;/description&gt;</span>
    <span class="nt">&lt;group&gt;</span>aws,security-hub,critical<span class="nt">&lt;/group&gt;</span>
  <span class="nt">&lt;/rule&gt;</span>

  <span class="c">&lt;!-- High severity Security Hub findings --&gt;</span>
  <span class="nt">&lt;rule</span> <span class="na">id=</span><span class="s">"100102"</span> <span class="na">level=</span><span class="s">"10"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;if_sid&gt;</span>100100<span class="nt">&lt;/if_sid&gt;</span>
    <span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">"event.severity"</span><span class="nt">&gt;</span>^1[012]$<span class="nt">&lt;/field&gt;</span>
    <span class="nt">&lt;description&gt;</span>HIGH: AWS Security Hub finding - $(event.title)<span class="nt">&lt;/description&gt;</span>
    <span class="nt">&lt;group&gt;</span>aws,security-hub,high<span class="nt">&lt;/group&gt;</span>
  <span class="nt">&lt;/rule&gt;</span>

  <span class="c">&lt;!-- Detect IAM credential exposure --&gt;</span>
  <span class="nt">&lt;rule</span> <span class="na">id=</span><span class="s">"100110"</span> <span class="na">level=</span><span class="s">"14"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;if_sid&gt;</span>80302<span class="nt">&lt;/if_sid&gt;</span>
    <span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">"aws.eventName"</span><span class="nt">&gt;</span>^GetSecretValue$|^GetParameter$<span class="nt">&lt;/field&gt;</span>
    <span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">"aws.errorCode"</span><span class="nt">&gt;</span>AccessDenied<span class="nt">&lt;/field&gt;</span>
    <span class="nt">&lt;description&gt;</span>AWS: Unauthorized attempt to access secrets - possible credential theft - User: $(aws.userIdentity.arn)<span class="nt">&lt;/description&gt;</span>
    <span class="nt">&lt;group&gt;</span>aws,iam,credential-theft<span class="nt">&lt;/group&gt;</span>
    <span class="nt">&lt;mitre&gt;</span>
      <span class="nt">&lt;id&gt;</span>T1528<span class="nt">&lt;/id&gt;</span>
    <span class="nt">&lt;/mitre&gt;</span>
  <span class="nt">&lt;/rule&gt;</span>

  <span class="c">&lt;!-- Detect CloudTrail tampering --&gt;</span>
  <span class="nt">&lt;rule</span> <span class="na">id=</span><span class="s">"100111"</span> <span class="na">level=</span><span class="s">"15"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;if_sid&gt;</span>80302<span class="nt">&lt;/if_sid&gt;</span>
    <span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">"aws.eventName"</span><span class="nt">&gt;</span>^StopLogging$|^DeleteTrail$|^UpdateTrail$<span class="nt">&lt;/field&gt;</span>
    <span class="nt">&lt;description&gt;</span>AWS: CloudTrail logging modified - possible defense evasion - Trail: $(aws.requestParameters.name)<span class="nt">&lt;/description&gt;</span>
    <span class="nt">&lt;group&gt;</span>aws,cloudtrail,defense-evasion<span class="nt">&lt;/group&gt;</span>
    <span class="nt">&lt;mitre&gt;</span>
      <span class="nt">&lt;id&gt;</span>T1562.008<span class="nt">&lt;/id&gt;</span>
    <span class="nt">&lt;/mitre&gt;</span>
  <span class="nt">&lt;/rule&gt;</span>

  <span class="c">&lt;!-- Detect security group opened to world --&gt;</span>
  <span class="nt">&lt;rule</span> <span class="na">id=</span><span class="s">"100112"</span> <span class="na">level=</span><span class="s">"12"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;if_sid&gt;</span>80302<span class="nt">&lt;/if_sid&gt;</span>
    <span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">"aws.eventName"</span><span class="nt">&gt;</span>^AuthorizeSecurityGroupIngress$<span class="nt">&lt;/field&gt;</span>
    <span class="nt">&lt;match&gt;</span>0.0.0.0/0<span class="nt">&lt;/match&gt;</span>
    <span class="nt">&lt;description&gt;</span>AWS: Security group rule allows access from 0.0.0.0/0 - $(aws.requestParameters.groupId)<span class="nt">&lt;/description&gt;</span>
    <span class="nt">&lt;group&gt;</span>aws,network,misconfiguration<span class="nt">&lt;/group&gt;</span>
    <span class="nt">&lt;mitre&gt;</span>
      <span class="nt">&lt;id&gt;</span>T1190<span class="nt">&lt;/id&gt;</span>
    <span class="nt">&lt;/mitre&gt;</span>
  <span class="nt">&lt;/rule&gt;</span>

  <span class="c">&lt;!-- Detect console login without MFA --&gt;</span>
  <span class="nt">&lt;rule</span> <span class="na">id=</span><span class="s">"100113"</span> <span class="na">level=</span><span class="s">"10"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;if_sid&gt;</span>80302<span class="nt">&lt;/if_sid&gt;</span>
    <span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">"aws.eventName"</span><span class="nt">&gt;</span>^ConsoleLogin$<span class="nt">&lt;/field&gt;</span>
    <span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">"aws.additionalEventData.MFAUsed"</span><span class="nt">&gt;</span>No<span class="nt">&lt;/field&gt;</span>
    <span class="nt">&lt;description&gt;</span>AWS: Console login without MFA - User: $(aws.userIdentity.arn)<span class="nt">&lt;/description&gt;</span>
    <span class="nt">&lt;group&gt;</span>aws,iam,authentication<span class="nt">&lt;/group&gt;</span>
    <span class="nt">&lt;mitre&gt;</span>
      <span class="nt">&lt;id&gt;</span>T1078<span class="nt">&lt;/id&gt;</span>
    <span class="nt">&lt;/mitre&gt;</span>
  <span class="nt">&lt;/rule&gt;</span>

  <span class="c">&lt;!-- Detect lateral movement via SSM --&gt;</span>
  <span class="nt">&lt;rule</span> <span class="na">id=</span><span class="s">"100114"</span> <span class="na">level=</span><span class="s">"11"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;if_sid&gt;</span>80302<span class="nt">&lt;/if_sid&gt;</span>
    <span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">"aws.eventName"</span><span class="nt">&gt;</span>^SendCommand$|^StartSession$<span class="nt">&lt;/field&gt;</span>
    <span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">"aws.sourceIPAddress"</span><span class="nt">&gt;</span>\.amazonaws\.com$<span class="nt">&lt;/field&gt;</span>
    <span class="nt">&lt;description&gt;</span>AWS: SSM command sent from AWS service - possible lateral movement - Target: $(aws.requestParameters.instanceIds)<span class="nt">&lt;/description&gt;</span>
    <span class="nt">&lt;group&gt;</span>aws,ssm,lateral-movement<span class="nt">&lt;/group&gt;</span>
    <span class="nt">&lt;mitre&gt;</span>
      <span class="nt">&lt;id&gt;</span>T1021<span class="nt">&lt;/id&gt;</span>
    <span class="nt">&lt;/mitre&gt;</span>
  <span class="nt">&lt;/rule&gt;</span>

  <span class="c">&lt;!-- Detect new IAM user created with console access --&gt;</span>
  <span class="nt">&lt;rule</span> <span class="na">id=</span><span class="s">"100115"</span> <span class="na">level=</span><span class="s">"10"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;if_sid&gt;</span>80302<span class="nt">&lt;/if_sid&gt;</span>
    <span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">"aws.eventName"</span><span class="nt">&gt;</span>^CreateLoginProfile$<span class="nt">&lt;/field&gt;</span>
    <span class="nt">&lt;description&gt;</span>AWS: Console access enabled for IAM user - $(aws.requestParameters.userName) by $(aws.userIdentity.arn)<span class="nt">&lt;/description&gt;</span>
    <span class="nt">&lt;group&gt;</span>aws,iam,persistence<span class="nt">&lt;/group&gt;</span>
    <span class="nt">&lt;mitre&gt;</span>
      <span class="nt">&lt;id&gt;</span>T1136.003<span class="nt">&lt;/id&gt;</span>
    <span class="nt">&lt;/mitre&gt;</span>
  <span class="nt">&lt;/rule&gt;</span>

<span class="nt">&lt;/group&gt;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="wazuh-vs-security-hub-complete-comparison">Wazuh vs Security Hub: Complete Comparison</h2>

<table>
  <thead>
    <tr>
      <th>Capability</th>
      <th>AWS Security Hub</th>
      <th>Wazuh</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Deployment Model</strong></td>
      <td>Fully managed SaaS</td>
      <td>Self-hosted (EC2, Docker, K8s)</td>
    </tr>
    <tr>
      <td><strong>Coverage</strong></td>
      <td>AWS only</td>
      <td>Multi-cloud, on-prem, endpoints</td>
    </tr>
    <tr>
      <td><strong>Endpoint Monitoring</strong></td>
      <td>None</td>
      <td>Full agent-based FIM, rootkit, process</td>
    </tr>
    <tr>
      <td><strong>File Integrity Monitoring</strong></td>
      <td>None</td>
      <td>eBPF-based real-time monitoring</td>
    </tr>
    <tr>
      <td><strong>Vulnerability Scanning</strong></td>
      <td>Via Inspector integration</td>
      <td>Built-in CVE scanner with CISA data</td>
    </tr>
    <tr>
      <td><strong>Custom Detection Rules</strong></td>
      <td>Limited (automation rules)</td>
      <td>Full custom rule engine (XML-based)</td>
    </tr>
    <tr>
      <td><strong>Active Response</strong></td>
      <td>Via Lambda + EventBridge</td>
      <td>Built-in: block IPs, kill processes, quarantine</td>
    </tr>
    <tr>
      <td><strong>Log Sources</strong></td>
      <td>AWS services only</td>
      <td>Any syslog, JSON, custom format</td>
    </tr>
    <tr>
      <td><strong>Compliance Frameworks</strong></td>
      <td>CIS, PCI DSS, NIST, HIPAA</td>
      <td>PCI DSS, HIPAA, GDPR, NIST, TSC, CIS</td>
    </tr>
    <tr>
      <td><strong>MITRE ATT&amp;CK Mapping</strong></td>
      <td>Partial</td>
      <td>Full framework mapping</td>
    </tr>
    <tr>
      <td><strong>Cost Model</strong></td>
      <td>Per-check + per-finding</td>
      <td>Free software + infrastructure costs</td>
    </tr>
    <tr>
      <td><strong>Scalability</strong></td>
      <td>Automatic</td>
      <td>Horizontal (add workers/indexer nodes)</td>
    </tr>
    <tr>
      <td><strong>Vendor Lock-In</strong></td>
      <td>High (AWS-only)</td>
      <td>None (portable)</td>
    </tr>
    <tr>
      <td><strong>Setup Complexity</strong></td>
      <td>Low (enable in console)</td>
      <td>Medium-High (deploy and maintain)</td>
    </tr>
    <tr>
      <td><strong>Maintenance</strong></td>
      <td>None (managed)</td>
      <td>You manage updates, storage, HA</td>
    </tr>
    <tr>
      <td><strong>Attack Path Visualization</strong></td>
      <td>Built-in (2025)</td>
      <td>Via dashboard + custom queries</td>
    </tr>
    <tr>
      <td><strong>Integration Ecosystem</strong></td>
      <td>80+ AWS/partner integrations</td>
      <td>API-based, custom decoders</td>
    </tr>
  </tbody>
</table>

<h2 id="real-monitoring-scenarios">Real Monitoring Scenarios</h2>

<h3 id="scenario-1-detecting-lateral-movement">Scenario 1: Detecting Lateral Movement</h3>

<p>An attacker compromises an EC2 instance through an exposed application vulnerability and attempts to move laterally using stolen IAM credentials.</p>

<p><strong>Security Hub sees</strong>: GuardDuty finding for unusual API calls from the instance, Inspector finding for the application vulnerability.</p>

<p><strong>Wazuh sees</strong>: The initial exploit attempt in application logs, file integrity changes on the compromised host, new processes spawned, outbound connections to C2 infrastructure, credential file access on the host, and the subsequent API calls across other instances where agents are deployed.</p>

<p><strong>Combined view</strong>: By feeding Security Hub findings into Wazuh, you see the entire kill chain from initial exploitation through lateral movement in a single timeline.</p>

<h3 id="scenario-2-compliance-violation-detection">Scenario 2: Compliance Violation Detection</h3>

<p>A developer modifies a security group to allow SSH from 0.0.0.0/0 during troubleshooting and forgets to revert it.</p>

<p><strong>Security Hub sees</strong>: CIS Benchmark failure, AWS Foundational Security Best Practices violation.</p>

<p><strong>Wazuh sees</strong>: The CloudTrail event for the security group modification (via AWS module), plus the custom rule <code class="language-plaintext highlighter-rouge">100112</code> fires with the specific security group ID and user who made the change.</p>

<p><strong>Combined</strong>: Security Hub provides the compliance context; Wazuh provides the who-what-when and can trigger an active response to automatically revert the change.</p>

<h3 id="scenario-3-ransomware-precursor-activity">Scenario 3: Ransomware Precursor Activity</h3>

<p>An attacker gains access to an on-premises file server and begins encrypting files before pivoting to cloud resources.</p>

<p><strong>Security Hub sees</strong>: Nothing — the on-premises server is outside its visibility.</p>

<p><strong>Wazuh sees</strong>: Mass file modifications detected by FIM on the on-premises server, suspicious process execution, the pivot attempt to cloud resources, and the subsequent GuardDuty findings forwarded from Security Hub.</p>

<h2 id="cost-analysis">Cost Analysis</h2>

<h3 id="security-hub-costs-50-aws-accounts-500-instances">Security Hub Costs (50 AWS Accounts, 500 Instances)</h3>

<table>
  <thead>
    <tr>
      <th>Component</th>
      <th>Monthly Cost</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Security Hub checks (100K/month)</td>
      <td>$100</td>
    </tr>
    <tr>
      <td>Finding ingestion (500K events)</td>
      <td>$15</td>
    </tr>
    <tr>
      <td>GuardDuty (all data sources)</td>
      <td>$800-$1,200</td>
    </tr>
    <tr>
      <td>Inspector (500 instances)</td>
      <td>$300</td>
    </tr>
    <tr>
      <td><strong>Total</strong></td>
      <td><strong>$1,215-$1,615/month</strong></td>
    </tr>
  </tbody>
</table>

<h3 id="self-hosted-wazuh-on-aws-500-agents">Self-Hosted Wazuh on AWS (500 Agents)</h3>

<table>
  <thead>
    <tr>
      <th>Component</th>
      <th>Monthly Cost</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Wazuh Manager (c5a.xlarge)</td>
      <td>$110</td>
    </tr>
    <tr>
      <td>Wazuh Indexer (r5.xlarge)</td>
      <td>$180</td>
    </tr>
    <tr>
      <td>EBS storage (1TB gp3)</td>
      <td>$80</td>
    </tr>
    <tr>
      <td>ALB</td>
      <td>$25</td>
    </tr>
    <tr>
      <td>Data transfer</td>
      <td>$50-$100</td>
    </tr>
    <tr>
      <td><strong>Total</strong></td>
      <td><strong>$445-$495/month</strong></td>
    </tr>
  </tbody>
</table>

<p><strong>Cost difference</strong>: Self-hosted Wazuh costs roughly <strong>60-70% less</strong> than the equivalent Security Hub stack, and you get endpoint monitoring, multi-cloud coverage, and custom rules included. The tradeoff is operational overhead — you are responsible for patching, backups, scaling, and high availability.</p>

<h3 id="hybrid-approach-recommended">Hybrid Approach (Recommended)</h3>

<p>Run both. Keep Security Hub enabled for its managed compliance checks and native AWS integration (approximately $115/month for checks + ingestion alone without GuardDuty/Inspector). Deploy Wazuh for the heavy lifting: endpoint monitoring, custom detection, multi-cloud visibility, and active response. Forward Security Hub findings into Wazuh for the unified view.</p>

<p><strong>Estimated hybrid cost for 500 agents</strong>: $560-$610/month.</p>

<h2 id="best-practices-for-hybrid-deployment">Best Practices for Hybrid Deployment</h2>

<p><strong>1. Use Wazuh as the Primary SIEM</strong></p>

<p>Route all logs, events, and findings into Wazuh. Use Security Hub as a signal source, not your primary console. This ensures analysts have one pane of glass regardless of where the threat originates.</p>

<p><strong>2. Automate Agent Deployment</strong></p>

<p>Use AWS Systems Manager to deploy and manage Wazuh agents across your EC2 fleet. Bake the agent into your AMIs or container images so every new instance reports to Wazuh from first boot.</p>

<p><strong>3. Tune Before You Scale</strong></p>

<p>Start with a focused ruleset. The default Wazuh rules generate significant alert volume. Disable noisy rules, tune thresholds, and add custom rules for your specific environment before scaling past 100 agents.</p>

<p><strong>4. Secure the SIEM</strong></p>

<p>Your SIEM is a high-value target. Encrypt all communication with TLS, restrict API access, use IAM roles instead of access keys for AWS integration, and monitor the Wazuh manager itself with a separate alerting channel.</p>

<p><strong>5. Plan for Storage</strong></p>

<p>Wazuh indexer storage grows with your agent count and log volume. Use index lifecycle management to automatically roll over and delete old indices. For long-term retention, archive to S3 using snapshot and restore.</p>

<p><strong>6. Test Active Response in Staging</strong></p>

<p>Wazuh active response can block IPs, kill processes, and modify firewall rules. Test every active response rule in staging before enabling it in production. A misconfigured rule can cause outages.</p>

<p><strong>7. Keep Security Hub for Compliance Evidence</strong></p>

<p>Even with Wazuh as your primary SIEM, Security Hub compliance checks provide auditor-friendly evidence for SOC 2, PCI DSS, and HIPAA assessments. The built-in compliance dashboards are difficult to replicate.</p>

<p><strong>8. Monitor Multi-Cloud with Agent Groups</strong></p>

<p>Use Wazuh agent groups to organize endpoints by cloud provider, environment, and function. This enables targeted rules and dashboards:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="c"># Create agent groups</span>
/var/ossec/bin/agent_groups <span class="nt">-a</span> <span class="nt">-g</span> aws-production
/var/ossec/bin/agent_groups <span class="nt">-a</span> <span class="nt">-g</span> azure-staging
/var/ossec/bin/agent_groups <span class="nt">-a</span> <span class="nt">-g</span> onprem-servers
/var/ossec/bin/agent_groups <span class="nt">-a</span> <span class="nt">-g</span> developer-endpoints
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="implementation-roadmap">Implementation Roadmap</h2>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 1</strong>: Deploy Wazuh on AWS using Docker Compose for evaluation</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 2</strong>: Install agents on 5-10 representative systems, tune baseline rules</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 3</strong>: Enable AWS integration module (CloudTrail, VPC Flow Logs)</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 4</strong>: Deploy Security Hub integration script, verify finding correlation</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 5</strong>: Write custom rules for your environment-specific threats</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 6</strong>: Migrate to Terraform-managed production infrastructure</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 7</strong>: Scale agent deployment via SSM, enable active response in staging</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 8</strong>: Production cutover with monitoring and alerting validation</li>
</ul>

<h2 id="related-articles">Related Articles</h2>

<ul>
  <li><a href="/posts/real-time-ids-using-guardduty/">Real-Time Intrusion Detection Using AWS GuardDuty</a> — Detailed guide on GuardDuty setup and Lambda-based automated response</li>
  <li><a href="/posts/aws-cloud-security-best-practices-implementation-guide/">AWS Cloud Security Best Practices Implementation Guide</a> — Comprehensive security baseline for AWS environments</li>
  <li><a href="/posts/building-a-resilient-security-posture-with-aws-security/">Building a Resilient Security Posture with AWS Security</a> — Multi-layered defense strategy for AWS workloads</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>Security Hub is an excellent managed service for AWS-native security posture management. If your entire infrastructure lives in AWS and you need compliance dashboards with minimal operational overhead, it delivers real value.</p>

<p>But the reality for most organizations is messier. You have on-premises servers, developer laptops, containers running across multiple clouds, and SaaS applications generating security-relevant logs. Security Hub cannot see any of that.</p>

<p>Wazuh deployed on AWS gives you the flexibility to monitor everything — endpoints, servers, cloud workloads, and containers — regardless of where they run. Combined with Security Hub findings flowing into Wazuh, you get a unified SIEM that leverages the best of AWS-native detection alongside open-source extensibility.</p>

<p>The open-source approach demands more operational investment. You manage the infrastructure, write the rules, and handle the upgrades. But you own your detection logic, avoid vendor lock-in, and pay a fraction of the cost of commercial SIEM solutions.</p>

<p>Build it yourself. See everything. Respond faster.</p>

<hr />

<p><em>Have questions about deploying Wazuh on AWS or building a multi-cloud security monitoring strategy? Connect with me on <a href="https://www.linkedin.com/in/jonpricelinux/">LinkedIn</a> to discuss your security architecture.</em></p>]]></content><author><name>Jon (JR) Price</name></author><category term="AWS" /><category term="Security" /><category term="multi-cloud" /><category term="open-source-security" /><category term="opensearch" /><category term="security-hub" /><category term="siem" /><category term="threat-detection" /><category term="wazuh" /><summary type="html"><![CDATA[Deploy open-source Wazuh SIEM on AWS for multi-cloud visibility, or use Security Hub for AWS-native monitoring. Complete comparison with deployment guides.]]></summary></entry><entry><title type="html">SBOM on AWS: Supply Chain Security with Syft and Grype</title><link href="https://www.red-team.sh/posts/sbom-supply-chain-security-syft-grype-aws/" rel="alternate" type="text/html" title="SBOM on AWS: Supply Chain Security with Syft and Grype" /><published>2026-03-18T09:00:00-07:00</published><updated>2026-03-18T09:00:00-07:00</updated><id>https://www.red-team.sh/posts/sbom-supply-chain-security-syft-grype-aws</id><content type="html" xml:base="https://www.red-team.sh/posts/sbom-supply-chain-security-syft-grype-aws/"><![CDATA[<p><img src="/assets/img/sbom-supply-chain-security-aws.png" alt="SBOM Supply Chain Security Pipeline on AWS" class="shadow" w="800" />
<em>End-to-end supply chain security pipeline: Source to signed SBOM with Syft, Grype, and Cosign on AWS</em></p>

<ul id="markdown-toc">
  <li><a href="#introduction-the-supply-chain-is-the-new-attack-surface" id="markdown-toc-introduction-the-supply-chain-is-the-new-attack-surface">Introduction: The Supply Chain Is the New Attack Surface</a></li>
  <li><a href="#the-regulatory-landscape-why-sboms-are-now-mandatory" id="markdown-toc-the-regulatory-landscape-why-sboms-are-now-mandatory">The Regulatory Landscape: Why SBOMs Are Now Mandatory</a>    <ul>
      <li><a href="#eu-cyber-resilience-act-cra" id="markdown-toc-eu-cyber-resilience-act-cra">EU Cyber Resilience Act (CRA)</a></li>
      <li><a href="#nist-and-executive-order-14028" id="markdown-toc-nist-and-executive-order-14028">NIST and Executive Order 14028</a></li>
      <li><a href="#fda-cybersecurity-requirements" id="markdown-toc-fda-cybersecurity-requirements">FDA Cybersecurity Requirements</a></li>
    </ul>
  </li>
  <li><a href="#what-is-an-sbom-spdx-vs-cyclonedx" id="markdown-toc-what-is-an-sbom-spdx-vs-cyclonedx">What Is an SBOM? SPDX vs CycloneDX</a>    <ul>
      <li><a href="#spdx-software-package-data-exchange" id="markdown-toc-spdx-software-package-data-exchange">SPDX (Software Package Data Exchange)</a></li>
      <li><a href="#cyclonedx" id="markdown-toc-cyclonedx">CycloneDX</a></li>
      <li><a href="#which-format-should-you-use" id="markdown-toc-which-format-should-you-use">Which Format Should You Use?</a></li>
    </ul>
  </li>
  <li><a href="#aws-inspector-sbom-the-native-starting-point" id="markdown-toc-aws-inspector-sbom-the-native-starting-point">AWS Inspector SBOM: The Native Starting Point</a>    <ul>
      <li><a href="#what-inspector-offers" id="markdown-toc-what-inspector-offers">What Inspector Offers</a></li>
      <li><a href="#exporting-sboms-with-the-aws-cli" id="markdown-toc-exporting-sboms-with-the-aws-cli">Exporting SBOMs with the AWS CLI</a></li>
      <li><a href="#using-inspector-sbomgen-locally" id="markdown-toc-using-inspector-sbomgen-locally">Using Inspector Sbomgen Locally</a></li>
      <li><a href="#inspector-limitations" id="markdown-toc-inspector-limitations">Inspector Limitations</a></li>
    </ul>
  </li>
  <li><a href="#syft-deep-dive-the-industry-standard-sbom-generator" id="markdown-toc-syft-deep-dive-the-industry-standard-sbom-generator">Syft Deep Dive: The Industry-Standard SBOM Generator</a>    <ul>
      <li><a href="#installation" id="markdown-toc-installation">Installation</a></li>
      <li><a href="#generating-sboms" id="markdown-toc-generating-sboms">Generating SBOMs</a></li>
      <li><a href="#supported-ecosystems" id="markdown-toc-supported-ecosystems">Supported Ecosystems</a></li>
      <li><a href="#syft-configuration-file" id="markdown-toc-syft-configuration-file">Syft Configuration File</a></li>
      <li><a href="#performance-characteristics" id="markdown-toc-performance-characteristics">Performance Characteristics</a></li>
    </ul>
  </li>
  <li><a href="#grype-deep-dive-vulnerability-scanning-against-sboms" id="markdown-toc-grype-deep-dive-vulnerability-scanning-against-sboms">Grype Deep Dive: Vulnerability Scanning Against SBOMs</a>    <ul>
      <li><a href="#installation-1" id="markdown-toc-installation-1">Installation</a></li>
      <li><a href="#scanning-for-vulnerabilities" id="markdown-toc-scanning-for-vulnerabilities">Scanning for Vulnerabilities</a></li>
      <li><a href="#vulnerability-database-sources" id="markdown-toc-vulnerability-database-sources">Vulnerability Database Sources</a></li>
      <li><a href="#grype-configuration-for-cicd" id="markdown-toc-grype-configuration-for-cicd">Grype Configuration for CI/CD</a></li>
      <li><a href="#interpreting-grype-output" id="markdown-toc-interpreting-grype-output">Interpreting Grype Output</a></li>
    </ul>
  </li>
  <li><a href="#complete-codepipeline-integration" id="markdown-toc-complete-codepipeline-integration">Complete CodePipeline Integration</a>    <ul>
      <li><a href="#codebuild-buildspecyml" id="markdown-toc-codebuild-buildspecyml">CodeBuild buildspec.yml</a></li>
      <li><a href="#terraform-for-codepipeline-with-security-stages" id="markdown-toc-terraform-for-codepipeline-with-security-stages">Terraform for CodePipeline with Security Stages</a></li>
    </ul>
  </li>
  <li><a href="#cosign-signing-sboms-and-container-images" id="markdown-toc-cosign-signing-sboms-and-container-images">Cosign: Signing SBOMs and Container Images</a>    <ul>
      <li><a href="#generating-a-cosign-key-pair" id="markdown-toc-generating-a-cosign-key-pair">Generating a Cosign Key Pair</a></li>
      <li><a href="#signing-workflow" id="markdown-toc-signing-workflow">Signing Workflow</a></li>
      <li><a href="#keyless-signing-with-sigstore" id="markdown-toc-keyless-signing-with-sigstore">Keyless Signing with Sigstore</a></li>
    </ul>
  </li>
  <li><a href="#comparison-aws-inspector-vs-syftgrype" id="markdown-toc-comparison-aws-inspector-vs-syftgrype">Comparison: AWS Inspector vs Syft/Grype</a></li>
  <li><a href="#automated-compliance-reporting-with-s3-and-athena" id="markdown-toc-automated-compliance-reporting-with-s3-and-athena">Automated Compliance Reporting with S3 and Athena</a>    <ul>
      <li><a href="#python-script-for-sbom-analysis-and-reporting" id="markdown-toc-python-script-for-sbom-analysis-and-reporting">Python Script for SBOM Analysis and Reporting</a></li>
      <li><a href="#athena-table-for-sbom-queries" id="markdown-toc-athena-table-for-sbom-queries">Athena Table for SBOM Queries</a></li>
    </ul>
  </li>
  <li><a href="#best-practices-for-sbom-lifecycle-management" id="markdown-toc-best-practices-for-sbom-lifecycle-management">Best Practices for SBOM Lifecycle Management</a>    <ul>
      <li><a href="#1-generate-sboms-at-every-build" id="markdown-toc-1-generate-sboms-at-every-build">1. Generate SBOMs at Every Build</a></li>
      <li><a href="#2-store-sboms-with-retention-policies" id="markdown-toc-2-store-sboms-with-retention-policies">2. Store SBOMs with Retention Policies</a></li>
      <li><a href="#3-sign-everything" id="markdown-toc-3-sign-everything">3. Sign Everything</a></li>
      <li><a href="#4-scan-continuously-not-just-at-build-time" id="markdown-toc-4-scan-continuously-not-just-at-build-time">4. Scan Continuously, Not Just at Build Time</a></li>
      <li><a href="#5-establish-a-vulnerability-response-sla" id="markdown-toc-5-establish-a-vulnerability-response-sla">5. Establish a Vulnerability Response SLA</a></li>
      <li><a href="#6-use-vex-to-communicate-exploitability" id="markdown-toc-6-use-vex-to-communicate-exploitability">6. Use VEX to Communicate Exploitability</a></li>
      <li><a href="#7-monitor-the-sbom-for-license-risk" id="markdown-toc-7-monitor-the-sbom-for-license-risk">7. Monitor the SBOM for License Risk</a></li>
    </ul>
  </li>
  <li><a href="#implementation-roadmap" id="markdown-toc-implementation-roadmap">Implementation Roadmap</a></li>
  <li><a href="#related-articles" id="markdown-toc-related-articles">Related Articles</a></li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ul>

<h2 id="introduction-the-supply-chain-is-the-new-attack-surface">Introduction: The Supply Chain Is the New Attack Surface</h2>

<p>Software supply chain attacks are no longer a theoretical risk. They are the dominant breach vector of 2025. The global cost of supply chain attacks reached <strong>$60 billion in 2025</strong>, up from $46 billion just two years prior, and projections place the figure at <strong>$138 billion by 2031</strong>. The 2025 Verizon Data Breach Investigations Report found that <strong>30% of all breaches now involve a third party</strong>, a 100% increase from prior reporting periods. Attack frequency has doubled year-over-year, with October 2025 setting a record of <strong>41 supply chain attacks in a single month</strong>.</p>

<p>The response from regulators has been decisive. The EU Cyber Resilience Act (CRA), NIST Executive Order 14028, and FDA cybersecurity requirements all converge on a single mandate: <strong>organizations must produce, maintain, and share Software Bills of Materials (SBOMs)</strong>.</p>

<p>If you sell software to the EU market, you have until <strong>September 11, 2026</strong> to comply with CRA vulnerability reporting obligations, and until <strong>December 11, 2027</strong> for full compliance. If you sell to the US federal government, SBOM requirements are already in effect.</p>

<p>The good news: the open-source tooling for SBOM generation and vulnerability scanning is mature, battle-tested, and free. <strong>Syft</strong> and <strong>Grype</strong> from Anchore are the industry standard — even AWS Inspector uses Anchore-derived technology under the hood. Combined with <strong>Cosign</strong> for artifact signing and <strong>AWS CodePipeline</strong> for orchestration, you can build a fully automated, regulatory-compliant supply chain security pipeline without a single commercial license.</p>

<p>This guide walks you through exactly how to do it.</p>

<p>This SBOM pattern supports the <a href="/ai-supply-chain-security/">AI supply chain security pillar</a> by making model-serving containers, training images, and supporting services traceable before they reach production.</p>

<h2 id="the-regulatory-landscape-why-sboms-are-now-mandatory">The Regulatory Landscape: Why SBOMs Are Now Mandatory</h2>

<h3 id="eu-cyber-resilience-act-cra">EU Cyber Resilience Act (CRA)</h3>

<p>The CRA entered into force on <strong>December 10, 2024</strong>, establishing the most sweeping software security regulation in history. Key SBOM requirements include:</p>

<ul>
  <li><strong>Mandatory SBOM generation</strong> for all products with digital elements sold in the EU market</li>
  <li><strong>Machine-readable format</strong> using a commonly used standard (SPDX or CycloneDX)</li>
  <li><strong>Top-level dependency listing</strong> at minimum, with full dependency trees recommended</li>
  <li><strong>Technical documentation</strong> that includes the SBOM, provided to market surveillance authorities on request</li>
  <li><strong>Vulnerability reporting</strong> within 24 hours to ENISA and national CSIRTs for actively exploited vulnerabilities, effective September 2026</li>
</ul>

<p>The penalties are severe: up to <strong>EUR 15 million or 2.5% of global annual turnover</strong>, whichever is higher. A draft horizontal standard is expected by mid-2026 to formalize the exact SBOM schema, but the law is clear — if you do not have SBOM generation and vulnerability management in place before September 2026, you cannot comply.</p>

<h3 id="nist-and-executive-order-14028">NIST and Executive Order 14028</h3>

<p>Executive Order 14028 (May 2021) defined SBOMs as a “formal record containing the details and supply chain relationships of various components used in building software.” NIST guidance mandates:</p>

<ul>
  <li><strong>Machine-readable SBOMs</strong> in SPDX, CycloneDX, or SWID format</li>
  <li><strong>Signed self-attestation</strong> of secure development practices aligned with NIST SP 800-218 (SSDF)</li>
  <li><strong>Traceable and current</strong> SBOMs that are updated with each software release</li>
  <li><strong>CISA minimum elements</strong> (2025 update) expanding required metadata for provenance and authenticity</li>
</ul>

<p>Software vendors selling to the US federal government must comply today. The August 2025 consortium deadline for SSDF alignment guidance has passed, and procurement requirements are tightening.</p>

<h3 id="fda-cybersecurity-requirements">FDA Cybersecurity Requirements</h3>

<p>For medical device manufacturers, the FDA now requires SBOMs as part of premarket submissions under the Consolidated Appropriations Act (Section 524B). This applies to any device with software components.</p>

<p><strong>The bottom line</strong>: If you ship software to any regulated market, SBOMs are not optional. The question is not whether to generate them, but how to automate the process reliably.</p>

<h2 id="what-is-an-sbom-spdx-vs-cyclonedx">What Is an SBOM? SPDX vs CycloneDX</h2>

<p>A Software Bill of Materials is a machine-readable inventory of every component in your software — libraries, frameworks, compilers, and their transitive dependencies. Think of it as a nutrition label for software.</p>

<h3 id="spdx-software-package-data-exchange">SPDX (Software Package Data Exchange)</h3>

<p>Developed by the Linux Foundation, SPDX is the <strong>only internationally recognized SBOM standard</strong> (ISO/IEC 5962:2021). Key characteristics:</p>

<ul>
  <li><strong>Primary focus</strong>: License compliance and intellectual property tracking</li>
  <li><strong>Current version</strong>: SPDX 3.0 (though many tools still use 2.3)</li>
  <li><strong>Formats</strong>: JSON, RDF, XML, YAML, tag-value</li>
  <li><strong>Strengths</strong>: Comprehensive license expression support, ISO standardization, strong adoption with Microsoft and Linux Foundation projects</li>
  <li><strong>Best for</strong>: Legal teams, IP due diligence, licensing compliance</li>
</ul>

<h3 id="cyclonedx">CycloneDX</h3>

<p>Developed by the OWASP community, CycloneDX is a lightweight, security-first SBOM format. Key characteristics:</p>

<ul>
  <li><strong>Primary focus</strong>: Security vulnerability tracking and risk assessment</li>
  <li><strong>Current version</strong>: CycloneDX 1.6.1</li>
  <li><strong>Formats</strong>: JSON, XML, Protocol Buffers</li>
  <li><strong>Strengths</strong>: Native VEX (Vulnerability Exploitability eXchange) support, dependency tree representation, hashing, and attestation fields</li>
  <li><strong>Best for</strong>: Security teams, DevSecOps pipelines, vulnerability management</li>
</ul>

<h3 id="which-format-should-you-use">Which Format Should You Use?</h3>

<p>Both formats share significant overlap, and both Syft and AWS Inspector support generating either. The practical recommendation:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Criteria</th>
      <th style="text-align: left">SPDX</th>
      <th style="text-align: left">CycloneDX</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">Regulatory compliance</td>
      <td style="text-align: left">Both accepted by EU CRA and NIST</td>
      <td style="text-align: left">Both accepted by EU CRA and NIST</td>
    </tr>
    <tr>
      <td style="text-align: left">License compliance</td>
      <td style="text-align: left">Preferred</td>
      <td style="text-align: left">Adequate</td>
    </tr>
    <tr>
      <td style="text-align: left">Security scanning</td>
      <td style="text-align: left">Adequate</td>
      <td style="text-align: left">Preferred</td>
    </tr>
    <tr>
      <td style="text-align: left">VEX support</td>
      <td style="text-align: left">Via companion docs</td>
      <td style="text-align: left">Native</td>
    </tr>
    <tr>
      <td style="text-align: left">ISO standardized</td>
      <td style="text-align: left">Yes (ISO/IEC 5962)</td>
      <td style="text-align: left">No (OWASP standard)</td>
    </tr>
    <tr>
      <td style="text-align: left">CI/CD integration</td>
      <td style="text-align: left">Good</td>
      <td style="text-align: left">Excellent</td>
    </tr>
    <tr>
      <td style="text-align: left">Tool ecosystem</td>
      <td style="text-align: left">Broad</td>
      <td style="text-align: left">Broad</td>
    </tr>
  </tbody>
</table>

<p><strong>Our recommendation</strong>: Generate CycloneDX for your security pipeline (native VEX support, lighter weight, better CI/CD tooling) and SPDX for regulatory submissions and legal compliance. With Syft, generating both is a single flag change.</p>

<h2 id="aws-inspector-sbom-the-native-starting-point">AWS Inspector SBOM: The Native Starting Point</h2>

<p>AWS Inspector provides built-in SBOM capabilities through its <strong>SBOM Generator (Sbomgen)</strong> and the <strong>Inspector Scan API</strong>. If you are already running Inspector for vulnerability management, this is your zero-effort baseline.</p>

<h3 id="what-inspector-offers">What Inspector Offers</h3>

<ul>
  <li><strong>Automated SBOM generation</strong> for monitored EC2 instances, Lambda functions, and ECR images</li>
  <li><strong>Export in CycloneDX 1.4 and SPDX 2.3</strong> formats</li>
  <li><strong>Integrated vulnerability scanning</strong> against the Inspector vulnerability database</li>
  <li><strong>Console and API access</strong> for SBOM export and scanning</li>
</ul>

<h3 id="exporting-sboms-with-the-aws-cli">Exporting SBOMs with the AWS CLI</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre></td><td class="rouge-code"><pre><span class="c"># Export SBOMs for all monitored resources</span>
aws inspector2 create-sbom-export <span class="se">\</span>
  <span class="nt">--report-format</span> CYCLONEDX_1_4 <span class="se">\</span>
  <span class="nt">--s3-destination</span> <span class="se">\</span>
    <span class="nv">bucketName</span><span class="o">=</span>my-sbom-bucket,<span class="se">\</span>
    <span class="nv">keyPrefix</span><span class="o">=</span>inspector-exports/,<span class="se">\</span>
    <span class="nv">kmsKeyArn</span><span class="o">=</span>arn:aws:kms:us-east-1:123456789012:key/my-key-id

<span class="c"># Check export status</span>
aws inspector2 get-sbom-export <span class="se">\</span>
  <span class="nt">--report-id</span> <span class="s2">"report-id-from-create"</span>

<span class="c"># Scan a single SBOM via the API</span>
aws inspector-scan scan-sbom <span class="se">\</span>
  <span class="nt">--sbom</span> file://my-sbom.json <span class="se">\</span>
  <span class="nt">--output-format</span> INSPECTOR
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="using-inspector-sbomgen-locally">Using Inspector Sbomgen Locally</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="c"># Download the Inspector SBOM Generator</span>
curl <span class="nt">-O</span> https://amazon-inspector-sbomgen.s3.amazonaws.com/latest/linux/amd64/inspector-sbomgen

<span class="nb">chmod</span> +x inspector-sbomgen

<span class="c"># Generate SBOM for a container image</span>
./inspector-sbomgen container <span class="se">\</span>
  <span class="nt">--image</span> my-app:latest <span class="se">\</span>
  <span class="nt">--outfile</span> sbom.json

<span class="c"># Generate SBOM for a directory</span>
./inspector-sbomgen directory <span class="se">\</span>
  <span class="nt">--path</span> /app <span class="se">\</span>
  <span class="nt">--outfile</span> sbom.json
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="inspector-limitations">Inspector Limitations</h3>

<p>While Inspector is a strong baseline, it has constraints that matter for serious supply chain security:</p>

<ul>
  <li><strong>Format versions</strong>: Limited to CycloneDX 1.4 and SPDX 2.3 (not the latest specifications)</li>
  <li><strong>Ecosystem coverage</strong>: Focused on AWS-monitored resources; does not cover arbitrary CI/CD artifacts</li>
  <li><strong>Portability</strong>: SBOMs are tied to the AWS ecosystem and Inspector’s vulnerability database</li>
  <li><strong>Cost</strong>: Inspector pricing is per-assessment, which scales with resource count</li>
  <li><strong>Customization</strong>: Limited control over scanning depth, exclusion rules, and output enrichment</li>
</ul>

<p>This is where open-source tooling fills the gap.</p>

<h2 id="syft-deep-dive-the-industry-standard-sbom-generator">Syft Deep Dive: The Industry-Standard SBOM Generator</h2>

<p><a href="https://github.com/anchore/syft">Syft</a> (v1.42.0 as of February 2026) is the most widely adopted open-source SBOM generator. It scans container images, filesystems, archives, and source code repositories to produce comprehensive, standards-compliant SBOMs.</p>

<h3 id="installation">Installation</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="c"># Install via curl (recommended for CI/CD)</span>
curl <span class="nt">-sSfL</span> https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh <span class="nt">-s</span> <span class="nt">--</span> <span class="nt">-b</span> /usr/local/bin

<span class="c"># Verify installation</span>
syft version
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="generating-sboms">Generating SBOMs</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
</pre></td><td class="rouge-code"><pre><span class="c"># Scan a container image (CycloneDX JSON)</span>
syft packages registry.example.com/my-app:v1.2.3 <span class="se">\</span>
  <span class="nt">-o</span> cyclonedx-json <span class="se">\</span>
  <span class="nt">--file</span> sbom-cyclonedx.json

<span class="c"># Scan a container image (SPDX JSON)</span>
syft packages registry.example.com/my-app:v1.2.3 <span class="se">\</span>
  <span class="nt">-o</span> spdx-json <span class="se">\</span>
  <span class="nt">--file</span> sbom-spdx.json

<span class="c"># Scan a local directory (source code)</span>
syft packages <span class="nb">dir</span>:/path/to/source <span class="se">\</span>
  <span class="nt">-o</span> cyclonedx-json <span class="se">\</span>
  <span class="nt">--file</span> sbom-source.json

<span class="c"># Scan a filesystem archive</span>
syft packages file:application.tar.gz <span class="se">\</span>
  <span class="nt">-o</span> cyclonedx-json <span class="se">\</span>
  <span class="nt">--file</span> sbom-archive.json

<span class="c"># Scan an OCI image from ECR</span>
syft packages <span class="se">\</span>
  123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest <span class="se">\</span>
  <span class="nt">-o</span> cyclonedx-json <span class="se">\</span>
  <span class="nt">--file</span> sbom-ecr.json

<span class="c"># Generate multiple formats simultaneously</span>
syft packages my-app:latest <span class="se">\</span>
  <span class="nt">-o</span> cyclonedx-json<span class="o">=</span>sbom-cdx.json <span class="se">\</span>
  <span class="nt">-o</span> spdx-json<span class="o">=</span>sbom-spdx.json <span class="se">\</span>
  <span class="nt">-o</span> table
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="supported-ecosystems">Supported Ecosystems</h3>

<p>Syft detects and catalogs packages across a broad ecosystem:</p>

<ul>
  <li><strong>OS packages</strong>: Alpine (apk), Debian/Ubuntu (dpkg), RHEL/CentOS (rpm), Arch (pacman)</li>
  <li><strong>Language ecosystems</strong>: npm, pip/pipenv/poetry, Maven/Gradle, Go modules, Ruby gems, Rust crates, PHP Composer, .NET NuGet, Swift CocoaPods, Dart pub, Haskell Cabal</li>
  <li><strong>Container metadata</strong>: Dockerfile instructions, base image identification, layer analysis</li>
  <li><strong>Binary analysis</strong>: Compiled Go and Rust binaries (extracts embedded dependency information)</li>
</ul>

<h3 id="syft-configuration-file">Syft Configuration File</h3>

<p>For consistent SBOM generation across your organization, use a <code class="language-plaintext highlighter-rouge">.syft.yaml</code> configuration file:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
</pre></td><td class="rouge-code"><pre><span class="c1"># .syft.yaml - Organization-wide Syft configuration</span>
<span class="na">output</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">cyclonedx-json</span>

<span class="c1"># Package cataloging options</span>
<span class="na">package</span><span class="pi">:</span>
  <span class="na">cataloger</span><span class="pi">:</span>
    <span class="na">enabled</span><span class="pi">:</span> <span class="kc">true</span>
    <span class="na">scope</span><span class="pi">:</span> <span class="s">all-layers</span>  <span class="c1"># Scan all image layers, not just squashed</span>

<span class="c1"># Exclusion patterns</span>
<span class="na">exclude</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s2">"</span><span class="s">**/test/**"</span>
  <span class="pi">-</span> <span class="s2">"</span><span class="s">**/node_modules/.cache/**"</span>
  <span class="pi">-</span> <span class="s2">"</span><span class="s">**/.git/**"</span>

<span class="c1"># Source metadata</span>
<span class="na">source</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">my-application"</span>
  <span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${APP_VERSION}"</span>
  <span class="na">metadata</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span>
      <span class="c1"># Include base image info for full provenance</span>
      <span class="na">layers</span><span class="pi">:</span> <span class="kc">true</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="performance-characteristics">Performance Characteristics</h3>

<p>Syft is designed for CI/CD performance:</p>

<ul>
  <li><strong>Typical scan time</strong>: 10-30 seconds for a standard application container image</li>
  <li><strong>Memory usage</strong>: 200-500MB depending on image size</li>
  <li><strong>Parallelism</strong>: Scans multiple layers concurrently</li>
  <li><strong>Caching</strong>: Supports local database caching for repeated scans</li>
</ul>

<h2 id="grype-deep-dive-vulnerability-scanning-against-sboms">Grype Deep Dive: Vulnerability Scanning Against SBOMs</h2>

<p><a href="https://github.com/anchore/grype">Grype</a> (v0.107.1 as of February 2026) is the companion vulnerability scanner to Syft. It takes an SBOM as input and cross-references every component against multiple vulnerability databases.</p>

<h3 id="installation-1">Installation</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="c"># Install via curl</span>
curl <span class="nt">-sSfL</span> https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh <span class="nt">-s</span> <span class="nt">--</span> <span class="nt">-b</span> /usr/local/bin

<span class="c"># Verify installation</span>
grype version

<span class="c"># Update vulnerability database</span>
grype db update
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="scanning-for-vulnerabilities">Scanning for Vulnerabilities</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="c"># Scan an SBOM directly</span>
grype sbom:sbom-cyclonedx.json

<span class="c"># Scan a container image (generates SBOM internally)</span>
grype registry.example.com/my-app:v1.2.3

<span class="c"># Scan with severity threshold (fail on Critical or High)</span>
grype sbom:sbom-cyclonedx.json <span class="nt">--fail-on</span> high

<span class="c"># Output in JSON for CI/CD processing</span>
grype sbom:sbom-cyclonedx.json <span class="nt">-o</span> json <span class="nt">--file</span> vuln-report.json

<span class="c"># Output as a SARIF report (for GitHub/GitLab integration)</span>
grype sbom:sbom-cyclonedx.json <span class="nt">-o</span> sarif <span class="nt">--file</span> vuln-report.sarif

<span class="c"># Scan with only fixed vulnerabilities shown</span>
grype sbom:sbom-cyclonedx.json <span class="nt">--only-fixed</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="vulnerability-database-sources">Vulnerability Database Sources</h3>

<p>Grype aggregates vulnerability data from:</p>

<ul>
  <li><strong>National Vulnerability Database (NVD)</strong></li>
  <li><strong>GitHub Advisory Database (GHSA)</strong></li>
  <li><strong>Alpine SecDB</strong></li>
  <li><strong>Amazon Linux ALAS</strong></li>
  <li><strong>Debian Security Tracker</strong></li>
  <li><strong>Red Hat RHSA/CVE</strong></li>
  <li><strong>Ubuntu CVE Tracker</strong></li>
  <li><strong>SUSE CVRF</strong></li>
  <li><strong>Oracle Linux ELSA</strong></li>
  <li><strong>Wolfi SecDB</strong></li>
  <li><strong>Chainguard SecDB</strong></li>
</ul>

<h3 id="grype-configuration-for-cicd">Grype Configuration for CI/CD</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
</pre></td><td class="rouge-code"><pre><span class="c1"># .grype.yaml - CI/CD vulnerability policy</span>
<span class="na">fail-on-severity</span><span class="pi">:</span> <span class="s">high</span>

<span class="c1"># Ignore specific CVEs (with justification)</span>
<span class="na">ignore</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">vulnerability</span><span class="pi">:</span> <span class="s">CVE-2023-XXXXX</span>
    <span class="na">reason</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Disputed,</span><span class="nv"> </span><span class="s">not</span><span class="nv"> </span><span class="s">exploitable</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">our</span><span class="nv"> </span><span class="s">configuration"</span>
  <span class="pi">-</span> <span class="na">vulnerability</span><span class="pi">:</span> <span class="s">GHSA-XXXX-XXXX-XXXX</span>
    <span class="na">reason</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Fixed</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">our</span><span class="nv"> </span><span class="s">forked</span><span class="nv"> </span><span class="s">dependency,</span><span class="nv"> </span><span class="s">awaiting</span><span class="nv"> </span><span class="s">upstream</span><span class="nv"> </span><span class="s">merge"</span>
  <span class="pi">-</span> <span class="na">package</span><span class="pi">:</span>
      <span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">test-utils"</span>
      <span class="na">type</span><span class="pi">:</span> <span class="s2">"</span><span class="s">npm"</span>
    <span class="na">reason</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Development</span><span class="nv"> </span><span class="s">dependency,</span><span class="nv"> </span><span class="s">not</span><span class="nv"> </span><span class="s">shipped</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">production</span><span class="nv"> </span><span class="s">image"</span>

<span class="c1"># Match configuration</span>
<span class="na">match</span><span class="pi">:</span>
  <span class="na">java</span><span class="pi">:</span>
    <span class="na">using-cpes</span><span class="pi">:</span> <span class="kc">true</span>
  <span class="na">python</span><span class="pi">:</span>
    <span class="na">using-cpes</span><span class="pi">:</span> <span class="kc">true</span>
  <span class="na">javascript</span><span class="pi">:</span>
    <span class="na">using-cpes</span><span class="pi">:</span> <span class="kc">false</span>  <span class="c1"># Reduce false positives for npm</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="interpreting-grype-output">Interpreting Grype Output</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre>NAME              INSTALLED  FIXED-IN  TYPE  VULNERABILITY   SEVERITY
lodash            4.17.20    4.17.21   npm   CVE-2021-23337  Critical
express           4.17.1     4.17.3    npm   CVE-2022-24999  High
openssl           1.1.1k     1.1.1l    deb   CVE-2021-3711   High
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Each finding includes the package name, installed version, the version that fixes the vulnerability, the package type, CVE identifier, and severity rating. The <code class="language-plaintext highlighter-rouge">--fail-on</code> flag lets you set a severity threshold that causes the scan to return a non-zero exit code, which breaks your CI/CD pipeline on policy violations.</p>

<h2 id="complete-codepipeline-integration">Complete CodePipeline Integration</h2>

<p>Here is the complete infrastructure to build an automated SBOM pipeline on AWS. We will use CodePipeline to orchestrate the workflow: build the application, generate the SBOM with Syft, scan for vulnerabilities with Grype, sign the artifact with Cosign, push to ECR, and store the SBOM in S3.</p>

<h3 id="codebuild-buildspecyml">CodeBuild buildspec.yml</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
</pre></td><td class="rouge-code"><pre><span class="c1"># buildspec.yml - SBOM generation and vulnerability scanning</span>
<span class="na">version</span><span class="pi">:</span> <span class="m">0.2</span>

<span class="na">env</span><span class="pi">:</span>
  <span class="na">variables</span><span class="pi">:</span>
    <span class="na">APP_NAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">my-application"</span>
    <span class="na">ECR_REPO</span><span class="pi">:</span> <span class="s2">"</span><span class="s">123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app"</span>
    <span class="na">SBOM_BUCKET</span><span class="pi">:</span> <span class="s2">"</span><span class="s">my-sbom-storage-bucket"</span>
    <span class="na">SEVERITY_THRESHOLD</span><span class="pi">:</span> <span class="s2">"</span><span class="s">high"</span>
  <span class="na">parameter-store</span><span class="pi">:</span>
    <span class="na">COSIGN_KEY</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/my-app/prod/cosign-private-key"</span>

<span class="na">phases</span><span class="pi">:</span>
  <span class="na">install</span><span class="pi">:</span>
    <span class="na">runtime-versions</span><span class="pi">:</span>
      <span class="na">docker</span><span class="pi">:</span> <span class="m">20</span>
    <span class="na">commands</span><span class="pi">:</span>
      <span class="c1"># Install Syft</span>
      <span class="pi">-</span> <span class="s">curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin</span>
      <span class="c1"># Install Grype</span>
      <span class="pi">-</span> <span class="s">curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin</span>
      <span class="c1"># Install Cosign</span>
      <span class="pi">-</span> <span class="s">curl -sSfL https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64 -o /usr/local/bin/cosign</span>
      <span class="pi">-</span> <span class="s">chmod +x /usr/local/bin/cosign</span>
      <span class="c1"># Update Grype vulnerability database</span>
      <span class="pi">-</span> <span class="s">grype db update</span>

  <span class="na">pre_build</span><span class="pi">:</span>
    <span class="na">commands</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">echo "Logging in to Amazon ECR..."</span>
      <span class="pi">-</span> <span class="s">aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin $ECR_REPO</span>
      <span class="pi">-</span> <span class="s">COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)</span>
      <span class="pi">-</span> <span class="s">IMAGE_TAG="${COMMIT_HASH:-latest}"</span>
      <span class="pi">-</span> <span class="s">TIMESTAMP=$(date +%Y%m%dT%H%M%S)</span>

  <span class="na">build</span><span class="pi">:</span>
    <span class="na">commands</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">echo "Building Docker image..."</span>
      <span class="pi">-</span> <span class="s">docker build -t $APP_NAME:$IMAGE_TAG .</span>
      <span class="pi">-</span> <span class="s">docker tag $APP_NAME:$IMAGE_TAG $ECR_REPO:$IMAGE_TAG</span>
      <span class="pi">-</span> <span class="s">docker tag $APP_NAME:$IMAGE_TAG $ECR_REPO:latest</span>

  <span class="na">post_build</span><span class="pi">:</span>
    <span class="na">commands</span><span class="pi">:</span>
      <span class="c1"># --- SBOM Generation ---</span>
      <span class="pi">-</span> <span class="s">echo "Generating SBOM with Syft..."</span>
      <span class="pi">-</span> <span class="s">syft packages $APP_NAME:$IMAGE_TAG</span>
          <span class="s">-o cyclonedx-json=sbom-cyclonedx.json</span>
          <span class="s">-o spdx-json=sbom-spdx.json</span>
          <span class="s">-o table</span>

      <span class="c1"># --- Vulnerability Scanning ---</span>
      <span class="pi">-</span> <span class="s">echo "Scanning SBOM for vulnerabilities with Grype..."</span>
      <span class="pi">-</span> <span class="s">grype sbom:sbom-cyclonedx.json</span>
          <span class="s">-o json --file vuln-report.json</span>
      <span class="pi">-</span> <span class="s">grype sbom:sbom-cyclonedx.json</span>
          <span class="s">-o table</span>
      <span class="pi">-</span> <span class="s">echo "Enforcing severity policy..."</span>
      <span class="pi">-</span> <span class="s">grype sbom:sbom-cyclonedx.json</span>
          <span class="s">--fail-on $SEVERITY_THRESHOLD</span>
          <span class="s">|| { echo "VULNERABILITY POLICY VIOLATION - blocking deployment"; exit 1; }</span>

      <span class="c1"># --- Push Image ---</span>
      <span class="pi">-</span> <span class="s">echo "Pushing image to ECR..."</span>
      <span class="pi">-</span> <span class="s">docker push $ECR_REPO:$IMAGE_TAG</span>
      <span class="pi">-</span> <span class="s">docker push $ECR_REPO:latest</span>

      <span class="c1"># --- Sign Image and SBOM ---</span>
      <span class="pi">-</span> <span class="s">echo "Signing container image with Cosign..."</span>
      <span class="pi">-</span> <span class="s">cosign sign --key env://COSIGN_KEY $ECR_REPO:$IMAGE_TAG</span>
      <span class="pi">-</span> <span class="s">echo "Attaching SBOM to image..."</span>
      <span class="pi">-</span> <span class="s">cosign attach sbom --sbom sbom-cyclonedx.json $ECR_REPO:$IMAGE_TAG</span>

      <span class="c1"># --- Store SBOMs in S3 ---</span>
      <span class="pi">-</span> <span class="s">echo "Uploading SBOMs to S3..."</span>
      <span class="pi">-</span> <span class="s">aws s3 cp sbom-cyclonedx.json</span>
          <span class="s">s3://$SBOM_BUCKET/$APP_NAME/$TIMESTAMP/sbom-cyclonedx.json</span>
      <span class="pi">-</span> <span class="s">aws s3 cp sbom-spdx.json</span>
          <span class="s">s3://$SBOM_BUCKET/$APP_NAME/$TIMESTAMP/sbom-spdx.json</span>
      <span class="pi">-</span> <span class="s">aws s3 cp vuln-report.json</span>
          <span class="s">s3://$SBOM_BUCKET/$APP_NAME/$TIMESTAMP/vuln-report.json</span>

      <span class="c1"># --- Summary ---</span>
      <span class="pi">-</span> <span class="s">echo "Pipeline complete."</span>
      <span class="pi">-</span> <span class="s">echo "Image</span><span class="err">:</span>  <span class="s">$ECR_REPO:$IMAGE_TAG"</span>
      <span class="pi">-</span> <span class="s">echo "SBOMs</span><span class="err">:</span>  <span class="s">s3://$SBOM_BUCKET/$APP_NAME/$TIMESTAMP/"</span>
      <span class="pi">-</span> <span class="s">VULN_CRITICAL=$(cat vuln-report.json | python3 -c "import sys,json; d=json.load(sys.stdin); print(len([m for m in d.get('matches',[]) if m.get('vulnerability',{}).get('severity')=='Critical']))")</span>
      <span class="pi">-</span> <span class="s">VULN_HIGH=$(cat vuln-report.json | python3 -c "import sys,json; d=json.load(sys.stdin); print(len([m for m in d.get('matches',[]) if m.get('vulnerability',{}).get('severity')=='High']))")</span>
      <span class="pi">-</span> <span class="s">echo "Vulnerabilities - Critical</span><span class="err">:</span> <span class="s">$VULN_CRITICAL, High</span><span class="err">:</span> <span class="s">$VULN_HIGH"</span>

<span class="na">artifacts</span><span class="pi">:</span>
  <span class="na">files</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">sbom-cyclonedx.json</span>
    <span class="pi">-</span> <span class="s">sbom-spdx.json</span>
    <span class="pi">-</span> <span class="s">vuln-report.json</span>
  <span class="na">discard-paths</span><span class="pi">:</span> <span class="s">yes</span>

<span class="na">reports</span><span class="pi">:</span>
  <span class="na">vulnerability-report</span><span class="pi">:</span>
    <span class="na">files</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">vuln-report.json</span>
    <span class="na">file-format</span><span class="pi">:</span> <span class="s">GENERICJSON</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="terraform-for-codepipeline-with-security-stages">Terraform for CodePipeline with Security Stages</h3>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
</pre></td><td class="rouge-code"><pre><span class="c1"># main.tf - CodePipeline with SBOM security stages</span>

<span class="nx">terraform</span> <span class="p">{</span>
  <span class="nx">required_version</span> <span class="o">=</span> <span class="s2">"&gt;= 1.5"</span>
  <span class="nx">required_providers</span> <span class="p">{</span>
    <span class="nx">aws</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">source</span>  <span class="o">=</span> <span class="s2">"hashicorp/aws"</span>
      <span class="nx">version</span> <span class="o">=</span> <span class="s2">"~&gt; 5.0"</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">backend</span> <span class="s2">"s3"</span> <span class="p">{</span>
    <span class="nx">bucket</span> <span class="o">=</span> <span class="s2">"skynet-tf-state-prod"</span>
    <span class="nx">key</span>    <span class="o">=</span> <span class="s2">"sbom-pipeline/terraform.tfstate"</span>
    <span class="nx">region</span> <span class="o">=</span> <span class="s2">"us-east-1"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">provider</span> <span class="s2">"aws"</span> <span class="p">{</span>
  <span class="nx">region</span> <span class="o">=</span> <span class="s2">"us-east-1"</span>

  <span class="nx">default_tags</span> <span class="p">{</span>
    <span class="nx">tags</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">Customer</span>    <span class="o">=</span> <span class="s2">"RedTeam"</span>
      <span class="nx">Application</span> <span class="o">=</span> <span class="s2">"SBOM-Pipeline"</span>
      <span class="nx">Environment</span> <span class="o">=</span> <span class="s2">"prod"</span>
      <span class="nx">Owner</span>       <span class="o">=</span> <span class="s2">"jon"</span>
      <span class="nx">Costcenter</span>  <span class="o">=</span> <span class="s2">"security"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># --- S3 Bucket for SBOM Storage ---</span>

<span class="nx">resource</span> <span class="s2">"aws_s3_bucket"</span> <span class="s2">"sbom_storage"</span> <span class="p">{</span>
  <span class="nx">bucket</span> <span class="o">=</span> <span class="s2">"redteam-sbom-storage-prod"</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_s3_bucket_versioning"</span> <span class="s2">"sbom_storage"</span> <span class="p">{</span>
  <span class="nx">bucket</span> <span class="o">=</span> <span class="nx">aws_s3_bucket</span><span class="p">.</span><span class="nx">sbom_storage</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">versioning_configuration</span> <span class="p">{</span>
    <span class="nx">status</span> <span class="o">=</span> <span class="s2">"Enabled"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_s3_bucket_lifecycle_configuration"</span> <span class="s2">"sbom_storage"</span> <span class="p">{</span>
  <span class="nx">bucket</span> <span class="o">=</span> <span class="nx">aws_s3_bucket</span><span class="p">.</span><span class="nx">sbom_storage</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">rule</span> <span class="p">{</span>
    <span class="nx">id</span>     <span class="o">=</span> <span class="s2">"archive-old-sboms"</span>
    <span class="nx">status</span> <span class="o">=</span> <span class="s2">"Enabled"</span>

    <span class="nx">transition</span> <span class="p">{</span>
      <span class="nx">days</span>          <span class="o">=</span> <span class="mi">90</span>
      <span class="nx">storage_class</span> <span class="o">=</span> <span class="s2">"STANDARD_IA"</span>
    <span class="p">}</span>

    <span class="nx">transition</span> <span class="p">{</span>
      <span class="nx">days</span>          <span class="o">=</span> <span class="mi">365</span>
      <span class="nx">storage_class</span> <span class="o">=</span> <span class="s2">"GLACIER"</span>
    <span class="p">}</span>

    <span class="nx">expiration</span> <span class="p">{</span>
      <span class="nx">days</span> <span class="o">=</span> <span class="mi">2555</span>  <span class="c1"># 7 years for compliance retention</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_s3_bucket_server_side_encryption_configuration"</span> <span class="s2">"sbom_storage"</span> <span class="p">{</span>
  <span class="nx">bucket</span> <span class="o">=</span> <span class="nx">aws_s3_bucket</span><span class="p">.</span><span class="nx">sbom_storage</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">rule</span> <span class="p">{</span>
    <span class="nx">apply_server_side_encryption_by_default</span> <span class="p">{</span>
      <span class="nx">sse_algorithm</span> <span class="o">=</span> <span class="s2">"aws:kms"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># --- ECR Repository ---</span>

<span class="nx">resource</span> <span class="s2">"aws_ecr_repository"</span> <span class="s2">"app"</span> <span class="p">{</span>
  <span class="nx">name</span>                 <span class="o">=</span> <span class="s2">"my-application"</span>
  <span class="nx">image_tag_mutability</span> <span class="o">=</span> <span class="s2">"IMMUTABLE"</span>

  <span class="nx">image_scanning_configuration</span> <span class="p">{</span>
    <span class="nx">scan_on_push</span> <span class="o">=</span> <span class="kc">true</span>
  <span class="p">}</span>

  <span class="nx">encryption_configuration</span> <span class="p">{</span>
    <span class="nx">encryption_type</span> <span class="o">=</span> <span class="s2">"KMS"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># --- CodeBuild Project ---</span>

<span class="nx">resource</span> <span class="s2">"aws_codebuild_project"</span> <span class="s2">"sbom_build"</span> <span class="p">{</span>
  <span class="nx">name</span>          <span class="o">=</span> <span class="s2">"sbom-security-build"</span>
  <span class="nx">description</span>   <span class="o">=</span> <span class="s2">"Build, generate SBOM, scan vulnerabilities, sign artifacts"</span>
  <span class="nx">build_timeout</span> <span class="o">=</span> <span class="mi">30</span>
  <span class="nx">service_role</span>  <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">codebuild_role</span><span class="p">.</span><span class="nx">arn</span>

  <span class="nx">artifacts</span> <span class="p">{</span>
    <span class="nx">type</span> <span class="o">=</span> <span class="s2">"CODEPIPELINE"</span>
  <span class="p">}</span>

  <span class="nx">environment</span> <span class="p">{</span>
    <span class="nx">compute_type</span>                <span class="o">=</span> <span class="s2">"BUILD_GENERAL1_MEDIUM"</span>
    <span class="nx">image</span>                       <span class="o">=</span> <span class="s2">"aws/codebuild/amazonlinux2-x86_64-standard:5.0"</span>
    <span class="nx">type</span>                        <span class="o">=</span> <span class="s2">"LINUX_CONTAINER"</span>
    <span class="nx">privileged_mode</span>             <span class="o">=</span> <span class="kc">true</span>
    <span class="nx">image_pull_credentials_type</span> <span class="o">=</span> <span class="s2">"CODEBUILD"</span>

    <span class="nx">environment_variable</span> <span class="p">{</span>
      <span class="nx">name</span>  <span class="o">=</span> <span class="s2">"ECR_REPO"</span>
      <span class="nx">value</span> <span class="o">=</span> <span class="nx">aws_ecr_repository</span><span class="p">.</span><span class="nx">app</span><span class="p">.</span><span class="nx">repository_url</span>
    <span class="p">}</span>

    <span class="nx">environment_variable</span> <span class="p">{</span>
      <span class="nx">name</span>  <span class="o">=</span> <span class="s2">"SBOM_BUCKET"</span>
      <span class="nx">value</span> <span class="o">=</span> <span class="nx">aws_s3_bucket</span><span class="p">.</span><span class="nx">sbom_storage</span><span class="p">.</span><span class="nx">id</span>
    <span class="p">}</span>

    <span class="nx">environment_variable</span> <span class="p">{</span>
      <span class="nx">name</span>  <span class="o">=</span> <span class="s2">"SEVERITY_THRESHOLD"</span>
      <span class="nx">value</span> <span class="o">=</span> <span class="s2">"high"</span>
    <span class="p">}</span>

    <span class="nx">environment_variable</span> <span class="p">{</span>
      <span class="nx">name</span>  <span class="o">=</span> <span class="s2">"COSIGN_KEY"</span>
      <span class="nx">value</span> <span class="o">=</span> <span class="s2">"/my-app/prod/cosign-private-key"</span>
      <span class="nx">type</span>  <span class="o">=</span> <span class="s2">"PARAMETER_STORE"</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">source</span> <span class="p">{</span>
    <span class="nx">type</span>      <span class="o">=</span> <span class="s2">"CODEPIPELINE"</span>
    <span class="nx">buildspec</span> <span class="o">=</span> <span class="s2">"buildspec.yml"</span>
  <span class="p">}</span>

  <span class="nx">logs_config</span> <span class="p">{</span>
    <span class="nx">cloudwatch_logs</span> <span class="p">{</span>
      <span class="nx">group_name</span>  <span class="o">=</span> <span class="s2">"/codebuild/sbom-security-build"</span>
      <span class="nx">stream_name</span> <span class="o">=</span> <span class="s2">"build-log"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># --- CodePipeline ---</span>

<span class="nx">resource</span> <span class="s2">"aws_codepipeline"</span> <span class="s2">"sbom_pipeline"</span> <span class="p">{</span>
  <span class="nx">name</span>     <span class="o">=</span> <span class="s2">"sbom-security-pipeline"</span>
  <span class="nx">role_arn</span> <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">codepipeline_role</span><span class="p">.</span><span class="nx">arn</span>

  <span class="nx">artifact_store</span> <span class="p">{</span>
    <span class="nx">location</span> <span class="o">=</span> <span class="nx">aws_s3_bucket</span><span class="p">.</span><span class="nx">pipeline_artifacts</span><span class="p">.</span><span class="nx">id</span>
    <span class="nx">type</span>     <span class="o">=</span> <span class="s2">"S3"</span>
  <span class="p">}</span>

  <span class="nx">stage</span> <span class="p">{</span>
    <span class="nx">name</span> <span class="o">=</span> <span class="s2">"Source"</span>

    <span class="nx">action</span> <span class="p">{</span>
      <span class="nx">name</span>             <span class="o">=</span> <span class="s2">"Source"</span>
      <span class="nx">category</span>         <span class="o">=</span> <span class="s2">"Source"</span>
      <span class="nx">owner</span>            <span class="o">=</span> <span class="s2">"AWS"</span>
      <span class="nx">provider</span>         <span class="o">=</span> <span class="s2">"CodeCommit"</span>
      <span class="nx">version</span>          <span class="o">=</span> <span class="s2">"1"</span>
      <span class="nx">output_artifacts</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"source_output"</span><span class="p">]</span>

      <span class="nx">configuration</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nx">RepositoryName</span> <span class="o">=</span> <span class="s2">"my-application"</span>
        <span class="nx">BranchName</span>     <span class="o">=</span> <span class="s2">"main"</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">stage</span> <span class="p">{</span>
    <span class="nx">name</span> <span class="o">=</span> <span class="s2">"Build-SBOM-Scan-Sign"</span>

    <span class="nx">action</span> <span class="p">{</span>
      <span class="nx">name</span>             <span class="o">=</span> <span class="s2">"BuildAndSecurityScan"</span>
      <span class="nx">category</span>         <span class="o">=</span> <span class="s2">"Build"</span>
      <span class="nx">owner</span>            <span class="o">=</span> <span class="s2">"AWS"</span>
      <span class="nx">provider</span>         <span class="o">=</span> <span class="s2">"CodeBuild"</span>
      <span class="nx">input_artifacts</span>  <span class="o">=</span> <span class="p">[</span><span class="s2">"source_output"</span><span class="p">]</span>
      <span class="nx">output_artifacts</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"build_output"</span><span class="p">]</span>
      <span class="nx">version</span>          <span class="o">=</span> <span class="s2">"1"</span>

      <span class="nx">configuration</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nx">ProjectName</span> <span class="o">=</span> <span class="nx">aws_codebuild_project</span><span class="p">.</span><span class="nx">sbom_build</span><span class="p">.</span><span class="nx">name</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">stage</span> <span class="p">{</span>
    <span class="nx">name</span> <span class="o">=</span> <span class="s2">"Approval"</span>

    <span class="nx">action</span> <span class="p">{</span>
      <span class="nx">name</span>     <span class="o">=</span> <span class="s2">"SecurityApproval"</span>
      <span class="nx">category</span> <span class="o">=</span> <span class="s2">"Approval"</span>
      <span class="nx">owner</span>    <span class="o">=</span> <span class="s2">"AWS"</span>
      <span class="nx">provider</span> <span class="o">=</span> <span class="s2">"Manual"</span>
      <span class="nx">version</span>  <span class="o">=</span> <span class="s2">"1"</span>

      <span class="nx">configuration</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nx">CustomData</span> <span class="o">=</span> <span class="s2">"Review SBOM and vulnerability report before deployment"</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">stage</span> <span class="p">{</span>
    <span class="nx">name</span> <span class="o">=</span> <span class="s2">"Deploy"</span>

    <span class="nx">action</span> <span class="p">{</span>
      <span class="nx">name</span>            <span class="o">=</span> <span class="s2">"Deploy"</span>
      <span class="nx">category</span>        <span class="o">=</span> <span class="s2">"Deploy"</span>
      <span class="nx">owner</span>           <span class="o">=</span> <span class="s2">"AWS"</span>
      <span class="nx">provider</span>        <span class="o">=</span> <span class="s2">"ECS"</span>
      <span class="nx">input_artifacts</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"build_output"</span><span class="p">]</span>
      <span class="nx">version</span>         <span class="o">=</span> <span class="s2">"1"</span>

      <span class="nx">configuration</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nx">ClusterName</span> <span class="o">=</span> <span class="s2">"production-cluster"</span>
        <span class="nx">ServiceName</span> <span class="o">=</span> <span class="s2">"my-application"</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># --- S3 Bucket for Pipeline Artifacts ---</span>

<span class="nx">resource</span> <span class="s2">"aws_s3_bucket"</span> <span class="s2">"pipeline_artifacts"</span> <span class="p">{</span>
  <span class="nx">bucket</span> <span class="o">=</span> <span class="s2">"redteam-pipeline-artifacts-prod"</span>
<span class="p">}</span>

<span class="c1"># --- IAM Role for CodeBuild ---</span>

<span class="nx">resource</span> <span class="s2">"aws_iam_role"</span> <span class="s2">"codebuild_role"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"sbom-codebuild-role"</span>

  <span class="nx">assume_role_policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">Version</span> <span class="o">=</span> <span class="s2">"2012-10-17"</span>
    <span class="nx">Statement</span> <span class="o">=</span> <span class="p">[</span>
      <span class="p">{</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="s2">"sts:AssumeRole"</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Principal</span> <span class="o">=</span> <span class="p">{</span>
          <span class="nx">Service</span> <span class="o">=</span> <span class="s2">"codebuild.amazonaws.com"</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">]</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_iam_role_policy"</span> <span class="s2">"codebuild_policy"</span> <span class="p">{</span>
  <span class="nx">role</span> <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">codebuild_role</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">Version</span> <span class="o">=</span> <span class="s2">"2012-10-17"</span>
    <span class="nx">Statement</span> <span class="o">=</span> <span class="p">[</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"ecr:GetAuthorizationToken"</span><span class="p">,</span>
          <span class="s2">"ecr:BatchCheckLayerAvailability"</span><span class="p">,</span>
          <span class="s2">"ecr:GetDownloadUrlForLayer"</span><span class="p">,</span>
          <span class="s2">"ecr:BatchGetImage"</span><span class="p">,</span>
          <span class="s2">"ecr:PutImage"</span><span class="p">,</span>
          <span class="s2">"ecr:InitiateLayerUpload"</span><span class="p">,</span>
          <span class="s2">"ecr:UploadLayerPart"</span><span class="p">,</span>
          <span class="s2">"ecr:CompleteLayerUpload"</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="s2">"*"</span>
      <span class="p">},</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"s3:PutObject"</span><span class="p">,</span>
          <span class="s2">"s3:GetObject"</span><span class="p">,</span>
          <span class="s2">"s3:GetBucketLocation"</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"${aws_s3_bucket.sbom_storage.arn}"</span><span class="p">,</span>
          <span class="s2">"${aws_s3_bucket.sbom_storage.arn}/*"</span><span class="p">,</span>
          <span class="s2">"${aws_s3_bucket.pipeline_artifacts.arn}"</span><span class="p">,</span>
          <span class="s2">"${aws_s3_bucket.pipeline_artifacts.arn}/*"</span>
        <span class="p">]</span>
      <span class="p">},</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"ssm:GetParameter"</span><span class="p">,</span>
          <span class="s2">"ssm:GetParameters"</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="s2">"arn:aws:ssm:us-east-1:*:parameter/my-app/*"</span>
      <span class="p">},</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"logs:CreateLogGroup"</span><span class="p">,</span>
          <span class="s2">"logs:CreateLogStream"</span><span class="p">,</span>
          <span class="s2">"logs:PutLogEvents"</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="s2">"*"</span>
      <span class="p">}</span>
    <span class="p">]</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="c1"># --- IAM Role for CodePipeline ---</span>

<span class="nx">resource</span> <span class="s2">"aws_iam_role"</span> <span class="s2">"codepipeline_role"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="s2">"sbom-codepipeline-role"</span>

  <span class="nx">assume_role_policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">Version</span> <span class="o">=</span> <span class="s2">"2012-10-17"</span>
    <span class="nx">Statement</span> <span class="o">=</span> <span class="p">[</span>
      <span class="p">{</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="s2">"sts:AssumeRole"</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Principal</span> <span class="o">=</span> <span class="p">{</span>
          <span class="nx">Service</span> <span class="o">=</span> <span class="s2">"codepipeline.amazonaws.com"</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">]</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_iam_role_policy"</span> <span class="s2">"codepipeline_policy"</span> <span class="p">{</span>
  <span class="nx">role</span> <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">codepipeline_role</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">policy</span> <span class="o">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">Version</span> <span class="o">=</span> <span class="s2">"2012-10-17"</span>
    <span class="nx">Statement</span> <span class="o">=</span> <span class="p">[</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"s3:GetObject"</span><span class="p">,</span>
          <span class="s2">"s3:PutObject"</span><span class="p">,</span>
          <span class="s2">"s3:GetBucketVersioning"</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"${aws_s3_bucket.pipeline_artifacts.arn}/*"</span>
        <span class="p">]</span>
      <span class="p">},</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"codebuild:BatchGetBuilds"</span><span class="p">,</span>
          <span class="s2">"codebuild:StartBuild"</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="nx">aws_codebuild_project</span><span class="p">.</span><span class="nx">sbom_build</span><span class="p">.</span><span class="nx">arn</span>
      <span class="p">},</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"codecommit:GetBranch"</span><span class="p">,</span>
          <span class="s2">"codecommit:GetCommit"</span><span class="p">,</span>
          <span class="s2">"codecommit:UploadArchive"</span><span class="p">,</span>
          <span class="s2">"codecommit:GetUploadArchiveStatus"</span><span class="p">,</span>
          <span class="s2">"codecommit:CancelUploadArchive"</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="s2">"*"</span>
      <span class="p">},</span>
      <span class="p">{</span>
        <span class="nx">Effect</span> <span class="o">=</span> <span class="s2">"Allow"</span>
        <span class="nx">Action</span> <span class="o">=</span> <span class="p">[</span>
          <span class="s2">"ecs:DescribeServices"</span><span class="p">,</span>
          <span class="s2">"ecs:DescribeTaskDefinition"</span><span class="p">,</span>
          <span class="s2">"ecs:DescribeTasks"</span><span class="p">,</span>
          <span class="s2">"ecs:ListTasks"</span><span class="p">,</span>
          <span class="s2">"ecs:RegisterTaskDefinition"</span><span class="p">,</span>
          <span class="s2">"ecs:UpdateService"</span>
        <span class="p">]</span>
        <span class="nx">Resource</span> <span class="o">=</span> <span class="s2">"*"</span>
      <span class="p">}</span>
    <span class="p">]</span>
  <span class="p">})</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="cosign-signing-sboms-and-container-images">Cosign: Signing SBOMs and Container Images</h2>

<p><a href="https://github.com/sigstore/cosign">Cosign</a> from the Sigstore project provides cryptographic signing for container images and SBOMs. Signing establishes <strong>provenance</strong> — proof that your artifacts were produced by your pipeline and have not been tampered with.</p>

<h3 id="generating-a-cosign-key-pair">Generating a Cosign Key Pair</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="c"># Generate a key pair</span>
cosign generate-key-pair

<span class="c"># Store the private key in SSM Parameter Store</span>
aws ssm put-parameter <span class="se">\</span>
  <span class="nt">--name</span> <span class="s2">"/my-app/prod/cosign-private-key"</span> <span class="se">\</span>
  <span class="nt">--value</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">cat </span>cosign.key<span class="si">)</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nt">--type</span> <span class="s2">"SecureString"</span> <span class="se">\</span>
  <span class="nt">--tier</span> <span class="s2">"Standard"</span>

<span class="c"># Store the public key in a known location for verification</span>
aws s3 <span class="nb">cp </span>cosign.pub s3://redteam-sbom-storage-prod/keys/cosign.pub
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="signing-workflow">Signing Workflow</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre></td><td class="rouge-code"><pre><span class="c"># Sign a container image</span>
cosign sign <span class="nt">--key</span> cosign.key <span class="nv">$ECR_REPO</span>:<span class="nv">$IMAGE_TAG</span>

<span class="c"># Attach the SBOM as a cosign attestation</span>
cosign attest <span class="nt">--key</span> cosign.key <span class="se">\</span>
  <span class="nt">--predicate</span> sbom-cyclonedx.json <span class="se">\</span>
  <span class="nt">--type</span> cyclonedx <span class="se">\</span>
  <span class="nv">$ECR_REPO</span>:<span class="nv">$IMAGE_TAG</span>

<span class="c"># Verify the signature (consumer side)</span>
cosign verify <span class="nt">--key</span> cosign.pub <span class="nv">$ECR_REPO</span>:<span class="nv">$IMAGE_TAG</span>

<span class="c"># Verify the SBOM attestation</span>
cosign verify-attestation <span class="nt">--key</span> cosign.pub <span class="se">\</span>
  <span class="nt">--type</span> cyclonedx <span class="se">\</span>
  <span class="nv">$ECR_REPO</span>:<span class="nv">$IMAGE_TAG</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="keyless-signing-with-sigstore">Keyless Signing with Sigstore</h3>

<p>For open-source projects or environments where key management is a burden, Cosign supports keyless signing via the Sigstore transparency log (Rekor):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="c"># Keyless sign (uses OIDC identity)</span>
<span class="nv">COSIGN_EXPERIMENTAL</span><span class="o">=</span>1 cosign sign <span class="nv">$ECR_REPO</span>:<span class="nv">$IMAGE_TAG</span>

<span class="c"># Keyless verify (checks Rekor transparency log)</span>
<span class="nv">COSIGN_EXPERIMENTAL</span><span class="o">=</span>1 cosign verify <span class="nv">$ECR_REPO</span>:<span class="nv">$IMAGE_TAG</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This ties the signature to your CI/CD identity (e.g., an AWS IAM role or GitHub Actions OIDC token) without managing private keys.</p>

<h2 id="comparison-aws-inspector-vs-syftgrype">Comparison: AWS Inspector vs Syft/Grype</h2>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Capability</th>
      <th style="text-align: left">AWS Inspector</th>
      <th style="text-align: left">Syft + Grype</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>SBOM formats</strong></td>
      <td style="text-align: left">CycloneDX 1.4, SPDX 2.3</td>
      <td style="text-align: left">CycloneDX 1.6, SPDX 3.0, plus 20+ output formats</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Scan targets</strong></td>
      <td style="text-align: left">EC2, Lambda, ECR images</td>
      <td style="text-align: left">Any container image, filesystem, archive, binary, source directory</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Vulnerability DBs</strong></td>
      <td style="text-align: left">AWS-curated database</td>
      <td style="text-align: left">NVD, GHSA, Alpine, Debian, RHEL, Ubuntu, SUSE, Oracle, Amazon, Wolfi, Chainguard</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>CI/CD integration</strong></td>
      <td style="text-align: left">Via API/CLI</td>
      <td style="text-align: left">Native CLI, exit codes, SARIF output, GitHub Actions</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Artifact signing</strong></td>
      <td style="text-align: left">Not included</td>
      <td style="text-align: left">Cosign integration</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Cost</strong></td>
      <td style="text-align: left">Per-assessment pricing</td>
      <td style="text-align: left">Free (open source)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Portability</strong></td>
      <td style="text-align: left">AWS only</td>
      <td style="text-align: left">Any environment (local, GitHub Actions, GitLab, Jenkins, AWS, GCP, Azure)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Scan depth</strong></td>
      <td style="text-align: left">Standard package detection</td>
      <td style="text-align: left">All image layers, binary analysis, compiled dependency extraction</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>VEX support</strong></td>
      <td style="text-align: left">Limited</td>
      <td style="text-align: left">Native CycloneDX VEX, Grype ignore rules</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Offline mode</strong></td>
      <td style="text-align: left">No</td>
      <td style="text-align: left">Yes (cached vulnerability database)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Customization</strong></td>
      <td style="text-align: left">Limited</td>
      <td style="text-align: left">Full configuration via YAML, custom matchers, exclusion rules</td>
    </tr>
  </tbody>
</table>

<p><strong>Verdict</strong>: Use AWS Inspector as your always-on baseline for monitored resources. Use Syft and Grype as your CI/CD pipeline tool for deep scanning, multi-format output, signing, and cross-cloud portability. They are complementary, not competing.</p>

<h2 id="automated-compliance-reporting-with-s3-and-athena">Automated Compliance Reporting with S3 and Athena</h2>

<p>SBOMs stored in S3 become a queryable compliance database when combined with Amazon Athena. This lets you answer questions like “which production images contain log4j?” across your entire fleet.</p>

<h3 id="python-script-for-sbom-analysis-and-reporting">Python Script for SBOM Analysis and Reporting</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
</pre></td><td class="rouge-code"><pre><span class="c1">#!/usr/bin/env python3
</span><span class="sh">"""</span><span class="s">
sbom_compliance_reporter.py

Analyzes SBOMs stored in S3 and generates compliance reports.
Queries vulnerability data across all applications and versions.
</span><span class="sh">"""</span>

<span class="kn">import</span> <span class="n">boto3</span>
<span class="kn">import</span> <span class="n">json</span>
<span class="kn">import</span> <span class="n">csv</span>
<span class="kn">import</span> <span class="n">io</span>
<span class="kn">from</span> <span class="n">datetime</span> <span class="kn">import</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">timedelta</span>
<span class="kn">from</span> <span class="n">typing</span> <span class="kn">import</span> <span class="n">Dict</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span>
<span class="kn">from</span> <span class="n">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>


<span class="nd">@dataclass</span>
<span class="k">class</span> <span class="nc">VulnerabilityFinding</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Represents a single vulnerability finding from Grype output.</span><span class="sh">"""</span>
    <span class="n">app_name</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">image_tag</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">scan_date</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">package_name</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">installed_version</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">fixed_version</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">vulnerability_id</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">severity</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">description</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="sh">""</span>


<span class="nd">@dataclass</span>
<span class="k">class</span> <span class="nc">ComplianceReport</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Aggregated compliance report across all applications.</span><span class="sh">"""</span>
    <span class="n">generated_at</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="nf">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="k">lambda</span><span class="p">:</span> <span class="n">datetime</span><span class="p">.</span><span class="nf">utcnow</span><span class="p">().</span><span class="nf">isoformat</span><span class="p">())</span>
    <span class="n">total_applications</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">total_sboms</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">total_vulnerabilities</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">critical_count</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">high_count</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">medium_count</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">low_count</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">applications_with_critical</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="nf">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
    <span class="n">findings</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">VulnerabilityFinding</span><span class="p">]</span> <span class="o">=</span> <span class="nf">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>


<span class="k">class</span> <span class="nc">SBOMComplianceReporter</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">Generates compliance reports from SBOMs stored in S3.</span><span class="sh">"""</span>

    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">bucket_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">region</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="sh">"</span><span class="s">us-east-1</span><span class="sh">"</span><span class="p">):</span>
        <span class="n">self</span><span class="p">.</span><span class="n">s3</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="nf">client</span><span class="p">(</span><span class="sh">"</span><span class="s">s3</span><span class="sh">"</span><span class="p">,</span> <span class="n">region_name</span><span class="o">=</span><span class="n">region</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">bucket</span> <span class="o">=</span> <span class="n">bucket_name</span>

    <span class="k">def</span> <span class="nf">list_applications</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
        <span class="sh">"""</span><span class="s">List all applications with SBOMs in the bucket.</span><span class="sh">"""</span>
        <span class="n">response</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">s3</span><span class="p">.</span><span class="nf">list_objects_v2</span><span class="p">(</span>
            <span class="n">Bucket</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">bucket</span><span class="p">,</span>
            <span class="n">Delimiter</span><span class="o">=</span><span class="sh">"</span><span class="s">/</span><span class="sh">"</span>
        <span class="p">)</span>
        <span class="k">return</span> <span class="p">[</span>
            <span class="n">prefix</span><span class="p">[</span><span class="sh">"</span><span class="s">Prefix</span><span class="sh">"</span><span class="p">].</span><span class="nf">rstrip</span><span class="p">(</span><span class="sh">"</span><span class="s">/</span><span class="sh">"</span><span class="p">)</span>
            <span class="k">for</span> <span class="n">prefix</span> <span class="ow">in</span> <span class="n">response</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">CommonPrefixes</span><span class="sh">"</span><span class="p">,</span> <span class="p">[])</span>
        <span class="p">]</span>

    <span class="k">def</span> <span class="nf">get_latest_sbom</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">app_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
        <span class="sh">"""</span><span class="s">Retrieve the most recent SBOM for an application.</span><span class="sh">"""</span>
        <span class="n">response</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">s3</span><span class="p">.</span><span class="nf">list_objects_v2</span><span class="p">(</span>
            <span class="n">Bucket</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">bucket</span><span class="p">,</span>
            <span class="n">Prefix</span><span class="o">=</span><span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="n">app_name</span><span class="si">}</span><span class="s">/</span><span class="sh">"</span><span class="p">,</span>
            <span class="n">Delimiter</span><span class="o">=</span><span class="sh">"</span><span class="s">/</span><span class="sh">"</span>
        <span class="p">)</span>

        <span class="c1"># Get the latest timestamp directory
</span>        <span class="n">prefixes</span> <span class="o">=</span> <span class="nf">sorted</span><span class="p">(</span>
            <span class="p">[</span><span class="n">p</span><span class="p">[</span><span class="sh">"</span><span class="s">Prefix</span><span class="sh">"</span><span class="p">]</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">response</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">CommonPrefixes</span><span class="sh">"</span><span class="p">,</span> <span class="p">[])],</span>
            <span class="n">reverse</span><span class="o">=</span><span class="bp">True</span>
        <span class="p">)</span>

        <span class="k">if</span> <span class="ow">not</span> <span class="n">prefixes</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">None</span>

        <span class="n">latest_prefix</span> <span class="o">=</span> <span class="n">prefixes</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>

        <span class="c1"># Download the CycloneDX SBOM
</span>        <span class="k">try</span><span class="p">:</span>
            <span class="n">obj</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">s3</span><span class="p">.</span><span class="nf">get_object</span><span class="p">(</span>
                <span class="n">Bucket</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">bucket</span><span class="p">,</span>
                <span class="n">Key</span><span class="o">=</span><span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="n">latest_prefix</span><span class="si">}</span><span class="s">sbom-cyclonedx.json</span><span class="sh">"</span>
            <span class="p">)</span>
            <span class="k">return</span> <span class="n">json</span><span class="p">.</span><span class="nf">loads</span><span class="p">(</span><span class="n">obj</span><span class="p">[</span><span class="sh">"</span><span class="s">Body</span><span class="sh">"</span><span class="p">].</span><span class="nf">read</span><span class="p">().</span><span class="nf">decode</span><span class="p">(</span><span class="sh">"</span><span class="s">utf-8</span><span class="sh">"</span><span class="p">))</span>
        <span class="k">except</span> <span class="n">self</span><span class="p">.</span><span class="n">s3</span><span class="p">.</span><span class="n">exceptions</span><span class="p">.</span><span class="n">NoSuchKey</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">None</span>

    <span class="k">def</span> <span class="nf">get_latest_vuln_report</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">app_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
        <span class="sh">"""</span><span class="s">Retrieve the most recent vulnerability report.</span><span class="sh">"""</span>
        <span class="n">response</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">s3</span><span class="p">.</span><span class="nf">list_objects_v2</span><span class="p">(</span>
            <span class="n">Bucket</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">bucket</span><span class="p">,</span>
            <span class="n">Prefix</span><span class="o">=</span><span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="n">app_name</span><span class="si">}</span><span class="s">/</span><span class="sh">"</span><span class="p">,</span>
            <span class="n">Delimiter</span><span class="o">=</span><span class="sh">"</span><span class="s">/</span><span class="sh">"</span>
        <span class="p">)</span>

        <span class="n">prefixes</span> <span class="o">=</span> <span class="nf">sorted</span><span class="p">(</span>
            <span class="p">[</span><span class="n">p</span><span class="p">[</span><span class="sh">"</span><span class="s">Prefix</span><span class="sh">"</span><span class="p">]</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">response</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">CommonPrefixes</span><span class="sh">"</span><span class="p">,</span> <span class="p">[])],</span>
            <span class="n">reverse</span><span class="o">=</span><span class="bp">True</span>
        <span class="p">)</span>

        <span class="k">if</span> <span class="ow">not</span> <span class="n">prefixes</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">None</span>

        <span class="n">latest_prefix</span> <span class="o">=</span> <span class="n">prefixes</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>

        <span class="k">try</span><span class="p">:</span>
            <span class="n">obj</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">s3</span><span class="p">.</span><span class="nf">get_object</span><span class="p">(</span>
                <span class="n">Bucket</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">bucket</span><span class="p">,</span>
                <span class="n">Key</span><span class="o">=</span><span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="n">latest_prefix</span><span class="si">}</span><span class="s">vuln-report.json</span><span class="sh">"</span>
            <span class="p">)</span>
            <span class="k">return</span> <span class="n">json</span><span class="p">.</span><span class="nf">loads</span><span class="p">(</span><span class="n">obj</span><span class="p">[</span><span class="sh">"</span><span class="s">Body</span><span class="sh">"</span><span class="p">].</span><span class="nf">read</span><span class="p">().</span><span class="nf">decode</span><span class="p">(</span><span class="sh">"</span><span class="s">utf-8</span><span class="sh">"</span><span class="p">))</span>
        <span class="k">except</span> <span class="n">self</span><span class="p">.</span><span class="n">s3</span><span class="p">.</span><span class="n">exceptions</span><span class="p">.</span><span class="n">NoSuchKey</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">None</span>

    <span class="k">def</span> <span class="nf">generate_compliance_report</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ComplianceReport</span><span class="p">:</span>
        <span class="sh">"""</span><span class="s">Generate a full compliance report across all applications.</span><span class="sh">"""</span>
        <span class="n">report</span> <span class="o">=</span> <span class="nc">ComplianceReport</span><span class="p">()</span>
        <span class="n">applications</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">list_applications</span><span class="p">()</span>
        <span class="n">report</span><span class="p">.</span><span class="n">total_applications</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">applications</span><span class="p">)</span>

        <span class="k">for</span> <span class="n">app_name</span> <span class="ow">in</span> <span class="n">applications</span><span class="p">:</span>
            <span class="n">sbom</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">get_latest_sbom</span><span class="p">(</span><span class="n">app_name</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">sbom</span><span class="p">:</span>
                <span class="n">report</span><span class="p">.</span><span class="n">total_sboms</span> <span class="o">+=</span> <span class="mi">1</span>

            <span class="n">vuln_data</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">get_latest_vuln_report</span><span class="p">(</span><span class="n">app_name</span><span class="p">)</span>
            <span class="k">if</span> <span class="ow">not</span> <span class="n">vuln_data</span><span class="p">:</span>
                <span class="k">continue</span>

            <span class="n">matches</span> <span class="o">=</span> <span class="n">vuln_data</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">matches</span><span class="sh">"</span><span class="p">,</span> <span class="p">[])</span>
            <span class="n">has_critical</span> <span class="o">=</span> <span class="bp">False</span>

            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="n">matches</span><span class="p">:</span>
                <span class="n">vuln</span> <span class="o">=</span> <span class="k">match</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">vulnerability</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
                <span class="n">artifact</span> <span class="o">=</span> <span class="k">match</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">artifact</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
                <span class="n">severity</span> <span class="o">=</span> <span class="n">vuln</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">severity</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Unknown</span><span class="sh">"</span><span class="p">)</span>

                <span class="n">finding</span> <span class="o">=</span> <span class="nc">VulnerabilityFinding</span><span class="p">(</span>
                    <span class="n">app_name</span><span class="o">=</span><span class="n">app_name</span><span class="p">,</span>
                    <span class="n">image_tag</span><span class="o">=</span><span class="n">vuln_data</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">source</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
                        <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">target</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
                        <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">tags</span><span class="sh">"</span><span class="p">,</span> <span class="p">[</span><span class="sh">"</span><span class="s">unknown</span><span class="sh">"</span><span class="p">])[</span><span class="mi">0</span><span class="p">],</span>
                    <span class="n">scan_date</span><span class="o">=</span><span class="n">vuln_data</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">descriptor</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
                        <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">timestamp</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">),</span>
                    <span class="n">package_name</span><span class="o">=</span><span class="n">artifact</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">),</span>
                    <span class="n">installed_version</span><span class="o">=</span><span class="n">artifact</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">version</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">),</span>
                    <span class="n">fixed_version</span><span class="o">=</span><span class="n">vuln</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">fix</span><span class="sh">"</span><span class="p">,</span> <span class="p">{})</span>
                        <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">versions</span><span class="sh">"</span><span class="p">,</span> <span class="p">[</span><span class="sh">"</span><span class="s">N/A</span><span class="sh">"</span><span class="p">])[</span><span class="mi">0</span><span class="p">]</span>
                        <span class="k">if</span> <span class="n">vuln</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">fix</span><span class="sh">"</span><span class="p">,</span> <span class="p">{}).</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">versions</span><span class="sh">"</span><span class="p">)</span>
                        <span class="k">else</span> <span class="sh">"</span><span class="s">N/A</span><span class="sh">"</span><span class="p">,</span>
                    <span class="n">vulnerability_id</span><span class="o">=</span><span class="n">vuln</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">id</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">),</span>
                    <span class="n">severity</span><span class="o">=</span><span class="n">severity</span><span class="p">,</span>
                    <span class="n">description</span><span class="o">=</span><span class="n">vuln</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">description</span><span class="sh">"</span><span class="p">,</span> <span class="sh">""</span><span class="p">)[:</span><span class="mi">200</span><span class="p">],</span>
                <span class="p">)</span>

                <span class="n">report</span><span class="p">.</span><span class="n">findings</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">finding</span><span class="p">)</span>
                <span class="n">report</span><span class="p">.</span><span class="n">total_vulnerabilities</span> <span class="o">+=</span> <span class="mi">1</span>

                <span class="k">if</span> <span class="n">severity</span> <span class="o">==</span> <span class="sh">"</span><span class="s">Critical</span><span class="sh">"</span><span class="p">:</span>
                    <span class="n">report</span><span class="p">.</span><span class="n">critical_count</span> <span class="o">+=</span> <span class="mi">1</span>
                    <span class="n">has_critical</span> <span class="o">=</span> <span class="bp">True</span>
                <span class="k">elif</span> <span class="n">severity</span> <span class="o">==</span> <span class="sh">"</span><span class="s">High</span><span class="sh">"</span><span class="p">:</span>
                    <span class="n">report</span><span class="p">.</span><span class="n">high_count</span> <span class="o">+=</span> <span class="mi">1</span>
                <span class="k">elif</span> <span class="n">severity</span> <span class="o">==</span> <span class="sh">"</span><span class="s">Medium</span><span class="sh">"</span><span class="p">:</span>
                    <span class="n">report</span><span class="p">.</span><span class="n">medium_count</span> <span class="o">+=</span> <span class="mi">1</span>
                <span class="k">elif</span> <span class="n">severity</span> <span class="o">==</span> <span class="sh">"</span><span class="s">Low</span><span class="sh">"</span><span class="p">:</span>
                    <span class="n">report</span><span class="p">.</span><span class="n">low_count</span> <span class="o">+=</span> <span class="mi">1</span>

            <span class="k">if</span> <span class="n">has_critical</span><span class="p">:</span>
                <span class="n">report</span><span class="p">.</span><span class="n">applications_with_critical</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">app_name</span><span class="p">)</span>

        <span class="k">return</span> <span class="n">report</span>

    <span class="k">def</span> <span class="nf">export_csv</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">report</span><span class="p">:</span> <span class="n">ComplianceReport</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
        <span class="sh">"""</span><span class="s">Export findings as CSV for stakeholder reporting.</span><span class="sh">"""</span>
        <span class="n">output</span> <span class="o">=</span> <span class="n">io</span><span class="p">.</span><span class="nc">StringIO</span><span class="p">()</span>
        <span class="n">writer</span> <span class="o">=</span> <span class="n">csv</span><span class="p">.</span><span class="nf">writer</span><span class="p">(</span><span class="n">output</span><span class="p">)</span>

        <span class="n">writer</span><span class="p">.</span><span class="nf">writerow</span><span class="p">([</span>
            <span class="sh">"</span><span class="s">Application</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Image Tag</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Scan Date</span><span class="sh">"</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">Package</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Installed Version</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Fixed Version</span><span class="sh">"</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">Vulnerability ID</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Severity</span><span class="sh">"</span>
        <span class="p">])</span>

        <span class="k">for</span> <span class="n">finding</span> <span class="ow">in</span> <span class="n">report</span><span class="p">.</span><span class="n">findings</span><span class="p">:</span>
            <span class="n">writer</span><span class="p">.</span><span class="nf">writerow</span><span class="p">([</span>
                <span class="n">finding</span><span class="p">.</span><span class="n">app_name</span><span class="p">,</span>
                <span class="n">finding</span><span class="p">.</span><span class="n">image_tag</span><span class="p">,</span>
                <span class="n">finding</span><span class="p">.</span><span class="n">scan_date</span><span class="p">,</span>
                <span class="n">finding</span><span class="p">.</span><span class="n">package_name</span><span class="p">,</span>
                <span class="n">finding</span><span class="p">.</span><span class="n">installed_version</span><span class="p">,</span>
                <span class="n">finding</span><span class="p">.</span><span class="n">fixed_version</span><span class="p">,</span>
                <span class="n">finding</span><span class="p">.</span><span class="n">vulnerability_id</span><span class="p">,</span>
                <span class="n">finding</span><span class="p">.</span><span class="n">severity</span><span class="p">,</span>
            <span class="p">])</span>

        <span class="k">return</span> <span class="n">output</span><span class="p">.</span><span class="nf">getvalue</span><span class="p">()</span>

    <span class="k">def</span> <span class="nf">upload_report</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">report</span><span class="p">:</span> <span class="n">ComplianceReport</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
        <span class="sh">"""</span><span class="s">Upload compliance report to S3.</span><span class="sh">"""</span>
        <span class="n">timestamp</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">.</span><span class="nf">utcnow</span><span class="p">().</span><span class="nf">strftime</span><span class="p">(</span><span class="sh">"</span><span class="s">%Y%m%dT%H%M%S</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">key</span> <span class="o">=</span> <span class="sa">f</span><span class="sh">"</span><span class="s">compliance-reports/</span><span class="si">{</span><span class="n">timestamp</span><span class="si">}</span><span class="s">/report.json</span><span class="sh">"</span>

        <span class="n">self</span><span class="p">.</span><span class="n">s3</span><span class="p">.</span><span class="nf">put_object</span><span class="p">(</span>
            <span class="n">Bucket</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">bucket</span><span class="p">,</span>
            <span class="n">Key</span><span class="o">=</span><span class="n">key</span><span class="p">,</span>
            <span class="n">Body</span><span class="o">=</span><span class="n">json</span><span class="p">.</span><span class="nf">dumps</span><span class="p">({</span>
                <span class="sh">"</span><span class="s">generated_at</span><span class="sh">"</span><span class="p">:</span> <span class="n">report</span><span class="p">.</span><span class="n">generated_at</span><span class="p">,</span>
                <span class="sh">"</span><span class="s">summary</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span>
                    <span class="sh">"</span><span class="s">total_applications</span><span class="sh">"</span><span class="p">:</span> <span class="n">report</span><span class="p">.</span><span class="n">total_applications</span><span class="p">,</span>
                    <span class="sh">"</span><span class="s">total_sboms</span><span class="sh">"</span><span class="p">:</span> <span class="n">report</span><span class="p">.</span><span class="n">total_sboms</span><span class="p">,</span>
                    <span class="sh">"</span><span class="s">total_vulnerabilities</span><span class="sh">"</span><span class="p">:</span> <span class="n">report</span><span class="p">.</span><span class="n">total_vulnerabilities</span><span class="p">,</span>
                    <span class="sh">"</span><span class="s">critical</span><span class="sh">"</span><span class="p">:</span> <span class="n">report</span><span class="p">.</span><span class="n">critical_count</span><span class="p">,</span>
                    <span class="sh">"</span><span class="s">high</span><span class="sh">"</span><span class="p">:</span> <span class="n">report</span><span class="p">.</span><span class="n">high_count</span><span class="p">,</span>
                    <span class="sh">"</span><span class="s">medium</span><span class="sh">"</span><span class="p">:</span> <span class="n">report</span><span class="p">.</span><span class="n">medium_count</span><span class="p">,</span>
                    <span class="sh">"</span><span class="s">low</span><span class="sh">"</span><span class="p">:</span> <span class="n">report</span><span class="p">.</span><span class="n">low_count</span><span class="p">,</span>
                    <span class="sh">"</span><span class="s">applications_with_critical</span><span class="sh">"</span><span class="p">:</span>
                        <span class="n">report</span><span class="p">.</span><span class="n">applications_with_critical</span><span class="p">,</span>
                <span class="p">},</span>
                <span class="sh">"</span><span class="s">findings</span><span class="sh">"</span><span class="p">:</span> <span class="p">[</span>
                    <span class="p">{</span>
                        <span class="sh">"</span><span class="s">app</span><span class="sh">"</span><span class="p">:</span> <span class="n">f</span><span class="p">.</span><span class="n">app_name</span><span class="p">,</span>
                        <span class="sh">"</span><span class="s">package</span><span class="sh">"</span><span class="p">:</span> <span class="n">f</span><span class="p">.</span><span class="n">package_name</span><span class="p">,</span>
                        <span class="sh">"</span><span class="s">version</span><span class="sh">"</span><span class="p">:</span> <span class="n">f</span><span class="p">.</span><span class="n">installed_version</span><span class="p">,</span>
                        <span class="sh">"</span><span class="s">fixed</span><span class="sh">"</span><span class="p">:</span> <span class="n">f</span><span class="p">.</span><span class="n">fixed_version</span><span class="p">,</span>
                        <span class="sh">"</span><span class="s">cve</span><span class="sh">"</span><span class="p">:</span> <span class="n">f</span><span class="p">.</span><span class="n">vulnerability_id</span><span class="p">,</span>
                        <span class="sh">"</span><span class="s">severity</span><span class="sh">"</span><span class="p">:</span> <span class="n">f</span><span class="p">.</span><span class="n">severity</span><span class="p">,</span>
                    <span class="p">}</span>
                    <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">report</span><span class="p">.</span><span class="n">findings</span>
                <span class="p">],</span>
            <span class="p">},</span> <span class="n">indent</span><span class="o">=</span><span class="mi">2</span><span class="p">),</span>
            <span class="n">ContentType</span><span class="o">=</span><span class="sh">"</span><span class="s">application/json</span><span class="sh">"</span><span class="p">,</span>
        <span class="p">)</span>

        <span class="c1"># Also upload CSV
</span>        <span class="n">csv_key</span> <span class="o">=</span> <span class="sa">f</span><span class="sh">"</span><span class="s">compliance-reports/</span><span class="si">{</span><span class="n">timestamp</span><span class="si">}</span><span class="s">/report.csv</span><span class="sh">"</span>
        <span class="n">self</span><span class="p">.</span><span class="n">s3</span><span class="p">.</span><span class="nf">put_object</span><span class="p">(</span>
            <span class="n">Bucket</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">bucket</span><span class="p">,</span>
            <span class="n">Key</span><span class="o">=</span><span class="n">csv_key</span><span class="p">,</span>
            <span class="n">Body</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="nf">export_csv</span><span class="p">(</span><span class="n">report</span><span class="p">),</span>
            <span class="n">ContentType</span><span class="o">=</span><span class="sh">"</span><span class="s">text/csv</span><span class="sh">"</span><span class="p">,</span>
        <span class="p">)</span>

        <span class="k">return</span> <span class="sa">f</span><span class="sh">"</span><span class="s">s3://</span><span class="si">{</span><span class="n">self</span><span class="p">.</span><span class="n">bucket</span><span class="si">}</span><span class="s">/</span><span class="si">{</span><span class="n">key</span><span class="si">}</span><span class="sh">"</span>


<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="sh">"</span><span class="s">__main__</span><span class="sh">"</span><span class="p">:</span>
    <span class="n">reporter</span> <span class="o">=</span> <span class="nc">SBOMComplianceReporter</span><span class="p">(</span>
        <span class="n">bucket_name</span><span class="o">=</span><span class="sh">"</span><span class="s">redteam-sbom-storage-prod</span><span class="sh">"</span>
    <span class="p">)</span>

    <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Generating compliance report...</span><span class="sh">"</span><span class="p">)</span>
    <span class="n">report</span> <span class="o">=</span> <span class="n">reporter</span><span class="p">.</span><span class="nf">generate_compliance_report</span><span class="p">()</span>

    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="se">\n</span><span class="s">--- SBOM Compliance Summary ---</span><span class="sh">"</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Applications scanned:     </span><span class="si">{</span><span class="n">report</span><span class="p">.</span><span class="n">total_applications</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">SBOMs on file:            </span><span class="si">{</span><span class="n">report</span><span class="p">.</span><span class="n">total_sboms</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Total vulnerabilities:    </span><span class="si">{</span><span class="n">report</span><span class="p">.</span><span class="n">total_vulnerabilities</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">  Critical:               </span><span class="si">{</span><span class="n">report</span><span class="p">.</span><span class="n">critical_count</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">  High:                   </span><span class="si">{</span><span class="n">report</span><span class="p">.</span><span class="n">high_count</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">  Medium:                 </span><span class="si">{</span><span class="n">report</span><span class="p">.</span><span class="n">medium_count</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">  Low:                    </span><span class="si">{</span><span class="n">report</span><span class="p">.</span><span class="n">low_count</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">report</span><span class="p">.</span><span class="n">applications_with_critical</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="se">\n</span><span class="s">Applications with CRITICAL vulnerabilities:</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">app</span> <span class="ow">in</span> <span class="n">report</span><span class="p">.</span><span class="n">applications_with_critical</span><span class="p">:</span>
            <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">  - </span><span class="si">{</span><span class="n">app</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

    <span class="n">location</span> <span class="o">=</span> <span class="n">reporter</span><span class="p">.</span><span class="nf">upload_report</span><span class="p">(</span><span class="n">report</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="se">\n</span><span class="s">Report uploaded to: </span><span class="si">{</span><span class="n">location</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="athena-table-for-sbom-queries">Athena Table for SBOM Queries</h3>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
</pre></td><td class="rouge-code"><pre><span class="c1">-- Create an Athena table over SBOM data stored in S3</span>
<span class="k">CREATE</span> <span class="k">EXTERNAL</span> <span class="k">TABLE</span> <span class="n">IF</span> <span class="k">NOT</span> <span class="k">EXISTS</span> <span class="n">sbom_vulnerabilities</span> <span class="p">(</span>
  <span class="n">app_name</span> <span class="n">STRING</span><span class="p">,</span>
  <span class="n">package_name</span> <span class="n">STRING</span><span class="p">,</span>
  <span class="n">installed_version</span> <span class="n">STRING</span><span class="p">,</span>
  <span class="n">fixed_version</span> <span class="n">STRING</span><span class="p">,</span>
  <span class="n">vulnerability_id</span> <span class="n">STRING</span><span class="p">,</span>
  <span class="n">severity</span> <span class="n">STRING</span><span class="p">,</span>
  <span class="n">scan_date</span> <span class="n">STRING</span>
<span class="p">)</span>
<span class="k">ROW</span> <span class="n">FORMAT</span> <span class="n">SERDE</span> <span class="s1">'org.openx.data.jsonserde.JsonSerDe'</span>
<span class="k">LOCATION</span> <span class="s1">'s3://redteam-sbom-storage-prod/compliance-reports/'</span>
<span class="n">TBLPROPERTIES</span> <span class="p">(</span><span class="s1">'has_encrypted_data'</span><span class="o">=</span><span class="s1">'true'</span><span class="p">);</span>

<span class="c1">-- Find all critical vulnerabilities across your fleet</span>
<span class="k">SELECT</span> <span class="n">app_name</span><span class="p">,</span> <span class="n">package_name</span><span class="p">,</span> <span class="n">vulnerability_id</span><span class="p">,</span> <span class="n">installed_version</span><span class="p">,</span> <span class="n">fixed_version</span>
<span class="k">FROM</span> <span class="n">sbom_vulnerabilities</span>
<span class="k">WHERE</span> <span class="n">severity</span> <span class="o">=</span> <span class="s1">'Critical'</span>
<span class="k">ORDER</span> <span class="k">BY</span> <span class="n">app_name</span><span class="p">;</span>

<span class="c1">-- Check if any application ships a specific vulnerable package</span>
<span class="k">SELECT</span> <span class="n">app_name</span><span class="p">,</span> <span class="n">installed_version</span><span class="p">,</span> <span class="n">vulnerability_id</span>
<span class="k">FROM</span> <span class="n">sbom_vulnerabilities</span>
<span class="k">WHERE</span> <span class="n">package_name</span> <span class="o">=</span> <span class="s1">'log4j-core'</span>
  <span class="k">AND</span> <span class="n">severity</span> <span class="k">IN</span> <span class="p">(</span><span class="s1">'Critical'</span><span class="p">,</span> <span class="s1">'High'</span><span class="p">);</span>

<span class="c1">-- Compliance dashboard: vulnerability counts by application</span>
<span class="k">SELECT</span>
  <span class="n">app_name</span><span class="p">,</span>
  <span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="k">AS</span> <span class="n">total_vulns</span><span class="p">,</span>
  <span class="k">SUM</span><span class="p">(</span><span class="k">CASE</span> <span class="k">WHEN</span> <span class="n">severity</span> <span class="o">=</span> <span class="s1">'Critical'</span> <span class="k">THEN</span> <span class="mi">1</span> <span class="k">ELSE</span> <span class="mi">0</span> <span class="k">END</span><span class="p">)</span> <span class="k">AS</span> <span class="n">critical</span><span class="p">,</span>
  <span class="k">SUM</span><span class="p">(</span><span class="k">CASE</span> <span class="k">WHEN</span> <span class="n">severity</span> <span class="o">=</span> <span class="s1">'High'</span> <span class="k">THEN</span> <span class="mi">1</span> <span class="k">ELSE</span> <span class="mi">0</span> <span class="k">END</span><span class="p">)</span> <span class="k">AS</span> <span class="n">high</span><span class="p">,</span>
  <span class="k">SUM</span><span class="p">(</span><span class="k">CASE</span> <span class="k">WHEN</span> <span class="n">severity</span> <span class="o">=</span> <span class="s1">'Medium'</span> <span class="k">THEN</span> <span class="mi">1</span> <span class="k">ELSE</span> <span class="mi">0</span> <span class="k">END</span><span class="p">)</span> <span class="k">AS</span> <span class="n">medium</span>
<span class="k">FROM</span> <span class="n">sbom_vulnerabilities</span>
<span class="k">GROUP</span> <span class="k">BY</span> <span class="n">app_name</span>
<span class="k">ORDER</span> <span class="k">BY</span> <span class="n">critical</span> <span class="k">DESC</span><span class="p">,</span> <span class="n">high</span> <span class="k">DESC</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="best-practices-for-sbom-lifecycle-management">Best Practices for SBOM Lifecycle Management</h2>

<h3 id="1-generate-sboms-at-every-build">1. Generate SBOMs at Every Build</h3>

<p>Do not treat SBOMs as a one-time artifact. Every CI/CD build should produce a fresh SBOM. Vulnerability databases are updated daily — an SBOM generated last week may miss newly disclosed CVEs.</p>

<h3 id="2-store-sboms-with-retention-policies">2. Store SBOMs with Retention Policies</h3>

<p>Regulatory frameworks require SBOM retention for auditing. The CRA requires technical documentation (including SBOMs) to be available for the product’s expected lifetime plus five years. Use S3 lifecycle policies to tier storage costs while maintaining compliance:</p>

<ul>
  <li><strong>0-90 days</strong>: S3 Standard (frequent access for active development)</li>
  <li><strong>90-365 days</strong>: S3 Standard-IA (infrequent access, still queryable)</li>
  <li><strong>1-7 years</strong>: S3 Glacier (archive for regulatory retention)</li>
</ul>

<h3 id="3-sign-everything">3. Sign Everything</h3>

<p>An unsigned SBOM has no provenance guarantee. Use Cosign to sign both the container image and the SBOM attestation. This creates a cryptographic chain: the SBOM was generated by your pipeline, for this specific image, at this specific time.</p>

<h3 id="4-scan-continuously-not-just-at-build-time">4. Scan Continuously, Not Just at Build Time</h3>

<p>New vulnerabilities are disclosed daily. Implement a scheduled Lambda function that re-scans stored SBOMs against the latest Grype database:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="c"># Cron job: re-scan all stored SBOMs weekly</span>
grype db update
<span class="k">for </span>sbom <span class="k">in</span> <span class="si">$(</span>aws s3 <span class="nb">ls </span>s3://redteam-sbom-storage-prod/ <span class="nt">--recursive</span> | <span class="nb">grep </span>sbom-cyclonedx.json<span class="si">)</span><span class="p">;</span> <span class="k">do
  </span>aws s3 <span class="nb">cp</span> <span class="s2">"s3://redteam-sbom-storage-prod/</span><span class="nv">$sbom</span><span class="s2">"</span> /tmp/sbom.json
  grype sbom:/tmp/sbom.json <span class="nt">--fail-on</span> critical <span class="nt">-o</span> json <span class="nt">--file</span> <span class="s2">"/tmp/rescan-</span><span class="si">$(</span><span class="nb">basename</span> <span class="si">$(</span><span class="nb">dirname</span> <span class="nv">$sbom</span><span class="si">))</span><span class="s2">.json"</span>
<span class="k">done</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="5-establish-a-vulnerability-response-sla">5. Establish a Vulnerability Response SLA</h3>

<p>Define clear SLAs for vulnerability remediation by severity:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Severity</th>
      <th style="text-align: left">Response SLA</th>
      <th style="text-align: left">Remediation SLA</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">Critical</td>
      <td style="text-align: left">4 hours</td>
      <td style="text-align: left">24 hours</td>
    </tr>
    <tr>
      <td style="text-align: left">High</td>
      <td style="text-align: left">24 hours</td>
      <td style="text-align: left">7 days</td>
    </tr>
    <tr>
      <td style="text-align: left">Medium</td>
      <td style="text-align: left">7 days</td>
      <td style="text-align: left">30 days</td>
    </tr>
    <tr>
      <td style="text-align: left">Low</td>
      <td style="text-align: left">30 days</td>
      <td style="text-align: left">Next release cycle</td>
    </tr>
  </tbody>
</table>

<h3 id="6-use-vex-to-communicate-exploitability">6. Use VEX to Communicate Exploitability</h3>

<p>Not every CVE is exploitable in your context. Vulnerability Exploitability eXchange (VEX) documents let you formally declare that a CVE is “not affected”, “under investigation”, or “fixed” in your deployment. CycloneDX supports VEX natively, and Grype’s ignore rules serve a similar purpose in your pipeline.</p>

<h3 id="7-monitor-the-sbom-for-license-risk">7. Monitor the SBOM for License Risk</h3>

<p>SBOMs are not just for security. They also reveal license obligations. A single AGPL-licensed transitive dependency in a commercial product can create significant legal exposure. Use the SPDX output from Syft to audit license compliance alongside vulnerability scanning.</p>

<h2 id="implementation-roadmap">Implementation Roadmap</h2>

<p>Adopting SBOM-based supply chain security does not have to be a big-bang project. Here is a phased approach:</p>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 1-2</strong>: Install Syft and Grype locally, generate SBOMs for your top three production images, review the output</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 3-4</strong>: Add SBOM generation to one CodeBuild project, store results in S3, set severity threshold to <code class="language-plaintext highlighter-rouge">critical</code> only</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Month 2</strong>: Roll out to all pipelines, lower threshold to <code class="language-plaintext highlighter-rouge">high</code>, implement Cosign signing</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Month 3</strong>: Deploy the compliance reporting script, create Athena queries, establish vulnerability SLAs</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Month 4-6</strong>: Implement continuous re-scanning, VEX workflows, and license compliance auditing</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Ongoing</strong>: Review and update SBOM policies quarterly as regulatory standards evolve</li>
</ul>

<h2 id="related-articles">Related Articles</h2>

<ul>
  <li><a href="/posts/ai-supply-chain-security-model-poisoning-defense/">AI Supply Chain Security: Model Poisoning Defense</a> — Extends supply chain security concepts to AI/ML model pipelines</li>
  <li><a href="/posts/aws-devsecops-pipeline-security-automation-implementation/">AWS DevSecOps Pipeline Security Automation</a> — Broader DevSecOps pipeline security patterns including SAST, DAST, and IaC scanning</li>
  <li><a href="/posts/aws-cloud-security-best-practices-implementation-guide/">AWS Cloud Security Best Practices Implementation Guide</a> — Foundational AWS security controls that complement supply chain security</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>Software supply chain security is no longer a nice-to-have — it is a regulatory requirement with real enforcement deadlines and substantial penalties. The EU CRA’s September 2026 vulnerability reporting obligations are less than six months away, and NIST requirements are already in effect for federal suppliers.</p>

<p>The path forward is clear: <strong>Syft</strong> generates comprehensive, multi-format SBOMs. <strong>Grype</strong> scans those SBOMs against the broadest vulnerability database available. <strong>Cosign</strong> signs everything for provenance. <strong>AWS CodePipeline</strong> orchestrates the workflow. <strong>S3 and Athena</strong> provide long-term storage and fleet-wide querying. And <strong>AWS Inspector</strong> serves as your always-on baseline for monitored resources.</p>

<p>Every tool in this stack is open source (except Inspector). You own the data, you control the pipeline, and you are not locked into any vendor. That is the way supply chain security should work.</p>

<p>Start with one pipeline. Generate one SBOM. Scan it. Fix what you find. Then scale.</p>

<hr />

<p><em>Jon Price is a DevSecOps consultant specializing in AWS security architecture and automated compliance. Connect with him on <a href="https://www.linkedin.com/in/jonpricelinux/">LinkedIn</a> to discuss supply chain security strategy for your organization.</em></p>]]></content><author><name>Jon (JR) Price</name></author><category term="AWS" /><category term="DevSecOps" /><category term="Security" /><category term="codepipeline" /><category term="eu-cra" /><category term="grype" /><category term="open-source" /><category term="sbom" /><category term="software-composition-analysis" /><category term="supply-chain-security" /><category term="syft" /><summary type="html"><![CDATA[Generate SBOMs with Syft and Grype in AWS CodePipeline. Meet EU CRA and NIST supply chain requirements using open-source tooling.]]></summary></entry><entry><title type="html">OPA Gatekeeper vs Kyverno on EKS: Policy-as-Code</title><link href="https://www.red-team.sh/posts/policy-as-code-opa-kyverno-eks-security/" rel="alternate" type="text/html" title="OPA Gatekeeper vs Kyverno on EKS: Policy-as-Code" /><published>2026-03-11T09:00:00-07:00</published><updated>2026-03-11T09:00:00-07:00</updated><id>https://www.red-team.sh/posts/policy-as-code-opa-kyverno-eks-security</id><content type="html" xml:base="https://www.red-team.sh/posts/policy-as-code-opa-kyverno-eks-security/"><![CDATA[<p><img src="/assets/img/policy-as-code-opa-kyverno-eks.png" alt="OPA Gatekeeper and Kyverno policy-as-code on EKS" class="shadow" w="800" />
<em>EKS policy-as-code flow with OPA Gatekeeper, Kyverno, and AWS Config governance</em></p>

<h2 id="introduction">Introduction</h2>

<p>OPA vs Kyverno on EKS is not just a tooling preference. It is the decision that determines how quickly your team can turn Kubernetes security requirements into enforceable policy-as-code. If you are comparing <strong>OPA Gatekeeper on EKS</strong>, <strong>Kyverno EKS security</strong>, <strong>EKS admission control</strong>, or <strong>Kubernetes policy-as-code</strong>, the practical question is where each control belongs: infrastructure posture in AWS Config, workload admission in Gatekeeper or Kyverno, and pre-merge checks in CI.</p>

<p>Kubernetes misconfigurations remain the single largest source of security incidents in containerized environments. Over 50% of organizations cite misconfigurations as the leading cause of Kubernetes security incidents, according to the Red Hat State of Kubernetes Security Report. Ninety percent of organizations experienced at least one Kubernetes security incident in the past twelve months, and new clusters face their first attack attempt within 18 minutes of deployment. Meanwhile, 82% of cloud misconfigurations are caused by human error, not software flaws.</p>

<p>Manual configuration reviews do not scale. Runbooks get stale. Tribal knowledge walks out the door when engineers change teams. The answer is <strong>policy-as-code</strong>: machine-enforceable rules that live in Git, run through CI/CD, and block non-compliant resources before they ever reach your cluster.</p>

<h3 id="current-landscape-statistics">Current Landscape Statistics</h3>

<ul>
  <li><strong>Misconfiguration Dominance</strong>: 50%+ of Kubernetes security incidents trace back to misconfigurations and exposures</li>
  <li><strong>Human Error</strong>: 82% of cloud misconfigurations stem from human error, making manual reviews inherently unreliable</li>
  <li><strong>Attack Velocity</strong>: Container-based lateral movement attacks increased 34% in 2025, targeting misconfigured workloads</li>
  <li><strong>Legacy Risk</strong>: 81% of EKS clusters still rely on deprecated CONFIG_MAP authentication, a risk that automated policy could eliminate</li>
  <li><strong>Production Impact</strong>: Real-world policy-as-code implementations report an 80% reduction in policy violations detected pre-merge</li>
</ul>

<p>This article walks through the AWS-native approach first with AWS Config managed rules for EKS, then shows how open-source policy engines – OPA Gatekeeper and Kyverno – deliver portable, version-controlled enforcement across any Kubernetes cluster, not just EKS. For runtime detection after admission, pair this preventive layer with the <a href="/posts/eks-runtime-security-falco-vs-guardduty/">EKS runtime security comparison of GuardDuty and Falco</a>.</p>

<h2 id="why-policy-as-code-matters">Why Policy-as-Code Matters</h2>

<p>Traditional security governance relies on documentation, training, and periodic audits. This model breaks down in Kubernetes environments where dozens of engineers push hundreds of deployments per day across multiple clusters.</p>

<p>Policy-as-code shifts enforcement from <strong>reactive</strong> (find violations after deployment) to <strong>proactive</strong> (block violations before admission). The key benefits include:</p>

<p><strong>Version Control and Auditability</strong>
Every policy change goes through pull request review, just like application code. You get a complete audit trail of who approved what rule and when it took effect. Compliance teams can point auditors at a Git repository instead of assembling spreadsheets.</p>

<p><strong>Automated Enforcement</strong>
Policies execute as Kubernetes admission controllers. When a developer runs <code class="language-plaintext highlighter-rouge">kubectl apply</code>, the API server sends the resource to your policy engine, which evaluates it against your rules and returns an allow or deny decision in milliseconds. No human in the loop, no delay, no exceptions unless you explicitly grant them.</p>

<p><strong>Portability Across Clusters and Clouds</strong>
OSS policy engines work on any conformant Kubernetes cluster: EKS, GKE, AKS, on-premises k3s, or bare-metal kubeadm. Your security posture travels with your workloads. AWS Config, by contrast, operates at the infrastructure layer and evaluates EKS cluster configuration, not the workloads running inside it.</p>

<p><strong>Shift Left Testing</strong>
Policy rules can run in CI before manifests ever reach a cluster. Tools like <code class="language-plaintext highlighter-rouge">conftest</code> evaluate Rego policies against YAML files in a pipeline step, catching violations at pull request time rather than deploy time.</p>

<h2 id="aws-config-for-eks-what-it-covers-and-where-it-falls-short">AWS Config for EKS: What It Covers and Where It Falls Short</h2>

<p>AWS Config provides managed rules that evaluate the configuration of your AWS resources, including EKS clusters. It operates at the AWS API layer, examining cluster-level settings.</p>

<h3 id="what-aws-config-covers">What AWS Config Covers</h3>

<p>AWS Config evaluates EKS infrastructure configuration with managed rules like:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
</pre></td><td class="rouge-code"><pre><span class="c"># Example AWS Config managed rules for EKS</span>
aws configservice put-config-rule <span class="nt">--config-rule</span> <span class="s1">'{
  "ConfigRuleName": "eks-cluster-supported-version",
  "Source": {
    "Owner": "AWS",
    "SourceIdentifier": "EKS_CLUSTER_SUPPORTED_VERSION"
  },
  "InputParameters": "{\"oldestVersionSupported\":\"1.28\"}"
}'</span>

<span class="c"># Check EKS endpoint public access</span>
aws configservice put-config-rule <span class="nt">--config-rule</span> <span class="s1">'{
  "ConfigRuleName": "eks-endpoint-no-public-access",
  "Source": {
    "Owner": "AWS",
    "SourceIdentifier": "EKS_ENDPOINT_NO_PUBLIC_ACCESS"
  }
}'</span>

<span class="c"># Ensure EKS cluster logging is enabled</span>
aws configservice put-config-rule <span class="nt">--config-rule</span> <span class="s1">'{
  "ConfigRuleName": "eks-cluster-logging-enabled",
  "Source": {
    "Owner": "AWS",
    "SourceIdentifier": "EKS_CLUSTER_LOGGING_ENABLED"
  }
}'</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Key EKS-focused AWS Config managed rules include:</p>

<ul>
  <li><strong>eks-cluster-supported-version</strong>: Flags clusters running unsupported Kubernetes versions</li>
  <li><strong>eks-endpoint-no-public-access</strong>: Checks whether the EKS API endpoint is publicly accessible</li>
  <li><strong>eks-cluster-logging-enabled</strong>: Verifies audit and API server logging is active</li>
  <li><strong>eks-secrets-encrypted</strong>: Confirms envelope encryption is enabled for Kubernetes Secrets</li>
</ul>

<p>AWS Config also integrates with Security Hub for centralized findings and supports conformance packs that bundle multiple rules into a compliance framework.</p>

<h3 id="where-aws-config-falls-short">Where AWS Config Falls Short</h3>

<p>AWS Config operates at the <strong>infrastructure layer</strong>. It can tell you whether your EKS cluster has logging enabled, but it cannot tell you:</p>

<ul>
  <li>Whether a Pod is running as root inside that cluster</li>
  <li>Whether a Deployment is pulling images from an unapproved registry</li>
  <li>Whether a container is requesting 32 GiB of RAM with no resource limits</li>
  <li>Whether a namespace is missing required labels for cost allocation</li>
  <li>Whether a service account has been granted <code class="language-plaintext highlighter-rouge">cluster-admin</code> privileges</li>
</ul>

<p>For workload-level governance, you need admission controllers running inside the cluster. This is where OPA Gatekeeper and Kyverno step in.</p>

<h2 id="opa-gatekeeper-deep-dive">OPA Gatekeeper Deep Dive</h2>

<p>The <a href="https://www.openpolicyagent.org/">Open Policy Agent</a> is a CNCF-graduated general-purpose policy engine. OPA Gatekeeper is its Kubernetes-native integration that acts as a validating admission webhook.</p>

<h3 id="architecture">Architecture</h3>

<p>Gatekeeper consists of three main components:</p>

<ol>
  <li><strong>Gatekeeper Controller Manager</strong>: Runs as a Deployment in the <code class="language-plaintext highlighter-rouge">gatekeeper-system</code> namespace. It watches for ConstraintTemplate and Constraint custom resources.</li>
  <li><strong>Validating Admission Webhook</strong>: Intercepts API server requests and evaluates them against loaded policies written in Rego.</li>
  <li><strong>Audit Controller</strong>: Periodically scans existing resources for compliance with constraints, catching pre-existing violations.</li>
</ol>

<h3 id="deploying-gatekeeper-on-eks">Deploying Gatekeeper on EKS</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="c"># Add the Gatekeeper Helm repository</span>
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update

<span class="c"># Install Gatekeeper into your EKS cluster</span>
helm <span class="nb">install </span>gatekeeper gatekeeper/gatekeeper <span class="se">\</span>
  <span class="nt">--namespace</span> gatekeeper-system <span class="se">\</span>
  <span class="nt">--create-namespace</span> <span class="se">\</span>
  <span class="nt">--set</span> <span class="nv">replicas</span><span class="o">=</span>3 <span class="se">\</span>
  <span class="nt">--set</span> audit.replicas<span class="o">=</span>1 <span class="se">\</span>
  <span class="nt">--set</span> audit.logLevel<span class="o">=</span>INFO <span class="se">\</span>
  <span class="nt">--set</span> controllerManager.logLevel<span class="o">=</span>INFO
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="writing-rego-policies-constrainttemplates-and-constraints">Writing Rego Policies: ConstraintTemplates and Constraints</h3>

<p>Gatekeeper uses a two-layer model. A <strong>ConstraintTemplate</strong> defines the policy logic in Rego. A <strong>Constraint</strong> applies that template to specific resources with parameters.</p>

<p><strong>Example: Block Privileged Containers</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
</pre></td><td class="rouge-code"><pre><span class="c1"># ConstraintTemplate: defines the Rego logic</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">templates.gatekeeper.sh/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ConstraintTemplate</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">k8spspprivilegedcontainer</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">crd</span><span class="pi">:</span>
    <span class="na">spec</span><span class="pi">:</span>
      <span class="na">names</span><span class="pi">:</span>
        <span class="na">kind</span><span class="pi">:</span> <span class="s">K8sPSPPrivilegedContainer</span>
  <span class="na">targets</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">target</span><span class="pi">:</span> <span class="s">admission.k8s.gatekeeper.sh</span>
      <span class="na">rego</span><span class="pi">:</span> <span class="pi">|</span>
        <span class="s">package k8spspprivilegedcontainer</span>

        <span class="s">violation[{"msg": msg}] {</span>
          <span class="s">container := input.review.object.spec.containers[_]</span>
          <span class="s">container.securityContext.privileged == true</span>
          <span class="s">msg := sprintf("Privileged container is not allowed: %v", [container.name])</span>
        <span class="s">}</span>

        <span class="s">violation[{"msg": msg}] {</span>
          <span class="s">container := input.review.object.spec.initContainers[_]</span>
          <span class="s">container.securityContext.privileged == true</span>
          <span class="s">msg := sprintf("Privileged init container is not allowed: %v", [container.name])</span>
        <span class="s">}</span>
<span class="s">---</span>
<span class="c1"># Constraint: applies the template to all Pods</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">constraints.gatekeeper.sh/v1beta1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">K8sPSPPrivilegedContainer</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">deny-privileged-containers</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">match</span><span class="pi">:</span>
    <span class="na">kinds</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">apiGroups</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">"</span><span class="pi">]</span>
        <span class="na">kinds</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">Pod"</span><span class="pi">]</span>
    <span class="na">excludedNamespaces</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">kube-system</span>
      <span class="pi">-</span> <span class="s">gatekeeper-system</span>
  <span class="na">enforcementAction</span><span class="pi">:</span> <span class="s">deny</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Example: Restrict Image Registries</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
</pre></td><td class="rouge-code"><pre><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">templates.gatekeeper.sh/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ConstraintTemplate</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">k8sallowedrepos</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">crd</span><span class="pi">:</span>
    <span class="na">spec</span><span class="pi">:</span>
      <span class="na">names</span><span class="pi">:</span>
        <span class="na">kind</span><span class="pi">:</span> <span class="s">K8sAllowedRepos</span>
      <span class="na">validation</span><span class="pi">:</span>
        <span class="na">openAPIV3Schema</span><span class="pi">:</span>
          <span class="na">type</span><span class="pi">:</span> <span class="s">object</span>
          <span class="na">properties</span><span class="pi">:</span>
            <span class="na">repos</span><span class="pi">:</span>
              <span class="na">type</span><span class="pi">:</span> <span class="s">array</span>
              <span class="na">items</span><span class="pi">:</span>
                <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
  <span class="na">targets</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">target</span><span class="pi">:</span> <span class="s">admission.k8s.gatekeeper.sh</span>
      <span class="na">rego</span><span class="pi">:</span> <span class="pi">|</span>
        <span class="s">package k8sallowedrepos</span>

        <span class="s">violation[{"msg": msg}] {</span>
          <span class="s">container := input.review.object.spec.containers[_]</span>
          <span class="s">not startswith(container.image, input.parameters.repos[_])</span>
          <span class="s">msg := sprintf(</span>
            <span class="s">"Container &lt;%v&gt; image &lt;%v&gt; not from an allowed registry. Allowed: %v",</span>
            <span class="s">[container.name, container.image, input.parameters.repos]</span>
          <span class="s">)</span>
        <span class="s">}</span>
<span class="s">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">constraints.gatekeeper.sh/v1beta1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">K8sAllowedRepos</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">require-approved-registries</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">match</span><span class="pi">:</span>
    <span class="na">kinds</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">apiGroups</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">"</span><span class="pi">]</span>
        <span class="na">kinds</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">Pod"</span><span class="pi">]</span>
  <span class="na">parameters</span><span class="pi">:</span>
    <span class="na">repos</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">123456789012.dkr.ecr.us-east-1.amazonaws.com/"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">123456789012.dkr.ecr.us-west-2.amazonaws.com/"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">public.ecr.aws/"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="gatekeeper-v322-validatingadmissionpolicy-integration">Gatekeeper v3.22: ValidatingAdmissionPolicy Integration</h3>

<p>As of Gatekeeper v3.22 (February 2026), the <code class="language-plaintext highlighter-rouge">sync-vap-enforcement-scope</code> flag defaults to true, unifying the ValidatingAdmissionPolicy enforcement surface with ConstraintTemplates. This means Gatekeeper now aligns with the upstream Kubernetes ValidatingAdmissionPolicy API, giving you a migration path toward native Kubernetes policy primitives while retaining the power of Rego for complex logic.</p>

<h2 id="kyverno-deep-dive">Kyverno Deep Dive</h2>

<p><a href="https://kyverno.io/">Kyverno</a> is a CNCF-graduated Kubernetes-native policy engine designed specifically for Kubernetes. Its defining feature: policies are written in YAML, not a custom language. If you can write a Kubernetes manifest, you can write a Kyverno policy.</p>

<h3 id="architecture-1">Architecture</h3>

<p>Kyverno operates as a dynamic admission controller with these components:</p>

<ol>
  <li><strong>Admission Webhook</strong>: Intercepts API server requests for validation, mutation, and generation</li>
  <li><strong>Background Controller</strong>: Scans existing resources for compliance and applies generate/mutate rules to existing resources</li>
  <li><strong>Reports Controller</strong>: Generates PolicyReport and ClusterPolicyReport custom resources for audit visibility</li>
</ol>

<h3 id="deploying-kyverno-on-eks">Deploying Kyverno on EKS</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="c"># Add the Kyverno Helm repository</span>
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update

<span class="c"># Install Kyverno into your EKS cluster</span>
helm <span class="nb">install </span>kyverno kyverno/kyverno <span class="se">\</span>
  <span class="nt">--namespace</span> kyverno <span class="se">\</span>
  <span class="nt">--create-namespace</span> <span class="se">\</span>
  <span class="nt">--set</span> <span class="nv">replicaCount</span><span class="o">=</span>3 <span class="se">\</span>
  <span class="nt">--set</span> backgroundController.replicas<span class="o">=</span>2 <span class="se">\</span>
  <span class="nt">--set</span> cleanupController.replicas<span class="o">=</span>2 <span class="se">\</span>
  <span class="nt">--set</span> reportsController.replicas<span class="o">=</span>2
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="kyverno-117-cel-based-policies-go-stable">Kyverno 1.17: CEL-Based Policies Go Stable</h3>

<p>Kyverno 1.17, released in February 2026, promotes the Common Expression Language (CEL) policy engine from beta to v1. CEL policies align with upstream Kubernetes ValidatingAdmissionPolicies and MutatingAdmissionPolicies, offering improved evaluation performance and a more familiar syntax for teams already using Kubernetes expressions.</p>

<p>Key 1.17 features include:</p>

<ul>
  <li><strong>Namespaced mutation and generation</strong>: Namespace owners can define their own policies without cluster-wide permissions, enabling true multi-tenancy</li>
  <li><strong>Expanded function libraries</strong>: Complex logic in CEL without falling back to JMESPath</li>
  <li><strong>Cosign v3 support</strong>: Enhanced supply chain security for image signature verification</li>
</ul>

<h3 id="writing-kyverno-policies">Writing Kyverno Policies</h3>

<p>Kyverno policies use familiar Kubernetes YAML syntax with <code class="language-plaintext highlighter-rouge">match</code>, <code class="language-plaintext highlighter-rouge">exclude</code>, and rule definitions.</p>

<p><strong>Example: Block Privileged Containers</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
</pre></td><td class="rouge-code"><pre><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kyverno.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterPolicy</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">disallow-privileged-containers</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">policies.kyverno.io/title</span><span class="pi">:</span> <span class="s">Disallow Privileged Containers</span>
    <span class="na">policies.kyverno.io/category</span><span class="pi">:</span> <span class="s">Pod Security Standards (Baseline)</span>
    <span class="na">policies.kyverno.io/severity</span><span class="pi">:</span> <span class="s">high</span>
    <span class="na">policies.kyverno.io/description</span><span class="pi">:</span> <span class="pi">&gt;-</span>
      <span class="s">Privileged mode disables most security mechanisms and must not be allowed.</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">validationFailureAction</span><span class="pi">:</span> <span class="s">Enforce</span>
  <span class="na">background</span><span class="pi">:</span> <span class="kc">true</span>
  <span class="na">rules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">deny-privileged</span>
      <span class="na">match</span><span class="pi">:</span>
        <span class="na">any</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">resources</span><span class="pi">:</span>
              <span class="na">kinds</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="s">Pod</span>
      <span class="na">exclude</span><span class="pi">:</span>
        <span class="na">any</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">resources</span><span class="pi">:</span>
              <span class="na">namespaces</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="s">kube-system</span>
                <span class="pi">-</span> <span class="s">kyverno</span>
      <span class="na">validate</span><span class="pi">:</span>
        <span class="na">message</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Privileged</span><span class="nv"> </span><span class="s">containers</span><span class="nv"> </span><span class="s">are</span><span class="nv"> </span><span class="s">not</span><span class="nv"> </span><span class="s">allowed."</span>
        <span class="na">pattern</span><span class="pi">:</span>
          <span class="na">spec</span><span class="pi">:</span>
            <span class="na">containers</span><span class="pi">:</span>
              <span class="pi">-</span> <span class="na">=(securityContext)</span><span class="pi">:</span>
                  <span class="na">=(privileged)</span><span class="pi">:</span> <span class="kc">false</span>
            <span class="na">=(initContainers)</span><span class="pi">:</span>
              <span class="pi">-</span> <span class="na">=(securityContext)</span><span class="pi">:</span>
                  <span class="na">=(privileged)</span><span class="pi">:</span> <span class="kc">false</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Example: Enforce Resource Limits</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
</pre></td><td class="rouge-code"><pre><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kyverno.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterPolicy</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">require-resource-limits</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">policies.kyverno.io/title</span><span class="pi">:</span> <span class="s">Require Resource Limits</span>
    <span class="na">policies.kyverno.io/category</span><span class="pi">:</span> <span class="s">Best Practices</span>
    <span class="na">policies.kyverno.io/severity</span><span class="pi">:</span> <span class="s">medium</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">validationFailureAction</span><span class="pi">:</span> <span class="s">Enforce</span>
  <span class="na">background</span><span class="pi">:</span> <span class="kc">true</span>
  <span class="na">rules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">require-limits</span>
      <span class="na">match</span><span class="pi">:</span>
        <span class="na">any</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">resources</span><span class="pi">:</span>
              <span class="na">kinds</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="s">Pod</span>
      <span class="na">validate</span><span class="pi">:</span>
        <span class="na">message</span><span class="pi">:</span> <span class="s2">"</span><span class="s">All</span><span class="nv"> </span><span class="s">containers</span><span class="nv"> </span><span class="s">must</span><span class="nv"> </span><span class="s">have</span><span class="nv"> </span><span class="s">CPU</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">memory</span><span class="nv"> </span><span class="s">limits</span><span class="nv"> </span><span class="s">defined."</span>
        <span class="na">pattern</span><span class="pi">:</span>
          <span class="na">spec</span><span class="pi">:</span>
            <span class="na">containers</span><span class="pi">:</span>
              <span class="pi">-</span> <span class="na">resources</span><span class="pi">:</span>
                  <span class="na">limits</span><span class="pi">:</span>
                    <span class="na">memory</span><span class="pi">:</span> <span class="s2">"</span><span class="s">?*"</span>
                    <span class="na">cpu</span><span class="pi">:</span> <span class="s2">"</span><span class="s">?*"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Example: Require Labels</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
</pre></td><td class="rouge-code"><pre><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kyverno.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterPolicy</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">require-labels</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">policies.kyverno.io/title</span><span class="pi">:</span> <span class="s">Require Labels</span>
    <span class="na">policies.kyverno.io/category</span><span class="pi">:</span> <span class="s">Best Practices</span>
    <span class="na">policies.kyverno.io/severity</span><span class="pi">:</span> <span class="s">medium</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">validationFailureAction</span><span class="pi">:</span> <span class="s">Enforce</span>
  <span class="na">background</span><span class="pi">:</span> <span class="kc">true</span>
  <span class="na">rules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">require-team-label</span>
      <span class="na">match</span><span class="pi">:</span>
        <span class="na">any</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">resources</span><span class="pi">:</span>
              <span class="na">kinds</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="s">Deployment</span>
                <span class="pi">-</span> <span class="s">StatefulSet</span>
                <span class="pi">-</span> <span class="s">DaemonSet</span>
      <span class="na">validate</span><span class="pi">:</span>
        <span class="na">message</span><span class="pi">:</span> <span class="s2">"</span><span class="s">The</span><span class="nv"> </span><span class="s">label</span><span class="nv"> </span><span class="s">'app.kubernetes.io/managed-by'</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">required."</span>
        <span class="na">pattern</span><span class="pi">:</span>
          <span class="na">metadata</span><span class="pi">:</span>
            <span class="na">labels</span><span class="pi">:</span>
              <span class="na">app.kubernetes.io/managed-by</span><span class="pi">:</span> <span class="s2">"</span><span class="s">?*"</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">require-cost-center</span>
      <span class="na">match</span><span class="pi">:</span>
        <span class="na">any</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">resources</span><span class="pi">:</span>
              <span class="na">kinds</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="s">Namespace</span>
      <span class="na">validate</span><span class="pi">:</span>
        <span class="na">message</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Namespaces</span><span class="nv"> </span><span class="s">must</span><span class="nv"> </span><span class="s">have</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">'cost-center'</span><span class="nv"> </span><span class="s">label</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">billing."</span>
        <span class="na">pattern</span><span class="pi">:</span>
          <span class="na">metadata</span><span class="pi">:</span>
            <span class="na">labels</span><span class="pi">:</span>
              <span class="na">cost-center</span><span class="pi">:</span> <span class="s2">"</span><span class="s">?*"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Example: Restrict Image Registries</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
</pre></td><td class="rouge-code"><pre><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kyverno.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterPolicy</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">restrict-image-registries</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">policies.kyverno.io/title</span><span class="pi">:</span> <span class="s">Restrict Image Registries</span>
    <span class="na">policies.kyverno.io/category</span><span class="pi">:</span> <span class="s">Supply Chain Security</span>
    <span class="na">policies.kyverno.io/severity</span><span class="pi">:</span> <span class="s">high</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">validationFailureAction</span><span class="pi">:</span> <span class="s">Enforce</span>
  <span class="na">background</span><span class="pi">:</span> <span class="kc">true</span>
  <span class="na">rules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">validate-registries</span>
      <span class="na">match</span><span class="pi">:</span>
        <span class="na">any</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">resources</span><span class="pi">:</span>
              <span class="na">kinds</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="s">Pod</span>
      <span class="na">validate</span><span class="pi">:</span>
        <span class="na">message</span><span class="pi">:</span> <span class="pi">&gt;-</span>
          <span class="s">Images must come from an approved ECR registry.</span>
          <span class="s">Allowed: 123456789012.dkr.ecr.*.amazonaws.com, public.ecr.aws</span>
        <span class="na">foreach</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">list</span><span class="pi">:</span> <span class="s2">"</span><span class="s">request.object.spec.containers"</span>
            <span class="na">deny</span><span class="pi">:</span>
              <span class="na">conditions</span><span class="pi">:</span>
                <span class="na">all</span><span class="pi">:</span>
                  <span class="pi">-</span> <span class="na">key</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>
                    <span class="na">operator</span><span class="pi">:</span> <span class="s">NotEquals</span>
                    <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">123456789012.dkr.ecr.*"</span>
                  <span class="pi">-</span> <span class="na">key</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>
                    <span class="na">operator</span><span class="pi">:</span> <span class="s">NotEquals</span>
                    <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">public.ecr.aws/*"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="kyverno-mutation-auto-inject-best-practices">Kyverno Mutation: Auto-Inject Best Practices</h3>

<p>One of Kyverno’s standout capabilities is <strong>mutation</strong> – automatically modifying resources to comply with policy rather than just blocking them.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kyverno.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterPolicy</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">add-default-security-context</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">rules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">add-run-as-non-root</span>
      <span class="na">match</span><span class="pi">:</span>
        <span class="na">any</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">resources</span><span class="pi">:</span>
              <span class="na">kinds</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="s">Pod</span>
      <span class="na">mutate</span><span class="pi">:</span>
        <span class="na">patchStrategicMerge</span><span class="pi">:</span>
          <span class="na">spec</span><span class="pi">:</span>
            <span class="na">securityContext</span><span class="pi">:</span>
              <span class="na">runAsNonRoot</span><span class="pi">:</span> <span class="kc">true</span>
              <span class="na">seccompProfile</span><span class="pi">:</span>
                <span class="na">type</span><span class="pi">:</span> <span class="s">RuntimeDefault</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This mutation policy automatically injects a security context into every Pod, ensuring <code class="language-plaintext highlighter-rouge">runAsNonRoot</code> and a <code class="language-plaintext highlighter-rouge">seccompProfile</code> are set even if the developer omits them.</p>

<h2 id="comparison-opa-gatekeeper-vs-kyverno-vs-aws-config">Comparison: OPA Gatekeeper vs Kyverno vs AWS Config</h2>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>OPA Gatekeeper</th>
      <th>Kyverno</th>
      <th>AWS Config</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Policy Language</strong></td>
      <td>Rego (custom DSL)</td>
      <td>YAML/CEL (Kubernetes-native)</td>
      <td>JSON rule definitions</td>
    </tr>
    <tr>
      <td><strong>Learning Curve</strong></td>
      <td>Steep (Rego requires dedicated learning)</td>
      <td>Low (YAML familiar to K8s users)</td>
      <td>Low (managed rules are pre-built)</td>
    </tr>
    <tr>
      <td><strong>Scope</strong></td>
      <td>In-cluster workloads</td>
      <td>In-cluster workloads</td>
      <td>AWS resource configuration</td>
    </tr>
    <tr>
      <td><strong>Validation</strong></td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td><strong>Mutation</strong></td>
      <td>No (validation only)</td>
      <td>Yes (patch, merge, JSON Patch)</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Generation</strong></td>
      <td>No</td>
      <td>Yes (create resources from policy)</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Image Verification</strong></td>
      <td>Via external data</td>
      <td>Native Cosign/Notary support</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Portability</strong></td>
      <td>Any Kubernetes cluster</td>
      <td>Any Kubernetes cluster</td>
      <td>AWS only</td>
    </tr>
    <tr>
      <td><strong>CNCF Status</strong></td>
      <td>Graduated (OPA)</td>
      <td>Graduated</td>
      <td>N/A (proprietary)</td>
    </tr>
    <tr>
      <td><strong>Multi-Cluster</strong></td>
      <td>Via GitOps</td>
      <td>Via GitOps</td>
      <td>Via AWS Organizations</td>
    </tr>
    <tr>
      <td><strong>Audit/Reporting</strong></td>
      <td>Constraint status + audit logs</td>
      <td>PolicyReport CRDs</td>
      <td>AWS Config dashboard + Security Hub</td>
    </tr>
    <tr>
      <td><strong>CI/CD Testing</strong></td>
      <td>conftest (Rego)</td>
      <td>Kyverno CLI</td>
      <td>CloudFormation Guard</td>
    </tr>
    <tr>
      <td><strong>Community Policies</strong></td>
      <td>Gatekeeper Library</td>
      <td>300+ policies on kyverno.io</td>
      <td>400+ managed rules</td>
    </tr>
    <tr>
      <td><strong>CEL Support</strong></td>
      <td>Yes (v3.22 VAP sync)</td>
      <td>Yes (v1 stable in 1.17)</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Best For</strong></td>
      <td>Complex cross-cutting logic, multi-system policy</td>
      <td>K8s-native teams wanting mutation + validation</td>
      <td>AWS infrastructure compliance</td>
    </tr>
  </tbody>
</table>

<h3 id="when-to-use-what">When to Use What</h3>

<p><strong>Choose OPA Gatekeeper</strong> when you need Rego’s expressive power for complex conditional logic, when your organization already uses OPA for non-Kubernetes policy (Terraform, CI pipelines, API authorization), or when you want a single policy language across your entire stack.</p>

<p><strong>Choose Kyverno</strong> when your team is Kubernetes-native and wants to write policies in YAML without learning a new language, when you need mutation and generation capabilities, or when supply chain security with native image verification is a priority.</p>

<p><strong>Use AWS Config alongside either</strong> for infrastructure-layer compliance: cluster version checks, endpoint visibility, logging configuration, and encryption settings that operate outside the Kubernetes API.</p>

<h2 id="cicd-integration-testing-policies-before-deployment">CI/CD Integration: Testing Policies Before Deployment</h2>

<p>Policy-as-code reaches its full potential when policies are tested in CI, long before manifests reach a cluster. This catches violations at pull request time and gives developers immediate feedback.</p>

<h3 id="testing-opa-policies-with-conftest">Testing OPA Policies with conftest</h3>

<p><a href="https://www.conftest.dev/">conftest</a> is a utility for testing structured data against OPA policies. It evaluates Rego rules against YAML, JSON, HCL, and other configuration formats.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
</pre></td><td class="rouge-code"><pre><span class="c"># Install conftest</span>
brew <span class="nb">install </span>conftest

<span class="c"># Project structure</span>
<span class="c"># policy/</span>
<span class="c">#   deny-privileged.rego</span>
<span class="c"># manifests/</span>
<span class="c">#   deployment.yaml</span>

<span class="c"># Write a conftest policy</span>
<span class="nb">cat</span> <span class="o">&gt;</span> policy/deny-privileged.rego <span class="o">&lt;&lt;</span> <span class="sh">'</span><span class="no">EOF</span><span class="sh">'
package main

deny[msg] {
  input.kind == "Pod"
  container := input.spec.containers[_]
  container.securityContext.privileged == true
  msg := sprintf("Container %s must not be privileged", [container.name])
}

deny[msg] {
  input.kind == "Deployment"
  container := input.spec.template.spec.containers[_]
  container.securityContext.privileged == true
  msg := sprintf("Container %s in Deployment must not be privileged", [container.name])
}

deny[msg] {
  input.kind == "Pod"
  container := input.spec.containers[_]
  not container.resources.limits.memory
  msg := sprintf("Container %s must define memory limits", [container.name])
}
</span><span class="no">EOF

</span><span class="c"># Run conftest against manifests</span>
conftest <span class="nb">test </span>manifests/ <span class="nt">--policy</span> policy/

<span class="c"># Example output for a non-compliant manifest:</span>
<span class="c"># FAIL - manifests/deployment.yaml - Container nginx must not be privileged</span>
<span class="c"># 1 test, 0 passed, 0 warnings, 1 failure</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="testing-kyverno-policies-with-the-kyverno-cli">Testing Kyverno Policies with the Kyverno CLI</h3>

<p>Kyverno ships its own CLI for offline policy evaluation:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="c"># Install the Kyverno CLI</span>
brew <span class="nb">install </span>kyverno

<span class="c"># Test a policy against a resource</span>
kyverno apply disallow-privileged-containers.yaml <span class="se">\</span>
  <span class="nt">--resource</span> deployment.yaml

<span class="c"># Example output:</span>
<span class="c"># Applying 1 policy rule to 1 resource...</span>
<span class="c"># pass: 0 fail: 1 warn: 0 error: 0 skip: 0</span>

<span class="c"># Run in CI with exit code</span>
kyverno apply policies/ <span class="nt">--resource</span> manifests/ <span class="nt">--exit-code</span> 1
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="codebuild-integration-example">CodeBuild Integration Example</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
</pre></td><td class="rouge-code"><pre><span class="c1"># buildspec.yml for AWS CodeBuild</span>
<span class="na">version</span><span class="pi">:</span> <span class="m">0.2</span>

<span class="na">phases</span><span class="pi">:</span>
  <span class="na">install</span><span class="pi">:</span>
    <span class="na">commands</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">curl -L https://github.com/open-policy-agent/conftest/releases/download/v0.55.0/conftest_0.55.0_Linux_x86_64.tar.gz | tar xz</span>
      <span class="pi">-</span> <span class="s">mv conftest /usr/local/bin/</span>
      <span class="pi">-</span> <span class="s">curl -L https://github.com/kyverno/kyverno/releases/download/v1.17.1/kyverno-cli_v1.17.1_linux_x86_64.tar.gz | tar xz</span>
      <span class="pi">-</span> <span class="s">mv kyverno /usr/local/bin/</span>

  <span class="na">pre_build</span><span class="pi">:</span>
    <span class="na">commands</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">echo "Running policy checks..."</span>
      <span class="c1"># OPA/conftest validation</span>
      <span class="pi">-</span> <span class="s">conftest test k8s-manifests/ --policy policy/ --output json &gt; conftest-results.json</span>
      <span class="c1"># Kyverno CLI validation</span>
      <span class="pi">-</span> <span class="s">kyverno apply kyverno-policies/ --resource k8s-manifests/ --output json &gt; kyverno-results.json</span>

  <span class="na">build</span><span class="pi">:</span>
    <span class="na">commands</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">echo "All policy checks passed. Proceeding with deployment..."</span>
      <span class="pi">-</span> <span class="s">kubectl apply -f k8s-manifests/</span>

<span class="na">reports</span><span class="pi">:</span>
  <span class="na">conftest</span><span class="pi">:</span>
    <span class="na">files</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">conftest-results.json</span>
  <span class="na">kyverno</span><span class="pi">:</span>
    <span class="na">files</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">kyverno-results.json</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="github-actions-integration">GitHub Actions Integration</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
</pre></td><td class="rouge-code"><pre><span class="c1"># .github/workflows/policy-check.yml</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Policy Validation</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="na">paths</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s1">'</span><span class="s">k8s-manifests/**'</span>
      <span class="pi">-</span> <span class="s1">'</span><span class="s">policy/**'</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">conftest</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Run conftest</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">instrumenta/conftest-action@v0.3.0</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">files</span><span class="pi">:</span> <span class="s">k8s-manifests/</span>
          <span class="na">policy</span><span class="pi">:</span> <span class="s">policy/</span>

  <span class="na">kyverno</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install Kyverno CLI</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">curl -LO https://github.com/kyverno/kyverno/releases/download/v1.17.1/kyverno-cli_v1.17.1_linux_x86_64.tar.gz</span>
          <span class="s">tar xzf kyverno-cli_v1.17.1_linux_x86_64.tar.gz</span>
          <span class="s">sudo mv kyverno /usr/local/bin/</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Validate manifests</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">kyverno apply kyverno-policies/ --resource k8s-manifests/ --exit-code </span><span class="m">1</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="defense-in-depth-combining-aws-config-with-opa-or-kyverno">Defense-in-Depth: Combining AWS Config with OPA or Kyverno</h2>

<p>The strongest posture layers AWS Config for infrastructure-level compliance with an in-cluster policy engine for workload-level enforcement. Each layer catches different classes of misconfiguration.</p>

<h3 id="layer-1-aws-config-infrastructure">Layer 1: AWS Config (Infrastructure)</h3>

<p>AWS Config evaluates the EKS cluster itself:</p>

<ul>
  <li>Is the cluster running a supported Kubernetes version?</li>
  <li>Is the API endpoint restricted to the VPC?</li>
  <li>Are audit logs flowing to CloudWatch?</li>
  <li>Is envelope encryption enabled for Secrets?</li>
</ul>

<p>Findings aggregate in Security Hub alongside GuardDuty, Inspector, and IAM Access Analyzer results.</p>

<h3 id="layer-2-opa-or-kyverno-workload">Layer 2: OPA or Kyverno (Workload)</h3>

<p>Your in-cluster policy engine evaluates what runs on the cluster:</p>

<ul>
  <li>Are Pods running as non-root?</li>
  <li>Do all containers have resource limits?</li>
  <li>Are images pulled only from approved ECR registries?</li>
  <li>Do Deployments carry required labels?</li>
  <li>Are ServiceAccounts scoped to least privilege?</li>
</ul>

<h3 id="layer-3-cicd-shift-left">Layer 3: CI/CD (Shift Left)</h3>

<p>conftest and the Kyverno CLI validate manifests before they reach the cluster:</p>

<ul>
  <li>Policy checks run on every pull request</li>
  <li>Developers get immediate feedback</li>
  <li>Non-compliant changes never merge</li>
</ul>

<h3 id="progressive-enforcement-strategy">Progressive Enforcement Strategy</h3>

<p>A production-hardened rollout follows this progression:</p>

<ol>
  <li><strong>Audit mode</strong> (<code class="language-plaintext highlighter-rouge">enforcementAction: warn</code> in Gatekeeper, <code class="language-plaintext highlighter-rouge">validationFailureAction: Audit</code> in Kyverno): Policies log violations without blocking. Monitor findings, identify false positives, and tune rules.</li>
  <li><strong>CI enforcement</strong>: Enable <code class="language-plaintext highlighter-rouge">conftest</code> or <code class="language-plaintext highlighter-rouge">kyverno apply</code> as required CI checks. Developers learn to fix violations before merge.</li>
  <li><strong>Cluster enforcement</strong>: Switch to <code class="language-plaintext highlighter-rouge">deny</code>/<code class="language-plaintext highlighter-rouge">Enforce</code> once the violation count stabilizes. Grant time-bound exceptions via Kyverno PolicyExceptions or Gatekeeper constraint exclusions.</li>
  <li><strong>Continuous drift detection</strong>: Gatekeeper audit and Kyverno background scanning catch resources that predate the policy or bypassed admission through direct etcd writes.</li>
</ol>

<h2 id="real-world-policy-patterns">Real-World Policy Patterns</h2>

<p>The following patterns address the most common Kubernetes security risks across production environments.</p>

<h3 id="pattern-1-pod-security-standards-enforcement">Pattern 1: Pod Security Standards Enforcement</h3>

<p>Map the Kubernetes Pod Security Standards (Baseline and Restricted profiles) to your policy engine. This replaces the deprecated PodSecurityPolicy API.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
</pre></td><td class="rouge-code"><pre><span class="c1"># Kyverno: Enforce Restricted pod security profile</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kyverno.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterPolicy</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">enforce-restricted-profile</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">validationFailureAction</span><span class="pi">:</span> <span class="s">Enforce</span>
  <span class="na">rules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">restrict-capabilities</span>
      <span class="na">match</span><span class="pi">:</span>
        <span class="na">any</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">resources</span><span class="pi">:</span>
              <span class="na">kinds</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="s">Pod</span>
      <span class="na">validate</span><span class="pi">:</span>
        <span class="na">message</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Containers</span><span class="nv"> </span><span class="s">must</span><span class="nv"> </span><span class="s">drop</span><span class="nv"> </span><span class="s">ALL</span><span class="nv"> </span><span class="s">capabilities</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">may</span><span class="nv"> </span><span class="s">only</span><span class="nv"> </span><span class="s">add</span><span class="nv"> </span><span class="s">NET_BIND_SERVICE."</span>
        <span class="na">pattern</span><span class="pi">:</span>
          <span class="na">spec</span><span class="pi">:</span>
            <span class="na">containers</span><span class="pi">:</span>
              <span class="pi">-</span> <span class="na">securityContext</span><span class="pi">:</span>
                  <span class="na">capabilities</span><span class="pi">:</span>
                    <span class="na">drop</span><span class="pi">:</span>
                      <span class="pi">-</span> <span class="s">ALL</span>
                    <span class="na">=(add)</span><span class="pi">:</span>
                      <span class="pi">-</span> <span class="s">NET_BIND_SERVICE</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="pattern-2-network-policy-requirement">Pattern 2: Network Policy Requirement</h3>

<p>Ensure every namespace has at least one NetworkPolicy defined to prevent unrestricted lateral movement.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
</pre></td><td class="rouge-code"><pre><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kyverno.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterPolicy</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">require-network-policy</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">validationFailureAction</span><span class="pi">:</span> <span class="s">Audit</span>
  <span class="na">rules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">require-netpol</span>
      <span class="na">match</span><span class="pi">:</span>
        <span class="na">any</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">resources</span><span class="pi">:</span>
              <span class="na">kinds</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="s">Deployment</span>
      <span class="na">preconditions</span><span class="pi">:</span>
        <span class="na">all</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">key</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>
            <span class="na">operator</span><span class="pi">:</span> <span class="s">NotIn</span>
            <span class="na">value</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">kube-system"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">kyverno"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">gatekeeper-system"</span><span class="pi">]</span>
      <span class="na">validate</span><span class="pi">:</span>
        <span class="na">message</span><span class="pi">:</span> <span class="s2">"</span><span class="s">A</span><span class="nv"> </span><span class="s">NetworkPolicy</span><span class="nv"> </span><span class="s">must</span><span class="nv"> </span><span class="s">exist</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">namespace</span><span class="nv"> </span><span class="s">before</span><span class="nv"> </span><span class="s">deploying</span><span class="nv"> </span><span class="s">workloads."</span>
        <span class="na">deny</span><span class="pi">:</span>
          <span class="na">conditions</span><span class="pi">:</span>
            <span class="na">all</span><span class="pi">:</span>
              <span class="pi">-</span> <span class="na">key</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>
                <span class="na">operator</span><span class="pi">:</span> <span class="s">AnyNotIn</span>
                <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="pattern-3-automount-serviceaccount-token-restriction">Pattern 3: Automount ServiceAccount Token Restriction</h3>

<p>Prevent Pods from automatically mounting the ServiceAccount token unless explicitly required, reducing the blast radius of a container compromise.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
</pre></td><td class="rouge-code"><pre><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kyverno.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterPolicy</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">restrict-automount-sa-token</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">validationFailureAction</span><span class="pi">:</span> <span class="s">Enforce</span>
  <span class="na">rules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">deny-automount</span>
      <span class="na">match</span><span class="pi">:</span>
        <span class="na">any</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">resources</span><span class="pi">:</span>
              <span class="na">kinds</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="s">Pod</span>
      <span class="na">exclude</span><span class="pi">:</span>
        <span class="na">any</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">resources</span><span class="pi">:</span>
              <span class="na">namespaces</span><span class="pi">:</span>
                <span class="pi">-</span> <span class="s">kube-system</span>
      <span class="na">validate</span><span class="pi">:</span>
        <span class="na">message</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Pods</span><span class="nv"> </span><span class="s">must</span><span class="nv"> </span><span class="s">set</span><span class="nv"> </span><span class="s">automountServiceAccountToken</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">false."</span>
        <span class="na">pattern</span><span class="pi">:</span>
          <span class="na">spec</span><span class="pi">:</span>
            <span class="na">automountServiceAccountToken</span><span class="pi">:</span> <span class="kc">false</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="implementation-roadmap">Implementation Roadmap</h2>

<p>Use this phased approach to roll out policy-as-code on your EKS clusters.</p>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 1-2</strong>: Deploy your chosen policy engine (Kyverno or Gatekeeper) in audit mode on a non-production cluster</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 2-3</strong>: Import community policies for Pod Security Standards, resource limits, and image registries. Tune for your environment</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 3-4</strong>: Integrate conftest or Kyverno CLI into CI pipelines as non-blocking checks. Monitor violation reports</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 4-6</strong>: Switch CI checks to blocking. Developers fix violations before merge</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 6-8</strong>: Enable enforcement on staging clusters. Run AWS Config EKS rules in parallel</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 8-10</strong>: Enable enforcement on production clusters with a defined exception process</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Week 10-12</strong>: Aggregate findings from policy engine reports, AWS Config, and Security Hub into a single compliance dashboard</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><strong>Ongoing</strong>: Review policy violations monthly, update rules for new threat patterns, and retire obsolete policies</li>
</ul>

<h2 id="related-articles">Related Articles</h2>

<ul>
  <li><a href="/posts/aws-devsecops-pipeline-security-automation-implementation/">AWS DevSecOps Pipeline Security: Complete Automation Implementation Guide</a></li>
  <li><a href="/posts/iac-security-scanning-checkov-tfsec-aws/">IaC Security Scanning: Checkov and tfsec vs AWS-Native Controls</a></li>
  <li><a href="/posts/eks-runtime-security-falco-vs-guardduty/">EKS Runtime Security: GuardDuty vs Falco for Container Threat Detection</a></li>
  <li><a href="/posts/aws-iam-zero-trust-identity-network-deep-dive/">AWS IAM Zero Trust: Identity and Network Deep Dive</a></li>
  <li><a href="/posts/aws-cloud-security-best-practices-implementation-guide/">AWS Cloud Security Best Practices Implementation Guide</a></li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>Manual configuration reviews cannot keep pace with Kubernetes. Policy-as-code transforms security governance from a periodic audit exercise into continuous, automated enforcement. AWS Config covers the infrastructure layer – cluster versions, endpoint visibility, encryption – while OPA Gatekeeper and Kyverno operate where the real risk lives: inside the cluster, at the admission control boundary.</p>

<p>If your team writes Kubernetes YAML daily, Kyverno’s YAML-native approach and mutation capabilities offer the fastest path to value. If your organization standardizes on OPA across multiple systems (Terraform, API gateways, CI pipelines), Gatekeeper gives you one language for all policy decisions. Either way, the open-source engines are portable across any Kubernetes distribution, giving you maximum freedom and flexibility without vendor lock-in.</p>

<p>Start in audit mode, shift left with CI testing, and graduate to enforcement. Your future self – and your compliance team – will thank you.</p>

<hr />

<p><em>Connect with me on <a href="https://www.linkedin.com/in/jonpricelinux/">LinkedIn</a> to discuss policy-as-code strategies, EKS security, and DevSecOps practices.</em></p>]]></content><author><name>Jon (JR) Price</name></author><category term="AWS" /><category term="Security" /><category term="admission-control" /><category term="eks-security" /><category term="gatekeeper" /><category term="kubernetes" /><category term="kyverno" /><category term="opa" /><category term="open-source" /><category term="policy-as-code" /><summary type="html"><![CDATA[Compare OPA Gatekeeper and Kyverno on EKS. Enforce admission control, centrally manage policies across clusters, and ship policy as code.]]></summary></entry></feed>