<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Cloud Security Lab a Week (S.L.A.W)</title>
    <description>One cloudsec lab. 15-30 minutes. Every week.</description>
    
    <link>https://slaw.securosis.com/</link>
    <atom:link href="https://rss.beehiiv.com/feeds/o5E9jNG0b8.xml" rel="self"/>
    
    <lastBuildDate>Thu, 11 Jun 2026 13:43:59 +0000</lastBuildDate>
    <pubDate>Thu, 18 Dec 2025 15:00:03 +0000</pubDate>
    <atom:published>2025-12-18T15:00:03Z</atom:published>
    <atom:updated>2026-06-11T13:43:59Z</atom:updated>
    
      <category>Software Engineering</category>
      <category>Education</category>
      <category>Cybersecurity</category>
    <copyright>Copyright 2026, Cloud Security Lab a Week (S.L.A.W)</copyright>
    
    <image>
      <url>https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/publication/logo/9847097e-3fcb-469b-9346-0309032785e0/SLAW.png</url>
      <title>Cloud Security Lab a Week (S.L.A.W)</title>
      <link>https://slaw.securosis.com/</link>
    </image>
    
    <docs>https://www.rssboard.org/rss-specification</docs>
    <generator>beehiiv</generator>
    <language>en-us</language>
    <webMaster>support@beehiiv.com (Beehiiv Support)</webMaster>

      <item>
  <title>Tame Findings and Use Your CSPM as a Threat Detector</title>
  <description>I don’t understand why most people look at a CSPM only as a vulnerability scanner, but not as a threat detector. We&#39;ll cover a smidgeon of prioritizing CSPM findings, then create and investigate an incident.</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/84fcf8e7-007d-4ca4-ade1-10b4deb967d0/the_secure_way-4.png" length="353559" type="image/png"/>
  <link>https://slaw.securosis.com/p/tame-findings-and-use-your-cspm-as-a-threat-detector</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/tame-findings-and-use-your-cspm-as-a-threat-detector</guid>
  <pubDate>Thu, 18 Dec 2025 15:00:03 +0000</pubDate>
  <atom:published>2025-12-18T15:00:03Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b>It’s been 2 years, take a breather!</b></p><p class="paragraph" style="text-align:left;">CloudSLAW is kind of weird because the newsletters go out in order, starting from the first lab, no matter when you sign up. I think that’s the best way to learn — from the start — but it means some labs could be out of date by the time you hit them, especially after 2 years. </p><p class="paragraph" style="text-align:left;">Well, I can’t have that!</p><p class="paragraph" style="text-align:left;"><i>After I publish this lab I will take about 6-8 weeks to fix a handful of labs that need updates.</i> If I had a team I’d redo all the ones on the “old” AWS UI, but that isn’t realistic for just me. Instead I’ll focus on labs with breaking changes. </p><p class="paragraph" style="text-align:left;">Then, when I return to current topics, I’ll focus on more “atomic” labs which don’t require a year or more of previous labs to get up and running. It’s time to improve the long-term accessibility of this experiment.</p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;"><a class="link" href="https://slaw.securosis.com/p/deploy-a-free-commercial-real-time-cspm?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=tame-findings-and-use-your-cspm-as-a-threat-detector" target="_blank" rel="noopener noreferrer nofollow">Our lab on deploying a commercial CSPM (FireMon Cloud Defense)</a> and your FireMon credentials. </p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Lesson</h1><p class="paragraph" style="text-align:left;">If you’ve been following along with these labs, you might remember we covered the <a class="link" href="https://slaw.securosis.com/p/stage-set-the-cloud-incident-response-foundation?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=tame-findings-and-use-your-cspm-as-a-threat-detector" target="_blank" rel="noopener noreferrer nofollow">different categories of cloud threat detectors</a>. We then followed with labs on threat detection using <a class="link" href="https://slaw.securosis.com/p/build-a-time-based-threat-detector-with-lambda-and-athena-2231?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=tame-findings-and-use-your-cspm-as-a-threat-detector" target="_blank" rel="noopener noreferrer nofollow">logs</a> and <a class="link" href="https://slaw.securosis.com/p/build-a-real-time-threat-detector-with-iac-4a3d?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=tame-findings-and-use-your-cspm-as-a-threat-detector" target="_blank" rel="noopener noreferrer nofollow">events</a>, but I only threatened (haha) to cover detection using configuration. </p><p class="paragraph" style="text-align:left;">&lt;batman voice&gt; <i>I don’t make threats; I make promises</i>. &lt;/batman voice&gt;</p><p class="paragraph" style="text-align:left;">Yeah, alright, maybe I’m overdoing the drama. Anyway…</p><p class="paragraph" style="text-align:left;">Although this lab is focused on threat detection, I actually want to cover two topics.</p><h2 class="heading" style="text-align:left;" id="managing-cspm-findings">Managing CSPM Findings</h2><p class="paragraph" style="text-align:left;">CSPMs and Cloud Native Application Protection Platforms (CNAPP, which adds workload and other tools to CSPM) aren’t really traditional vulnerability scanners because they look for misconfigurations, but they do share one trait. They tend to find… a lot… of things. Like, a <b>LOT.</b> But like a vulnerability scanner, not all of them are of equal value, and not all are relevant in the context of your given deployment.</p><p class="paragraph" style="text-align:left;">For example most tools look for default NACLs, which we didn’t cover because I never use them. This finding will appear once for every subnet you have. It isn’t a misconfiguration, it’s a reasonable choice, but all the tools look for it since someone slapped it into some industry benchmark.</p><p class="paragraph" style="text-align:left;">Looking at my current CloudSLAW org scan with Cloud Defense, I have 138 critical and high misconfigurations.</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/9c919779-2530-439b-ab67-6912604894dd/image.png?t=1765924884"/></div><p class="paragraph" style="text-align:left;">That number is right, but in a non-useful way. I probably only care about a handful of those. Having helped build a CSPM, the problem as the vendor/maintainer is that you need to be comprehensive, but you will never have enough context. We saw this in our Prowler scan as well.</p><p class="paragraph" style="text-align:left;">Okay, so how do I prioritize? I always start by looking for the Big Gaping Security Holes, then correlating findings with my high-value accounts:</p><ul><li><p class="paragraph" style="text-align:left;">Static credentials (IAM users)</p></li><li><p class="paragraph" style="text-align:left;">Users/roles with full admin credentials</p></li><li><p class="paragraph" style="text-align:left;">Anything public</p></li></ul><p class="paragraph" style="text-align:left;">I work my way down the list eventually, but at the start if you keep a laser focus on the main ways you get exploited (public and IAM stuff) it really helps you filter out the noise. I also override severities to meet my own preferences, with tools which support this.</p><p class="paragraph" style="text-align:left;">I suggest you poke around your own findings. We don’t really have time for depth in this lab, but here’s the challenge which I’ll walk through it quickly in a sec when we log in:</p><ul><li><p class="paragraph" style="text-align:left;"><b>Identify and remediate any security group with port 22 exposed to the Internet. </b>That’s a BGSH, so see how fast you can find and fix it.</p></li></ul><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/f9f0e5c3-b444-45c0-8e6b-f6b0dae52231/image.png?t=1765926189"/></div><h2 class="heading" style="text-align:left;" id="cspm-for-threat-detection">CSPM for Threat Detection</h2><p class="paragraph" style="text-align:left;">I think we all agree that big holes are bad, but what does that have to do with threat detection? Well, the evidence from multiple cloud providers and incident response teams consistently shows that the main vector for attacking cloud is to steal credentials and use them to create misconfigurations which expose data or allow them to hijack resources.</p><p class="paragraph" style="text-align:left;">For example, if an attacker steals an access key/secret key which allows them to change a security group, they can open up port 22 and start attacking an instance directly. Or they’ll make something public, or share it with an account under their control. There are… a lot of options. Thus…</p><p class="paragraph" style="text-align:left;"><i><b>A misconfiguration may be your first and best indicator of attack!</b></i></p><p class="paragraph" style="text-align:left;">An attacker in the management plane will change things in ways that probably don’t look right. The faster you can detect and respond, the better. They aren’t just brute forcing virtual machines, they are stealing credentials using infostealer malware and then using those credentials to change configurations to steal or damage you.</p><p class="paragraph" style="text-align:left;">Imagine you detect a public S3 bucket full of customer data. Is that a mistake? Someone violating policy? An attacker using an employee’s credentials to steal data?</p><p class="paragraph" style="text-align:left;">Yeah, <b>GuardDuty</b> will probably detect my super obvious example, but the list is much longer (actually infinite). Like taking a snapshot of an instance with customer data and sharing it with an account under the attacker’s control.</p><p class="paragraph" style="text-align:left;">Any CSPM can help augment threat detection, but I prefer ones with two capabilities: real-time detection, and capture of management plane API calls. Depending on your tool of choice, it might support real-time but that may not be the default deployment option, so just be aware it isn’t supported by all tools and might require manual enablement.</p><p class="paragraph" style="text-align:left;">Also, not every CSPM’s findings are good for threat detection. We don’t have time to get into all the options today, especially since they also change based on your particular environment. </p><h3 class="heading" style="text-align:left;" id="todays-key-points-are">Key Lesson Points</h3><ul><li><p class="paragraph" style="text-align:left;">When getting started with CSPM, focus on the Big Gaping Security Holes to reduce overload.</p></li><li><p class="paragraph" style="text-align:left;">CSPM is an excellent source for threat detection, since it identifies the outcomes (misconfigurations) of activities attackers use to compromise cloud.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lab"><b>The Lab</b></h1><p class="paragraph" style="text-align:left;">First I’ll walk through finding a BGSH (Big Gaping Security Hole) and then we’ll create an exposure and investigate it in real time.</p><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/nDfR2l-silY" width="100%"></iframe><h2 class="heading" style="text-align:left;" id="stepby-step">Step-by-Step</h2><p class="paragraph" style="text-align:left;"><b>Open up 2 tabs</b>, and then log into your <b>Sign-in portal </b>and <a class="link" href="https://defense.prod2.firemon.cloud?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=tame-findings-and-use-your-cspm-as-a-threat-detector" target="_blank" rel="noopener noreferrer nofollow">https://defense.prod2.firemon.cloud</a>. </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/bdf25a5d-221e-4a01-96f5-fb13eef69c9a/image.png?t=1766003351"/></div><p class="paragraph" style="text-align:left;">We’ll start in Cloud Defense and find one of those BGSHs. This time, a security group with port 22 exposed to the Internet. Start in <b>Posture Monitoring &gt; Checks &gt; Security Group Allows Unrestricted Access to Ports with High Risk:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/1d036735-c2b9-4bd0-a8a2-28e5569f10dd/image.png?t=1766003610"/></div><p class="paragraph" style="text-align:left;">I like the <i>Checks</i> view for identifying systemic issues, since it quickly shows you which posture checks have the most findings. In this case that sliver of red is for failing security groups. See a lot of red in the bar? That’s one where you consistently fail.</p><p class="paragraph" style="text-align:left;">Scroll down and drill down to see the exact exposed security group. I picked the one in Oregon. With the account ID and region we can quickly access the offending account for remediation:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/8f5aa8f0-4ed4-4343-a1ec-37413e04db20/image.png?t=1766003800"/></div><p class="paragraph" style="text-align:left;">I’m jumping through quickly, but you should be able to follow along.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/cb5f59ea-218a-44b7-bc15-a8bbe040f21c/image.png?t=1766003826"/></div><p class="paragraph" style="text-align:left;">From there it’s easy to go into that account and region. Here’s a hint: it will be your <b>TestAccount1</b> account and <b>Oregon.</b> <b>Delete that rule from the security group in the AWS console.</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ad67a565-2e70-4915-8230-21cc817174b1/image.png?t=1766003923"/></div><p class="paragraph" style="text-align:left;">Within a few seconds it will show is <i>Pass</i> in Cloud Defense, but the failure will still show up in the history.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/5ce0a097-8a18-4707-974c-46012e375b03/image.png?t=1766003991"/></div><p class="paragraph" style="text-align:left;">Yeah, real-time is pretty cool. </p><p class="paragraph" style="text-align:left;">Okay, on to <i>threat detection!</i> </p><p class="paragraph" style="text-align:left;">First we need to set up email alerts. <b>Click the user icon in the upper right corner &gt; User Settings:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/e99d7a30-622d-4cb0-8862-99b12d2f5df8/image.png?t=1766004296"/></div><p class="paragraph" style="text-align:left;">You’ll need to set a day and time for scheduled alerts, but don’t worry, we won’t enable them. Instead just <b>check Send CSPM alert emails</b> <b>&gt; Save:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/56e79836-7e00-4f4c-931e-f1ec9adb39d7/image.png?t=1766004565"/></div><p class="paragraph" style="text-align:left;">Now go to <b>Posture Monitoring &gt; Alerts &gt; + Alert: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/c59d4639-6dd0-414d-9c5d-95912cd99fdd/image.png?t=1766004889"/></div><p class="paragraph" style="text-align:left;">Let’s build an alert for anytime a snapshot becomes public. </p><p class="paragraph" style="text-align:left;">We only need to change 2 settings to alert on a specific finding. <b>Set results to “Fail”, and Checks to “EBS Snapshot is Publicly Accessible”: </b></p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/303da656-2c0d-4b77-9789-f6caa6c0da41/image.png?t=1766005647"/></div><p class="paragraph" style="text-align:left;">Now let’s trigger it. <b>Sign-in portal &gt; TestAccount1 &gt; Oregon region &gt; EC2 &gt; Snapshots &gt; check the one labeled CSI-incident-1</b> if you have it. If not? Any snapshot will work. Don’t have one? Go to <b>Volumes,</b> pick one, and create a snapshot. Don’t have a volume? Launch an instance (any instance) and then snapshot. Then <b>Modify permissions:</b> </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/352e0740-7b7a-4bad-a619-f003b861c6b4/image.png?t=1766006010"/></div><p class="paragraph" style="text-align:left;">Then check <b>Public &gt; Modify permissions</b>: </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/f9458e4f-00a0-4469-ae73-8b927caa96fe/image.png?t=1766006078"/></div><p class="paragraph" style="text-align:left;"><b>WHY DID YOU HIT YOURSELF IN THE FACE???</b></p><p class="paragraph" style="text-align:left;">Okay, with that exposure in place, you probably received an alert (maybe 2, depending on Security Hub). Let’s go back into <b>Cloud Defense &gt; Posture Monitoring &gt;Findings </b>to start investigation. (Or click the link in the email notification).</p><p class="paragraph" style="text-align:left;">You’ll see the EBS snapshot finding right at the top. <b>Click it:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/345a7955-3fff-426c-bb90-518f13899401/image.png?t=1766006408"/></div><p class="paragraph" style="text-align:left;">A lot of the information you want is right on this first page. When it happened, who caused it, their IP address, and the API call that caused the misconfiguration.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/73bd2e26-1072-4f7a-90da-543b0c537f97/image.png?t=1766006456"/></div><p class="paragraph" style="text-align:left;">With the tabs on this screen you can investigate most of the incident using my <a class="link" href="https://securosis.com/blog/resolve-90-of-cloud-incidents-with-recipe-picks/?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=tame-findings-and-use-your-cspm-as-a-threat-detector" target="_blank" rel="noopener noreferrer nofollow">RECIPE PICKS mnemonic</a> for incident response. In my case I hit a wall when I looked at the related resources — it identified the volume the snapshot came from, but I deleted that volume a long time ago. </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/bad49679-1502-4afb-8847-2dec5e9e74a3/image.png?t=1766006719"/></div><p class="paragraph" style="text-align:left;">Go ahead and click around — you can see all the consolidated API calls referring to that resource and all the calls from the IAM role which triggered the event, and review the configurations of all the items in inventory.</p><p class="paragraph" style="text-align:left;">This example was very simple. A single API call created a clear misconfiguration. But there are many other attacks which are more complex and involve multiple API calls. The advantage of using your CSPM for threat detection is it can alert on the misconfigurations which correlate best with attacker activity. It’s more an <i>Indicator of Compromise</i> than an <i>Indicator of Attack</i>. What’s the difference? An IoC is a sign the attacker is already in and did something bad. An IoA will fire off during an attack, including (sometimes) attacks which were stopped.</p><p class="paragraph" style="text-align:left;"><b>To clean out Cloud Defense</b></p><p class="paragraph" style="text-align:left;">You can keep it for at least 60 days. Even if it goes past 60 days, you won’t be charged. To remove it from your organization just go into your organization management account and delete the stack (<b>not</b> the StackSet — deleting the stack will take care of that).</p><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=9a61ffc9-a299-4bb1-a6fe-40ba34e9a229&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Deploy a (Free) Commercial Real-Time CSPM</title>
  <description>For the first time we&#39;ll integrate an external tool into our Organization, and in the process learn about real-time CSPM vs. scans, some of which you&#39;ve already built without knowing it (perhaps...)!</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/9b31e35a-8e80-4a46-b77c-a9de3ab662d6/the_secure_way-3.png" length="391781" type="image/png"/>
  <link>https://slaw.securosis.com/p/deploy-a-free-commercial-real-time-cspm</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/deploy-a-free-commercial-real-time-cspm</guid>
  <pubDate>Thu, 04 Dec 2025 15:00:41 +0000</pubDate>
  <atom:published>2025-12-04T15:00:41Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b><a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=deploy-a-free-commercial-real-time-cspm" target="_blank" rel="noopener noreferrer nofollow">Go Pro for Support and Extras!</a></b></p><p class="paragraph" style="text-align:left;">CloudSLAW is, and always will be, free. But to help cover costs and keep the content up to date we have an optional Patreon. For $10 per month you get access to our support Discord, office hours, exclusive subscriber content (not labs — those are free — just extras) and more. <a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=deploy-a-free-commercial-real-time-cspm" target="_blank" rel="noopener noreferrer nofollow">Check it out!</a></p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;">An AWS Organization. This one works even if you don’t have our standard lab structure.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Lesson</h1><p class="paragraph" style="text-align:left;">At the start of our series on Cloud Security Posture Management I mentioned that we would explore the three main options, each of which you will encounter at some point if your job touches cloud security:</p><ul><li><p class="paragraph" style="text-align:left;">Open Source. We used Prowler, which also offers a commercial version.</p></li><li><p class="paragraph" style="text-align:left;">Cloud Service Provider (CSP) native. We used AWS Security Hub (which is in transition as I write these labs).</p></li><li><p class="paragraph" style="text-align:left;">Commercial. Which we will cover today, using the Cloud Defense product from FireMon.</p></li></ul><p class="paragraph" style="text-align:left;">Note that this barely scratches the surface — there are <b>many</b> open source and commercial tools on the market (heck, there are currently <b>2</b> Security Hubs!). But by the time we are finished you will have a very good sense of different deployment options, capabilities, and user experiences. </p><p class="paragraph" style="text-align:left;">There are two main learning objectives for this lab:</p><ul><li><p class="paragraph" style="text-align:left;">See how to deploy a third-party, SaaS-based security tool connected to your AWS Organization.</p></li><li><p class="paragraph" style="text-align:left;">Understand a real-time detection architecture. (Similar to what we deployed for our S3 lab series, but at larger scale).</p></li><li><p class="paragraph" style="text-align:left;">Get a big-picture understanding of CSPM capabilities.</p></li></ul><p class="paragraph" style="text-align:left;">We will follow up with one more lab which goes into more detail on using a CSPM to detect and investigate misconfiguration. </p><h2 class="heading" style="text-align:left;" id="disclaimers-and-disclosures">Disclaimers and Disclosures</h2><p class="paragraph" style="text-align:left;">I debated a fair bit on using commercial tools in any of these labs, or even an Open Source tool (with commercial options) like Prowler. But you’ll encounter these tools in the wild, and I can’t teach certain skills using only what AWS provides. Besides, that would also be problematically biased because, in many cases, a third-party tool will be the better choice. With that out of the way…</p><ul><li><p class="paragraph" style="text-align:left;">Today we will use <a class="link" href="https://defense.firemon.cloud?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=deploy-a-free-commercial-real-time-cspm" target="_blank" rel="noopener noreferrer nofollow">FireMon Cloud Defense</a>, a commercial CSPM.</p></li><li><p class="paragraph" style="text-align:left;">I founded the startup that was acquired by FireMon and became Cloud Defense. I worked there full-time for 3 years and still have an ownership stake, so if anyone ever buys FireMon I can stop paying for beer with stock certificates (VC/tech stuff is weird).</p></li><li><p class="paragraph" style="text-align:left;">FireMon already had a free tier, but they stood up a special, isolated version for these labs. It’s a complex architecture but all Infrastructure as Code, so they literally just copied and pasted into a new AWS account(s).</p></li><li><p class="paragraph" style="text-align:left;">FireMon created a special CloudSLAW deployment process which automates nearly everything in a single CloudFormation stack run from the management account. It even auto-registers using your management account email address.</p></li><li><p class="paragraph" style="text-align:left;">Everything is free for 60 days, then it disables itself. You will never be billed. They wouldn’t know how to bill you even if they wanted to.</p></li></ul><p class="paragraph" style="text-align:left;">Having our own little sandbox for these labs is pretty great, and I want to thank Brandy Peterson (one of my co-founders) for making it all possible. </p><h2 class="heading" style="text-align:left;" id="why-every-csp-ms-has-an-inventory">Why Every CSPMs Has an Inventory</h2><p class="paragraph" style="text-align:left;">Let’s think about everything we’ve learned about assessing configurations, in the context of preventing attacks.</p><p class="paragraph" style="text-align:left;">You’ve already seen multiple ways of scanning for misconfigurations. From the <a class="link" href="https://slaw.securosis.com/p/schedule-security-scanning-with-a-serverless-fanout-pattern?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=deploy-a-free-commercial-real-time-cspm" target="_blank" rel="noopener noreferrer nofollow">timed serverless scans we built ourselves</a>, to <a class="link" href="https://slaw.securosis.com/p/run-a-cross-account-prowler-cspm-scan?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=deploy-a-free-commercial-real-time-cspm" target="_blank" rel="noopener noreferrer nofollow">running code in an instance like Open Source Prowler</a>, to manually checking things. The problems with all of them are time, scale, and the overhead of actually looking for misconfigurations. And that’s before we get into the problem of API service limits (the number of API calls you can make in a period of time).</p><p class="paragraph" style="text-align:left;">This is why every single major CSPM platform uses its own inventory. Instead of making a lot of costly API calls to run different assessments on the same (or different) resources, the tools:</p><ul><li><p class="paragraph" style="text-align:left;">Sweep through accounts and run the API calls to gather all the configuration information on what’s running.</p></li><li><p class="paragraph" style="text-align:left;">Store it in a database.</p></li><li><p class="paragraph" style="text-align:left;">Run assessments against that database, which is more flexible and efficient.</p></li><li><p class="paragraph" style="text-align:left;">Keep the database updated.</p></li></ul><p class="paragraph" style="text-align:left;">Think about the problem: if I want to run 20 different kinds of assessments on an Instance, that would be a lot of repeated API calls. And sometimes I want to know things like “is this instance in this security group?” which is also a pain to run on its own. But if I have all that data in a database, those are queries rather than API calls, and a lot more efficient. Having an inventory gives you a better picture without having to poke the cloud provider every time.</p><p class="paragraph" style="text-align:left;">The problem is that you still need to collect that information, and keep it up to date. Different tools handle that on their own schedules — ranging from every few minutes, to every hour, to once a day. They still need to make those DESCRIBE API calls to get the updated into.</p><p class="paragraph" style="text-align:left;">A few platforms, including Cloud Defense, update their inventory in real time. It’s kind of a cool approach (I didn’t code it, but I did work on the initial design). It is similar to what we previously did in <a class="link" href="https://slaw.securosis.com/p/distributing-and-testing-security-autoremediation-events?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=deploy-a-free-commercial-real-time-cspm" target="_blank" rel="noopener noreferrer nofollow">our real-time autoremediation lab</a>.</p><p class="paragraph" style="text-align:left;">Here’s how it works:</p><ul><li><p class="paragraph" style="text-align:left;">Capture all the CloudTrail calls via EventBridge.</p></li><li><p class="paragraph" style="text-align:left;">Filter those calls for any “change” events (create, update, delete).</p></li><li><p class="paragraph" style="text-align:left;">For each event, extract the resource identifier (<i>e.g.,</i> the instance ID).</p></li><li><p class="paragraph" style="text-align:left;">This triggers a lambda which reads the configuration of only that resource.</p></li><li><p class="paragraph" style="text-align:left;">This updated configuration is stored in the database.</p></li><li><p class="paragraph" style="text-align:left;">That change event triggers all the assessments associated with that resource type.</p></li></ul><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/d9bf0dc3-39db-49f8-aad6-3514b702d8d6/image.png?t=1764790985"/></div><p class="paragraph" style="text-align:left;">This is weirdly more efficient that running timed scans, since it only requests <i>updated</i> information. However, since things can slip through the cracks, any platform which uses this model should still run a daily scan (and with Cloud Defense, you’ll see it also run an initial scan when it first onboards a new account). </p><p class="paragraph" style="text-align:left;">Go reread our old labs, because we already implemented simplified versions of this. I mean, I originally came up with the idea, so I need to milk it for all it’s worth. </p><h2 class="heading" style="text-align:left;" id="automating-deployment">Automating Deployment</h2><p class="paragraph" style="text-align:left;">There’s kind of a cool thing going on with the deployment that you’ll see in a minute, and it’s worth explaining. Actually, two cool things:</p><ul><li><p class="paragraph" style="text-align:left;">We only deploy a single CloudFormation Stack in a single account, but somehow it deploys the tool into all our accounts. What happens is the Stack creates the StackSet, which then deploys the required resources (IAM role and EventBridge stuff) into the rest of the organization. It’s more complicated to build, but done well can be easier and more reliable than messing with StackSets directly. </p></li><li><p class="paragraph" style="text-align:left;">The stack auto-registers with the Cloud Defense platform and creates your account there. This uses Lambda functions, and you can read all the code in the template. For our labs FireMon created a special version which pulls our management account email and auto-registers using that as your username (I requested this to save us steps). </p></li></ul><p class="paragraph" style="text-align:left;">These are some common techniques used in more-advanced infrastructure as code, and if you read through the template you might notice some of the other things we’ve learned in these labs (like unique external IDs).</p><h1 class="heading" style="text-align:left;" id="the-lab"><b>The Lab</b></h1><p class="paragraph" style="text-align:left;">This lab is mostly setup and exploration — we will follow up with the next lab, where we’ll run through the core capabilities of CSPM. </p><p class="paragraph" style="text-align:left;">The setup is kind of cool, especially that they been it tuned for CloudSLAW, even though it’s a commercial tool. And as a reminder this is running in a separate version of Cloud Defense that FireMon stood up just for these labs, isolated from their current production environment. </p><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/JzZBsqgBLhI" width="100%"></iframe><h2 class="heading" style="text-align:left;" id="stepby-step">Step-by-Step</h2><p class="paragraph" style="text-align:left;">Start in your <b>Sign-in portal &gt; CloudSLAW &gt; AdministratorAccess &gt; (double check you are in the Oregon region) &gt; Organizations &gt; copy out your root OU ID (r-xxxx): </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/7b666756-e881-4a0b-891e-f16ccb40c1a3/image.png?t=1764791931"/></div><p class="paragraph" style="text-align:left;"></p><p class="paragraph" style="text-align:left;"><b>CloudFormation &gt; Create stack &gt; With new resources:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/070991b8-6297-4383-bdc3-47e9ea17f58c/image.png?t=1764791485"/></div><p class="paragraph" style="text-align:left;">Use the following:</p><ul><li><p class="paragraph" style="text-align:left;">Name: <b>CloudDefense</b></p></li><li><p class="paragraph" style="text-align:left;">S3 URL: <a class="link" href="https://cloudslaw.s3-us-west-2.amazonaws.com/lab68.template?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=deploy-a-free-commercial-real-time-cspm" target="_blank" rel="noopener noreferrer nofollow">https://cloudslaw.s3-us-west-2.amazonaws.com/lab68.template</a></p></li></ul><p class="paragraph" style="text-align:left;">The ONLY parameter you should change is to paste in your <b>org root OU ID </b>(which starts with ‘r-’, <b>not</b> ‘o-’): </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/74b06aa2-0b2b-4a45-b2e9-c1432790286a/image.png?t=1764792033"/></div><p class="paragraph" style="text-align:left;">Don’t change anything else, run through the rest of the screens, and <b>Submit.</b></p><p class="paragraph" style="text-align:left;">This is a complicated stack which:</p><ul><li><p class="paragraph" style="text-align:left;">Creates some resources in your management account for onboarding that account with Cloud Defense. Remember, normally a StackSet <i>won’t deploy into the management account!</i> Using a normal stack gets us around that.</p></li><li><p class="paragraph" style="text-align:left;">It creates a Lambda function to collect the email of the management account, and then use that to create a new customer account with Cloud Defense. This was the magic little bit I suggested to FireMon to make the lab easier. This feature is <b>not</b> part of the process as a paying customer, where you might want things configured differently.</p></li><li><p class="paragraph" style="text-align:left;">It creates StackSets in Virginia and Oregon and pushes them into every account. Technically we could have deployed to just certain OUs (this was the parameter where we used the root OU instead). </p></li></ul><p class="paragraph" style="text-align:left;">That’s the magic bit special for this CloudSLAW lab — auto-registration. Outside this lab you need to create an account with your SaaS provider first. </p><p class="paragraph" style="text-align:left;">I recommend taking a look at this stack, and then the StackSets it deploys. You’ll see an external ID, as we discussed in our Prowler lab, and some other more-advanced techniques like integrating Lambda registration functions (<a class="link" href="https://slaw.securosis.com/p/skills-challenge-investigate-your-own-aws-attack-with-athena-3d0d?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=deploy-a-free-commercial-real-time-cspm" target="_blank" rel="noopener noreferrer nofollow">which we previously used</a>) and nested stacks (common in larger deployments). </p><p class="paragraph" style="text-align:left;">As this stack is running, go <b>check your management account email!</b> You’ll have an account registration message from FireMon (check your spamtrap if you don’t see it within 5 minutes). <b>Click Activate My Account</b>:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/be4c1ea6-0177-40a0-bb44-9d68774416a7/image.png?t=1764792898"/></div><p class="paragraph" style="text-align:left;">I’m planning at least one more lab on this platform, so set a password you won’t lose. As a reminder, this account will be shut down in 60 days, and I’ll show you how to remove the stack in the next lab:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/3f7a4247-d478-4e8a-859c-0292bfafb6a2/image.png?t=1764793006"/></div><p class="paragraph" style="text-align:left;"><b>IF YOU RUN INTO ANY ISSUES CONTACT ME DIRECTLY AT </b><a class="link" href="mailto:RMOGULL@SECUROSIS.COM" target="_blank" rel="noopener noreferrer nofollow"><b>RMOGULL@SECUROSIS.COM</b></a><b>. </b>Since this is free I’ll be handling support — not FireMon.</p><p class="paragraph" style="text-align:left;">Once you’re logged in, click the little side arrow to expand the left menu and start exploring:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/09e9e4d8-662b-44b3-aa04-cdc01bdc8451/image.png?t=1764793081"/></div><p class="paragraph" style="text-align:left;">Your CloudFormation stack is probably still running and provisioning all your accounts (FireMon set a limit of 10 accounts, which is plenty for us… their commercial version supports thousands). It takes time for Cloud Defense to do its first sweep of all your accounts to fill in the Inventory. But you can click it to watch it work.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/0f596271-599a-4843-8b2c-a3f9a0948121/image.png?t=1764793238"/></div><p class="paragraph" style="text-align:left;">Assessments are all performed instantly as each resource lands in the Inventory, so you’ll see those results filling in as well — the entire platform is serverless and event-driven:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/891b2647-4087-43e8-9688-84354af4ff26/image.png?t=1764793322"/></div><p class="paragraph" style="text-align:left;">This is a fully functional version of the platform. The only limit is the 10 accounts, and it has some internal governors running to keep costs down, so it may be a little slower if hundreds of you sign up all at once (or maybe not — nobody’s ever tested this lab version).</p><p class="paragraph" style="text-align:left;">Feel free to poke around and explore the capabilities. In the next lab we’ll learn how to filter results and create exemptions, then investigate a misconfiguration. These are core skills you’ll need no matter which platform you end up using.</p><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=254561d6-f2bf-4606-bbd4-8f5a83a71220&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Enable Whichever Version of Security Hub AWS is Supporting These Days</title>
  <description>Security Hub is Amazon&#39;s built-in CSPM and... other stuff... and... AWS keeps changing it and is in the middle of a major transition. We&#39;ll turn it on and see what we get!</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/88166e4d-3ca4-4d4b-86bf-b38feb44edf7/1.png" length="390584" type="image/png"/>
  <link>https://slaw.securosis.com/p/enable-whichever-version-of-security-hub-aws-is-supporting-these-days</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/enable-whichever-version-of-security-hub-aws-is-supporting-these-days</guid>
  <pubDate>Thu, 20 Nov 2025 15:00:51 +0000</pubDate>
  <atom:published>2025-11-20T15:00:51Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b><a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=enable-whichever-version-of-security-hub-aws-is-supporting-these-days" target="_blank" rel="noopener noreferrer nofollow">Go Pro for Support and Extras!</a></b></p><p class="paragraph" style="text-align:left;">CloudSLAW is, and always will be, free. But to help cover costs and keep the content up to date we have an optional Patreon. For $10 per month you get access to our support Discord, office hours, exclusive subscriber content (not labs — those are free — just extras) and more. <a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=enable-whichever-version-of-security-hub-aws-is-supporting-these-days" target="_blank" rel="noopener noreferrer nofollow">Check it out!</a></p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;">The <a class="link" href="https://slaw.securosis.com/p/run-a-cross-account-prowler-cspm-scan?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=enable-whichever-version-of-security-hub-aws-is-supporting-these-days" target="_blank" rel="noopener noreferrer nofollow">target environment configured in our second Prowler lab</a>.</p></li><li><p class="paragraph" style="text-align:left;">Our first <a class="link" href="https://slaw.securosis.com/p/best-way-start-aws-security-hub?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=enable-whichever-version-of-security-hub-aws-is-supporting-these-days" target="_blank" rel="noopener noreferrer nofollow">Security Hub lab</a>, which delegated administration to our SecurityAudit account.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Lesson</h1><p class="paragraph" style="text-align:left;">Okay, this is the weirdest lab I’ve built yet. Why? Because I have absolutely no idea how long it will last or what things will look like when you log in. Sure, I try to keep these things updated, but I’m JUST ONE PERSON, and some things change darn quickly in the cloud.</p><p class="paragraph" style="text-align:left;">Here’s the TL;DR: Security Hub is Amazon’s built-in security center, and it included CSPM capabilities. But AWS recently split out the non-CSPM parts, named them all Security Hub, renamed the <i>old</i> Security Hub “Security Hub CSPM”, and has said more changes are coming since the new version is only in <b>public preview,</b> so… YOLO!</p><h2 class="heading" style="text-align:left;" id="the-curious-case-of-two-security-hu">The Curious Case of Two Security Hubs</h2><p class="paragraph" style="text-align:left;">Security Hub CSPM is what you&#39;re probably thinking of when someone says &quot;Security Hub,&quot; if you laid hands on AWS before this change. It runs all those security standards checks, compliance frameworks, and best practice assessments. This is the part that competes with tools like Prowler, which we covered in the last lab. Security Hub CSPM generates findings about misconfigurations, compliance violations, and security gaps.</p><p class="paragraph" style="text-align:left;">The &quot;new&quot; Security Hub (which is still in public preview as I write this, but will probably reach general availability soon) is actually a meta-layer that sits on top. Think of it as a security data aggregator and correlation engine. It takes findings from Security Hub CSPM, but also pulls in alerts and findings from GuardDuty, Inspector, Macie, and other AWS security services. Then it correlates all that data, reduces noise, and helps you prioritize what actually matters.</p><p class="paragraph" style="text-align:left;">I’ll let AWS marketing do the writing for me: “<i>Security Hub is a unified cloud security solution that prioritizes your critical security issues and helps you respond at scale. Security Hub detects security issues by automatically correlating and enriching security signals from multiple sources, such as posture management, vulnerability management (Amazon Inspector), sensitive data (Amazon Macie), and threat detection (Amazon GuardDuty). … Through intuitive visualizations, Security Hub transforms complex security signals into actionable insights…</i>”</p><p class="paragraph" style="text-align:left;">Here&#39;s the flow: <b>Security Hub CSPM</b> checks your environment and generates findings <b>→</b> those findings feed into <b>Security Hub</b> <b>→ Security Hub correlates</b> them with other security data (GuardDuty, Access Analyzer, Inspector, etc.) <b>→ you get a prioritized, deduplicated view of what needs attention</b>. </p><p class="paragraph" style="text-align:left;">Now I know you are asking yourself, “didn’t Security Hub always aggregate findings from other services?” Yep. But … it didn’t really do any fancy correlation, so Security Hub wasn’t very competitive with other Cloud Service Provider (CSP) native security tools — like Defender for Cloud and the Google Security Command Center. </p><p class="paragraph" style="text-align:left;">Now, about those costs I mentioned. Security Hub CSPM relies heavily on AWS Config to do its job. Config is what actually tracks the state of your resources and detects changes. Some checks happen in real time when resources change, while others run on a schedule. The problem? Config ain’t cheap, so we can’t (yet) just turn on all of Security Hub + Security Hub CSPM without keeping an eye on our bill.</p><h2 class="heading" style="text-align:left;" id="new-cspm-who-dis">New CSPM, Who Dis?</h2><p class="paragraph" style="text-align:left;">Although I expect Security Hub to change significantly as it reaches general release and continues to evolve, for educational purposes this new version offers more baseline CSPM capabilities we can use to expand our knowledge:</p><ul><li><p class="paragraph" style="text-align:left;"><b>Inventory:</b> While Security Hub doesn’t offer a full and complete cross-account inventory, it <b>does</b> aggregate its inventory for covered resources — the big ones like EC2 instances and Lambda functions.</p></li><li><p class="paragraph" style="text-align:left;"><b>Findings:</b> Similar to our Prowler findings, these cover CSPM results (misconfigurations), but also any findings from other tools, such as a vulnerability detected by Inspector (a vulnerability scanner which we haven’t used yet). This starts sounding more like a CNAPP (<span style="color:rgb(45, 45, 45);font-family:Helvetica, Arial, sans-serif;font-size:16px;">Cloud Native Application Protection Platform)</span>: it can combine management plane misconfigurations with vulnerability info.</p></li><li><p class="paragraph" style="text-align:left;"><b>Correlation:</b> Combine different sources so I mostly only need to look in one place to figure out what’s going on. This ties tightly into the Inventory (resource) view so I can see misconfigurations, GuardDuty alerts, and more without bouncing into 5 tools.</p></li><li><p class="paragraph" style="text-align:left;"><b>Exposure and Attack Graph:</b> <i>Exposure findings</i> are higher-priority issues along the lines of, “Oh crap, <b>that</b> is public.” Is something misconfigured, reachable, and/or vulnerable? The attack graph then shows you visually how it might be exploited. It’s eye candy, but who doesn’t love candy?</p></li><li><p class="paragraph" style="text-align:left;"><b>Automation:</b> Mostly this is “create a ticket somewhere for someone to fix this thing” or “write stupidly complex EventBridge-Lambda-based autoremediations like that insane dude who runs the coleslaw newsletter.”</p></li></ul><p class="paragraph" style="text-align:left;">All of these capabilities (and more) are available in major third-party CSPM/CNAPP tools. They tend to have much more robust capabilities in terms of what they look for, compliance and security standards they support, report generation, filtering and organization of findings, etc. But it’s nice to see AWS building more of this natively, and it works well for us to take our learning to the next level.</p><p class="paragraph" style="text-align:left;">Remember, my advice is to turn on your CSPM on day one. We only waited this long to cover the tools in labs because I wanted to give you a solid baseline of security knowledge first.</p><h3 class="heading" style="text-align:left;" id="todays-key-points-are">Key Lesson Points</h3><ul><li><p class="paragraph" style="text-align:left;">Modern CSPM tools (many of which are full CNAPP tools) do more than just find basic misconfigurations. They correlate a wide range of security telemetry to help identify and remediate issues.</p></li><li><p class="paragraph" style="text-align:left;">Security Hub is Amazon’s latest security center, with core CNAPP capabilities to aggregate findings from multiple sources, including workload security scans.</p></li><li><p class="paragraph" style="text-align:left;">Security Hub CSPM is the “old” Security Hub, and is now the feature that only handles management plane misconfigurations and compliance. It feeds its results into the <i>new</i> Security Hub.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lab"><b>The Lab</b></h1><div class="section" style="background-color:transparent;border-color:#ff0300;border-radius:5px;border-style:solid;border-width:5px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;">🚨 <b>WARNING WARNING WARNING!!!!! </b>🚨 </p><p class="paragraph" style="text-align:center;">Do not forget to shut everything down when your are done. Also, <b>you will get possibly thousands of emails</b> as Security Hub finds things. To shut down, you will need to:</p><ul><li><p class="paragraph" style="text-align:left;">Disable Config</p></li><li><p class="paragraph" style="text-align:left;">Disable Security Hub</p></li><li><p class="paragraph" style="text-align:left;">Disable Security Hub CSPM (Security Standards)</p></li></ul><p class="paragraph" style="text-align:left;">Otherwise your bill will go up. And honestly, it ain’t worth it for how little you’ll learn. Heck, <b><i>feel free to just read this lab and skip the hands-on… I won’t be offended.</i></b> YOU HAVE BEEN WARNED!</p><p class="paragraph" style="text-align:left;"><b>This lab will be outdated pretty quickly. The core principles will be the same, but not the details. I fully intend to update this after the next big AWS update.</b></p></div><p class="paragraph" style="text-align:left;">As I warned, this lab is a bit… rough around the edges. Aside from the AWS issues, I also managed to make a change in my account that I failed to record, and can’t go back and reset. We will:</p><ul><li><p class="paragraph" style="text-align:left;">Update delegated administration for Security Hub</p></li><li><p class="paragraph" style="text-align:left;">Enable Security Hub</p></li><li><p class="paragraph" style="text-align:left;">Enable a Security Standard in Security Hub CSPM</p></li><li><p class="paragraph" style="text-align:left;">Enable Config</p></li><li><p class="paragraph" style="text-align:left;">Regret our life decisions</p></li></ul><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/77LgYZpDZqY" width="100%"></iframe><h2 class="heading" style="text-align:left;" id="stepby-step">Step by Step</h2><p class="paragraph" style="text-align:left;">First we need to <i>update our delegated administration policy</i>. <b>THIS MAY BE DIFFERENT depending on when you finished our first Security Hub lab (since you might already be on the new policy). </b>Start in your <b>Sign-in portal &gt; CloudSLAW &gt; AdministratorAccess &gt; Security Hub</b> (remember, NOT Security Hub CSPM — and don’t blame me!). Then click <b>Get Started:</b></p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/969a5ecf-5b85-4bda-87f4-f45b84e693a8/image.png?t=1763587304"/></div><p class="paragraph" style="text-align:left;">This is where things may get weird. You <i>may need to update your delegated administrator policy,</i> or you might be fine and just need to <b>Enable Security Hub for my account.</b> This is due to the transition. You are a pro now, so figure it out. Then <b>Configure.</b> Here was mine:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:1px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/37372ccd-c543-47aa-a1b3-781c7665a582/image.png?t=1763587610"/></div><p class="paragraph" style="text-align:left;">Now we need to get Config up and running, since that what Security Hub CSPM currently relies on to get its… configuration information. Since we are already in our management account we can do this with <b>CloudFormation &gt; StackSets &gt; Create stack:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/dc8b00a2-16ab-4b19-9efe-2b1d0aeaa4a7/image.png?t=1763588148"/></div><p class="paragraph" style="text-align:left;">You’ve done this <b>a lot,</b> so here are the settings to change as you walk through the screens:</p><ul><li><p class="paragraph" style="text-align:left;"><b>Create stack set</b></p></li><li><p class="paragraph" style="text-align:left;">Use the template: <a class="link" href="https://cloudslaw.s3-us-west-2.amazonaws.com/lab67.template?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=enable-whichever-version-of-security-hub-aws-is-supporting-these-days" target="_blank" rel="noopener noreferrer nofollow">https://cloudslaw.s3-us-west-2.amazonaws.com/lab67.template</a></p></li><li><p class="paragraph" style="text-align:left;"><b>Name: “Config”</b></p></li><li><p class="paragraph" style="text-align:left;"><b>Deploy new stacks</b></p></li><li><p class="paragraph" style="text-align:left;"><b>Deploy to organization</b></p></li><li><p class="paragraph" style="text-align:left;">Specify regions <b>us-west-2 and us-east-1</b></p></li><li><p class="paragraph" style="text-align:left;">Max concurrent accounts: 10</p></li><li><p class="paragraph" style="text-align:left;">Failure tolerance: 9</p></li><li><p class="paragraph" style="text-align:left;"><b>Submit </b>and wait until it’s <i>Succeeded</i></p></li><li><p class="paragraph" style="text-align:left;"><b>Exit the management account (close the tab)</b></p></li></ul><p class="paragraph" style="text-align:left;">Okay, now let’s go to <b>Sign in portal &gt; SecurityAudit &gt; Administrator Access &gt; Security Hub &gt; Getting started </b>(yes, again) <b>&gt; Enable Security Hub: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/9c304278-10b0-4e00-8df1-c1cc8306cd07/image.png?t=1763588677"/></div><p class="paragraph" style="text-align:left;">You can see the four “feeder” services that fuel Security Hub here. Including… Security Hub (CSPM). We already have GuardDuty running, and ‘sorta’ Security Hub CSPM. Depending on the state of this lab, you likely enabled it but without any Security Standards enabled (so it isn’t looking for much) since we didn’t want to run Config. Skip past this for now and <b>Go to Security Hub.</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/e737e734-d89e-45b6-b448-ddce92ac43bc/image.png?t=1763588939"/></div><p class="paragraph" style="text-align:left;">Next we want to set Security Hub to aggregate all findings to our current region. You’ll need to go to <b>Settings &gt; General &gt; Cross-Region aggregation &gt; US West (Oregon) </b> and then also select <b>US East Virginia,</b> then <b>Save: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/2e970b18-8136-422d-8375-80022f8f1d50/image.png?t=1763589324"/></div><p class="paragraph" style="text-align:left;">Right now this only has Security Hub running in this one account. We need to enable it for our entire organization so go to <b>Configurations &gt; Policies &gt; Create policy: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/2bfba3cf-9266-4d62-af53-2b0ee9c1a2b6/image.png?t=1763589616"/></div><ul><li><p class="paragraph" style="text-align:left;"><b>Name: Default</b></p></li><li><p class="paragraph" style="text-align:left;"><b>Enable all regions</b></p></li><li><p class="paragraph" style="text-align:left;"><b>Scroll to the bottom and click Next, then Create:</b></p></li></ul><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/5e8dc74f-8542-43f1-979f-df89d1e1042a/image.png?t=1763589756"/></div><div class="section" style="background-color:transparent;border-color:#ff0300;border-radius:5px;border-style:solid;border-width:5px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;">🚨 <b>WARNING WARNING WARNING!!!!! </b>🚨 </p><p class="paragraph" style="text-align:center;">We are perilously close to the step that will flood your inbox with <b>thousands of emails!</b></p><p class="paragraph" style="text-align:center;">Why? Because we previously set up an EventBridge rule to forward all Security Hub alerts to our email. </p><p class="paragraph" style="text-align:center;">You can turn this off. Just go to <b>EventBridge &gt; Rules &gt; SecurityHubAlerts &gt; check the circle, and Disable</b></p></div><p class="paragraph" style="text-align:left;">Even without Security Hub CSPM fully enabled, findings will start showing up. Most of them are warnings related to not having all the AWS services turned on.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/f7a2420b-c9b3-4668-9166-5dc301da2173/image.png?t=1763590388"/></div><p class="paragraph" style="text-align:left;">You can already see the problem with CSPM. As important and powerful as it is, basically every tool generates a <b>ton</b> of findings that may or may not matter. In fact, <i>this is why the new Security Hub exists!</i> As you’ll see in a second; while it takes all these feeds it tries to aggregate, correlate, and prioritize.</p><p class="paragraph" style="text-align:left;">How well does it work? I don’t know for sure yet, since I’m very early in my testing.</p><p class="paragraph" style="text-align:left;">Let’s finish turning on posture checks so we get more information and can compare it more directly to other tools, like Prowler. Go to <b>Detection engines &gt; Security Hub CSPM,</b> which will pop open another window. </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/1e0ac0cb-fe12-4c8f-84ea-5f6013b98108/image.png?t=1763590633"/></div><p class="paragraph" style="text-align:left;">To enable CSPM for our entire organization we need to use a Policy. Go to <b>Configuration &gt; Policies &gt; check the default one already there &gt; Edit</b> (If you don’t see one, go with Create and use the same settings): </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/d561deb8-7dde-4d5a-9902-433df7625769/image.png?t=1763590782"/></div><p class="paragraph" style="text-align:left;"><b>Enable AWS Foundational Security Best Practices:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/b8312412-87d1-488a-b6b2-fa464668226d/image.png?t=1763590847"/></div><p class="paragraph" style="text-align:left;">Then <b>Enable all controls</b> and <b>un-check Customize control parameters:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/b7524694-dc9d-44fe-b6c4-a61e8e87efa7/image.png?t=1763590930"/></div><p class="paragraph" style="text-align:left;">Then <b>Next &gt; Save policy and apply:</b></p><p class="paragraph" style="text-align:left;">This will take a bit to roll out through your accounts, but it is updating everything to start collecting EVEN MORE CSPM FINDINGS BWAHAHAHAHA!!!!</p><p class="paragraph" style="text-align:left;">It takes hours for findings to show up. I left mine running overnight and it found… a lot. Too much, since most of them I don’t care about.</p><p class="paragraph" style="text-align:left;">Yes, I do need to know that for compliance, but not always for security. Or maybe it’s only a security issue sometimes. This is the challenge of CSPM, and why we waited.</p><p class="paragraph" style="text-align:left;">The new Security Hub attempts to address this by offering some more-focused finding types such as Threats and Exposures. None of these triggered in my account even after running it overnight, but our organization should be in pretty good shape, even though we opened a few things up. </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/9beb7f6e-fc2d-45b7-aa70-40db97770072/image.png?t=1763591228"/></div><p class="paragraph" style="text-align:left;">At this point, feel free to click around, look at Resources and Findings, and maybe even see if you can create an exposure finding and see the attack graph. I… ran out of time to write this lab up. :)</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/d7639462-6628-45d6-a847-2a36695ec7ce/image.png?t=1763591568"/><div class="image__source"><span class="image__source_text"><p>It may take a bit of time for these to show up.</p></span></div></div><p class="paragraph" style="text-align:left;"><b>I’d leave it running for a day or overnight, then delete:</b></p><h2 class="heading" style="text-align:left;" id="deletion-instructions">Deletion instructions</h2><ul><li><p class="paragraph" style="text-align:left;">In <b>SecurityAudit</b></p><ul><li><p class="paragraph" style="text-align:left;">Go to <b>Security Hub CSPM</b></p><ul><li><p class="paragraph" style="text-align:left;"><b>Configuration &gt; Policies &gt; check your policy &gt; Edit &gt; delete (click the X) for the Security Best Practices</b></p></li><li><p class="paragraph" style="text-align:left;"><b>Save and apply</b></p></li></ul></li><li><p class="paragraph" style="text-align:left;">Go to <b>Security Hub</b></p><ul><li><p class="paragraph" style="text-align:left;"><b>Configurations</b> <b>&gt; Policies &gt; Default &gt; Disable all regions</b></p><ul><li><p class="paragraph" style="text-align:left;">This actually <b>prevents</b> Security Hub from being turned on manually anywhere — remember that for the future.</p></li></ul></li></ul></li></ul></li><li><p class="paragraph" style="text-align:left;">Go to <b>Sign-in portal &gt; CloudSLAW &gt; AdministratorAccess &gt; Organizations and copy your root OU ID (it starts with “r-xxxx”)</b></p><ul><li><p class="paragraph" style="text-align:left;"><b>CloudFormation &gt; StackSets &gt; Config &gt; Delete stacks from stack set</b></p><ul><li><p class="paragraph" style="text-align:left;"><b>Enter the root OU</b></p></li><li><p class="paragraph" style="text-align:left;"><b>Add all regions</b></p></li><li><p class="paragraph" style="text-align:left;"><b>Max concurrent accounts: 10</b></p></li><li><p class="paragraph" style="text-align:left;"><b>Failure tolerance: 9</b></p></li><li><p class="paragraph" style="text-align:left;"><b>Submit</b></p></li></ul></li><li><p class="paragraph" style="text-align:left;">Then <b>Actions &gt; Delete stack set</b></p></li></ul></li></ul><p class="paragraph" style="text-align:left;">That’s it! All Security Hub versions and Config are now disabled.</p><h3 class="heading" style="text-align:left;" id="key-points">Lab Key Points</h3><ul><li><p class="paragraph" style="text-align:left;">Security Hub is the new security center for AWS and it aggregates, correlates, and prioritizes security information.</p></li><li><p class="paragraph" style="text-align:left;">Security Hub CSPM is the old version of Security Hub, which now “feeds” posture findings into the new Security Hub.</p></li><li><p class="paragraph" style="text-align:left;">Our labs all used delegated administration, which we previously configured and had to update.</p></li><li><p class="paragraph" style="text-align:left;">These services are not free.</p></li></ul><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=a289c16d-71e6-4ccf-819b-f14ea36e1ea8&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Run a Cross-Account Prowler CSPM Scan</title>
  <description>It&#39;s time to learn a little more about CSPM, and run our first cross-account scan. </description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/21df0ba6-454f-45a3-852e-edcedef7cd10/the_secure_way-2.png" length="416107" type="image/png"/>
  <link>https://slaw.securosis.com/p/run-a-cross-account-prowler-cspm-scan</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/run-a-cross-account-prowler-cspm-scan</guid>
  <pubDate>Thu, 30 Oct 2025 15:00:04 +0000</pubDate>
  <atom:published>2025-10-30T15:00:04Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b><a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=run-a-cross-account-prowler-cspm-scan" target="_blank" rel="noopener noreferrer nofollow">Go Pro for Support and Extras!</a></b></p><p class="paragraph" style="text-align:left;">CloudSLAW is, and always will be, free. But to help cover costs and keep the content up to date we have an optional Patreon. For $10 per month you get access to our support Discord, office hours, exclusive subscriber content (not labs — those are free — just extras) and more. <a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=run-a-cross-account-prowler-cspm-scan" target="_blank" rel="noopener noreferrer nofollow">Check it out!</a></p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;">Our <a class="link" href="https://slaw.securosis.com/p/the-secure-way-to-integrate-cloudsec-tools-using-external-ids-cspm-lab-1?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=run-a-cross-account-prowler-cspm-scan" target="_blank" rel="noopener noreferrer nofollow">first Prowler lab</a>. </p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Lesson</h1><p class="paragraph" style="text-align:left;">In our last lab we set up our environment to run the Open Source version of Prowler from our SecurityAudit account. We configured it to use an External ID to prevent the confused deputy attack when it assumes our cross account role to run scans.</p><p class="paragraph" style="text-align:left;">Now it’s time to learn a little more about CSPM, it’s big brother CNAPP, and why these tools are so important that they are one of the first things I install when I set up a new (non-lab) AWS organization.</p><h1 class="heading" style="text-align:left;" id="cspm-capabilities">CSPM Capabilities</h1><p class="paragraph" style="text-align:left;">I’ll be honest, I kind of hate the name “Cloud Security Posture Management”. Like, who cares if my cloud is slouching in its chair? But I also have to admit it makes sense. We can’t call these cloud vulnerability scanners because they aren’t necessarily vulnerabilities, they are just things configured in a way that maybe in the right context could be useful to an attacker. These aren’t really vulnerabilities in the traditional sense because they are allowed configurations, just ones that could be problematic. We <i>need</i> public S3 buckets, but a public bucket <i>might also be bad if we didn’t actually want that particular bucket public</i>.</p><p class="paragraph" style="text-align:left;">When I first started in cloud we had to figure all this out ourselves by hand. I quickly moved from doing everything in the console to building my own tools. Then I found Prowler (which we installed last week) and swapped over. Tony kept Prowler up to date with great coverage. It was originally a really complicated bash script that used the AWS CLI!</p><p class="paragraph" style="text-align:left;">Obviously everyone had this problem, and thus vendors were born (<a class="link" href="https://defense.firemon.cloud?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=run-a-cross-account-prowler-cspm-scan" target="_blank" rel="noopener noreferrer nofollow">including mine, although we had a bit of a different focus</a>). Over time these tools consolidated to a core feature set and eventually Gartner caught up and branded the category “CSPM”. At their simplest, modern CSPM tools:</p><ul><li><p class="paragraph" style="text-align:left;">Connect to a cloud account and read the configuration of the services</p></li><li><p class="paragraph" style="text-align:left;">Identify <b>misconfigurations</b> (based on their own list and public standards)</p></li><li><p class="paragraph" style="text-align:left;">Rate the misconfiguration severity</p></li><li><p class="paragraph" style="text-align:left;">Generate reports</p></li></ul><p class="paragraph" style="text-align:left;">Practically speaking, modern tools all have additional features:</p><ul><li><p class="paragraph" style="text-align:left;">They run centrally or as a SaaS platform and can assess large multi-account and multi-cloud deployments</p></li><li><p class="paragraph" style="text-align:left;">Store all the configuration information in their own internal database, providing customers a full <b>inventory</b> of what is running</p></li><li><p class="paragraph" style="text-align:left;">Include an extensive <b>security and compliance database</b> to assess against multiple standards</p></li><li><p class="paragraph" style="text-align:left;">Offer extensive complex analysis, such as identifying IAM misconfigurations that could lead to privilege escalation</p></li><li><p class="paragraph" style="text-align:left;">Support visualizations of relationships, such as showing the security groups and IAM roles associated with an instance (often called a <i>security graph</i>)</p></li><li><p class="paragraph" style="text-align:left;">Offer alerting and complex reporting</p></li></ul><p class="paragraph" style="text-align:left;">It’s really the scale and deep multi-cloud support that sets these tools apart from our earlier ones. But there’s more you might find:</p><ul><li><p class="paragraph" style="text-align:left;">Real time inventory and assessments (most tools are time based, and some only update daily)</p></li><li><p class="paragraph" style="text-align:left;">Correlation with CloudTrail and other logs (e.g. identify who caused a misconfiguration)</p></li><li><p class="paragraph" style="text-align:left;">Advanced security graphs that identify potential attack sequences</p></li><li><p class="paragraph" style="text-align:left;">And more!</p></li></ul><h1 class="heading" style="text-align:left;" id="from-cspm-to-cnapp">From CSPM to CNAPP</h1><p class="paragraph" style="text-align:left;">CSPM is the foundation, helping you visualize and manage your cloud “posture” (configuration). They’ve become essential for managing security and compliance, and every major CSP has one built in (for a fee). </p><p class="paragraph" style="text-align:left;">Yeah, you’d think they would include a critical security capability in your subscription, but nope. On the upside, third-party tools historically have done a much better job.</p><p class="paragraph" style="text-align:left;">While CSPM focuses on core security posture, another tool started developing in parallel. <i>Cloud Workload Protection Platforms (CWPP… blame Gartner not me)</i> actually ARE vulnerability scanners- for instances, containers, and even serverless functions. “Wait” you ask, “didn’t we already have vulnerability scanners for operating systems and stuff?”.</p><p class="paragraph" style="text-align:left;">Why yes we did! And they royally sucked for cloud! Those vendors were too fat and happy and asked us to do painful things like open up our security groups for scans, or install agents in containers that… really don’t run well in containers. CWPP products made up cool tricks like scanning snapshots and images so they didn’t have to muck with running things, or build better agents.</p><p class="paragraph" style="text-align:left;">Anyway, as you might imagine CWPP started showing up in CSPM tools, or they were bought, or they bought the other guys, or all of the above. And then vendors started adding or buying up features to scan Infrastructure as Code, assess APIs, perform Cloud Detection and Response, focus on identities (a category called Cloud Identity and Entitlement Management (CIEM) because WE JUST CAN’T HELP OURSELVES WITH THESE NAMES AND OH PLEASE MAKE IT STOP!!!!</p><p class="paragraph" style="text-align:left;">Anyway, that’s Cloud Native Application Protection Platform (CNAPP). It started as CSPM+CWPP and now includes whatever the vendor can buy or copy from someone else. This isn’t actually a criticism, just the reality that when you see CNAPP it most definitely will include CSPM and workload protection, and might include a LOT of other things you may or may not need depending on who you are and what you are doing. </p><h2 class="heading" style="text-align:left;" id="why-i-subjected-you-to-this-painful">Why I subjected you to this painful history lesson</h2><p class="paragraph" style="text-align:left;">My goal with CloudSLAW is to provide you deep, practical knowledge to work as a cloud security professional. Although this lab is over a year into the program, CSPM/CNAPP will be one of the first tools you likely touch if you get a cloud security job. It’s important to know what they are and how they work, which is why we are running through this series and you will get hands-on with multiple different types of tools.</p><p class="paragraph" style="text-align:left;">Today is a very basic deployment of Prowler, which was the first Open Source CSPM available, long before anyone put those four words together. <a class="link" href="https://prowler.com?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=run-a-cross-account-prowler-cspm-scan" target="_blank" rel="noopener noreferrer nofollow">Prowler also has a commercial version</a> and can do a lot more than how we are going to use it. </p><p class="paragraph" style="text-align:left;">This week we will keep it simple. Last week you scanned your local account, and this week we will use our cross-account role to scan our <b>Workload1</b> account after we create some misconfigurations. Then you’ll have the chance to change settings and see some different results, or even scan ALL your accounts (which will probably break since our instance size is too small).</p><p class="paragraph" style="text-align:left;"><i>This lab is just level 1- a simple cross account, non-parallelized scan.</i> It’s something I might still do if I’m performing a hand assessment, but mostly I would use a SaaS platform our automate Prowler to run at larger scale with parallel operations.</p><h3 class="heading" style="text-align:left;" id="todays-key-points-are">Today’s lesson key points are:</h3><ul><li><p class="paragraph" style="text-align:left;">Cloud Security Posture Management is an essential security tool to find and fix security and compliance misconfigurations.</p></li><li><p class="paragraph" style="text-align:left;">Cloud Native Application Protection Platforms are the “big brother/sister” of CSPM and add in other capabilities, like vulnerability scanning the operating systems and applications in instances.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lab"><b>The Lab</b></h1><p class="paragraph" style="text-align:left;">This is an easy one- we’ll deploy a CloudFormation template in a target account, start up our Prowler instance, log in, make a quick config change, run a scan, and then review the results.</p><p class="paragraph" style="text-align:left;">One important point: when I set up this lab I used an instance size for the free tier. This is really underpowered, and in my testing the instance would hang on a full Prowler scan. We’ll use a command line to narrow the scope of the scan which speeds things up and works every time. <i>If you want to run a bigger scan, you can always pull the AMI for the instance, launch a new one that’s sized larger, and then run the scan.</i></p><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/MhZOEoy1Shw" width="100%"></iframe><h2 class="heading" style="text-align:left;" id="stepby-step">Step-by-Step</h2><p class="paragraph" style="text-align:left;">Start in your <b>Sign-in portal &gt; TestAccount1 &gt; AdministratorAccess &gt; CloudFormation &gt; Create Stack (with new resources)</b>. You’ll use these settings:</p><ul><li><p class="paragraph" style="text-align:left;">Template URL: <a class="link" href="https://cloudslaw.s3-us-west-2.amazonaws.com/lab66.template?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=run-a-cross-account-prowler-cspm-scan" target="_blank" rel="noopener noreferrer nofollow">https://cloudslaw.s3-us-west-2.amazonaws.com/lab66.template</a></p></li><li><p class="paragraph" style="text-align:left;">Name: <b>misconfigurations</b></p></li></ul><p class="paragraph" style="text-align:left;">The rest is just the defaults, including clicking the checkbox in the blue box to enable creation of IAM resources.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/de515da2-20e5-4b5c-bedb-7104225cf6de/image.png?t=1761508552"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/f9822f14-196f-4f77-995a-f392aaf9fd33/image.png?t=1761508595"/></div><p class="paragraph" style="text-align:left;"><b>Wait until it’s complete in case there are any errors.</b></p><p class="paragraph" style="text-align:left;">Okay, now <b>close that tab &gt; sign in portal &gt; SecurityAudit &gt; AdministratorAccess &gt; us-west-2 (Oregon) region &gt; EC2 &gt; Instances</b>. <i>Note… if you don’t see any instances make sure you don’t have the screen filtered to “instance state = running” since we stopped the instance at the end of the lab last week.</i></p><p class="paragraph" style="text-align:left;">Then:</p><ul><li><p class="paragraph" style="text-align:left;"><b>Click the checkbox next to the instance</b></p></li><li><p class="paragraph" style="text-align:left;"><b>Instance state</b></p></li><li><p class="paragraph" style="text-align:left;"><b>Start instance</b></p></li><li><p class="paragraph" style="text-align:left;"><b>Wait about 3 minutes</b></p></li></ul><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/4f6baee7-a055-42d3-8308-39497524b454/image.png?t=1761508942"/></div><p class="paragraph" style="text-align:left;">We need to provide the instance enough time to start and for the SSM agent to run and connect to the Session Manager part of the service. Then <b>Connect &gt; Session Manager &gt; Connect: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/f27455eb-b882-4c21-865f-06cc4a8ce967/image.png?t=1761509059"/></div><p class="paragraph" style="text-align:left;">Once you’re connected, we will run three command lines to get things set up: </p><ul><li><p class="paragraph" style="text-align:left;"><code>sudo su - ec2-user</code> (log in as the default ec2-user)</p></li></ul><p class="paragraph" style="text-align:left;">Then <b>copy and paste</b> this line: </p><div class="codeblock"><pre><code>sed -i &#39;/prowler aws \\/a\  --services iam s3 ec2 \\&#39; scan-account.sh</code></pre></div><p class="paragraph" style="text-align:left;">What’s this doing? This is our fix for that performance issue I ran into. This line gets added to the command line we use to call Prowler, and tells it to only scan 3 services: aim, s3, and ec2. The full script looks like this:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/6ad4200f-dcd4-454d-a5bd-8b6b2bec2d6d/image.png?t=1761509311"/></div><p class="paragraph" style="text-align:left;">The script:</p><ul><li><p class="paragraph" style="text-align:left;">Sets some variables, including the target account that we will send in from the command line, and the external ID which is set is our configuration file (review last week to see how we did this).</p></li><li><p class="paragraph" style="text-align:left;">It also sets our target S3 directory for the report.</p></li><li><p class="paragraph" style="text-align:left;">It runs Prowler with those settings, and limits the scan to only those 3 services.</p></li><li><p class="paragraph" style="text-align:left;">It outputs links to get to the reports for the scan. This just makes your life a lot easier (as you’ll see in a few minutes).</p></li></ul><p class="paragraph" style="text-align:left;">If you want to see the scripts in the directory just type <code>ls</code> to list the contents and <code>cat &lt;filename&gt;</code> to see the file.</p><p class="paragraph" style="text-align:left;">All your prep is done, so hop back over to your sign in portal and <b>copy out the account ID of TestAccount1: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/211ee323-e545-478f-bc43-15988fdac7df/image.png?t=1761509555"/></div><p class="paragraph" style="text-align:left;">Now start your scan with <code>bash scan-account.sh &lt;paste in the account ID&gt;</code></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/e9d15666-efdb-49e2-8d4e-df2bac5fc201/image.png?t=1761509625"/><div class="image__source"><span class="image__source_text"><p>5</p></span></div></div><p class="paragraph" style="text-align:left;">There’s a little cloud magic going on now:</p><ul><li><p class="paragraph" style="text-align:left;">Your instance has a role with permissions to assume the scanning role in the target account.</p></li><li><p class="paragraph" style="text-align:left;">We provide Prowler with the role and the external ID in our script.</p></li><li><p class="paragraph" style="text-align:left;">Prowler assumes the role in TestAccount1 using the external ID that was passed in originally in the user-data field (last lab).</p></li><li><p class="paragraph" style="text-align:left;">This setup will work to scan any account in your organization except your management account.</p></li></ul><p class="paragraph" style="text-align:left;">It will take 5 minutes or so to run, maybe less. Once it’s finished click the link (or copy/paste) to go to the directory in S3 with your results: </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/c949ee7f-5154-49d9-808e-46fe51a61d60/image.png?t=1761509858"/></div><p class="paragraph" style="text-align:left;">You can download any or all of them, but for this lab start with the HTML version. Click on it, then download, then open it up in your browser (just drag and drop the html file into your browser):</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/420860a0-4e99-4216-9dde-6027e55acb92/image.png?t=1761509955"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/41a0c995-9f21-403d-b334-e6b337255df7/image.png?t=1761509983"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/9084f24a-9c4d-4643-ab55-e463063b9109/image.png?t=1761510012"/></div><p class="paragraph" style="text-align:left;">Woah, that’s a LOT of results! And even if you filter out to only FAIL you still… have a LOT of failed checks. <i>This is the fundamental problem of CSPM!</i> Every single tool on the market is VERY noisy. It’s up to you to understand and prioritize the results. This is why I really worry when I advise an organization and they are completely tool driven and the security team doesn’t really understand the fundamentals of cloud. That’s one of the main reasons I waited a year for a lab on a tool I normally use day 1; I needed to make sure you had solid fundamentals so you could interpret the results.</p><p class="paragraph" style="text-align:left;"><i>AND WE ONLY SCANNED 3 SERVICES!!!</i></p><p class="paragraph" style="text-align:left;">For example, look at the Network ACL results. There are a lot of failed findings there for things like leaving default NACLs. Guess what? I <b>always leave default NACLs!</b> This isn’t Prowler’s fault, since some compliance standards say you should lock down your NACLs all the time. This standards were probably written by someone who hasn’t ever seen a cloud in the sky, never mind AWS/Azure/GCP/TheBadPlace.</p><p class="paragraph" style="text-align:left;">Okay, the next bit of this lab is a little choose your own adventure. I recommend you do the following:</p><ul><li><p class="paragraph" style="text-align:left;">Filter the results and try to identify which ones you would consider important. </p></li><li><p class="paragraph" style="text-align:left;">See if you can identify the 3 misconfigurations (well, maybe 4) that I have in the CloudFormation template. Then go review the template to see if you found them all.</p></li><li><p class="paragraph" style="text-align:left;">There’s a stopped instance in TestAccount1. Start it, run another scan, and see if your results change and how.</p></li><li><p class="paragraph" style="text-align:left;">Scan different accounts. </p></li><li><p class="paragraph" style="text-align:left;">Try the <a class="link" href="http://scan-all-org.sh?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=run-a-cross-account-prowler-cspm-scan" target="_blank" rel="noopener noreferrer nofollow">scan-all-org.sh</a> script. If you do this you MUST increase your instance size or add the services line to the script so it doesn’t max out your instance. (use the same <code>sed</code> line but change the target file name). </p><ul><li><p class="paragraph" style="text-align:left;">This scripts calls the AWS Organizations API, lists all your accounts, and then runs the scan on each account serially.</p></li></ul></li><li><p class="paragraph" style="text-align:left;">Download the CSV file and play around with sorting and other spreadsheet stuff I can’t do because I hate spreadsheets. </p></li></ul><p class="paragraph" style="text-align:left;">There’s a LOT of information in this deceptively simple lab. Take your time, play with Prowler, and get a sense of things. As a reminder <i>this is not the best way to run Prowler!!!!</i> I have it running in a way that works very well for labs, but in a real situation I would use the web interface instead of saving files to S3.</p><p class="paragraph" style="text-align:left;">Phew. Cya in the next lab where we’ll learn about yet another way to use CSPMs!</p><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=db902837-3484-40be-a5d6-b11994c61472&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Invite to the CSA AWS Outage Briefing Tomorrow!</title>
  <description>I&#39;m giving a special briefing for the Cloud Security Alliance corporate members tomorrow on the AWS outage and resiliency. And all you CloudSLAW subscribers are invited to check it out</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/6ce87b3b-f0dd-45c0-8ab3-282ba96ec5b4/image.png" length="210791" type="image/png"/>
  <link>https://slaw.securosis.com/p/invite-to-the-csa-aws-outage-briefing-tomorrow</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/invite-to-the-csa-aws-outage-briefing-tomorrow</guid>
  <pubDate>Thu, 23 Oct 2025 20:13:37 +0000</pubDate>
  <atom:published>2025-10-23T20:13:37Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><p class="paragraph" style="text-align:left;">Hey everyone,</p><p class="paragraph" style="text-align:left;">Just a quick note and invitation. As part of my new role as Chief Analyst for the Cloud Security Alliance I’m expanding the program with exclusive briefings for our corporate members.</p><p class="paragraph" style="text-align:left;">Tomorrow (Friday) will be the first one, and I have permission to invite all 5000+ of you to check it out!</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/6ce87b3b-f0dd-45c0-8ab3-282ba96ec5b4/image.png?t=1761250352"/></div><p class="paragraph" style="text-align:left;">We probably won’t do this often, but as I build out new CSA programs don’t be surprised if I try some things out. I’m also working on a little extra topping for Pro members.</p><p class="paragraph" style="text-align:left;">Here are the details. </p><h1 class="heading" style="text-align:left;" id="member-briefing-lessons-from-the-aw"><b>Member Briefing: Lessons from the AWS Outage</b></h1><p class="paragraph" style="text-align:left;">CSA is hosting a member-only emergency briefing this <b>Friday, October 24 at 10:00 AM PT</b> to unpack what this week’s AWS outage tells us about cloud resiliency and the realities of multi-cloud. Register below and we’ll add you to the Zoom invite. Seats are limited to CSA Corporate Members.</p><p class="paragraph" style="text-align:left;">Sign up here: <a class="link" href="https://airtable.com/appZnEzwBda493De4/pag9p5q9rr9jwliHq/form?prefill_Source=CloudSLAW&utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=invite-to-the-csa-aws-outage-briefing-tomorrow" target="_blank" rel="noopener noreferrer nofollow">https://airtable.com/appZnEzwBda493De4/pag9p5q9rr9jwliHq/form?prefill_Source=CloudSLAW</a></p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=cbaa7e50-74f6-40ce-8895-e650abdf8216&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>The Secure Way to Integrate Cloudsec Tools using External IDs (CSPM Lab 1)</title>
  <description>We kick off our lab series on Cloud Security Posture Management by setting everything up to securely run tools from our SecurityAudit account</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/a1443c7b-6413-4266-a09e-540df703500a/the_secure_way.png" length="400011" type="image/png"/>
  <link>https://slaw.securosis.com/p/the-secure-way-to-integrate-cloudsec-tools-using-external-ids-cspm-lab-1</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/the-secure-way-to-integrate-cloudsec-tools-using-external-ids-cspm-lab-1</guid>
  <pubDate>Thu, 09 Oct 2025 15:00:00 +0000</pubDate>
  <atom:published>2025-10-09T15:00:00Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b><a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=the-secure-way-to-integrate-cloudsec-tools-using-external-ids-cspm-lab-1" target="_blank" rel="noopener noreferrer nofollow">Go Pro for Support and Extras!</a></b></p><p class="paragraph" style="text-align:left;">CloudSLAW is, and always will be, free. But to help cover costs and keep the content up to date we have an optional Patreon. For $10 per month you get access to our support Discord, office hours, exclusive subscriber content (not labs — those are free — just extras) and more. <a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=the-secure-way-to-integrate-cloudsec-tools-using-external-ids-cspm-lab-1" target="_blank" rel="noopener noreferrer nofollow">Check it out!</a></p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;">An AWS Organization with a SecurityAudit account and delegated administration for running StackSets.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Lesson</h1><p class="paragraph" style="text-align:left;">You know how I said we’d build all this security stuff in pretty much the same order as I would do it for real?</p><p class="paragraph" style="text-align:left;">Yeah, I kinda blew it.</p><p class="paragraph" style="text-align:left;">This is a lab we should have done quite a while back, but there are a lot of little logistical things that kept putting it off. <b>But today is our day!</b> (How’s that for motivational speaking?)</p><p class="paragraph" style="text-align:left;">Today we finally get around to starting our setup to deploy a<b> Cloud Security Posture Management (CSPM)</b> tool. In real life this is something I try to do on day 1 when I walk into a new org, but these are the sort of tools that make a lot more sense after you have a strong cloud security knowledge foundation. </p><p class="paragraph" style="text-align:left;">So congratulations, young padawans. You are ready for the next big step on your journey to cloud security Jedi status.</p><p class="paragraph" style="text-align:left;">(We may have had a little coffee issue today and perhaps my brain is… being weird).</p><h2 class="heading" style="text-align:left;" id="cloud-security-posture-management">Cloud Security Posture Management</h2><p class="paragraph" style="text-align:left;">I’ve mentioned CSPM a few times in previous posts. You saw hints of it when we enabled Security Hub (but not the actual CSPM part), and when I talked about it as an essential element of cloud incident response.</p><p class="paragraph" style="text-align:left;">The TL;DR is that CSPM tools connect to your cloud accounts via API, run a crap ton of read API calls (e.g. Describe/List/Get) and try to <i>identify misconfigurations</i> which could lead to security and compliance issues. Modern tools can do a lot more, but they still all rely on this core capability.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ae43b5b6-27ed-40aa-b8e8-0eb6d1d2e8a7/image.png?t=1759260360"/><div class="image__source"><span class="image__source_text"><p>FireMon Cloud Defense, a CSPM tool</p></span></div></div><p class="paragraph" style="text-align:left;">There was a time when I was one of a very small group of people performing cloud security assessments. Many of us had to write our own tools since there were no features in the cloud platforms (heck, Azure didn’t even exist) and trying everything manually via the CLI or console wasn’t scalable. Netflix was the first to talk about building their own internal tool (Security Monkey) and, long story short, eventually commercial tools flooded the market (including mine — we’ll get there).</p><p class="paragraph" style="text-align:left;">What’s a misconfiguration you ask? It can be as simple as a public S3 bucket or port 22 exposed in a security group, or as complex as a misconfigured role trust policy that could lead to account takeover. </p><p class="paragraph" style="text-align:left;">These aren’t traditional vulnerabilities <i>per se,</i> because we aren’t talking about an unpatched flaw an attacker can exploit. They are more like &quot;unforced errors” that can potentially be exploited by an adversary in an attack. Why am I wording this so weirdly? It isn’t my coffee issue (broken grinder, fixed now), but rather that every misconfiguration is something that technically has a valid purpose or the capability wouldn’t be in the platform in the first place. </p><p class="paragraph" style="text-align:left;">We’ll talk a lot more about CSPM over the next few labs — including CNAPP, CSPM’s younger, bigger cousin.</p><h2 class="heading" style="text-align:left;" id="stop-confused-deputies-with-externa">Stop Confused Deputies with External IDs</h2><p class="paragraph" style="text-align:left;">To explore CSPM we will use some external tools (Prowler Open Source and FireMon Cloud Defense). Prowler will run inside one of our accounts, but FireMon is a SaaS platform out there… on the Internet someplace. </p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/a7bff0b0-a968-4496-a909-b6495e4d1adf/barney-fife.jpg?t=1759264255"/></div><p class="paragraph" style="text-align:left;"></p><p class="paragraph" style="text-align:left;">This creates an interesting situation. We will allow an outside account (via a role) access inside our Organization… and that account will have a <b>lot</b> of power.</p><p class="paragraph" style="text-align:left;">But that outside account? It’s a multi-tenant application connecting to a lot of different customers. And the normal pattern for these is for the SaaS platform to use the same IAM role to connect to all their customers. This creates an interesting dilemma: what if one of those customers figures out how to trick the platform into doing something in <i>our</i> account instead of <i>their</i> account? They… confuse… the application (the “deputy”) into accessing the wrong account.</p><p class="paragraph" style="text-align:left;">This is known as the <a class="link" href="https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=the-secure-way-to-integrate-cloudsec-tools-using-external-ids-cspm-lab-1" target="_blank" rel="noopener noreferrer nofollow">confused deputy problem</a>, and it isn’t unique to AWS. Heck, it’s well known that compromising a security tool is one of the best ways to get broad access to a target. </p><p class="paragraph" style="text-align:left;">Okay, let’s break down how this works:</p><ul><li><p class="paragraph" style="text-align:left;">Security Tool A in its own AWS account has access to Bob’s customer account via the <b>SecToolVendor</b> role, which has permission to assume the <b>SecToolCustomer</b> role in Bob’s account.</p></li><li><p class="paragraph" style="text-align:left;">Alice uses the same security tool, which uses the <b>SecToolVendor</b> role to assume access to her accounts.</p></li><li><p class="paragraph" style="text-align:left;">Alice (why is it always Alice?) tells Security Tool A that they own the account with ID of Bob’s account.</p></li><li><p class="paragraph" style="text-align:left;">Since Bob already trusts the <b>SecToolVendor</b> role, if the tool is tricked by Alice and assumes into Bob’s account, the tool is now doing whatever <b>Alice</b> tells it to, to <b>Bob’s</b> account<i>.</i></p></li></ul><p class="paragraph" style="text-align:left;">AWS came up with a good way to reduce this risk. The <b>External ID</b> is a shared secret we build into our role trust policy. The External ID has to be provided <i>during the </i><b><i>AssumeRole</i></b><i> API call,</i> and must match. It’s a value in the <b>SecToolCustomer</b> role trust policy. Thus…</p><ul><li><p class="paragraph" style="text-align:left;">Alice tries to add Bob’s account, since she knows they both use Security Tool A.</p></li><li><p class="paragraph" style="text-align:left;">When Alice tries to <b>AssumeRole</b> into Bob’s account, Security Tool A uses her External ID in the <b>AssumeRole</b> call (the vendors keep these in a database or secure configuration someplace on their side).</p></li><li><p class="paragraph" style="text-align:left;">That External ID doesn’t match Bob’s external ID in the <b>SecToolCustomer</b> role, so it fails.</p></li></ul><p class="paragraph" style="text-align:left;">But if Alice can trick Security Tool A into using Bob’s External ID, this falls apart. Or if Security Tool A shares its external ID across customers, <a class="link" href="https://www.praetorian.com/blog/aws-iam-assume-role-vulnerabilities/?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=the-secure-way-to-integrate-cloudsec-tools-using-external-ids-cspm-lab-1" target="_blank" rel="noopener noreferrer nofollow">well, no one would be that stupid</a>. (Narrator: “<a class="link" href="https://www.youtube.com/watch?v=c6Lv02JBVs0&utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=the-secure-way-to-integrate-cloudsec-tools-using-external-ids-cspm-lab-1" target="_blank" rel="noopener noreferrer nofollow">they were that stupid</a>”). (Thanks to Kesten Broughton for that excellent research and fwd:cloudsec presentation).</p><p class="paragraph" style="text-align:left;">Here’s our old <b>AssumeRole</b> diagram, updated with the External ID:</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/9735f8a4-bbf7-4ae0-b0e6-044706a65531/image.png?t=1759268447"/><div class="image__source"><span class="image__source_text"><p>The External ID isn’t stored with the SecurityToolVendor role, it’s kept someplace else and only used during the AssumeRole API call</p></span></div></div><p class="paragraph" style="text-align:left;">Okay, enough background for today. This is the first lab set where we will deploy third-party tools into our, organization so it’s a good time to play with External IDs. Then, CSPM is so central to cloud security that we will explore multiple options over a series of labs:</p><ul><li><p class="paragraph" style="text-align:left;">Preparing our organization (today’s lab)</p></li><li><p class="paragraph" style="text-align:left;">Open source (using Prowler)</p></li><li><p class="paragraph" style="text-align:left;">Commercial (using FireMon Cloud Defense’s free trial)</p></li><li><p class="paragraph" style="text-align:left;">Security Hub</p></li></ul><p class="paragraph" style="text-align:left;">You’ll see different deployment options, user interfaces, and capabilities. We’ll also see how to use CSPM for incident response, not just as a “vulnerability scanner”. As always, the labs are configured to be as inexpensive as possible. </p><h3 class="heading" style="text-align:left;" id="todays-key-points-are">Key Lesson Points</h3><ul><li><p class="paragraph" style="text-align:left;">Cloud Security Posture Management is a foundational cloud security tool.</p></li><li><p class="paragraph" style="text-align:left;">CSPM identifies cloud configurations that could lead to security issues.</p></li><li><p class="paragraph" style="text-align:left;">A common pattern to access our accounts is for a multi-tenant, external third-party tool to assume a role inside our accounts.</p></li><li><p class="paragraph" style="text-align:left;">This could enable another user of the tool to trick their way into our accounts. This is called the confused deputy problem.</p></li><li><p class="paragraph" style="text-align:left;">An External ID — a separate, shared secret — reduces the risk of a confused deputy.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lab"><b>The Lab</b></h1><p class="paragraph" style="text-align:left;">Today is the boring setup lab. The main learning objective is how to configure a cross-account role with an External ID. Although we don’t technically need the External ID, since everything is running inside our organization, this is an important skill to learn for connecting external tools. </p><p class="paragraph" style="text-align:left;"><i>Our tool of the day is Prowler — an Open Source (and also commercial) CSPM. We will go into more details on CSPM and Prowler in the next lab!</i></p><p class="paragraph" style="text-align:left;"><b>But!</b> We also need to configure a hodgepodge of other settings to make our CSPM labs run smoothly:</p><ul><li><p class="paragraph" style="text-align:left;">Change our session timeout so we can stay logged in longer.</p></li><li><p class="paragraph" style="text-align:left;">Deploy a CloudFormation template which creates:</p><ul><li><p class="paragraph" style="text-align:left;">A role for Prowler to use</p></li><li><p class="paragraph" style="text-align:left;">A VPC for Prowler to run in</p></li><li><p class="paragraph" style="text-align:left;">An instance with Prowler running</p></li></ul></li><li><p class="paragraph" style="text-align:left;">Use a StackSet to deploy the role our tool will assume when it assesses accounts.</p></li></ul><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/hJ751flyyew" width="100%"></iframe><h2 class="heading" style="text-align:left;" id="stepby-step">Step-by-Step</h2><p class="paragraph" style="text-align:left;">As I was building this lab I got super frustrated with session timeouts. The lab steps themselves are pretty quick, but running Prowler can take some time, and definitely may blast past our default of an hour. Let’s expand that to 4 hours. This is some pretty simple ClickOps so I’ll just blast you through it with a couple screenshots.</p><p class="paragraph" style="text-align:left;">Start in your <b>Sign-in portal &gt; CloudSLAW &gt; AdministratorAccess &gt; IAM Identity Center </b>(like, is there a different Identity Center?) <b>&gt; Permission Sets &gt; Administrator Access &gt; Edit &gt; Session duration &gt; set 4 hours &gt; Save changes &gt; </b><i><b>Close the tab.</b></i></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/5365c103-f8a8-496e-bb9a-078de93957d5/image.png?t=1759884146"/></div><p class="paragraph" style="text-align:left;">It won’t kill you if you skip that, but… you’ll regret it. </p><p class="paragraph" style="text-align:left;">Let’s go deploy the Prowler template. Head over to <b>SecurityAudit &gt; AdministratorAccess &gt; CloudFormation &gt; Create stack (with new resources):</b> </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/d1019aeb-564e-407a-a10a-da1cea115c96/image.png?t=1759955207"/></div><p class="paragraph" style="text-align:left;">Use the template <a class="link" href="https://cloudslaw.s3-us-west-2.amazonaws.com/lab65-1.template?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=the-secure-way-to-integrate-cloudsec-tools-using-external-ids-cspm-lab-1" target="_blank" rel="noopener noreferrer nofollow">https://cloudslaw.s3-us-west-2.amazonaws.com/lab65-1.template</a></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#FFFFFF;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/10d31d5d-ce90-4d43-8474-d7f01b2e9a88/image.png?t=1759955272"/></div><p class="paragraph" style="text-align:left;"><b>Name it “prowler” and set your External ID</b>. This needs to be 8-64 characters, and you need to write it down or remember it. The External ID isn’t stored in IAM or with the role — instead it is passed in as User Data with a bash script that saves it to a file in the <b>ec2-user</b> home directory. This image includes some scripts which launch Prowler and pass it the External ID from the saved configuration file.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/0e256e99-6b27-48db-87e7-1c8df4ee81f2/image.png?t=1759955494"/></div><p class="paragraph" style="text-align:left;">Click through the next couple screens and <b>Submit</b>. Then wait for it to finish, since we need the IAM Role that Prowler will use to be created before we try to deploy our cross-account role which trusts it. (You can’t create the trust relationship until the role is created — AWS will error out on a role ARN in a trust policy if the role doesn’t exist.)</p><p class="paragraph" style="text-align:left;">Now <i><b>Close the tab</b></i><b> &gt; Sign-in portal &gt; SecurityOperations &gt; AdministratorAccess &gt; CloudFormation &gt; StackSets &gt; Service-managed &gt; Create StackSet.</b> It’s really darn important to swap into <b>SecurityOperations,</b> since that’s the account with delegated administration permission to deploy stacks. <b>SecurityAudit</b> does not have any permissions to make changes in other accounts. </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/9321ae79-fb86-4ed2-ae6a-e26e3f8153e9/image.png?t=1759955796"/></div><p class="paragraph" style="text-align:left;">This time <b>use the template</b> <a class="link" href="https://cloudslaw.s3-us-west-2.amazonaws.com/lab65-2.template?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=the-secure-way-to-integrate-cloudsec-tools-using-external-ids-cspm-lab-1" target="_blank" rel="noopener noreferrer nofollow">https://cloudslaw.s3-us-west-2.amazonaws.com/lab65-2.template</a></p><p class="paragraph" style="text-align:left;">Then <b>name: prowler-role, set the SAME EXTERNAL ID!!!! and grab the SecurityAudit account ID from your Sign-in Portal tab.</b> It will look like this…</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/0f24d788-712d-4989-89ba-667901b7f427/image.png?t=1759955949"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/109f2010-e007-4508-be69-5d2f330aeb9c/image.png?t=1759956112"/></div><p class="paragraph" style="text-align:left;">This ExternalId <b>IS embedded into the role trust policy! </b>It looks like this:</p><div class="codeblock"><pre><code>&#123;
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        &#123;
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Principal&quot;: &#123;
                &quot;AWS&quot;: &quot;arn:aws:iam::533267272350:role/ProwlerLabInstanceRole&quot;
            &#125;,
            &quot;Action&quot;: &quot;sts:AssumeRole&quot;,
            &quot;Condition&quot;: &#123;
                &quot;StringEquals&quot;: &#123;
                    &quot;sts:ExternalId&quot;: &quot;ProwlerLabExternalId456&quot;
                &#125;
            &#125;
        &#125;
    ]
&#125;</code></pre></div><p class="paragraph" style="text-align:left;">You’ve done this more than a few times before, so the only other settings that need to change are:</p><ul><li><p class="paragraph" style="text-align:left;">Deploy to organization</p></li><li><p class="paragraph" style="text-align:left;">Only the <b>us-east-1</b> (Virginia) region, since this is an IAM role which will end up there anyway (if you try to deploy to multiple regions it will fail, since the role will already exist).</p></li><li><p class="paragraph" style="text-align:left;">Under <b>Deployment options</b> I recommend setting the max concurrent accounts to 10, and the failure tolerance at 9.</p></li></ul><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/77355ee7-fbf1-4632-b7fd-e2f125e5f230/image.png?t=1759956454"/></div><p class="paragraph" style="text-align:left;">Then <b>Submit.</b></p><p class="paragraph" style="text-align:left;">No need to wait for this one to deploy. <b>Close the tab &gt; Sign-in portal &gt; SecurityAudit &gt; AdministratorAccess &gt; Systems Manager &gt; Session Manager &gt; Preferences &gt; Edit:</b></p><p class="paragraph" style="text-align:left;">We will extend our idle session timeout for Session Manager. It won’t matter this week, but when we run Prowler scans next week, they won’t finish within the default of 20 minutes, and if the session closes you lose your scan. Max it out to 60 minutes:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/88a24b1b-186a-4fd0-9ae9-d0fa9fd928fd/image.png?t=1759956660"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/32e5dab6-d872-465c-b764-c814ff9d6098/image.png?t=1759956686"/></div><p class="paragraph" style="text-align:left;">Now let’s connect to our Prowler instance. Just so you know what that template did, it:</p><ul><li><p class="paragraph" style="text-align:left;">Created a role for Prowler with a bunch of Read permissions. These are the default policies for read only and view only (don’t ask), and some extended permissions Prowler recommends to read settings that aren’t included in those two policies.</p></li><li><p class="paragraph" style="text-align:left;">Created a VPC with only a public subnet and a security group with <b>no inbound access.</b> Why did I use a public subnet? To save money — this instance will be running for multiple labs so if we used a NAT gateway it would cost a lot, and if we wanted to use service endpoints we would need them for every service Prowler uses to scan. We’ll rely on our locked-down security group.</p></li><li><p class="paragraph" style="text-align:left;">An S3 bucket to save Prowler results. Prowler has a pretty great Web UI, but then we would need to deal with opening up our security group/ports. You can do that on your own if you want, but I decided dumping reports to S3 was easiest when I have 5,000+ of you running your own labs in your own accounts.</p></li><li><p class="paragraph" style="text-align:left;">An EC2 instance of an image I created with Prowler pre-installed and some bash scripts to run it in different ways, using our External ID if needed. </p></li></ul><p class="paragraph" style="text-align:left;">Okay, go to <b>EC2 &gt; check the Prowler instance &gt; Connect &gt; Session Manager &gt; Connect: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/5ad9b68d-e5cf-4818-8ea6-3d4302204ece/image.png?t=1759957054"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/34cfee71-5e2a-459a-817a-02c608fd02fc/image.png?t=1759957109"/></div><p class="paragraph" style="text-align:left;">Now run the following command lines to test it:</p><ul><li><p class="paragraph" style="text-align:left;"><span style="font-family:Courier,"Lucida Typewriter",monospace;">sudo su - ec2-user</span> <i># switch to be </i><b><i>ec2-user,</i></b><i> in that home directory</i></p></li><li><p class="paragraph" style="text-align:left;"><span style="font-family:Courier,"Lucida Typewriter",monospace;">bash </span><span style="font-family:Courier,"Lucida Typewriter",monospace;"><a class="link" href="https://quick-scan.sh?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=the-secure-way-to-integrate-cloudsec-tools-using-external-ids-cspm-lab-1" target="_blank" rel="noopener noreferrer nofollow">quick-scan.sh</a></span><span style="font-family:Courier,"Lucida Typewriter",monospace;"> </span><i># run a quick Prowler scan and save the results to S3</i></p></li></ul><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ff212272-5dec-450c-8190-b3bef2503793/image.png?t=1759957275"/></div><p class="paragraph" style="text-align:left;">That should execute within a couple minutes, and look like this when done:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ce3452ed-0cc1-47d1-805c-ef2ca7ccdc68/image.png?t=1759957333"/></div><p class="paragraph" style="text-align:left;">This scan ran just on the local account and didn’t use our External ID — we’ll get to that next week. If you want a little side quest, you can check out the User Data by going to <b>Instance settings &gt; Edit user data.</b></p><p class="paragraph" style="text-align:left;">Before we look at our results, go back to your instance and <b>Instance state &gt; Stop instance</b> to save a little moolah.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/de3f52ca-121e-43ee-9c3e-c508df72f8d0/image.png?t=1759957618"/></div><p class="paragraph" style="text-align:left;">Okay, let’s look at our results! Go to <b>S3 &gt; prowler-reports-&lt;your account ID&gt; </b>and follow the directory tree until you reach the reports. <b>Click the HTML report:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/b4abf92c-0b02-43c3-9594-5553f843ec62/image.png?t=1759957723"/></div><p class="paragraph" style="text-align:left;">Then click <b>Open</b> and it will pop out the report in a new tab:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/fc40871f-47f7-4e25-aa48-076cbf5090e2/image.png?t=1759957788"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/8acdcc5b-4907-4327-9d64-13f57630f66e/image.png?t=1759957825"/></div><p class="paragraph" style="text-align:left;">And there you go! These are just quick results — in the next lab we will run a deeper scan on a target with… some problems.</p><h3 class="heading" style="text-align:left;" id="key-points">Lab Key Points</h3><ul><li><p class="paragraph" style="text-align:left;">External ID is a shared secret to prevent the confused deputy problem.</p><ul><li><p class="paragraph" style="text-align:left;">It’s a condition in the trust policy of a role being assumed.</p></li><li><p class="paragraph" style="text-align:left;">The assuming party (<i>e.g.,</i> your SaaS vendor) only provides it during <b>AssumeRole,</b> and it should be different for each customer.</p></li></ul></li><li><p class="paragraph" style="text-align:left;">Prowler is a CSPM tool for identifying misconfigurations. </p></li></ul><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=96475acb-9cac-4a4f-b8c2-f85903ef2286&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>CloudSLAW News: Welcome to the Cloud Security Alliance!</title>
  <description>Big news for me, not much changes for you (yet).</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/4ec5abec-b0ff-4c37-ae78-d9e8338ec48a/CSA_Logo.png" length="24512" type="image/png"/>
  <link>https://slaw.securosis.com/p/cloudslaw-news-welcome-to-the-cloud-security-alliance</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/cloudslaw-news-welcome-to-the-cloud-security-alliance</guid>
  <pubDate>Wed, 01 Oct 2025 21:00:00 +0000</pubDate>
  <atom:published>2025-10-01T21:00:00Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><p class="paragraph" style="text-align:left;">I know what you’re thinking…</p><p class="paragraph" style="text-align:left;"><i>“Rich is such a slacker! Another week without a new lab? We’re only getting them every 2-3 weeks anymore!”</i></p><p class="paragraph" style="text-align:left;">Yeah, I own that. For the couple thousand of you in the front of the herd the recent new labs have been a little erratic, and for those of you hiding in the middle of the pack I’ve been a bit slow to respond to comments and feedback.</p><p class="paragraph" style="text-align:left;">But hey, I have good reasons! And, uh, this is all for free so, like, you know. The tl;dr:</p><ul><li><p class="paragraph" style="text-align:left;">The latest labs take a LOT more prep work. Some of these are up to 8-12 hours of prep, even though they are still only 30 minutes for you. We are on some advanced topics and the hard part is figuring out how to structure things and keep them as cheap as possible.</p></li><li><p class="paragraph" style="text-align:left;">I’ve been transitioning into a <i>new job</i>.</p></li></ul><h2 class="heading" style="text-align:left;" id="new-day-sorta-new-job">New day, “sorta” new job</h2><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/4ec5abec-b0ff-4c37-ae78-d9e8338ec48a/CSA_Logo.png?t=1759346419"/></div><p class="paragraph" style="text-align:left;">I’m excited to announce that as of today I’m the new Chief Analyst at the <a class="link" href="https://cloudsecurityalliance.org?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=cloudslaw-news-welcome-to-the-cloud-security-alliance" target="_blank" rel="noopener noreferrer nofollow">Cloud Security Alliance</a>! This is a brand-new role they created for me so I… better not screw it up. I’ll spare you the details, but I <a class="link" href="https://cloudsecurityalliance.org/blog/2025/09/16/why-i-m-joining-csa?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=cloudslaw-news-welcome-to-the-cloud-security-alliance" target="_blank" rel="noopener noreferrer nofollow">wrote it all up at this post</a>.</p><p class="paragraph" style="text-align:left;">The short version is that my full time job is now research, advising CSA members, and training. Basically to help professionals and organizations as they deal with complex cloud and AI security topics. I’ve been involved with the CSA since the start and built the CCSK training program, wrote multiple versions of the CSA Security Guidance, wrote the Cloud Security maturity Model, and participated in a lot of different working groups and initiatives. </p><p class="paragraph" style="text-align:left;">It’s been a 15 year job interview and I finally closed the deal! Time for coffee!</p><p class="paragraph" style="text-align:left;">As for the recent lab slowdowns? Closing out my work at FireMon (where I’m still an advisor) so I could transition over to the CSA changed my priorities for the past couple of months and I had less time to devote to CloudSLAW.</p><h2 class="heading" style="text-align:left;" id="the-future-of-cloud-slaw-is-bright">The future of CloudSLAW is bright</h2><p class="paragraph" style="text-align:left;">As of now there are no intended changes to CloudSLAW and it’s now something I can do actively as part of my job, not something that has to be on the side. CloudSLAW will stay where it is and stay free, and I’m keeping Pro since I still handle all the costs through Securosis.</p><p class="paragraph" style="text-align:left;">New labs will still be on the every 2-3 week schedule since they really are harder to create.</p><p class="paragraph" style="text-align:left;">For those of you interested in continuing education credits and certifications, this is where things will get interesting. While we haven’t figured out exactly how we are going to configure things, we are looking at taking CloudSLAW labs, copying them into the CSA’s training platform, and adding in the tracking and quizzes required to issue credits and certificates.</p><p class="paragraph" style="text-align:left;"><b><i>The main CloudSLAW will remain free to the public and is the first place for new labs!</i></b><b> </b>I’m not taking anything away, and we have no plans to lock down old or new labs. The nice thing about what we have going now is that it allows me to build, test, and experiment with labs in a way I can’t do if everything is tied to certificates. \</p><p class="paragraph" style="text-align:left;">Remember, the Cloud Security Alliance is a non-profit and its mission is to define and promote cloud and AI security best practices. Membership and certifications are two of the main ways the CSA funds its mission, and keeping CloudSLAW out there for free is completely aligned with the goals of the CSA.</p><h2 class="heading" style="text-align:left;" id="upcoming-content">Upcoming Content</h2><p class="paragraph" style="text-align:left;">As long as I have your attention…</p><ul><li><p class="paragraph" style="text-align:left;">The next lab will be a week late due (and come out next week). It’s mostly drafted but I ran out of time to test and refine the tech side. </p></li><li><p class="paragraph" style="text-align:left;">That lab starts a series on Cloud Security Posture Management (CSPM) and will have about 5-6 labs. </p></li><li><p class="paragraph" style="text-align:left;">At the end we will circle back to our Incident Response series (since one facet of incident response is CSPM) and hopefully cover some basics of detection as code.</p></li><li><p class="paragraph" style="text-align:left;">I’m unsure about what topic to pick after that and plan on polling the CloudSLAW Pro members. One idea is securing AI apps in AWS. Another is containers. And at some point we need to get to data perimeters.</p><ul><li><p class="paragraph" style="text-align:left;">I’m very open to additional ideas, so send them over!</p></li></ul></li></ul><p class="paragraph" style="text-align:left;">My sincere thanks for your time and support as this crazy experiment continues. People have been coming up to me to talk about CloudSLAW as I’ve been out at conferences and it&#39;s really motivational to know that these bits I spew out into the aether sometimes actually help people.</p><p class="paragraph" style="text-align:left;">-rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=4627f5d5-d738-4b72-aa29-998637475545&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Stage Set: The Cloud Incident Response Foundation</title>
  <description>Sit back, relax, and enjoy some light reading this week instead of a lab as we review where we are and where we&#39;re headed, continuing down the incident response road.</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/fde07d4f-bcec-45d6-84a1-7adf6ee3afb5/IMG_6324.jpeg" length="2084551" type="image/jpeg"/>
  <link>https://slaw.securosis.com/p/stage-set-the-cloud-incident-response-foundation</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/stage-set-the-cloud-incident-response-foundation</guid>
  <pubDate>Thu, 11 Sep 2025 15:00:00 +0000</pubDate>
  <atom:published>2025-09-11T15:00:00Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b><a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=stage-set-the-cloud-incident-response-foundation" target="_blank" rel="noopener noreferrer nofollow">Go Pro for Support and Extras!</a></b></p><p class="paragraph" style="text-align:left;">CloudSLAW is, and always will be, free. But to help cover costs and keep the content up to date we have an optional Patreon. For $10 per month you get access to our support Discord, office hours, exclusive subscriber content (not labs — those are free — just extras) and more. <a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=stage-set-the-cloud-incident-response-foundation" target="_blank" rel="noopener noreferrer nofollow">Check it out!</a></p></div><p class="paragraph" style="text-align:left;">I realize you&#39;re probably asking yourself, &quot;What the heck is a &#39;stage set&#39;&quot;?!? &quot;Where&#39;s my lab?&quot; Or &quot;couldn&#39;t he at least have done a stage check with a video?!?!&quot; Or maybe even, &quot;did he get his nested quotes correct in the first sentence, and would those work in Python?&quot;</p><p class="paragraph" style="text-align:left;">Well, you see, sometimes when you run a long-term newsletter/blog/training program, you get caught up in the absolute relentless pace, and realize you mighta missed a couple of things or need a small breather.</p><p class="paragraph" style="text-align:left;">Today, my friends, is one of those days.</p><p class="paragraph" style="text-align:left;">I try to write these labs as if they exist out of time, since some of you see them the day they release but many more won&#39;t see them for over a year. But sometimes the real-time version of my life interferes with getting out timely labs, and rather than have a big gap I thought this would be a great time to zoom out and discuss the big picture of what we have been building with recent labs. Unlike a stage check we aren&#39;t finished yet, but I realized as I looked back at what we&#39;ve covered that I wasn&#39;t doing a great job of providing very important context. Sure, some of it is in there, but we are pretty deep in the technical weeds so it&#39;s easy to lose perspective.</p><p class="paragraph" style="text-align:left;">Let&#39;s clip in, hang back, and enjoy the mid-wall view for a moment (yeah, that&#39;s a rock climbing reference). I&#39;m going to break out the main components of cloud incident response, show you where our labs fit in, and give you a preview of what&#39;s next. And I&#39;ll try and keep it all to about 7 minutes of reading time. This is absolutely one of my favorite topics, having built multiple Black Hat training classes on it and integrating it into the Cloud Security Alliance CCSK course and a few other places.</p><p class="paragraph" style="text-align:left;">Oh — no video for this one, at least not yet. I&#39;m writing it on an airplane as I head out to speak at 2 different events in 2 different countries. Like I said, life&#39;s been busy.</p><h1 class="heading" style="text-align:left;" id="cloud-incident-response-101-ish">Cloud Incident Response 101(ish)</h1><p class="paragraph" style="text-align:left;">The TL;DR is that across our labs, in some cases going back to the first few months of CloudSLAW, we&#39;ve been slowly building out a pretty robust incident response program. There&#39;s still a lot to do, but we&#39;ve made some big strides.</p><p class="paragraph" style="text-align:left;"><b>Our overall objective is to effectively detect and respond to security incidents.</b></p><p class="paragraph" style="text-align:left;">Simple enough, eh? But to do that we need to:</p><ul><li><p class="paragraph" style="text-align:left;">Collect the right information.</p></li><li><p class="paragraph" style="text-align:left;">Detect security incidents.</p></li><li><p class="paragraph" style="text-align:left;">Have the data and skills to analyze and investigate.</p></li><li><p class="paragraph" style="text-align:left;">Use it all to contain, eradicate, and recover.</p></li></ul><p class="paragraph" style="text-align:left;">There are a few different incident response frameworks out there, and I semi-normalized them when building my CSA and Black Hat classes. Here&#39;s a slide I stole from the official CCSK training (Certificate of Cloud Computing Security Knowledge, from the Cloud Security Alliance... which I wrote):</p><div class="section" style="background-color:transparent;border-color:#222222;border-radius:1px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:0.0px 0.0px 0.0px 0.0px;"><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/a42f7a65-d7db-4dbf-825d-716761efe725/image.png?t=1757558197"/></div></div><p class="paragraph" style="text-align:left;">You have completed multiple labs on preparation, detection, and analysis. Now let&#39;s see where that all fits.</p><h1 class="heading" style="text-align:left;" id="preparation-step-1-feeds-and-speeds"><b>Preparation Step 1: Feeds and Speeds</b></h1><p class="paragraph" style="text-align:left;">The very first thing we need for incident response is the right security telemetry to detect and analyze/investigate incidents. What&#39;s security telemetry? Basically, any logs or other information sources germane to security. This can potentially include any and every log file and event source, but some sources are better than others for security.</p><p class="paragraph" style="text-align:left;">It&#39;s also critically important to know the timing of these security feeds, especially in cloud. It&#39;s hard to overstate the speed and scale of operating in the cloud, and this is just as true for attackers as for defenders. Attackers today are highly automated, work directly with cloud APIs, and can decimate environments in seconds or minutes. In the early days of cloud many organizations fed their cloud data into their on-premises Security Information and Event Management (SIEM) tooling, then had teams work exclusively from that data. The problem is those pipelines can take an hour or more before coughing up the data, so they were always working behind the attackers.</p><p class="paragraph" style="text-align:left;">You don&#39;t need everything in real time, but you do need to know which sources you&#39;re collecting and how fresh the data is. Feeds and speeds.</p><p class="paragraph" style="text-align:left;">Our labs set up a good core:</p><ul><li><p class="paragraph" style="text-align:left;">CloudTrail data for all accounts and regions, saved to S3 with about a 5-15 minute lag. This is the most important log source for our cloud management plane, and our primary tool for detecting AWS-native attacks.</p><ul><li><p class="paragraph" style="text-align:left;">In <a class="link" href="https://slaw.securosis.com/p/assume-role-centralized-logging-part-1?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=stage-set-the-cloud-incident-response-foundation" target="_blank" rel="noopener noreferrer nofollow">Assume the Role! (Centralized Logging, Part 1)</a></p></li></ul></li><li><p class="paragraph" style="text-align:left;">CloudTrail data is also available in near-real-time via EventBridge, but only when we <a class="link" href="https://slaw.securosis.com/p/centralize-security-triggers-with-stacksets-and-eventbridge?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=stage-set-the-cloud-incident-response-foundation" target="_blank" rel="noopener noreferrer nofollow">push out EventBridge rules into the accounts using CloudFormation StackSets</a>.</p></li><li><p class="paragraph" style="text-align:left;"><a class="link" href="https://slaw.securosis.com/p/best-way-start-aws-security-hub?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=stage-set-the-cloud-incident-response-foundation" target="_blank" rel="noopener noreferrer nofollow">Security Hub for all accounts and regions</a>, which is available in the service itself (we aren&#39;t saving it to S3 yet) with all alerts sent to email. Security Hub alerts are real-time, but the services that feed Security Hub have their own timings:</p><ul><li><p class="paragraph" style="text-align:left;"><a class="link" href="https://slaw.securosis.com/p/enable-guardduty-right-way?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=stage-set-the-cloud-incident-response-foundation" target="_blank" rel="noopener noreferrer nofollow">GuardDuty, which is currently our main &quot;intrusion detection&quot; for cloud</a>. Some GuardDuty alerts are pretty quick, but most don&#39;t appear for 20-40 minutes after the offending activity.</p></li><li><p class="paragraph" style="text-align:left;"><a class="link" href="https://slaw.securosis.com/p/sensors-up-start-a-data-perimeter-with-access-analyzer?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=stage-set-the-cloud-incident-response-foundation" target="_blank" rel="noopener noreferrer nofollow">Access Analyzer for detecting publicly exposed resources</a> (it can do more, but that&#39;s all we have set so far). It&#39;s pretty quick in my testing, but I don&#39;t have a good enough sense to give you consistent timing.</p></li></ul></li><li><p class="paragraph" style="text-align:left;">We&#39;ve dabbled with some other logs, but haven&#39;t built much to use them yet. These are also of lower security value. Not &quot;<b>no</b> security value&quot; — just lower.</p><ul><li><p class="paragraph" style="text-align:left;">CloudWatch Logs, which in our case is mostly storing logs from Lambda functions.</p></li><li><p class="paragraph" style="text-align:left;">Session Manager logs which we learned about in <a class="link" href="https://slaw.securosis.com/p/enabling-logs-session-manager?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=stage-set-the-cloud-incident-response-foundation" target="_blank" rel="noopener noreferrer nofollow">Enabling Logs in Session Manager</a>.</p></li></ul></li></ul><p class="paragraph" style="text-align:left;">It&#39;s a good start. Here&#39;s a short sample of sources we haven&#39;t touched yet:</p><ul><li><p class="paragraph" style="text-align:left;">Network logs (DNS and VPC Flow logs).</p></li><li><p class="paragraph" style="text-align:left;">Workload/system logs from instances (and containers, which we aren&#39;t using yet).</p></li><li><p class="paragraph" style="text-align:left;">Load balancer and API gateway logs.</p></li><li><p class="paragraph" style="text-align:left;">Logs from S3 &quot;data events&quot; (accessing the data — we do get management events like changing configurations).</p></li></ul><p class="paragraph" style="text-align:left;">I personally categorize sources based on two broad criteria:</p><ul><li><p class="paragraph" style="text-align:left;">Are they security specific (GuardDuty and most security tools), or operational logs which also have security value (CloudTrail)?</p></li><li><p class="paragraph" style="text-align:left;">Is the source logs, events, or from tools? How can I use it for threat detection? Let&#39;s talk about that one for a moment...</p></li></ul><h1 class="heading" style="text-align:left;" id="preparation-step-2-threat-detectors"><b>Preparation Step 2: Threat Detectors</b></h1><p class="paragraph" style="text-align:left;">Different sources play different roles in threat detection and analysis. When I first started training traditional incident responders in cloud I found it helpful to break them into these categories. This one comes from my Black Hat training (okay, it&#39;s also in the CCSK):</p><div class="image"><img alt="" class="image__image" style="border-radius:1px;border-style:solid;border-width:1px;box-sizing:border-box;border-color:#222222;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/b5c8822f-ba14-4bd2-84f0-bd28d1967d97/image.png?t=1757558269"/></div><p class="paragraph" style="text-align:left;">In recent labs we built threat detectors for two of these three source types:</p><ul><li><p class="paragraph" style="text-align:left;"><b>Logs:</b> That&#39;s what we did with <a class="link" href="https://slaw.securosis.com/p/build-a-time-based-threat-detector-with-lambda-and-athena-2231?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=stage-set-the-cloud-incident-response-foundation" target="_blank" rel="noopener noreferrer nofollow"><b>Build a Time-Based Threat Detector with Lambda and Athena</b></a><b>.</b></p></li><li><p class="paragraph" style="text-align:left;"><b>Events:</b> Most recently we built this with <a class="link" href="https://slaw.securosis.com/p/build-a-real-time-threat-detector-with-iac-4a3d?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=stage-set-the-cloud-incident-response-foundation" target="_blank" rel="noopener noreferrer nofollow"><b>Build a Real Time Threat Detector with IaC</b></a><b>.</b></p><ul><li><p class="paragraph" style="text-align:left;">We also receive alerts through Security Hub, per <a class="link" href="https://slaw.securosis.com/p/use-eventbridge-security-hub-alerts?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=stage-set-the-cloud-incident-response-foundation" target="_blank" rel="noopener noreferrer nofollow"><b>Use EventBridge for Security Hub Alerts</b></a><b>. </b>This feeds us everything from GuardDuty and Access Analyzer.</p></li></ul></li><li><p class="paragraph" style="text-align:left;"><b>Configuration:</b> We get a little of this from GuardDuty, and a little from when we built out S3 autoremediation for exposed buckets (<a class="link" href="https://slaw.securosis.com/p/schedule-security-scanning-with-a-serverless-fanout-pattern?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=stage-set-the-cloud-incident-response-foundation" target="_blank" rel="noopener noreferrer nofollow"><b>Schedule Security Scanning with a Serverless Fanout Pattern</b></a>) But this is really about CSPM, on which we will soon have labs.</p></li></ul><p class="paragraph" style="text-align:left;">Hopefully this gives you some perspective on how these preparation labs fit together. We aren&#39;t done with the topic by any means, and will be integrating more sources and building more threat detectors in future labs, as well as learning about detection as code, but we&#39;ve largely covered the core concepts for Feeds and Speeds and detection engineering<span style="font-family:UICTFontTextStyleBody;font-size:17px;">.</span></p><h1 class="heading" style="text-align:left;" id="analysis"><b>Analysis</b></h1><p class="paragraph" style="text-align:left;">Okay, our threat detector fires... so what now? Panic? Send Rich an email begging for help? Nah, you got this. Weirdly, I covered analysis before threat detection in <a class="link" href="https://slaw.securosis.com/p/getting-started-with-cloudtrail-security-queries?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=stage-set-the-cloud-incident-response-foundation" target="_blank" rel="noopener noreferrer nofollow"><b>Getting Started with CloudTrail Security Queries</b></a><b>, </b>since we needed to learn about Athena and queries. These labs are a decent introduction to basic analysis of management plane events, and we even simulated a full attack in our <a class="link" href="https://slaw.securosis.com/p/skills-challenge-investigate-your-own-aws-attack-with-athena-3d0d?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=stage-set-the-cloud-incident-response-foundation" target="_blank" rel="noopener noreferrer nofollow">Skills Challenge</a> and <a class="link" href="https://slaw.securosis.com/p/skills-solution-investigate-your-own-aws-attack-with-athena-4934?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=stage-set-the-cloud-incident-response-foundation" target="_blank" rel="noopener noreferrer nofollow">Skills Solution</a><b>.</b> Remember my RECIPE PICKS mnemonic?</p><p class="paragraph" style="text-align:left;">And yes, more later. Of course. :)</p><h1 class="heading" style="text-align:left;" id="where-we-are-where-were-going"><b>Where We Are, Where We&#39;re Going</b></h1><p class="paragraph" style="text-align:left;"><i>Cloud incident response is an entire career</i>, so there&#39;s no way we can cover everything, but at this point we have a good foundation. We have:</p><ul><li><p class="paragraph" style="text-align:left;">Prepared our AWS Organization by enabling fundamental security feeds.</p></li><li><p class="paragraph" style="text-align:left;">Built threat detectors based on data in our logs, cloud events, and cloud security tools.</p></li><li><p class="paragraph" style="text-align:left;">Learned the basics of analyzing an attack with a live simulation.</p></li></ul><p class="paragraph" style="text-align:left;">The big gap in these labs is our lack of discussion on cloud <i>configuration management</i> and how that fits within incident response. We also haven&#39;t discussed managing threat detectors. These are both huge topics, so I&#39;m engineering some nice labs to cover the basics and give you ideas for more to explore on your own.</p><p class="paragraph" style="text-align:left;">Well, they just told me it&#39;s time to detach my keyboard from the iPad so the plane can land. Hopefully this has helped you orient yourself, and understand how these labs all fit together.</p><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=f7b9a46c-e0f6-4c5d-b384-b8b999fd73cf&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Build a Real Time Threat Detector with IaC</title>
  <description>Instead of clicky-clicky, today we&#39;ll build a new EventBridge setup for real-time threat detection and deploy our first detector.</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/f74ee60f-2535-41a5-af2c-a00f5b543076/Real_Time_Threat_Detection.png" length="1451377" type="image/png"/>
  <link>https://slaw.securosis.com/p/build-a-real-time-threat-detector-with-iac-4a3d</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/build-a-real-time-threat-detector-with-iac-4a3d</guid>
  <pubDate>Thu, 21 Aug 2025 15:00:00 +0000</pubDate>
  <atom:published>2025-08-21T15:00:00Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b><a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=build-a-real-time-threat-detector-with-iac" target="_blank" rel="noopener noreferrer nofollow">Go Pro for Support and Extras!</a></b></p><p class="paragraph" style="text-align:left;">CloudSLAW is, and always will be, free. But to help cover costs and keep the content up to date we have an optional Patreon. For $10 per month you get access to our support Discord, office hours, exclusive subscriber content (not labs — those are free — just extras) and more. <a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=build-a-real-time-threat-detector-with-iac" target="_blank" rel="noopener noreferrer nofollow">Check it out!</a></p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;">An AWS organization with accounts named <b>SecurityAudit</b> and <b>TestAccount1,</b> and an OU named <b>Workloads.</b> I mean, are any of you really just dropping into arbitrary labs without doing all the other ones? Really?</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Lesson</h1><p class="paragraph" style="text-align:left;">In our last lab we <a class="link" href="https://slaw.securosis.com/p/build-a-time-based-threat-detector-with-lambda-and-athena-2231?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=build-a-real-time-threat-detector-with-iac" target="_blank" rel="noopener noreferrer nofollow">built a time-series threat detector using AWS Athena and Lambda, based on CloudTrail logs</a>. When it comes to threat detection, logs are about as foundational as it gets. Heck, back in the old days they were pretty much all we had.</p><p class="paragraph" style="text-align:left;">But cloud is awesome, and aside from logs we have real-time events (<a class="link" href="https://slaw.securosis.com/p/use-eventbridge-security-hub-alerts?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=build-a-real-time-threat-detector-with-iac" target="_blank" rel="noopener noreferrer nofollow">which I explained using Star Wars LEGO way back in </a><i><a class="link" href="https://slaw.securosis.com/p/use-eventbridge-security-hub-alerts?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=build-a-real-time-threat-detector-with-iac" target="_blank" rel="noopener noreferrer nofollow">this</a></i><a class="link" href="https://slaw.securosis.com/p/use-eventbridge-security-hub-alerts?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=build-a-real-time-threat-detector-with-iac" target="_blank" rel="noopener noreferrer nofollow"> lab</a>). So far we’ve worked with two types of events: those coming from tools (like <b>Security Hub</b> in that post) and those generated by AWS services, as illustrated in our <a class="link" href="https://slaw.securosis.com/p/centralize-security-triggers-with-stacksets-and-eventbridge?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=build-a-real-time-threat-detector-with-iac" target="_blank" rel="noopener noreferrer nofollow">autoremediation lab, where we triggered based on S3 events sent to CloudTrail.</a> </p><p class="paragraph" style="text-align:left;">Today we will build on those skills and create a new pipeline for CloudTrail event-based threat detectors. But to add a twist, I’ll walk you through the entire process of creating and deploying your own CloudFormation template. Sure, you can skip to the end I suppose, but that’s cheating and cheaters never win! (I mean, outside politics, business, sports, and… well, every other facet of life, I guess. But hey! Sometimes they get caught! I mean… nevermind).</p><h3 class="heading" style="text-align:left;" id="the-setup">The setup</h3><p class="paragraph" style="text-align:left;">We will be building the simplest kind of event-based threat detector: one that triggers off a single API call. As powerful as they are, these are also the most prone to being pains in the ass. Because we are triggering on an activity that <i>might</i> be authorized, without any nuance or checking to understand the context.</p><p class="paragraph" style="text-align:left;">In other words, these can be very noisy. They aren’t <i>false</i> positives, because they alert on exactly what we asked for, but they can and do alert on legitimate authorized activity. </p><p class="paragraph" style="text-align:left;">Today I picked an event I <b>always</b> want to know about: the creation of a new IAM user. Yeah, this could be <b>super noisy</b> most places, but I do know real large enterprises which don’t allow IAM user creation without a documented approval.</p><p class="paragraph" style="text-align:left;">To make the lab more interesting, we also want to deploy our threat detector to our <b>Workloads</b> OU, but send out all alarms from our <b>SecurityAudit</b> account using our existing SNS topic. Here’s our design criteria:</p><p class="paragraph" style="text-align:left;"><i>As a security administrator I want a real-time alert every time someone creates an IAM user in any of my accounts. I want these alerts to come from my existing SNS topic in my SecurityAudit account</i>.</p><p class="paragraph" style="text-align:left;">Okay, translating that user story, we need to do the following:</p><ul><li><p class="paragraph" style="text-align:left;">Capture the <b>IAM:CreateUser</b> event in every account.</p></li><li><p class="paragraph" style="text-align:left;">Send that event to our <b>SecurityAudit</b> account.</p></li><li><p class="paragraph" style="text-align:left;">Trigger our SNS topic for the alert.</p></li></ul><p class="paragraph" style="text-align:left;">This is different than how we set up <b>Security Hub,</b> since that service already centralized events for our entire organization. For this one we need to build that centralization ourselves.</p><p class="paragraph" style="text-align:left;"><b>I want to be very clear: the objective of this lab is to only teach the foundational technique for capturing these events. In a future lab we will build in more intelligence by routing to Lambda, as in the last lab, instead of directly forwarding all events!</b></p><h1 class="heading" style="text-align:left;" id="the-lab"><b>The Lab</b></h1><p class="paragraph" style="text-align:left;">To pull this off we will build two CloudFormation templates: one to set up our <b>SecurityAudit</b> account with a new event bus to receive events and forward them to SNS, and another we deploy with <b>StackSets</b> to forward all the events we want centrally.</p><p class="paragraph" style="text-align:left;">Instead of hosting a template for you, this time you will build it yourself and host it in your own S3 bucket.</p><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/apejjKMtcUA" width="100%"></iframe><h2 class="heading" style="text-align:left;" id="stepby-step">Step-by-Step</h2><p class="paragraph" style="text-align:left;">We’ll start by building our two templates and storing them locally. Once they’re done, we will build an S3 bucket and upload them. Then you’ll go into each account and deploy the appropriate template.</p><p class="paragraph" style="text-align:left;">First, <b>open up a text editor.</b> I recommend one meant for coding/scripting like <a class="link" href="https://www.sublimetext.com?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=build-a-real-time-threat-detector-with-iac" target="_blank" rel="noopener noreferrer nofollow">Sublime Text</a>.</p><h3 class="heading" style="text-align:left;" id="the-security-audit-template">The SecurityAudit template</h3><p class="paragraph" style="text-align:left;">We need this one first, since we need the event bus set up before we can send it events. As a reminder, <b>CloudFormation</b> can use JSON or YAML; we will use YAML because it’s easier to work with. <i>YAML requires proper indentation. If you run into issues and don&#39;t see any obvious errors, check your tabs and spaces</i>.</p><p class="paragraph" style="text-align:left;">First start with our usual <b>CloudFormation</b> header and a description so we can remember what this thing is for:</p><div class="codeblock"><pre><code>AWSTemplateFormatVersion: &quot;2010-09-09&quot;
Description: &gt;
  EventBridge bus to collect CloudTrail and other events from accounts within the same organization and forward them to SNS.</code></pre></div><p class="paragraph" style="text-align:left;">The recommended practice is to list the template version (despite it being old).</p><p class="paragraph" style="text-align:left;">The next section collects our <b>Parameters.</b> They are the variables we enter when we run the template. We <i>could</i> hardcode both of them, since we won’t use this template anywhere else, but this reduces copy/paste errors among students. No, not <i>you</i> — that person sitting next to you, of course.</p><div class="codeblock"><pre><code>Parameters:
  OrganizationId:
    Type: String
    Description: AWS Organizations ID (e.g., o-abc123def4)
    AllowedPattern: &quot;^o-[a-z0-9]&#123;10,32&#125;$&quot;
  SnsTopicArn:
    Type: String
    Description: ARN of the SNS topic (e.g., arn:aws:sns:us-west-2:123456789012:SecurityHubAlerts)</code></pre></div><p class="paragraph" style="text-align:left;">Now we start our <b>Resources</b> section, where we tell <b>CloudFormation</b> what to build. I pair our new event bus with the resource policy which allows any account in our Organization to send it events. Notice how we use our parameter for the Organization ID to fill in the blank?</p><div class="codeblock"><pre><code>Resources:
  SecurityMonitoringBus:
    Type: AWS::Events::EventBus
    Properties:
      Name: SecurityMonitoring

  AllowOrgPutEvents:
    Type: AWS::Events::EventBusPolicy
    Properties:
      EventBusName: !Ref SecurityMonitoringBus
      StatementId: AllowPutEventsFromOrg
      Action: events:PutEvents
      Principal: &quot;*&quot;
      Condition:
        Type: StringEquals
        Key: aws:PrincipalOrgID
        Value: !Ref OrganizationId</code></pre></div><p class="paragraph" style="text-align:left;">That’s the magic <b>!Ref.</b> Notice we use that substitution twice? The first time is at <b>EventBusName</b> in the resource policy, and we <b>refer</b> to whatever name was assigned when we made the event bus. The second time we refer to our <b>OrganizationId</b> parameter. </p><p class="paragraph" style="text-align:left;">Referring to other values in a template is a critical capability for infrastructure as code. In this case we hardcoded the name of our event bus, but you may recall we don’t always know the unique identifier for a resource until AWS creates it and provides the identifier, like an instance ID. Referring to other resources in the same template tells <b>CloudFormation</b> to grab the value it needs. <b>CloudFormation</b> will then figure out the order to build things and wait until the first thing is ready before it creates the second that relies on it (usually — it ain’t like tech is totally reliable, or anything).</p><p class="paragraph" style="text-align:left;">Okay, we have our event bus, and other accounts can send it events. Now let’s create a new <b>EventBridge Rule</b> to send those events to SNS:</p><div class="codeblock"><pre><code>AllEventsToSnsRule:
    Type: AWS::Events::Rule
    Properties:
      Name: AllEventsToSns
      Description: &quot;Forward all events on the SecurityMonitoring bus to SNS&quot;
      EventBusName: !Ref SecurityMonitoringBus
      State: ENABLED
      EventPattern: |
        &#123;
          &quot;source&quot;: [&#123;
            &quot;exists&quot;: true
          &#125;]
        &#125;
      Targets:
        - Id: SecurityMonitoringSnsTarget
          Arn: !Ref SnsTopicArn</code></pre></div><p class="paragraph" style="text-align:left;">Putting it all together (because let’s be real, this is the part you’ll copy and paste), we get our full template to create the bus, allow cross-account access, and forward all events to our existing SNS topic:</p><div class="codeblock"><pre><code>AWSTemplateFormatVersion: &quot;2010-09-09&quot;
Description: &gt;
  EventBridge bus to collect CloudTrail and other events from accounts within the same organization and forward them to SNS.

Parameters:
  OrganizationId:
    Type: String
    Description: AWS Organizations ID (e.g., o-abc123def4)
    AllowedPattern: &quot;^o-[a-z0-9]&#123;10,32&#125;$&quot;
  SnsTopicArn:
    Type: String
    Description: ARN of the SNS topic (e.g., arn:aws:sns:us-west-2:123456789012:SecurityHubAlerts)

Resources:
  SecurityMonitoringBus:
    Type: AWS::Events::EventBus
    Properties:
      Name: SecurityMonitoring

  AllowOrgPutEvents:
    Type: AWS::Events::EventBusPolicy
    Properties:
      EventBusName: !Ref SecurityMonitoringBus
      StatementId: AllowPutEventsFromOrg
      Action: events:PutEvents
      Principal: &quot;*&quot;
      Condition:
        Type: StringEquals
        Key: aws:PrincipalOrgID
        Value: !Ref OrganizationId

  AllEventsToSnsRule:
    Type: AWS::Events::Rule
    Properties:
      Name: AllEventsToSns
      Description: &quot;Forward all events on the SecurityMonitoring bus to SNS&quot;
      EventBusName: !Ref SecurityMonitoringBus
      State: ENABLED
      EventPattern: |
        &#123;
          &quot;source&quot;: [&#123;
            &quot;exists&quot;: true
          &#125;]
        &#125;
      Targets:
        - Id: SecurityMonitoringSnsTarget
          Arn: !Ref SnsTopicArn</code></pre></div><p class="paragraph" style="text-align:left;"><b>Save that file locally</b> with the name <b>securityauditeventcollector.template.</b></p><h3 class="heading" style="text-align:left;" id="the-workloads-ou-stackset-template">The Workloads OU Stackset Template</h3><p class="paragraph" style="text-align:left;">This one is even shorter. All we need is an <b>EventBridge Rule</b> which matches the <b>CloudTrail Action</b> we want, and forwards that to the central event bus we just created. But there’s a little twist — can you spot it?</p><div class="codeblock"><pre><code>AWSTemplateFormatVersion: &#39;2010-09-09&#39;
Description: &#39;Forward IAM CreateUser events to EventBus in another account (us-west-2)&#39;

Parameters:
  TargetEventBusArn:
    Type: String
    Description: ARN of the target EventBus in another account (us-west-2)
    AllowedPattern: &#39;^arn:aws:events:us-west-2:\d&#123;12&#125;:event-bus/[\w\-]+$&#39;
    ConstraintDescription: Must be a valid EventBus ARN in us-west-2

Conditions:
  IsUSEast1: !Equals [!Ref &#39;AWS::Region&#39;, &#39;us-east-1&#39;]

Resources:
  # IAM Role for EventBridge to assume when putting events to target bus
  EventBridgeRole:
    Type: AWS::IAM::Role
    Condition: IsUSEast1
    Properties:
      RoleName: ThreatDetectorRole
      AssumeRolePolicyDocument:
        Version: &#39;2012-10-17&#39;
        Statement:
          - Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Action: &#39;sts:AssumeRole&#39;
      Policies:
        - PolicyName: PutEventsToTargetBus
          PolicyDocument:
            Version: &#39;2012-10-17&#39;
            Statement:
              - Effect: Allow
                Action:
                  - &#39;events:PutEvents&#39;
                Resource: !Ref TargetEventBusArn

  # EventBridge Rule to capture IAM CreateUser events
  IAMCreateUserEventRule:
    Type: AWS::Events::Rule
    Properties:
      Description: Captures IAM CreateUser API calls and forwards to target EventBus
      State: ENABLED
      EventPattern:
        source:
          - aws.iam
        detail-type:
          - AWS API Call via CloudTrail
        detail:
          eventSource:
            - iam.amazonaws.com
          eventName:
            - CreateUser
      Targets:
        - Arn: !Ref TargetEventBusArn
          Id: CrossAccountEventBusTarget
          RoleArn: !If
            - IsUSEast1
            - !GetAtt EventBridgeRole.Arn
            - !Sub &#39;arn:aws:iam::$&#123;AWS::AccountId&#125;:role/ThreatDetectorRole&#39;</code></pre></div><p class="paragraph" style="text-align:left;">Since we want to monitor every region of every account, and <b>CloudTrail</b> events are only generated in each region of each account, we’ll need to push this using <b>StackSets.</b> But there’s one small problem: we can’t create the IAM role twice, since roles are always in <b>us-east-1.</b> This creates an error.</p><p class="paragraph" style="text-align:left;">To get around this problem we use the ‘code’ part of infrastructure as code. That <b>Condition</b> block creates something called <b>IsUSEast1</b> which only evaluate to <b>True</b> when the template is running in <b>us-east-1.</b> Then, in the <b>Resource</b> block for the <b>Role,</b> we use “<b>Condition: IsUSEast1</b>”, which says “only create this in <b>us-east-1</b>”. </p><p class="paragraph" style="text-align:left;">Pretty cool, right? The template will create our <b>EventBridge Rule</b> in every region where we run the template, but only create the role in one region.</p><p class="paragraph" style="text-align:left;">Now I want you to focus on the event pattern. There’s also a small redundancy in there… can you see it?</p><p class="paragraph" style="text-align:left;">We specify <b>source</b> as the originating service for the event, but then <i>within that event</i> we also validate the <b>eventSource</b>. This is optional — technically we only need one or the other. Using both slightly reduces the risk of someone spoofing an event (they can spoof <b>eventSource,</b> but not <b>source</b>).</p><p class="paragraph" style="text-align:left;">And one last note: we didn’t create an event bus in every region, just in the one region. One nice capability of <b>EventBridge</b> is that we can forward events across regions and accounts pretty easily. </p><p class="paragraph" style="text-align:left;">Okay, <b>save</b> that template as <b>threatdetectors.template</b>.</p><div class="section" style="background-color:transparent;border-color:#00ad11;border-radius:2px;border-style:solid;border-width:1px;margin:0.0px 0.0px 0.0px 0.0px;padding:0.0px 0.0px 0.0px 0.0px;"><p class="paragraph" style="text-align:center;"><b>Why we only forward some events</b></p><p class="paragraph" style="text-align:left;">If you think about it, we <b>could</b> just forward <b>every</b> <b>CloudTrail</b> event to our central bus and build our rules there. In fact, I’ve built that very architecture before. However, only forwarding the events we want is more efficient. Either option works, although technically the option we chose today is a bit cheaper (which is irrelevant at our scale). The drawback is that anyone who can read that <b>EventBridge</b> rule can see what we are looking for. I don’t really have strong feelings either way, but please send your feedback if you have a preference!</p></div><h3 class="heading" style="text-align:left;" id="time-to-deploy">Time to Deploy!</h3><p class="paragraph" style="text-align:left;">Start in your <b>Sign-in portal &gt; SecurityOperations &gt; AdministratorAccess &gt; Organizations.</b> Then <b>copy your Organization ID and paste it into a new text file.</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/c3972fb0-4b70-4892-83c4-0f80df83ad2a/image.png?t=1755641854"/></div><p class="paragraph" style="text-align:left;">Then go to <b>S3 &gt; Create bucket:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/7687b066-c6ca-4a8a-8d27-47f4b39cbb73/image.png?t=1755641971"/></div><p class="paragraph" style="text-align:left;"><b>Name it</b> “<i><b>cloudslaw-templates-&lt;your org ID&gt;-&lt;some random characters&gt;</b></i>” and <b>Create bucket:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ae8c6247-ec76-4605-bebb-ec5fe8c069cb/image.png?t=1755642084"/></div><p class="paragraph" style="text-align:left;">Then <b>click your bucket &gt; Permissions &gt; Bucket policy &gt; Edit</b> and copy and paste in this policy. <b>Then replace the bucket ARN and organization ID!!! </b>Make sure you leave “/*” at the end of the ARN:</p><div class="codeblock"><pre><code>&#123;
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    &#123;
      &quot;Sid&quot;: &quot;AllowOrgReadObjects&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: &quot;*&quot;,
      &quot;Action&quot;: &quot;s3:GetObject&quot;,
      &quot;Resource&quot;: &quot;arn:aws:s3:::&lt;bucketName&gt;/*&quot;,
      &quot;Condition&quot;: &#123;
        &quot;StringEquals&quot;: &#123;
          &quot;aws:PrincipalOrgID&quot;: &quot;&lt;o-YourOrgId&gt;&quot;
        &#125;
      &#125;
    &#125;
  ]
&#125;</code></pre></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/bdb6e31c-101a-49de-b215-3a7e5f3755fe/image.png?t=1755642298"/></div><p class="paragraph" style="text-align:left;">Then <b>Save changes.</b> Then <b>Objects &gt; Upload</b> and upload your two template files:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/58559c08-8922-4905-9d08-8e78a64a1fd6/image.png?t=1755642420"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/eb8e348e-d82d-4d46-bc10-f98a9615b7e7/image.png?t=1755642468"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/c17ca838-9485-4bb3-a825-4d9d5dc02d3b/image.png?t=1755642511"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/df010533-7817-4780-a03c-6af43a58e19a/image.png?t=1755642545"/></div><p class="paragraph" style="text-align:left;">With those hosted in S3, go back to <b>Objects,</b> then <b>click each file and copy its URL:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/b2856c50-d46e-4868-b7ec-50fe2bae2760/image.png?t=1755642641"/></div><p class="paragraph" style="text-align:left;">Paste both into your open text file, since you’ll need them to use <b>CloudFormation:</b> </p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/163002f1-70fd-4b58-a2ae-27edf95ce3ba/image.png?t=1755642701"/></div><p class="paragraph" style="text-align:left;">Now we just need to deploy the darn things. To start we need to create the central event bus and our forwarding rule in <b>SecurityAudit.</b> So… <b>close the tab &gt; sign in portal &gt; SecurityAudit &gt; AdministratorAccess &gt; CloudFormation &gt; Create stack &gt; With new resources:</b></p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/f167cea1-0847-47d2-a20a-9e60dacb4c07/image.png?t=1755643304"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/03fc3805-80f4-43d1-b0ff-7ebf271362f1/image.png?t=1755643366"/></div><p class="paragraph" style="text-align:left;"><b>Paste in YOUR template URL for securityauditeventcollector.template:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/cc1aa30f-6673-44cc-bfaf-eb33dfe28d4f/image.png?t=1755643478"/></div><p class="paragraph" style="text-align:left;">You’ll need the ARN of your existing SNS topic, which should be <b>arn:aws:sns:us-west-2:&lt;your account ID&gt;:SecurityHubAlerts.</b> <b>Name it SecurityEventCollector &gt; paste in your Organization ID and SNS topic ARN</b>. Remember, if you forget your account ID just click in the upper-right to grab it:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ee0497c9-964a-499e-9077-39266cccc34a/image.png?t=1755643724"/></div><p class="paragraph" style="text-align:left;">Click through everything else and <b>Submit.</b> Once it’s complete go to <b>EventBridge &gt; Event buses &gt; copy the ARN of your new event bus:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/a5b2a92a-6124-4374-8689-4169b6c2de1d/image.png?t=1755643871"/></div><p class="paragraph" style="text-align:left;">Now <b>close the tab &gt;</b> <b>sign in portal &gt; SecurityOperations &gt; AdministratorAccess &gt; CloudFormation &gt; StackSets &gt; Create StackSet:</b> </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/3227b16b-4aa1-4f0f-a4f0-1fb4a65fdaf6/image.png?t=1755643984"/></div><p class="paragraph" style="text-align:left;">This time <b>paste in your threat detectors.template URL:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/1f2391a2-a196-4ca5-bdce-3e7a559fbb32/image.png?t=1755644061"/></div><p class="paragraph" style="text-align:left;"><b>Name it ThreatDetectors</b> and <b>paste</b> in the ARN of your <b>Event Bus: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/968cf28c-8005-4d9d-a90a-02e58df43e8e/image.png?t=1755644127"/></div><p class="paragraph" style="text-align:left;">For <b>Set deployment options,</b> choose the following:</p><ul><li><p class="paragraph" style="text-align:left;">Deploy to organization</p></li><li><p class="paragraph" style="text-align:left;">Specify regions: <b>us-east-1</b> and <b>us-west-2</b></p></li><li><p class="paragraph" style="text-align:left;">Maximum current accounts: 10</p></li><li><p class="paragraph" style="text-align:left;">Failure tolerance: 9</p></li><li><p class="paragraph" style="text-align:left;">Region concurrency: <b>Parallel</b></p></li></ul><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/9f4d2fe9-64ce-4b1b-93fa-4fc910e5c3ea/image.png?t=1755644383"/></div><p class="paragraph" style="text-align:left;">Then <b>Submit.</b></p><p class="paragraph" style="text-align:left;">This is now deploying concurrently across all your accounts and regions. It should only take a few minutes. And being an “organization” <b>StackSet,</b> it will automatically deploy into all new accounts. Pretty cool, eh?</p><p class="paragraph" style="text-align:left;">Okay, testing this one is optional. The easy way is go into any of your accounts and create an IAM user, but don’t attach any policies. You’ll see an alert within 15 seconds, and I strongly recommend then going back to delete that IAM user.</p><h3 class="heading" style="text-align:left;" id="key-points">Lab Key Points</h3><ul><li><p class="paragraph" style="text-align:left;">Real-time event-based threat detectors must deploy to every account <i>and region</i> where you want to track activity.</p></li><li><p class="paragraph" style="text-align:left;">When hosting in S3 you need a bucket policy to allow access from other accounts in your organization.</p></li><li><p class="paragraph" style="text-align:left;"><b>EventBridge Rules</b> can forward events from one event bus to another, even across regions.</p></li></ul><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=8cfff4b7-d2e1-4de8-8312-b3f9585660a8&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Build a Time-Based Threat Detector with Lambda and Athena</title>
  <description>Time-based threat detectors allow us to identify activity beyond just a single API call. Today we&#39;ll build one with Lambda and Athena and learn about sliding windows.</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/bb51935c-275e-4b6d-8235-dd330f0ae553/Build_a_Time-Based_Threat_Detector_with_Lambda_and_Athena.png" length="1088464" type="image/png"/>
  <link>https://slaw.securosis.com/p/build-a-time-based-threat-detector-with-lambda-and-athena-2231</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/build-a-time-based-threat-detector-with-lambda-and-athena-2231</guid>
  <pubDate>Thu, 07 Aug 2025 15:00:00 +0000</pubDate>
  <atom:published>2025-08-07T15:00:00Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b>If You’re Reading This, Drop Me A Line!</b></p><p class="paragraph" style="text-align:left;">We are honing in on 70 labs in this crazy little project, and while I get to see all the stats I’m just curious at who has actually been keeping up with these lessons and labs now that we are in more advanced material. So drop me a quick email at <a class="link" href="mailto:rmogull@securosis.com" target="_blank" rel="noopener noreferrer nofollow">rmogull@securosis.com</a>, say hi, and let me know what else you want to see!</p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;"><a class="link" href="https://slaw.securosis.com/p/cross-account-aws-athena-for-secops-security-operations-incident-response?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=build-a-time-based-threat-detector-with-lambda-and-athena" target="_blank" rel="noopener noreferrer nofollow">Complete the Cross-Account Athena lab</a> (which has a few prerequisites itself).</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Lesson</h1><p class="paragraph" style="text-align:left;">Over the past few labs we learned how to run security-style queries with Athena, which in the realm of SecOps (incident response) is one of our best tools for analyzing incidents. Keep in mind that everything we’ve covered would also translate to a commercial Security Information and Event Management (SIEM) product.</p><p class="paragraph" style="text-align:left;">Today we’ll expand on the concept and learn how to use queries as <i>threat detectors.</i> A <b>threat detector</b> is basically an alarm. In SecOps we talk a lot about “indicators of compromise” and “indicators of attack” and other signs that an adversary is trying to break in (IoA) or has broken in (IoC). There’s a ton of art and science to these and we have various ways we can implement them. </p><p class="paragraph" style="text-align:left;">There are many techniques and places to implement threat detection, including use of specialized tools like Intrusion Prevention System (IPS) network appliances. GuardDuty? Yep, that’s a threat detection service run by AWS that tracks various activities in your account looking for something suspicious based on <i>what they are doing </i>(e.g. making a bucket public) or <i>who is doing it</i> (like activity from a known-suspicious IP address). (If you are interested in detection engineering I highly recommend the <a class="link" href="https://www.detectionengineering.net?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=build-a-time-based-threat-detector-with-lambda-and-athena" target="_blank" rel="noopener noreferrer nofollow">Detection Engineering Weekly Newsletter by Zack Allen</a>.).</p><p class="paragraph" style="text-align:left;">We’ve already set up “detections” for public S3 buckets and GuardDuty alerts, but what about activity based on multiple things happening in our logs?</p><p class="paragraph" style="text-align:left;">The following, for example, are potential threat activities that could show up in CloudTrail but we can’t capture with a single EventBridge rule or relying on something like GuardDuty:</p><ul><li><p class="paragraph" style="text-align:left;">Multiple failed/denied API calls over a time period that could indicate someone is trying to see what privileges an identity has.</p></li><li><p class="paragraph" style="text-align:left;">A change in the user agent used for making API calls that indicates maybe a credential was compromised and is now used in an attack tool.</p></li><li><p class="paragraph" style="text-align:left;">A sequence of actions that aren’t suspicious alone, but could indicate an attack when looked at together. Like the combination of GetCallerIdentity followed by Describe API calls followed by taking a snapshot of an instance and sharing it with a new account (as we showed in our <a class="link" href="https://slaw.securosis.com/p/skills-solution-investigate-your-own-aws-attack-with-athena-4934?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=build-a-time-based-threat-detector-with-lambda-and-athena" target="_blank" rel="noopener noreferrer nofollow">incident analysis skills challenge</a>). </p></li></ul><p class="paragraph" style="text-align:left;">All of these are in the logs, but to make them <b>threat detectors</b> we need to figure out how to trigger based on the activity.</p><h2 class="heading" style="text-align:left;" id="sliding-query-windows-for-threat-de">Sliding Query Windows for Threat Detection</h2><p class="paragraph" style="text-align:left;">In my examples above, you probably noticed that some of the examples look like we could see them in a single query, while others might take a little more analysis. You can probably see how to run the queries yourself to look for the activity, but for threat detection we want something that runs automatically on a schedule.</p><p class="paragraph" style="text-align:left;">This does have two problems:</p><ul><li><p class="paragraph" style="text-align:left;">You can run queries on a schedule, but what if one step of the attack happened before your query window? </p></li><li><p class="paragraph" style="text-align:left;">You can’t always detect these patterns with a single query. For example, you can’t just write SQL for “find me every time the user agent for this role changes”.</p></li></ul><p class="paragraph" style="text-align:left;">Let me illustrate the first point. Imagine an attack with four steps, and it’s only a potential threat if we see all four of those things in a row. In an ideal world, we could run queries for any user that does all of those four things over, say, the past 15 minutes.</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/b67b39c3-33a4-4159-87aa-780052ce3242/image.png?t=1754083417"/></div><p class="paragraph" style="text-align:left;">But what if those steps occur partially in one query’s time window, and partially in another?</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ee6444e7-20b8-4458-9a77-d8cbb1ce4b3b/image.png?t=1754083486"/></div><p class="paragraph" style="text-align:left;">Oops, we missed! But hey, you are all smart people (I mean, to a point, you are silly enough to be listening to me which shows terrible personal judgement). All we need to do is have overlapping/sliding query windows like this:</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/3f5f7b05-7e7f-40f6-8201-b7639541d7cb/image.png?t=1754083781"/></div><p class="paragraph" style="text-align:left;">Notice how I <i>extended</i> the query window for EACH query by 5 minutes (to a 20 minute window) and run them every 5 minutes? With this overlap I shouldn’t miss the sequence that indicates the attack. </p><p class="paragraph" style="text-align:left;">How do we figure out our time windows? There’s actually a formula that helps (ah crap, math 😭 ). Say we want to find 5 failed API calls within an hour, we just divide the detection window (60 minutes) by the number of actions (5) and thus run our query every 12 minutes.</p><p class="paragraph" style="text-align:left;">This is better than how I illustrated it, since if you did the math you can see I should have kept my 15 minute query window and run it every 3.75 minutes. Either approach is valid depending on how important timing is. Usually, for threat detection, we don’t care if X actions occur in Y timeframe so we can play with our windows a bit to reduce the frequency of queries and save a little cash. </p><p class="paragraph" style="text-align:left;">What I’ve illustrated here is the most basic way to approach the problem. It probably wouldn’t shock you to know that there are dedicated real-time stream-data analysis tools <a class="link" href="https://flink.apache.org?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=build-a-time-based-threat-detector-with-lambda-and-athena" target="_blank" rel="noopener noreferrer nofollow">like Apache Flink</a> for all sorts of advanced fun. Or we can send events we care about into a database like DynamoDB and run code or queries on top of that. </p><p class="paragraph" style="text-align:left;">Anyway, the Flink logo is so cute I’m just going to paste it here and call it a day. We’ll cover some of the other scenarios in future labs.</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/00784ba1-d8bc-4473-96a3-68ba57cc5e60/image.png?t=1754087231"/></div><h3 class="heading" style="text-align:left;" id="todays-key-points-are">Today’s lesson key points are:</h3><ul><li><p class="paragraph" style="text-align:left;">Threat detectors look for activity that is known to be a potential indicator of attack or indicator of compromise (IoA means someone is possibly attacking, IoC means they maybe succeeded).</p></li><li><p class="paragraph" style="text-align:left;">Scheduled queries are one common type of threat detector.</p></li><li><p class="paragraph" style="text-align:left;">When using scheduled queries and look for time-series events that could indicate an attack, we use sliding windows so we don’t miss the threat.</p></li><li><p class="paragraph" style="text-align:left;">Unless you are already using </p></li></ul><h1 class="heading" style="text-align:left;" id="the-lab"><b>The Lab</b></h1><p class="paragraph" style="text-align:left;">Today we will use a combination of EventBridge, Lambda, and Athena to run a query-based threat detector. Specifically we will look for 5 failed API calls over the past hour and use that 12 minute sliding time window. Why Lambda? Because there is no other way to schedule Athena queries.</p><div class="section" style="background-color:#ff0800;margin:0.0px 0.0px 0.0px 0.0px;padding:0.0px 0.0px 0.0px 0.0px;"><p class="paragraph" style="text-align:left;"><span style="color:#F9FAFB;">WARNING: If you don’t follow the instructions at the end to shut this down after the lab you will end up with a big AWS bill!!!</span></p></div><p class="paragraph" style="text-align:left;">Oh, and then we get all fancy and will transform our results into the Amazon Security Findings Format before we email them off (using our existing Security Hub alert SNS topic). Why ASFF? Because that’s what Security Hub uses natively in case we want to change things up later and push our findings into Security Hub directly.</p><p class="paragraph" style="text-align:left;"><i>Maybe I should have used OCSF, if you know what that is, but for our lab it doesn’t matter.</i></p><p class="paragraph" style="text-align:left;">We’ll finish by running a CloudFormation template in our TestAccount1 that creates an IAM role and lambda function that can’t really do anything that then tries to do things and generates failed API calls. </p><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/D9dlwpncpdA" width="100%"></iframe><h2 class="heading" style="text-align:left;" id="stepby-step">Step-by-Step</h2><p class="paragraph" style="text-align:left;">Start in your <b>Sign-in portal &gt; SecurityAudi &gt; SecurityFullAdmin &gt; us-west-2 region &gt; CloudFormation</b>.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/eb2bf6dc-bae6-4ca9-a2c9-705c22963c33/image.png?t=1754166665"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/86ace242-9f35-4997-9186-8039fde60c68/image.png?t=1754166725"/></div><p class="paragraph" style="text-align:left;">Quickly <b>snag your Account ID from the upper right corner</b> and paste it someplace safe since you will need it twice. </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/041e3134-ec77-4635-bd2a-64f13461b0d9/image.png?t=1754166794"/></div><p class="paragraph" style="text-align:left;">Then <b>Create stack:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/244522f6-a678-453f-a671-3835dc77a16d/image.png?t=1754166864"/></div><ul><li><p class="paragraph" style="text-align:left;">Deploy <a class="link" href="https://cloudslaw.s3-us-west-2.amazonaws.com/lab63.template?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=build-a-time-based-threat-detector-with-lambda-and-athena" target="_blank" rel="noopener noreferrer nofollow">https://cloudslaw.s3-us-west-2.amazonaws.com/lab63.template</a></p></li><li><p class="paragraph" style="text-align:left;">Name it <b>Threat Detector</b></p></li><li><p class="paragraph" style="text-align:left;">Paste in your <b>Account ID</b></p></li><li><p class="paragraph" style="text-align:left;">Click through the rest and <b>Submit</b></p></li></ul><p class="paragraph" style="text-align:left;">I highly encourage you to read this one while it deploys. The tl;dr is it:</p><ul><li><p class="paragraph" style="text-align:left;">Creates a new role for our lambda function with permissions for Athena, S3, CloudWatch Logs, and SNS.</p></li><li><p class="paragraph" style="text-align:left;">Creates a lambda function that:</p><ul><li><p class="paragraph" style="text-align:left;">Runs our query, which includes every user that has 5 failed API calls within the last hour (see below).</p></li><li><p class="paragraph" style="text-align:left;">For every user with the 5 failed API calls, it creates an ASFF-formatted finding and sends it to our Security Hub SNS topic (that we made a while ago).</p></li></ul></li><li><p class="paragraph" style="text-align:left;">Creates an EventBridge rule to run our lambda function every 12 minutes. </p></li></ul><p class="paragraph" style="text-align:left;">The python code is pretty long so I encourage you to read that yourself, but here’s the core logic for our threat detector (our Athena query):</p><div class="codeblock"><pre><code>SELECT 
     useridentity.arn as user_arn,
     COUNT(*) as failed_count,
     ARRAY_JOIN(ARRAY_AGG(DISTINCT eventname), &#39;|||&#39;) as failed_actions,
     ARRAY_JOIN(ARRAY_AGG(DISTINCT useragent), &#39;|||&#39;) as user_agents,
      MIN(eventtime) as first_failed_event_time,
      MAX(eventtime) as last_failed_event_time
FROM &#123;DATABASE_NAME&#125;.&#123;TABLE_NAME&#125;
WHERE 
      from_iso8601_timestamp(eventtime) &gt;= CURRENT_TIMESTAMP - INTERVAL &#39;75&#39; MINUTE
       AND errorcode IN (&#39;AccessDenied&#39;, &#39;UnauthorizedAccess&#39;, &#39;UnauthorizedOperation&#39;)
       AND useridentity.arn IS NOT NULL
       AND useridentity.arn != &#39;&#39;
       GROUP BY useridentity.arn
       HAVING COUNT(*) &gt; &#123;FAILED_API_THRESHOLD&#125;</code></pre></div><p class="paragraph" style="text-align:left;">It’s pulling the user identity, the event names (what the user tried to do), the user agent, (e.g. python or browser), the time of the first failed event and the time of the last failed event (useful for investigating). </p><p class="paragraph" style="text-align:left;">Yes, I used an LLM to write that… and all the python code. And the CloudFormation. DON’T JUDGE ME!!!!</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/0ea5492b-2068-4ce3-8441-1be49dd87231/image.png?t=1754167163"/></div><p class="paragraph" style="text-align:left;">Okay with that deployed we need to make some modifications to the bucket policy in our <b>LogAudit</b> account since, right now, only our SecurityFullAdmin role has the cross-account privileges <i>and this poor little lambda function is erroring out left and right until we give it access</i>.</p><p class="paragraph" style="text-align:left;"><b>Close the tab &gt; Sign in portal &gt; LogArchive &gt; AdministratorAccess &gt; S3 &gt; click your cloudtrail bucket &gt; Permissions &gt; scroll to Bucket Policy</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/4729fc43-acea-42d8-9472-c3a52f00c029/image.png?t=1754167528"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/934a5fc6-2461-456c-a7d7-d7574f238f4f/image.png?t=1754167581"/></div><p class="paragraph" style="text-align:left;">The very top of your policy looks like this with a different account ID:</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ed05c17b-5b07-4ae8-8656-755b8ee287b4/image.png?t=1754167773"/></div><div class="codeblock"><pre><code>&#123;
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        &#123;
            &quot;Sid&quot;: &quot;SecurityFullAdminCloudTrailAccess&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Principal&quot;: &#123;
                &quot;AWS&quot;: &quot;arn:aws:iam::533267272350:role/aws-reserved/sso.amazonaws.com/us-west-2/AWSReservedSSO_SecurityFullAdmin_c45dc754d51c1875&quot;
            &#125;,</code></pre></div><p class="paragraph" style="text-align:left;">You need to <b>edit the policy</b> to add in the IAM role used by the lambda function. This is a little tricky, so copy this and paste it into a text file so you can edit in your own details:</p><div class="codeblock"><pre><code>&#123;
	&quot;Version&quot;: &quot;2012-10-17&quot;,
	&quot;Statement&quot;: [
		&#123;
			&quot;Sid&quot;: &quot;SecurityFullAdminCloudTrailAccess&quot;,
			&quot;Effect&quot;: &quot;Allow&quot;,
			&quot;Principal&quot;: &#123;
				&quot;AWS&quot;: [
				&quot;THE ARN OF YOUR SECURITYFULLADMIN ROLE IN THE OLD POLICY&quot;,
				&quot;arn:aws:sts::YOUR ACCOUNT ID:assumed-role/TimedThreatDetectorsLambdaRole/TimedThreatDetectors&quot;
				]
			&#125;,</code></pre></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/5b02ba8c-7722-49c9-ba88-541c2edc8984/image.png?t=1754167823"/></div><p class="paragraph" style="text-align:left;">Now <b>paste the update in-place over the “old” version, making sure to check you didn’t mess up any commas or other characters</b>.</p><p class="paragraph" style="text-align:left;">The full policy will look like this when you are done.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/4e7994bc-3f05-4152-bf12-51632629f209/image.png?t=1754167946"/></div><p class="paragraph" style="text-align:left;"><b>Scroll down and click Save changes!</b></p><p class="paragraph" style="text-align:left;">Now it’s time to test! We will run a CloudFormation template that creates a lambda function with a role that doesn’t have any privileges, but still tries to do things, and an EventBridge rule to run it on a schedule. This will generate plenty of Denied API calls for our logs.</p><p class="paragraph" style="text-align:left;"> <b>Close the tab &gt; Sign in portal &gt; TestAccount1 &gt; CloudFormation &gt; Create stack</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/6f503c27-8e7d-45b7-a66a-8bd337cf2227/image.png?t=1754168063"/></div><ul><li><p class="paragraph" style="text-align:left;"><b>Use </b><a class="link" href="https://cloudslaw.s3-us-west-2.amazonaws.com/lab63-test.template?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=build-a-time-based-threat-detector-with-lambda-and-athena" target="_blank" rel="noopener noreferrer nofollow">https://cloudslaw.s3-us-west-2.amazonaws.com/lab63-test.template</a></p></li><li><p class="paragraph" style="text-align:left;">Name it <b>deleteme</b></p></li><li><p class="paragraph" style="text-align:left;">Click through the rest and <b>Submit</b></p><p class="paragraph" style="text-align:left;"></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/65b5ed30-18cf-45ef-bbd1-d389aad2d89d/image.png?t=1754168189"/></div><p class="paragraph" style="text-align:left;"><b>Walk away from your computer for 15 minutes, then Delete the stack!!!!</b></p></li></ul><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/9ce9863d-ee12-4d08-99c8-be91e8f1d241/image.png?t=1754168478"/></div><p class="paragraph" style="text-align:left;">Now wait about 15-30 minutes (since it takes a little time for CloudTrail to write the logs to S3) and check your email. You should have an alert that looks like this:</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/5c955997-d5d9-4eba-ab58-f7c50da7707d/image.png?t=1754168425"/></div><p class="paragraph" style="text-align:left;">Pretty cool, eh? We have a nicely-formatted alert based on an Athena query that’s running on a timed schedule with sliding windows!</p><div class="section" style="background-color:transparent;border-color:#ff0800;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:0.0px 0.0px 0.0px 0.0px;"><p class="paragraph" style="text-align:center;">🚨 WARNING: SHUT DOWN OR PAY THE PRICE! 🚨 </p><p class="paragraph" style="text-align:left;">After writing this lab, but before I released it, <b>my billing alert went off</b>. Running a lot of Athena queries on 3 months of logs is generating a lot of S3 overhead.</p><p class="paragraph" style="text-align:left;">Your safest option is to <b>delete the CloudFormation stack in SecurityAudit!</b></p><p class="paragraph" style="text-align:left;">Alternatively, you can disable the EventBridge Rule, which stops the lambda from running and executing the queries. Deleting the stack is safest, and if we need this detector in the future for a lab I will provide a new stack to deploy it again.</p></div><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=b069cebc-df33-41e7-ae39-ce87252f29d7&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Skills Solution: Investigate Your Own AWS Attack with Athena</title>
  <description>Time for some answers.</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/b4e26f8f-b53f-4c71-abe6-0c52fd67453e/Copy_of_CloudSLAW.png" length="246189" type="image/png"/>
  <link>https://slaw.securosis.com/p/skills-solution-investigate-your-own-aws-attack-with-athena-4934</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/skills-solution-investigate-your-own-aws-attack-with-athena-4934</guid>
  <pubDate>Thu, 24 Jul 2025 15:00:00 +0000</pubDate>
  <atom:published>2025-07-24T15:00:00Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b><a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=skills-solution-investigate-your-own-aws-attack-with-athena" target="_blank" rel="noopener noreferrer nofollow">Go Pro for Support and Extras!</a></b></p><p class="paragraph" style="text-align:left;">CloudSLAW is, and always will be, free. But to help cover costs and keep the content up to date we have an optional Patreon. For $10 per month you get access to our support Discord, office hours, exclusive subscriber content (not labs — those are free — just extras) and more. <a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=skills-solution-investigate-your-own-aws-attack-with-athena" target="_blank" rel="noopener noreferrer nofollow">Check it out!</a></p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;">This is the solution for the challenge <a class="link" href="https://slaw.securosis.com/p/skills-challenge-investigate-your-own-aws-attack-with-athena-3d0d?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=skills-solution-investigate-your-own-aws-attack-with-athena" target="_blank" rel="noopener noreferrer nofollow">Skills Challenge: </a><b><a class="link" href="https://slaw.securosis.com/p/skills-challenge-investigate-your-own-aws-attack-with-athena-3d0d?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=skills-solution-investigate-your-own-aws-attack-with-athena" target="_blank" rel="noopener noreferrer nofollow">Investigate Your Own AWS Attack with Athena</a></b>.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Solution</h1><p class="paragraph" style="text-align:left;">The prior lab simulated an attack in your account to generate logs we can use to start learning the basics of security queries for Athena and how to investigate incidents. This is all based on my RECIPE PICKS mnemonic, which <a class="link" href="https://securosis.com/blog/aws-cloud-incident-analysis-query-cheatsheet/?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=skills-solution-investigate-your-own-aws-attack-with-athena" target="_blank" rel="noopener noreferrer nofollow">I wrote up in this blog post</a>. </p><p class="paragraph" style="text-align:left;">In this lab I’ll walk you through my process and how I ran through the challenge. <b>Remember, this is not a full incident investigation — we are still in the early learning phase, so there are parts I skipped!</b></p><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/qq_eD8tUrLw" width="100%"></iframe><p class="paragraph" style="text-align:left;">To start here’s the graphical mnemonic, and I’ll run through everything in order:</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/492c2e1b-4e69-4397-9e09-8263e68211a2/RECIPE-PICKS.jpg?t=1753117638"/><div class="image__source"><span class="image__source_text"><p>Maybe I should use an AI to make this look cooler!</p></span></div></div><p class="paragraph" style="text-align:left;">Now, how do I even know what I need to investigate? Well, it turns out I got this handy email from Access Analyzer via Security Hub (since we configured email alerts… many labs ago). </p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/123d57de-5c24-437f-bd6d-1fe2df8f34cb/image.png?t=1753117755"/></div><div class="section" style="background-color:#00ad11;margin:0.0px 0.0px 0.0px 0.0px;padding:0.0px 0.0px 0.0px 0.0px;"><p class="paragraph" style="text-align:center;"><b>Guiding Principles</b></p><p class="paragraph" style="text-align:left;">I use two phrases in any investigation that come from my work as a paramedic. <i>Sick or Not Sick</i> means as I go through the incident, I’m always trying to see whether there is a key indicator that things are bad. In security this means… does this indicate an attack or a breach? The second is <i>Stop the Bleed</i>… in other words, “Do I need to take action now so this doesn’t get worse?” Both of those are in my head whenever I’m investigating, and I ask myself these questions whenever I see something new.</p></div><h3 class="heading" style="text-align:left;" id="resource">Resource</h3><p class="paragraph" style="text-align:left;">That email tells me I have a public snapshot, and it provides the account ID and the snapshot ID. The first thing I did was copy that snapshot ID and put it into a text file for reference.</p><p class="paragraph" style="text-align:left;">Then I logged into that account, which will always be via <b>your sign in portal &gt; TestAccount1</b> &gt; <b>AdministratorAccess, </b>and went to view any snapshots via <b>EC2 &gt; Snapshots &gt; deleteme.</b> Remember, <i>we did not delete the CloudFormation stack yet, so should still have the resources we need, but if you DID delete it before investigating you can run it again, or just rely on what you see in the logs</i>.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/91e03bb1-9abb-43f8-aa7f-38b85862ba17/image.png?t=1753118259"/></div><p class="paragraph" style="text-align:left;">I’ll call this one <b>sick</b>, so I want to <b>stop the bleed</b>. Why? Because it’s a public snapshot and I really shouldn’t have those if I wasn’t expecting them. If you want you can click <b>Modify permissions</b> and make the snapshot private.</p><p class="paragraph" style="text-align:left;">In a real investigation I might also start looking at the instance the snapshot is based on, but we’ll skip that for this exercise.</p><h3 class="heading" style="text-align:left;" id="events">Events</h3><p class="paragraph" style="text-align:left;"><b>Go to sign-in portal &gt; SecurityAudit &gt; SecurityFullAdmin &gt; Athena</b> to run your queries.</p><p class="paragraph" style="text-align:left;">This is the query we ran last week. I like to know the event, when it happened, and who did it. In our sample we don’t pull the <b>requestParameters,</b> but I often also grab those to see the full parameters of the API call.</p><div class="codeblock"><pre><code>SELECT
useridentity.arn,
eventname,
sourceipaddress,
eventtime,
resources
FROM cloudtrail_logs.organization_trail
WHERE requestparameters like &#39;%snap-YOUR ID%&#39; OR responseelements like &#39;%snap-YOUR ID%&#39;
ORDER BY eventtime</code></pre></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/f3198d7c-cd5f-4108-908f-1aa98f751807/image.png?t=1753118609"/></div><p class="paragraph" style="text-align:left;">As a reminder, this shows every API call where the request or response includes that snapshot ID. I can see the original create call, the tag (with the <b>deleteme</b> name), a bunch of <b>describe</b> calls as the code was waiting for the snapshot to finish (you’ll see them a lot in logs), and then that <b>ModifySnapshotAttribute,</b> which I just happen to know is the call to make it public. </p><p class="paragraph" style="text-align:left;">Nearly every call came from the <b>slaw-demo</b> IAM user, although you can also see Access Analyzer and Security Hub at work. </p><h3 class="heading" style="text-align:left;" id="changes">Changes</h3><p class="paragraph" style="text-align:left;">These are the before and after states. For the snapshot in this lab we see the full history (not public, then public). But this is a very simple demo, and in a larger incident we would need full change management tracking from AWS Config or a CSPM tool. That’s more than we have time for today.</p><h3 class="heading" style="text-align:left;" id="identity">Identity</h3><p class="paragraph" style="text-align:left;">Who made the API calls? We can see that it’s an IAM user (bad on you) with the name <b>slaw-demo-&lt;account ID&gt;.</b> Paste this into your notes, and we will come back later. There’s also an interesting <b>CreateTags,</b> with no associated identity, which we will ignore today (unless you want to look yourself), since it’s something internal to AWS. </p><p class="paragraph" style="text-align:left;">At this point we know that IAM user created a snapshot of an instance and made it public, so we have a good sense of what’s going on here.</p><h3 class="heading" style="text-align:left;" id="permissions">Permissions</h3><p class="paragraph" style="text-align:left;">This tells us the <b>IAM blast radius </b>of that IAM user. What permissions does it have? How much damage can it do? </p><p class="paragraph" style="text-align:left;">To make life easy, I just logged into <b>TestAccount1 &gt; IAM &gt; Users,</b> clicked that user, and looked at the permissions. Instead of a truncated screenshot, here’s the full permissions policy:</p><div class="codeblock"><pre><code>&#123;
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        &#123;
            &quot;Action&quot;: [
                &quot;iam:GetUser&quot;,
                &quot;sts:GetCallerIdentity&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;
        &#125;,
        &#123;
            &quot;Action&quot;: [
                &quot;ec2:DescribeInstances&quot;,
                &quot;ec2:DescribeVolumes&quot;,
                &quot;ec2:DescribeSnapshots&quot;,
                &quot;ec2:DescribeSnapshotAttribute&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;
        &#125;,
        &#123;
            &quot;Action&quot;: [
                &quot;ec2:CreateSnapshot&quot;,
                &quot;ec2:CreateTags&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;
        &#125;,
        &#123;
            &quot;Action&quot;: [
                &quot;ec2:ModifySnapshotAttribute&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;
        &#125;
    ]
&#125;</code></pre></div><p class="paragraph" style="text-align:left;"><i>I’d call this patient </i><b><i>sick</i></b>. Why? Because that IAM user can describe and snapshot any of my instances, and change the attributes to make them public!</p><p class="paragraph" style="text-align:left;">In a real incident, I’d probably add a <b>DenyAll</b> policy to <b>stop the bleed,</b> but not today.</p><h3 class="heading" style="text-align:left;" id="entitlements">Entitlements</h3><p class="paragraph" style="text-align:left;">Okay, sometimes you just need to make a mnemonic work! I wrote this before any nifty AI could help out. In this case we are shifting to the permissions of the resource. Does a snapshot ever have permissions? Nope. But the instance it’s based on? Maybe</p><p class="paragraph" style="text-align:left;">You can go into <b>EC2 &gt; instances</b> and poke around and see whether your instance has an IAM attached role (it doesn’t). It might still have embedded credentials for something, but save some time and don’t bother — I just gave you the answer. Too many instances to figure out which one is affected? You can pull the ID of the storage volume the snapshot was based on in the <b>RequestParameters,</b> then look at that volume to see which instance is attached. </p><p class="paragraph" style="text-align:left;">Like I said, today is just the basics.</p><h3 class="heading" style="text-align:left;" id="public">Public</h3><p class="paragraph" style="text-align:left;">Been there, done that, and the answer was in the alert that said “this thing is public.”</p><h3 class="heading" style="text-align:left;" id="ip">IP</h3><p class="paragraph" style="text-align:left;">For this I did two things. First, I pulled the IP from the query results and ran it through an IP checker (<a class="link" href="http://whatismyip.com?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=skills-solution-investigate-your-own-aws-attack-with-athena" target="_blank" rel="noopener noreferrer nofollow"><b>whatismyip.com</b></a>). </p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/f363ac8d-3932-43fe-82b1-939c85782caa/image.png?t=1753119751"/></div><p class="paragraph" style="text-align:left;">Well, it came from within AWS. And to save you time, AWS uses a lot of instances to run things (including lambda functions, which is what we did today) so that… is not overly helpful. However, this query will tell us all the API calls to our account from that IP, and might give us a better sense of what’s going on:</p><p class="paragraph" style="text-align:left;"><b>Open another query window and use the provided JSON</b></p><div class="codeblock"><pre><code>SELECT
awsregion,
eventname,
eventtime,
useragent
FROM cloudtrail_logs.organization_trail
WHERE sourceIpAddress = &#39;IPADDRESS&#39;
ORDER BY eventtime</code></pre></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/8d8f713e-7f56-4382-af82-588def9f7742/image.png?t=1753119925"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ec2b5189-a6f1-4f67-85a1-d02fc8d1187c/image.png?t=1753119977"/></div><p class="paragraph" style="text-align:left;">Well that’s interesting, the useragent string shows this originated from a lambda function running python (that’s thanks to the Boto part of the string, which is the name of the python SDK for AWS).</p><p class="paragraph" style="text-align:left;">I also see <b>GetCallerIdentity,</b> which can be an indicator of attack, because it’s the API call you make when you… don’t know who you are. If you are an attacker and you get an access key and secret key, you often start with <b>GetCallerIdentity</b> so you can learn what account you just got access to — this is a noisy method, there are other quieter ones out there.</p><h3 class="heading" style="text-align:left;" id="caller">Caller</h3><p class="paragraph" style="text-align:left;">Let’s look at all the API calls from that IAM user. <b>Open another query window and use this JSON:</b></p><div class="codeblock"><pre><code>SELECT eventname, useridentity.username, sourceIPAddress, eventtime, requestparameters
from cloudtrail_logs.organization_trail
where useridentity.username = &#39;slaw-demo-YOURID&#39;
order by eventtime asc;</code></pre></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/d3efb295-e056-4cd0-add5-466ccc2c03f5/image.png?t=1753120250"/></div><p class="paragraph" style="text-align:left;">I have more results than you, since I ran this multiple times, and we are searching on a name (if I looked for the access key it would only show unique results, since those aren’t reused… mostly). I also added the <b>RequestParameters</b> to the results, just to show how you can add and remove the data you are pulling based on what you are doing at the time.</p><p class="paragraph" style="text-align:left;">As I look at this, I don’t see anything I didn’t already know from pulling the other logs. The IAM user runs <b>GetCallerIdentity,</b> then describes instances, takes a snapshot of one, then makes it public.</p><h3 class="heading" style="text-align:left;" id="track">Track</h3><p class="paragraph" style="text-align:left;">This is where we look for indications of a pivot. There isn’t really a query for this, so we look for indicators like multiple IAM users or roles being used from the same weird IP address, signs of role chaining and similar. It’s more than we can really cover in this basic scenario, sorry!</p><p class="paragraph" style="text-align:left;">But hey, I don’t want to leave you with nothing! Here’s a query to identify any API calls which were denied or had errors. It can be super noisy and not always useful, but combined with the other information we gathered, it might help give you a sense of what security boundaries the attacker was trying to get past:</p><div class="codeblock"><pre><code>SELECT count (*) as TotalEvents, useridentity.arn, eventsource, eventname, errorCode, errorMessage
FROM cloudtrail_logs.organization_trail
WHERE (errorcode like &#39;%Denied%&#39; or errorcode like &#39;%Unauthorized%&#39;)
GROUP by eventsource, eventname, errorCode, errorMessage, useridentity.arn
ORDER by eventsource, eventname</code></pre></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/eb32cd38-9154-46e3-bb9e-71c9ef018071/image.png?t=1753122101"/></div><p class="paragraph" style="text-align:left;">Nothing really stood out to me when I went through these. Normally I would time-bound around the attack, but I was lazy and pulled them all from the last 3 months.</p><h3 class="heading" style="text-align:left;" id="forensics">Forensics</h3><p class="paragraph" style="text-align:left;">This is… everything else. Do I need to see everything running on the instance or what data was stored on it? Do I have other kinds of logs like database or VPC flow logs to review? </p><h2 class="heading" style="text-align:left;" id="incident-summary">Incident Summary</h2><p class="paragraph" style="text-align:left;">Our objective today was to gain more experience with Athena security queries through a very basic incident. Based on our queries and looking around, we now know:</p><ul><li><p class="paragraph" style="text-align:left;">A lambda function from another account:</p><ul><li><p class="paragraph" style="text-align:left;">Used <b>GetCallerIdentity</b> to figure out what account it belonged to.</p></li><li><p class="paragraph" style="text-align:left;">Described our instances.</p></li><li><p class="paragraph" style="text-align:left;">Took a snapshot of an instance (technically, of the storage volume).</p></li><li><p class="paragraph" style="text-align:left;">Tagged the snapshot <b>deleteme.</b></p></li><li><p class="paragraph" style="text-align:left;">Used <b>ModifySnapshotAttribute</b> to make the snapshot public.</p></li></ul></li><li><p class="paragraph" style="text-align:left;">The IAM user had permissions which allowed it to snapshot any instance and make it public.</p></li><li><p class="paragraph" style="text-align:left;">The lambda function used the python SDK.</p></li><li><p class="paragraph" style="text-align:left;">The instance did not have any permissions (roles) assigned to it.</p></li></ul><p class="paragraph" style="text-align:left;">I’d guess this was an exposed credentials situation, largely due to the <b>GetCallerIdentity</b> API call. Also, because I coded the attack and I sent your credentials to my account in the first place.</p><p class="paragraph" style="text-align:left;">I hope this was useful. We’ll come back to this exact scenario later, and go into much greater depth to fill in the gaps I glossed over. This set of queries is nearly always where I start, and if you are using a commercial SIEM you can easily modify them for the appropriate query language for your platform.</p><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=3e688c51-3ed0-4c46-92e8-bbf11e8a8a2e&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Skills Challenge: Investigate Your Own AWS Attack with Athena</title>
  <description>Let’s pretend to hack your account, so you’ll see the base Athena queries for security incidents.</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/38997398-c5c1-4685-8295-0f444cc2aa80/CloudSLAW.png" length="246684" type="image/png"/>
  <link>https://slaw.securosis.com/p/skills-challenge-investigate-your-own-aws-attack-with-athena-3d0d</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/skills-challenge-investigate-your-own-aws-attack-with-athena-3d0d</guid>
  <pubDate>Thu, 10 Jul 2025 15:00:00 +0000</pubDate>
  <atom:published>2025-07-10T15:00:00Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b><a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=skills-challenge-investigate-your-own-aws-attack-with-athena" target="_blank" rel="noopener noreferrer nofollow">Go Pro for Support and Extras!</a></b></p><p class="paragraph" style="text-align:left;">CloudSLAW is, and always will be, free. But to help cover costs and keep the content up to date we have an optional Patreon. For $10 per month you get access to our support Discord, office hours, exclusive subscriber content (not labs — those are free — just extras) and more. <a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=skills-challenge-investigate-your-own-aws-attack-with-athena" target="_blank" rel="noopener noreferrer nofollow">Check it out!</a></p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;">Athena set up so you can query CloudTrail.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Lesson and Lab</h1><p class="paragraph" style="text-align:left;">Today we&#39;ll do a bit of a skills challenge. Unlike some of our other skills challenges, this one&#39;s not designed to test new or existing skills. It&#39;s designed to actually help you learn and work your way through a particular problem.</p><h2 class="heading" style="text-align:left;" id="the-setup">The Setup</h2><p class="paragraph" style="text-align:left;">In our last lab we learned how to set up Athena to query CloudTrail logs, and tried a few basic queries. For this challenge I’ll point you towards a <a class="link" href="https://securosis.com/blog/aws-cloud-incident-analysis-query-cheatsheet/?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=skills-challenge-investigate-your-own-aws-attack-with-athena" target="_blank" rel="noopener noreferrer nofollow">blog post I use as my reference for initial incident response</a>, and then we will simulate an attack on your account. </p><p class="paragraph" style="text-align:left;">Your challenge, should you choose to accept it, is to use those queries to piece together more of what happened. Being early in our incident response learning, I don’t expect you to run completely through the process, but you should work with a few of the queries to get a sense of how things piece together. In the next lab I’ll run through it myself, showing you how I approach the problem.</p><p class="paragraph" style="text-align:left;"><i><b>Our goal is to reinforce how to write Athena queries for security — this isn’t a test, but a chance to learn and explore more on your own!</b></i> </p><p class="paragraph" style="text-align:left;">Here’s how it will work:</p><p class="paragraph" style="text-align:left;">You’ll run a CloudFormation Template in <b>TestAccount1.</b> This will build some things, including an IAM User with static credentials, and share that information with me over EventBridge. On my side it will trigger a simulated attack: a series of API calls (run in a Lambda function) similar to an attacker finding credentials and then using them to exfiltrate data.</p><p class="paragraph" style="text-align:left;">Everything is tightly constrained and I’m happy to share the Lambda source code if you have concerns. The TL;DR is that my simulator can only work with resources with a very specific naming pattern which embeds the account ID, and the account ID is cross-referenced with the metadata from EventBridge, so you can’t spoof the system and trick it into touching an account other than the one which originated the message. Nor can you trick it into touching resources which don’t match the names it expects.</p><p class="paragraph" style="text-align:left;">You will:</p><ol start="1"><li><p class="paragraph" style="text-align:left;">Run that CloudFormation.</p></li><li><p class="paragraph" style="text-align:left;">Let it run for a few minutes.</p></li><li><p class="paragraph" style="text-align:left;">That simulates an attack.</p></li><li><p class="paragraph" style="text-align:left;"><i>If you’ve done our other labs, you’ll get an alert in email!</i></p></li><li><p class="paragraph" style="text-align:left;">Then it shuts everything down.</p></li><li><p class="paragraph" style="text-align:left;">You investigate with the queries.</p></li><li><p class="paragraph" style="text-align:left;">You delete the stack (you can delete it whenever you want after it initially runs — what we need is already in the logs at that point).</p></li></ol><p class="paragraph" style="text-align:left;">It’s a one and done, and all the hard work happens with the logs.</p><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/Vopn3WBxLo0" width="100%"></iframe><h2 class="heading" style="text-align:left;" id="lets-do-this">Let&#39;s Do This!</h2><p class="paragraph" style="text-align:left;">First <b>open the blog post at </b><a class="link" href="https://securosis.com/blog/aws-cloud-incident-analysis-query-cheatsheet/?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=skills-challenge-investigate-your-own-aws-attack-with-athena" target="_blank" rel="noopener noreferrer nofollow">https://securosis.com/blog/aws-cloud-incident-analysis-query-cheatsheet/</a></p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/9d65e28a-5536-4f47-bf5f-c54dbc27b17d/image.png?t=1752015421"/></div><p class="paragraph" style="text-align:left;">Sign into the console, go to <b>TestAccount1</b> &gt; <b>AdministratorAccess &gt; region us-west-2 (Oregon) &gt; CloudFormation &gt; Create stack:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/dcf3c5b0-0416-4d97-bd08-fd3188097121/image.png?t=1752015535"/></div><p class="paragraph" style="text-align:left;">At this point, you know the drill:</p><ul><li><p class="paragraph" style="text-align:left;">Use the <b>S3 url </b><a class="link" href="https://cloudslaw.s3-us-west-2.amazonaws.com/lab61.template?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=skills-challenge-investigate-your-own-aws-attack-with-athena" target="_blank" rel="noopener noreferrer nofollow">https://cloudslaw.s3-us-west-2.amazonaws.com/lab61.template</a></p></li><li><p class="paragraph" style="text-align:left;">Name it <b>simulation.</b></p></li><li><p class="paragraph" style="text-align:left;">Leave everything else at defaults and <b>Submit.</b></p></li></ul><p class="paragraph" style="text-align:left;">You’ve done this like 87.5 times now, so no more screenshots!</p><h2 class="heading" style="text-align:left;" id="whats-happening-behind-the-scenes">What&#39;s Happening Behind the Scenes</h2><p class="paragraph" style="text-align:left;">In the background this:</p><ul><li><p class="paragraph" style="text-align:left;">Creates a VPC (because you didn&#39;t have any in your account) with a private-only subnet.</p></li><li><p class="paragraph" style="text-align:left;">Launches an instance in that subnet.</p></li><li><p class="paragraph" style="text-align:left;">Immediately stops that instance so you don’t get billed for it.</p></li><li><p class="paragraph" style="text-align:left;">Then creates an IAM user with an access key and secret key.</p></li></ul><p class="paragraph" style="text-align:left;">An access key and a secret? Remember when I said never to allow this? Well, now we&#39;re going to learn why, because we will actually simulate what an attack would look like.</p><p class="paragraph" style="text-align:left;">That access key and secret key are sent to me over EventBridge. Those feed into a Lambda function. Now, just so you know, <b>I don’t storing them anywhere</b>. They are not logged. They are not stored in S3. They&#39;re not stored <i>anywhere.</i></p><p class="paragraph" style="text-align:left;">That Lambda function uses those credentials to create a snapshot of our specially-named instance, and to make the snapshot public.</p><h2 class="heading" style="text-align:left;" id="finding-your-starting-point">Finding Your Starting Point</h2><p class="paragraph" style="text-align:left;">Two things should happen within a few minutes:</p><ol start="1"><li><p class="paragraph" style="text-align:left;">If you set up Access Analyzer correctly in our previous labs, you&#39;ll actually get an email alert coming out of Security Hub from Access Analyzer that something is now public. <i>If you get that email, </i><b><i>copy the snapshot ID.</i></b></p></li><li><p class="paragraph" style="text-align:left;">Otherwise, you can go into <b>EC2 &gt; Snapshots </b> and look for one named <b>deleteme </b>which is public. <b>Copy the snapshot ID.</b></p></li></ol><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/bc60e9b6-00b0-4a1e-aa59-df4c5fc933f5/image.png?t=1752016349"/></div><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/a9f864e2-1692-4a8a-8035-76e6fdf484eb/image.png?t=1752016418"/><div class="image__source"><span class="image__source_text"><p>Ugly email, but it gets the job done</p></span></div></div><p class="paragraph" style="text-align:left;">I love how our controls work together, so a lab from months ago triggers an alert near real-time. With that snapshot ID it’s time to start your queries.</p><h2 class="heading" style="text-align:left;" id="time-to-investigate">Time to Investigate</h2><p class="paragraph" style="text-align:left;">Will we query the logs here in this account? Nope, we <b>close the tab &gt; sign in portal &gt; SecurityAudit &gt; SecurityFullAdmin &gt; Athena.</b> Why? Because that’s where Athena is set up from previous labs.</p><p class="paragraph" style="text-align:left;">You likely have tabs open from our last lab. Close them out. While I want you to work with the queries over at the <a class="link" href="https://securosis.com/blog/aws-cloud-incident-analysis-query-cheatsheet/?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=skills-challenge-investigate-your-own-aws-attack-with-athena" target="_blank" rel="noopener noreferrer nofollow">Securosis blog post on incident investigations</a> on your own, I’ll show you the first one to get started.</p><h3 class="heading" style="text-align:left;" id="your-first-query">Your First Query</h3><p class="paragraph" style="text-align:left;">Here’s the first query, taken right from that post, with the table name pre-filled, since we are all using the same one:</p><div class="codeblock"><pre><code>SELECT
useridentity.arn,
eventname,
sourceipaddress,
eventtime,
resources
FROM cloudtrail_logs.organization_trail
WHERE requestparameters like &#39;%&lt;your snapshot id&gt;%&#39; OR responseelements like &#39;%&lt;your snapshot id&gt;%&#39;
ORDER BY eventtime</code></pre></div><p class="paragraph" style="text-align:left;">Copy this, paste it into your Athena window, replace <i>&lt;your snapshot id&gt;</i> with your snapshot ID (don’t leave those brackets!) and wait about 45 seconds for results:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/6cfd2e11-38cf-4314-8870-babd0aac0b91/image.png?t=1752016960"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/3658e481-6a3d-489a-b344-1f28932b9de2/image.png?t=1752017001"/></div><p class="paragraph" style="text-align:left;">You will see all results where the request or response included the snapshot ID, which is a pretty good representation of everything involving that snapshot.</p><h2 class="heading" style="text-align:left;" id="your-mission">Your Mission</h2><p class="paragraph" style="text-align:left;">There are a bunch of queries on this list. What I’d like you to do is play with the queries on your own to gather more information. Your objective: piece together the story of what my little attack script did.</p><p class="paragraph" style="text-align:left;">You’re starting with that snapshot ID because that’s where you got your alert. From that alert, you want to piece together, well, what actually happened? We have all of our events — that&#39;s what we did with Athena. But there are other areas to look at, such as:</p><ul><li><p class="paragraph" style="text-align:left;">Who or what made the API calls?</p></li><li><p class="paragraph" style="text-align:left;">What is that snapshot of?</p></li><li><p class="paragraph" style="text-align:left;">What permissions did that user have? (You may need to go back into TestAccount1 to look).</p></li></ul><p class="paragraph" style="text-align:left;">Your objective here isn’t to do everything you would in an incident response. I want you to focus on the basic Athena queries. Try to spend about 15 or 20 minutes working on this, but feel free to do less or more. I’m not your dad. (I can say that because my kids never read anything I write anyway). </p><p class="paragraph" style="text-align:left;">If you follow the RECIPE PICKS process, you should be able to figure out the timeline of events, the potential origin (hint, it was leaked credentials), the blast radius (what else could that identity do?), and what was exposed.</p><h2 class="heading" style="text-align:left;" id="hints-and-cleaning-up">Hints and Cleaning Up</h2><p class="paragraph" style="text-align:left;">I told you exactly what happened, since this is about testing out those queries — <b>not</b> performing a full incident investigation. We’ll do that next lab, as I walk through the solution step by step. </p><p class="paragraph" style="text-align:left;">The biggest hint is to have a private window or another browser open that stays logged into TestAccount1. That will enable you to look at the identity and resources (instance, storage volume, and snapshot) so you have context for your investigation. These are <b>much</b> harder to figure out when you are just looking at the logs.</p><p class="paragraph" style="text-align:left;">Once you’re done <b>go back into TestAccount1</b> to clean things up. <b>Delete the snapshot and the CloudFormation stack</b>. Yep, it’s that simple.</p><p class="paragraph" style="text-align:left;">That’s it, and see you soon with a full walkthrough of how I approach this!</p><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=c8e0b201-4238-45b4-baae-4927de89dfbb&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Getting Started with CloudTrail Security Queries</title>
  <description>Unlock the mysteries of CloudTrail logs with a few simple starter queries. We&#39;ll use these later as the basis of threat detectors and for incident analysis.</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/85393be7-7c7f-4070-8339-77e9c3713022/Getting_started_with_cloudtrail_security_queries.png" length="498149" type="image/png"/>
  <link>https://slaw.securosis.com/p/getting-started-with-cloudtrail-security-queries</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/getting-started-with-cloudtrail-security-queries</guid>
  <pubDate>Thu, 19 Jun 2025 15:00:00 +0000</pubDate>
  <atom:published>2025-06-19T15:00:00Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b><a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=getting-started-with-cloudtrail-security-queries" target="_blank" rel="noopener noreferrer nofollow">Go Pro for Support and Extras!</a></b></p><p class="paragraph" style="text-align:left;">CloudSLAW is, and always will be, free. But to help cover costs and keep the content up to date we have an optional Patreon. For $10 per month you get access to our support Discord, office hours, exclusive subscriber content (not labs — those are free — just extras) and more. <a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=getting-started-with-cloudtrail-security-queries" target="_blank" rel="noopener noreferrer nofollow">Check it out!</a></p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;">Have Athena configured for querying CloudTrail,<a class="link" href="https://slaw.securosis.com/p/cross-account-aws-athena-for-secops-security-operations-incident-response?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=getting-started-with-cloudtrail-security-queries" target="_blank" rel="noopener noreferrer nofollow"> like we showed you in this previous lab</a>.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Lesson AND the Lab!</h1><p class="paragraph" style="text-align:left;">That’s right, this week we are skipping to the fun part — for the first time the Lesson and Lab are combined! Why? Because usually I use the Lesson section to focus on core principles, and then the Lab to put them into action. But for today’s topic it makes more sense to walk you through some basic queries as we explore the structure of CloudTrail, instead of just having you read about it and then… stare at it.</p><p class="paragraph" style="text-align:left;">Today we will dig into CloudTrail and focus on searching on and extracting key data, which we tend to use in threat detectors and incident analysis. CloudTrail logs and events have a consistent structure, but since every AWS service is run by a different team, and the team generates its own CloudTrail events, there’s enough variation to… be kind of annoying at times. It isn’t all that bad once you learn some basics, so that’s our focus today. </p><p class="paragraph" style="text-align:left;">Remember that a CloudTrail log entry is a record of an API call. Every API call includes an identity, an action, the parameters of that action (<i>e.g.,</i> “delete THIS S3 object”), maybe a response from AWS (not all API calls provide a verbose response), and metadata like the time, region, source IP address, etc.</p><p class="paragraph" style="text-align:left;">Bah, enough talk — let’s do!</p><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/_8053HbUyEU" width="100%"></iframe><p class="paragraph" style="text-align:left;">First, let’s make a couple API calls, which will generate log entries we can dig into. </p><p class="paragraph" style="text-align:left;">Log into your <b>Sign in Portal &gt; TestAccount1 &gt; AdministratorAccess &gt; EC2:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/d1b0a9fe-f545-4ea5-950e-426606858266/image.png?t=1750287850"/></div><p class="paragraph" style="text-align:left;">We’ll keep it simple and run a basic instance, let it finish booting up, and then terminate it. Follow these steps…</p><p class="paragraph" style="text-align:left;"><b>Launch Instance &gt; name it ‘delete’ &gt; Launch instance:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/921a73a8-fdb5-4077-aeac-901a35f04f84/image.png?t=1750287925"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/43cd94c5-a7f2-4d09-8c02-114cba2cd224/image.png?t=1750287976"/></div><p class="paragraph" style="text-align:left;">Click through to <b>proceed without key pair &gt; Launch instance: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/e533c753-7f18-4ab4-bc7f-c1473071ffe4/image.png?t=1750288068"/></div><p class="paragraph" style="text-align:left;">Now wait 3-5 minutes. Then <b>Instances &gt; click your instance &gt; Instance state &gt; Terminate (delete) instance:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/839566fa-3f88-4d14-bdc3-99e6f4accd4d/image.png?t=1750288180"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/981cbf90-9163-421a-ae63-5f7651cda2f4/image.png?t=1750288221"/></div><p class="paragraph" style="text-align:left;">Great, we generated what we need, and it should up in our central CloudTrail within 5 minutes. So, go take 5 minutes and do what you want. <b>NO, NOT THAT!!!</b> Oh, never mind, just… see you in a few minutes.</p><p class="paragraph" style="text-align:left;">Now <b>close the TestAccount1 tab &gt; Sign In Portal &gt; SecurityAudit &gt; SecurityFullAdmin &gt; Athena:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/0bb01e09-cb2c-4681-bbbd-509cd0286aeb/image.png?t=1750288344"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/2a75ba16-2333-4f38-a6f2-8a55f569e531/image.png?t=1750288393"/></div><p class="paragraph" style="text-align:left;">My Athena queries from the last lab still showed up, so I just closed them out and deleted whatever was in the Query 1 tab.</p><p class="paragraph" style="text-align:left;">Let’s set up a basic scenario. Imagine you get a call from a scared intern who noticed that a critical EC2 instance is… not there anymore, and the entire Internet is down. I mean, this is the sort of things interns tend to do, but in this case the intern swears they didn’t touch anything. In fact, they were at the beach when it went down (please don’t tell their manager). Also, they have <b>no idea</b> who even owns the instance — they just know the Internet is down and their little brother can’t watch Bluey and <b>this is bad.</b></p><p class="paragraph" style="text-align:left;">Okay, where to start?</p><p class="paragraph" style="text-align:left;">Well, if we think about it, we want to know:</p><ul><li><p class="paragraph" style="text-align:left;">Who terminated the instance and when. </p></li><li><p class="paragraph" style="text-align:left;">Who originally launched the instance, which could indicate who owns it.</p></li><li><p class="paragraph" style="text-align:left;">Whether this was a mistake, or maybe a hack? I mean we don’t even have an instance ID to work with at this point.</p></li></ul><p class="paragraph" style="text-align:left;">Look, this is highly contrived and barely makes sense, but I’ve had multiple 5 and 6 am work calls this week, so it’s the best you’re going to get. </p><p class="paragraph" style="text-align:left;">Let’s start by running a query to find recently terminated instances and order them with the most recent ones at the top. That should help narrow it down. And notice in the query that I’m searching based only the Action (TerminateInstances). </p><p class="paragraph" style="text-align:left;"><i>Querying based on the Action is a common starting point, especially when building a threat detector</i>.</p><p class="paragraph" style="text-align:left;"><b>Assuming you have the same table name as me from the prior lab, this query will work without any modification and you can just Copy &gt; Paste &gt; Run. If you used different names you will need to change it for your &lt;databasename&gt;.&lt;tablename&gt;:</b></p><div class="codeblock"><pre><code>SELECT 
    eventtime,
    eventname,
    sourceipaddress,
    useridentity.type as user_type,
    useridentity.principalid,
    useridentity.arn as user_arn,
    useridentity.userName,
    awsregion,
    requestparameters,
    responseelements,
    errorcode,
    errormessage,
    recipientaccountid
FROM cloudtrail_logs.organization_trail
WHERE eventname = &#39;TerminateInstances&#39;
ORDER BY eventtime DESC;</code></pre></div><p class="paragraph" style="text-align:left;">Most of the fields are very obvious. Notice that <b>userIdentity</b> contains a lot of valuable data, it and we pulled out a few specific fields with the “.” nomenclature. Not every possible field is always present, so be careful how you write queries when searching on identity information.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/bec21e80-6d02-40c5-8509-091c5da6cd87/image.png?t=1750288665"/></div><p class="paragraph" style="text-align:left;">Scroll around to the right and take a look at the data. Most of the rest, like the <b>eventSource</b> (the service) and <b>eventName</b> (the action) are pretty obvious, but there are two very important fields which can be confusing. <b>requestParameters</b> includes all the parameters in the request (in this case, the instance we are terminating) and the <b>responseElements</b> is everything AWS sent back in response to the API call.</p><p class="paragraph" style="text-align:left;"><b>Now here’s a bummer. </b>Although there is sometimes a field for the <b>resource,</b> it’s … almost always missing or empty. You’d think every API call would include its resource (the full ARN, or at least the ID) but it doesn’t work that way. Why? Because sometimes you are making a request with a specific resource ID (<i>e.g.,</i> <b>TerminateInstances</b> to terminate <b>this</b> instance) but in other cases you don’t even have an ID yet. For example, <b>RunInstances</b> — it isn’t like we get to pick our instance ID. AWS assigns that and returns it in the response. </p><p class="paragraph" style="text-align:left;"><b>Look in the </b><i><b>requestParameters</b></i><b> column, find the instance ID, and copy that into a text file.</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/adf5386c-9f13-42ac-9c60-8bb17f6485bd/image.png?t=1750288882"/></div><p class="paragraph" style="text-align:left;"><i>This leads to another common starting point: a specific resource, when you have the ID. When searching all API calls for a resource, if you want all actions on that resource you need to search in </i><b><i>both</i></b><i> the </i><b><i>requestParameters</i></b><i> and </i><b><i>responseElements!</i></b></p><p class="paragraph" style="text-align:left;">And both of those fields embed other fields, so here’s how I handle it: the ever powerful <b>“LIKE”</b> search with wildcards! <b>In SQL the ‘%’ sign is the wildcard, not ‘*’!!! </b></p><p class="paragraph" style="text-align:left;">This query below pulls <b>every single action</b> involving that instance, since it’s looking in both requests and responses.</p><p class="paragraph" style="text-align:left;"><b>Hit “+” and paste this into Query 2, replacing your instance ID where indicated:</b></p><div class="codeblock"><pre><code>SELECT 
    eventtime,
    eventname,
    eventsource,
    sourceipaddress,
    useridentity.type as user_type,
    useridentity.arn as user_arn,
    useridentity.userName,
    awsregion,
    requestparameters,
    responseelements,
    errorcode,
    errormessage,
    recipientaccountid
FROM cloudtrail_logs.organization_trail
WHERE requestparameters LIKE &#39;%YOUR INSTANCE ID%&#39; 
   OR responseelements LIKE &#39;%YOUR INSTANCE ID%&#39;
ORDER BY eventtime DESC;</code></pre></div><p class="paragraph" style="text-align:left;">This is pretty great. We now see every action on the instance in reverse chronological order. Look at the <b>responseElements</b> field of the <b>RunInstances</b> API call — that’s where AWS first assigned the instance ID. This is why we need to look in both fields. Another example: if I ran a generic <b>DescribeInstances</b> to find one to attack, that would return every instance, even though I never specified an instance ID. </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/86d60d37-9022-4311-aaf3-b0f2a685cc7a/image.png?t=1750289244"/></div><p class="paragraph" style="text-align:left;">It’s call and response! “Give me an instance!” “Okay, here is the ID for your instance, and everything else I think is important.”</p><p class="paragraph" style="text-align:left;"><i>These don’t show very well in screenshots, so explore your results and pay particular attention to the request and the response for both the </i><b><i>Run</i></b><i> and </i><b><i>Terminate</i></b><i> actions.</i> Or you can check out the video and get dizzy watching me scroll around.</p><p class="paragraph" style="text-align:left;">Now what about all those <b>Describe</b> actions? That’s because we are working in the console, and the console wants to show us stuff, so every time we click into a new screen or view it makes a <b>Describe</b> call to … show us stuff. </p><p class="paragraph" style="text-align:left;">To level set, we now have starting points based on an <b>action</b> or based on a <b>resource</b>, but what about the <b>identity</b>?</p><p class="paragraph" style="text-align:left;">Looking at our two actions (<b>Run</b> and <b>Terminate</b>) they both use the same identity. We can just query directly on that identity and get a sense of everything that user/role did:</p><p class="paragraph" style="text-align:left;"><b>Copy useridentity.arn &gt; Click “+” for Query 3 &gt; Copy and Paste this query &gt; replace with your useridentity.arn:</b></p><div class="codeblock"><pre><code>SELECT 
    eventtime,
    eventname,
    eventsource,
    sourceipaddress,
    awsregion,
    requestparameters,
    responseelements,
    errorcode,
    errormessage,
    recipientaccountid,
    useragent
FROM cloudtrail_logs.organization_trail
WHERE useridentity.arn = &#39;YOUR ARN&#39;
ORDER BY eventtime DESC;</code></pre></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/710900e5-e659-4c2c-b187-fcc8dba35a66/image.png?t=1750289687"/></div><p class="paragraph" style="text-align:left;">Very nice. Well, all that looks like normal stuff. Heck, it looks like I’m the one who terminated my very own instance! How about some clues if I think this is nefarious activity?</p><p class="paragraph" style="text-align:left;">Well, we can look at all the IP addresses that originated the API calls. We can also look at the <b>useragent</b><i> </i>and see if it suddenly changes to something different. Changing IP addresses and user agents often indicate use of stolen credentials or sessions.</p><p class="paragraph" style="text-align:left;">Keep in mind that although we are querying our <b>Organizations trail,</b> which has all the logs from every account (which is why are queries are slow, so eventually we will need to partition)… this particular user identity query only finds things in one account. Why? Well, we are looking at the activity after assuming a role, and if you review that ARN it has the account ID and a unique role name.</p><p class="paragraph" style="text-align:left;">What if we want to look in <b>every</b> account? That’s important when we suspect an attacker is bouncing around accounts with that one base IAM Identity Center user. This makes it tougher to be 100% accurate, but we don’t need 100%. This query goes back to using LIKE and our ‘%’ wildcard, and only specifies the last username.</p><p class="paragraph" style="text-align:left;"><b>Copy and Paste into a new query window, and replace “rmogull” with whatever is after the last / in your ARN:</b></p><div class="codeblock"><pre><code>SELECT 
    eventtime,
    eventname,
    eventsource,
    sourceipaddress,
    useridentity.arn as user_arn,
    useridentity.userName,
    awsregion,
    recipientaccountid
FROM cloudtrail_logs.organization_trail
WHERE useridentity.arn LIKE &#39;%/rmogull&#39;
ORDER BY eventtime DESC;</code></pre></div><p class="paragraph" style="text-align:left;">Now scroll to the right and look at the <b>recipientaccountid</b> — you now see the API calls across every account in your org:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/40506bea-ce5f-4bb8-853f-1c7268e12b64/image.png?t=1750290233"/></div><p class="paragraph" style="text-align:left;">And thus we cover our three main security entry points for queries:</p><ul><li><p class="paragraph" style="text-align:left;">The action</p></li><li><p class="paragraph" style="text-align:left;">The resource</p></li><li><p class="paragraph" style="text-align:left;">The identity</p></li></ul><p class="paragraph" style="text-align:left;">In future labs we will simulate and trace a full attack, but first we need a better understanding of how to write Athena queries with a focus on the data in CloudTrail logs. <a class="link" href="https://securosis.com/blog/aws-cloud-incident-analysis-query-cheatsheet/?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=getting-started-with-cloudtrail-security-queries" target="_blank" rel="noopener noreferrer nofollow">Feel free to peek ahead and see some more complex queries in this Securosis blog post</a>.</p><h3 class="heading" style="text-align:left;" id="todays-key-points-are">Key Lesson Points</h3><ul><li><p class="paragraph" style="text-align:left;">Actions, resources, and identities tend to be starting points for security related queries.</p></li><li><p class="paragraph" style="text-align:left;">We can use these queries both as threat detectors and for incident analysis (we will cover both in future labs).</p></li><li><p class="paragraph" style="text-align:left;">Using the <b>LIKE</b> keyword and a “%resource or other id%” wildcard helps query, even when we don’t know all the exact parameters or response elements.</p></li></ul><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=e1bd524a-0926-4050-88fc-b79c76543c70&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Cross Account AWS Athena for SecOps (Security Operations/Incident Response)</title>
  <description>Athena enables us to write SQL-like queries and search CloudTrail and other logs in S3 without any pesky databases. This can be an excellent tool for security operations when you aren&#39;t using a big expensive external tool.</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ec8d53a1-8dad-4e3f-8187-15abc2da1942/Ephesus.png" length="515900" type="image/png"/>
  <link>https://slaw.securosis.com/p/cross-account-aws-athena-for-secops-security-operations-incident-response</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/cross-account-aws-athena-for-secops-security-operations-incident-response</guid>
  <pubDate>Thu, 05 Jun 2025 15:00:00 +0000</pubDate>
  <atom:published>2025-06-05T15:00:00Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b><a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=cross-account-aws-athena-for-secops-security-operations-incident-response" target="_blank" rel="noopener noreferrer nofollow">Go Pro for Support and Extras!</a></b></p><p class="paragraph" style="text-align:left;">CloudSLAW is, and always will be, free. But to help cover costs and keep the content up to date we have an optional Patreon. For $10 per month you get access to our support Discord, office hours, exclusive subscriber content (not labs — those are free — just extras) and more. <a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=cross-account-aws-athena-for-secops-security-operations-incident-response" target="_blank" rel="noopener noreferrer nofollow">Check it out!</a></p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;">Any account with <b>CloudTrail</b> will work, but I recommend completing <a class="link" href="https://slaw.securosis.com/p/enabling-org-trail-centralized-logging-part-3?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=cross-account-aws-athena-for-secops-security-operations-incident-response" target="_blank" rel="noopener noreferrer nofollow">our Organizations CloudTrail</a> lab first.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Lesson</h1><p class="paragraph" style="text-align:left;">Everyone likes to think their job is cool. And if you have “security” in your profession, apparently the more you can use terms out of a James Bond, Jason Bourne, or other special forces movie… the better. </p><p class="paragraph" style="text-align:left;">So it’s time to introduce you to <b>SecOps:</b> security operations. I put it in bold so you know how important it is! </p><p class="paragraph" style="text-align:left;">The term Security Operations is a bit of a loaded one — it means different things to different people. It <i>can</i> cover anything operational in security, which is a huge scope that spans from running tools and fixing things, through domains including security architecture (design), to security engineering (installing and maintaining security tools). That said, if you go to a conference and see “SecOps” on a banner, they probably mean everything we wrap around incident detection and response.</p><p class="paragraph" style="text-align:left;">Look, don’t get too hung up on this stuff. I’ve been in the industry nearly 30 years and try not to get emotionally attached to, I dunno, dictionaries? </p><p class="paragraph" style="text-align:left;">But SecOps sounds cool and makes good clickbait for your friendly neighborhood cloud security newsletter/blog, so that’s what I’ll use for our new series of labs exploring the different domains of Security Operations and Cloud Incident Response.</p><h2 class="heading" style="text-align:left;" id="security-monitoring-your-feeds-and-">Security Monitoring: Your Feeds and Speeds</h2><p class="paragraph" style="text-align:left;">When I wrote the <a class="link" href="https://sf-cdn.iansresearch.com/sitefinity/docs/default-source/ians-documents/csmm/csmm-02202025.pdf?sfvrsn=242acec2_1&utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=cross-account-aws-athena-for-secops-security-operations-incident-response" target="_blank" rel="noopener noreferrer nofollow">Cloud Security Maturity Model</a>, I placed Security Monitoring in the Foundational domain. It’s one of the most important cloud security activities, one I slot just behind IAM. We’ve already set up nearly all our core monitoring, but I never really gave you the full context for how we tie this all together for incident detection, analysis and response.</p><p class="paragraph" style="text-align:left;">This is one of those areas I’ve been writing, teaching, and speaking about since before <b>CloudTrail</b> even existed. I call it the <i>Feeds and Speeds</i> talk because effective security monitoring relies on knowing what security telemetry sources to collect, how to collect them, and the time it takes from something happening to someone seeing it. This is a huge deal in cloud since attackers use automated tools which operate lighting fast, and many traditional methods of security monitoring lag 15-60 minutes behind an attack.</p><p class="paragraph" style="text-align:left;">Today I’ll limit myself to reviewing the 3 main categories of security feeds:</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/145e5895-7f3a-4de5-8289-cf2f0a821aff/image.png?t=1748897467"/></div><ul><li><p class="paragraph" style="text-align:left;"><b>Logs</b> are activity streams saved into files stored someplace. So far we’ve set up <b>CloudTrail</b> feeding S3 as our primary log for management plane activity. Another example of logs, which we haven’t used yet, is VPC Flow Logs.</p></li><li><p class="paragraph" style="text-align:left;"><b>Events</b> are the real-time stream of activity, alerts, and… uh… events issued by various services. In our labs we have used <b>CloudTrail</b> events (when we need real time, instead of the slower S3 logs), and <b>GuardDuty</b> and <b>Access Analyzer</b> via <b>Security Hub.</b> </p></li><li><p class="paragraph" style="text-align:left;"><b>Configuration(s)</b> are detected misconfigurations. We haven’t set this up yet and will get to it, but there is an entire category of tools called Cloud Security Posture Management (CSPM) to look for these misconfigurations. Many security professionals treat these more like a vulnerability scanner (scan, get report, complain to someone to go fix their sh-t), but savvy incident responders know that most cloud native attackers create misconfigurations to exfiltrate data and do bad things, and we should treat some of these as security alerts.</p></li></ul><p class="paragraph" style="text-align:left;">Each of these provides a security pro with a different piece of the puzzle. Many <b>many</b> services generate logs, and these are our bread and butter for monitoring and detection (even for you gluten-free types). They support everything from fast detection, to deep investigation, to meeting compliance requirements.</p><p class="paragraph" style="text-align:left;">Events, as you’ve seen if you’ve competed our previous labs, offer near-real-time security visibility. Logs always have a delay to reach analysis tools because they must be generated, batched, and transferred; while events come in a real-time stream of information (which isn’t saved unless we take action to save it).</p><p class="paragraph" style="text-align:left;">Configuration is a bit special, and I don’t want to get distracted by it today. The TL;DR is that posture tells us the <i>outcome</i> of actions, and can identify risky or malicious outcomes such as sharing an S3 bucket to an unknown account.</p><h2 class="heading" style="text-align:left;" id="log-analysis-with-athena">Log Analysis with Athena</h2><p class="paragraph" style="text-align:left;">We’ve been collecting <b>CloudTrail</b> logs since our earliest labs, and today we’ll set up a service we can use for threat detection and analysis. <i>Threat detection</i> is the process of identifying potential attacker activity, while <i>analysis</i> is the process of determining what happened and tracing attacks. A <i>threat detector</i> is an automated alert for “oh crap, someone may have broken into the safe,” while analysis helps you figure out whether it was an attacker or just a legit employee who forgot to tell someone they were going into the safe.</p><p class="paragraph" style="text-align:left;">In previous labs we showed how to alert directly off a <b>CloudTrail</b> event in <b>EventBridge.</b> That’s great for simple triggers, but analysis and more complex triggers require searching and analyzing logs. That’s where <b>AWS Athena</b> comes in.</p><p class="paragraph" style="text-align:left;">Athena is a service which enables you to run SQL-like queries on data in S3. What’s cool is that it works with different types of data — it doesn’t need to be in a structured database. Under the hood <a class="link" href="https://aws.amazon.com/what-is/presto/?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=cross-account-aws-athena-for-secops-security-operations-incident-response" target="_blank" rel="noopener noreferrer nofollow">Athena uses Presto</a>, which gets quickly into data science above my pay grade. To use it you define a schema. The schema is essentially a logical mapping layer which tells Athena, &quot;when you read these JSON files in S3, interpret field X as a timestamp, field Y as a string, etc.&quot; without actually modifying or preprocessing the underlying files.</p><p class="paragraph" style="text-align:left;"><b>Athena</b> isn’t free, and there is a lot of nuance to working with it at scale, but we can get into those details later (hello, partitions!). Our costs should be extremely low, especially since <a class="link" href="https://slaw.securosis.com/p/meaning-lifecycles-versions-ransomware?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=cross-account-aws-athena-for-secops-security-operations-incident-response" target="_blank" rel="noopener noreferrer nofollow">we configured S3 so we only keep 3 months of CloudTrail logs</a>.</p><p class="paragraph" style="text-align:left;">But there’s one major nuance for our labs: our logs are in <b>LogArchive,</b> but we plan to run security investigations from our <b>SecurityAudit</b> account. The good news is that it’s pretty simple to configure <b>Athena</b> in one account to query a database in a different account, with just a permissions update.</p><div class="section" style="background-color:transparent;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b>What the heck is a SIEM, and where does Athena fit in?</b></p><p class="paragraph" style="text-align:left;">SIEM stands for <i>Security Information and Event Management</i>. It’s a category of tools purpose-built for security monitoring and alerting. There are many commercial SIEMs on the market. These tools collect and normalize security-relevant logs and enable you to build alerts, run investigations, and more. </p><p class="paragraph" style="text-align:left;">Athena is just a query tool we can use to analyze any kind of data. When I teach incident response and detection engineering for cloud, I use Athena a lot because it’s native to AWS and cost effective. It is <b>not</b> a replacement for a commercial SIEM without a <b>lot</b> of work. What’s nice is that I can teach you on Athena, and the skills will translate to whatever SIEM your employer uses, but you’ll understand working with native data.</p></div><h3 class="heading" style="text-align:left;" id="todays-key-points-are">Key Lesson Points</h3><ul><li><p class="paragraph" style="text-align:left;">Different security telemetry feeds have different latency.</p></li><li><p class="paragraph" style="text-align:left;">There are three major categories of feeds:</p><ul><li><p class="paragraph" style="text-align:left;">Logs are saved to files, and are the most common and detailed.</p></li><li><p class="paragraph" style="text-align:left;">Events are real-time, but aren’t saved unless we capture them when they happen.</p></li><li><p class="paragraph" style="text-align:left;">Configuration “alerts” come from Cloud Security Posture Management tools and tell us when something is misconfigured, which can indicate an attack.</p></li></ul></li><li><p class="paragraph" style="text-align:left;"><b>AWS Athena</b> enables us to directly query logs saved in S3, including <b>CloudTrail,</b> for threat detection and incident analysis.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lab"><b>The Lab</b></h1><p class="paragraph" style="text-align:left;">For our lab we will add a permission to our <b>CloudTrail</b> bucket in S3, so we can run <b>Athena</b> in our <b>SecurityAudit</b> account. Then we’ll configure <b>Athena</b> to work with an Organizations <b>CloudTrail,</b> which is a bit different than setting it up in a single account. We’ll finish by running a test query, seeing how crappy the performance is; then I’ll say “partitions later”, since we want to keep things simple for now.</p><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/aek-M04lnWs" width="100%"></iframe><h2 class="heading" style="text-align:left;" id="stepby-step">Step-by-Step</h2><p class="paragraph" style="text-align:left;">First up: for this lab there is a lot of copying and pasting, so <b>open up a blank text file.</b></p><p class="paragraph" style="text-align:left;">Then go to your <b>Sign-in portal &gt; SecurityAudit &gt; IAM &gt; copy and paste the ARN of AWSReservedSSO_SecurityFullAdmin_xxx.</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/c6684bea-f8d1-48ae-b5cf-a075dc14d43a/image.png?t=1749076970"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/5b60a6b0-d2e0-41ca-821c-eed9ada76a8f/image.png?t=1749077084"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/710a0cf6-bd3a-40b5-a28f-213116d0c5fc/image.png?t=1749077118"/></div><p class="paragraph" style="text-align:left;">We also need our Organizations ID. Go to <b>Organizations &gt; copy and paste the Org ID: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ba8179c9-dc8c-4f4f-96d2-29643577d2a3/image.png?t=1749077327"/></div><p class="paragraph" style="text-align:left;">Really, you want these in a text file for reference later:</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/9b05208e-d4d3-429e-8e6a-9aedd340c06e/image.png?t=1749077388"/></div><p class="paragraph" style="text-align:left;"><b>NOW CLOSE THAT TAB AND &gt; Sign-in portal &gt; LogArchive &gt; AdministratorAccess &gt; S3 &gt; Click your CloudTrail bucket:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/d241883a-66c6-4470-b38c-ce5c613a5a33/image.png?t=1749077789"/></div><p class="paragraph" style="text-align:left;">Time for more copy and paste!<b> Properties &gt;</b> <b>Copy and paste the bucket ARN and the bucket name:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/daf707ed-7ac4-4a25-aeae-cf40a9ffaf0f/image.png?t=1749077990"/></div><p class="paragraph" style="text-align:left;">My text file now looks like this:</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ce04f36e-06dc-480b-bc24-3227d3cb983b/image.png?t=1749078023"/></div><p class="paragraph" style="text-align:left;"><b>Permissions &gt; Bucket policy &gt; Edit:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/593132a9-a369-4b56-b4e3-7b667b3e8d8f/image.png?t=1749078121"/></div><p class="paragraph" style="text-align:left;">We need to add a statement to the policy to allow our <b>SecurityFullAdmin</b> role from <b>SecurityAudit</b> access to this bucket so it can run <b>Athena</b> queries. First up, copy this JSON and paste your role ARN and the bucket ARN in the indicated spots (***replace all this***). <i>You should copy and paste a total of 3 times!</i></p><div class="codeblock"><pre><code>&#123;
    &quot;Sid&quot;: &quot;SecurityFullAdminCloudTrailAccess&quot;,
    &quot;Effect&quot;: &quot;Allow&quot;,
    &quot;Principal&quot;: &#123;
        &quot;AWS&quot;: &quot;***arn of the AWSReservedSSO_SecurityFullAdmin role***&quot;
    &#125;,
    &quot;Action&quot;: [
        &quot;s3:GetObject&quot;,
        &quot;s3:ListBucket&quot;,
        &quot;s3:GetBucketLocation&quot;
    ],
    &quot;Resource&quot;: [
        &quot;***bucket arn***&quot;,
        &quot;***bucket arn***/*&quot;
    ]
&#125;,</code></pre></div><p class="paragraph" style="text-align:left;">Then copy your updated JSON and paste it right before the first statement, to look like this:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/d974c3cb-1d28-4237-9c28-f0df16dfaee8/image.png?t=1749078381"/></div><p class="paragraph" style="text-align:left;">Now it looks like this, and I highlighted the text I replaced:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/a459f393-feee-46d1-a9ad-d730111ba72f/image.png?t=1749078499"/></div><p class="paragraph" style="text-align:left;">Scroll down and click <b>Save Changes.</b></p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/55e2ce27-8144-46fc-b9a4-ea6ae31ead94/image.png?t=1749078618"/><div class="image__source"><span class="image__source_text"><p>See? Now I’m pretty!</p></span></div></div><p class="paragraph" style="text-align:left;">Normally this is where we would set up an S3 bucket to hold our <b>Athena</b> queries. That&#39;s great, but can add to our costs and, as you know, I&#39;m nearly as cheap as all of you. Our costs would have been low, but hey, now we have a free option! AWS just launched (as in, I learned about it <i>yesterday</i>) a new managed query capability which stores queries and results 24 hours for free. This is a great new option for security operations since we tend to either run <i>ad hoc</i> queries for incident investigations, or automated queries for threat detection, and don&#39;t need to necessarily store those results long-term.</p><p class="paragraph" style="text-align:left;">There&#39;s one exception, and it&#39;s a big one. When analyzing a <b>real</b> security incident, you absolutely need to save all your query results. But you can export or save them selectively — you don&#39;t need to keep every query ever written until the dawn of time (or until someone complains about your storage costs).</p><p class="paragraph" style="text-align:left;"><b>NOW CLOSE THAT TAB AND &gt; Sign-in portal &gt; SecurityAudit &gt; SecurityFullAdmin: </b></p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/48d86aae-e86a-436d-9625-7a226c991a98/image.png?t=1749079774"/></div><p class="paragraph" style="text-align:left;"><b>Then Go to Athena &gt; Click the &quot;hamburger&quot; (three lines in the upper left corner) &gt; Workgroups &gt; Create workgroup:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/53759843-8f12-4260-b4d2-4ccf323a913e/image.png?t=1749078816"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/730f766a-fbe6-4e73-a892-e4089300254d/image.png?t=1749078852"/></div><p class="paragraph" style="text-align:left;">Name it <b>security</b> and leave all the defaults, scroll to the bottom, and click <b>Create workgroup.</b> The main defaults we are using are <b>Athena SQL</b> for the query engine, and setting up the brand new very shiny <b><i>Athena managed query result configuration.</i></b> This keeps your queries and results for 24 hours free. Why would you want it longer? That’s more for ongoing data analysis stuff we security grunts try to avoid.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/d6f4d26b-2762-4fb6-9bad-354c53893f0a/image.png?t=1749078940"/></div><p class="paragraph" style="text-align:left;">Click <b>Query editor:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/af4fdc76-aeda-4abd-8de3-dc02f9371a14/image.png?t=1749079141"/></div><p class="paragraph" style="text-align:left;">Okay, we have our data in S3 and our permissions all ready to go, but we need to set <b>Athena</b> up to talk to our bucket ‘o <b>CloudTrail</b> logs, which is hanging out in <b>LogArchive.</b> If you’ve worked with databases before, you are probably used to running SQL queries to do everything — including creating databases and tables. That’s how <b>Athena</b> works; we start by creating our database, then we create our table. When we “create the database”, that doesn&#39;t actually create a new database — it actually creates the metadata and mapping to our files in S3. Don&#39;t think about it too hard — data stuff is weird, and beyond our scope as security plebes. You’ll paste in each of these commands and then click <b>Run</b>.</p><p class="paragraph" style="text-align:left;">First <b>Workgroup &gt; security: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/6680ac19-575c-4d09-9b4f-123124889d9c/image.png?t=1749079970"/></div><p class="paragraph" style="text-align:left;">Copy and paste, then <b>Run:</b></p><div class="codeblock"><pre><code>CREATE DATABASE IF NOT EXISTS cloudtrail_logs</code></pre></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/0194c79e-9a50-4f64-94bc-07ef238783d3/image.png?t=1749080128"/></div><p class="paragraph" style="text-align:left;">Easy, right? Now we need to create our table, which maps our expected JSON fields into table columns. This is the magic of this distributed database stuff. For this one you need to copy this SQL, then <b>swap in your S3 bucket name AND your organization’s ID</b> at the bottom (look for *** on the last line):</p><div class="codeblock"><pre><code>CREATE EXTERNAL TABLE IF NOT EXISTS cloudtrail_logs.organization_trail (
    eventversion STRING,
    useridentity STRUCT&lt;
        type: STRING,
        principalid: STRING,
        arn: STRING,
        accountid: STRING,
        invokedby: STRING,
        accesskeyid: STRING,
        userName: STRING,
        sessioncontext: STRUCT&lt;
            attributes: STRUCT&lt;
                mfaauthenticated: STRING,
                creationdate: STRING&gt;,
            sessionissuer: STRUCT&lt;
                type: STRING,
                principalId: STRING,
                arn: STRING,
                accountId: STRING,
                userName: STRING&gt;&gt;&gt;,
    eventtime STRING,
    eventsource STRING,
    eventname STRING,
    awsregion STRING,
    sourceipaddress STRING,
    useragent STRING,
    errorcode STRING,
    errormessage STRING,
    requestparameters STRING,
    responseelements STRING,
    additionaleventdata STRING,
    requestid STRING,
    eventid STRING,
    resources ARRAY&lt;STRUCT&lt;
        ARN: STRING,
        accountId: STRING,
        type: STRING&gt;&gt;,
    eventtype STRING,
    apiversion STRING,
    readonly STRING,
    recipientaccountid STRING,
    serviceeventdetails STRING,
    sharedeventid STRING,
    vpcendpointid STRING
)
ROW FORMAT SERDE &#39;com.amazon.emr.hive.serde.CloudTrailSerde&#39;
STORED AS INPUTFORMAT &#39;com.amazon.emr.cloudtrail.CloudTrailInputFormat&#39;
OUTPUTFORMAT &#39;org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat&#39;
LOCATION &#39;s3://***bucket name***/AWSLogs/***org-id***/&#39;</code></pre></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/3f355672-c4da-4e4f-84fe-01457ca6a9db/image.png?t=1749080468"/></div><p class="paragraph" style="text-align:left;">Take a minute to look at that. You can see that we defined all the potential <b>CloudTrail</b> fields you see in the JSON, then mapped them to strings and stuff. This is the schema.</p><p class="paragraph" style="text-align:left;">There&#39;s one big flaw in what we just did. As I mentioned, <b>Athena</b> supports partitioning to improve performance and reduce query costs. I went back and forth on implementing partitioning in this first lab, but my wife reminded me that when you are first learning the, queries it&#39;s easier not to deal with the added complexity. Partitioning comes with some big complexity demands in our scenario, because once you partition you need to specify more criteria in your queries. Since we only keep 3 months of logs for a relatively small number of accounts, we should be fine on costs and can get by with simpler queries. I&#39;ll cover partitioning in a future lab, since it&#39;s important to understand.</p><p class="paragraph" style="text-align:left;">With all that out of the way, let&#39;s run our first query to make sure everything works:</p><div class="codeblock"><pre><code>SELECT eventtime, eventname, useridentity.accountid, awsregion
FROM cloudtrail_logs.organization_trail
ORDER BY eventtime DESC
LIMIT 100</code></pre></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/f93805e6-3969-4c09-9625-0957184d91a3/image.png?t=1749080645"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/9420651d-9017-466a-a6e5-bb7ff62d205a/image.png?t=1749080689"/></div><p class="paragraph" style="text-align:left;">Notice how long that took? That&#39;s the <b>big</b> downside of not using partitions: <b>Athena</b> needs to scan everything. Costs aren&#39;t bad, $5 per terabyte scanned (per query) for our region, but you can see that could add up quickly. Queries also take a very long time to run (about a minute for mine). You tend not to see these issues in a single account setup, but organizations run into them quickly.</p><p class="paragraph" style="text-align:left;">Don&#39;t worry — once we cover all the query basics we will partition, and then I&#39;ll show you how to use lambda functions to automate these queries and use them as threat detectors.</p><h3 class="heading" style="text-align:left;" id="key-points">Key Lab Points</h3><ul><li><p class="paragraph" style="text-align:left;">To query S3 in one account using <b>Athena</b> in another account, start by setting up permissions for the role(s) which will access S3.</p></li><li><p class="paragraph" style="text-align:left;"><b>Athena</b> now supports managed query storage, which keeps your queries free for 24 hours. You configure it in Workgroup settings.</p></li><li><p class="paragraph" style="text-align:left;">Working with <b>Athena</b> is like working with any other database. You use SQL queries for everything.</p></li><li><p class="paragraph" style="text-align:left;">Without partitioning <b>Athena</b> can be slow and expensive. We will get to partitioning later.</p></li></ul><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=c29441e1-c8ae-44c1-a6f8-f33f5b844d08&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Using the AWS CLI and Securing CloudShell</title>
  <description>We&#39;ve done a lot of clicky-clicky in the AWS console. Today we&#39;ll learn how to type our way to success with the AWS command line, all inside the console.</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/7f682265-a9f4-4d91-bc53-6207cb585c73/aws_cloudshell.png" length="661673" type="image/png"/>
  <link>https://slaw.securosis.com/p/using-the-aws-cli-and-securing-cloudshell-ec33</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/using-the-aws-cli-and-securing-cloudshell-ec33</guid>
  <pubDate>Thu, 22 May 2025 15:00:00 +0000</pubDate>
  <atom:published>2025-05-22T15:00:00Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b><a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=using-the-aws-cli-and-securing-cloudshell" target="_blank" rel="noopener noreferrer nofollow">Go Pro for Support and Extras!</a></b></p><p class="paragraph" style="text-align:left;">CloudSLAW is, and always will be, free. But to help cover costs and keep the content up to date we have an optional Patreon. For $10 per month you get access to our support Discord, office hours, exclusive subscriber content (not labs — those are free — just extras) and more. <a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=using-the-aws-cli-and-securing-cloudshell" target="_blank" rel="noopener noreferrer nofollow">Check it out!</a></p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;">All you need are admin credentials in an organization with the <b>OrganizationAccountAccessRole</b> available.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Lesson</h1><p class="paragraph" style="text-align:left;"><i><b>It didn’t fit well into the lesson, but during the lab we’ll be using the command line to explore more like a hacker trying to knock down annoying walls.</b></i></p><p class="paragraph" style="text-align:left;">I’ve been teaching cloud security for something like 15 years now, and there is nothing I fear more than walking into a classroom, staring at a sea of personal and work laptops, and knowing I need to figure out how to get every one of them to run an SSH terminal so we can use class scripts and commands.</p><p class="paragraph" style="text-align:left;">DO YOU POSSIBLY UNDERSTAND THE NIGHTMARE OF FIGURING OUT HOW TO MAKE AN SSH CONNECTION ON A WINDOWS CORPORATE LAPTOP?!?! DO YOU?!?!?!</p><p class="paragraph" style="text-align:left;">I… have…scars. And, if you recall our prior use of <b>Session Manager</b> (from, like, a bunch of previous labs) we now have a great way to connect to instances using the web console without ever needing a local terminal program. </p><p class="paragraph" style="text-align:left;">While we’ve made those connections to learn skills like managing credentials, we haven’t really used the AWS command line much. Nearly all our work has been using the GUI that is the AWS console — which we fondly call <b>ClickOps</b> because… we click all the things. We have also used a ton of Infrastructure as Code (IaC, via <b>CloudFormation</b>) which is closer to how you want to manage applications and infrastructure stacks. </p><p class="paragraph" style="text-align:left;">As your cloud skills mature, you’ll also find yourself using the AWS command line more. Personally I don’t use it a ton — I weirdly tend to swap around between the console, IaC, and Python scripts for work. When I automate I use Python, but there are a lot of you out there who automate primarily with bash or even PowerShell. </p><p class="paragraph" style="text-align:left;">Using the command line, and understanding its security implications, is a valuable skill I probably should have included earlier. But hey, better late than never! And it isn’t like most of you are paying for this!</p><p class="paragraph" style="text-align:left;">One option is to use <b>Session Manager</b> and connect to an instance, and run commands from there. Although we’ve used this a few times, it still requires a running instance to connect to. Sometimes we just want to use some AWS command lines to manage our account, and don’t necessarily want or need a persistent image. This is where AWS <b>CloudShell</b> comes into play</p><h2 class="heading" style="text-align:left;" id="on-cloud-shell-the-cli-and-credenti">On CloudShell, the CLI, and Credentials</h2><p class="paragraph" style="text-align:left;"><b>CloudShell</b> is a terminal which runs right in the AWS console. I don’t know what it is under the hood (okay, poking around, it’s a container), but when you launch it AWS creates a compute environment, connects 1GB of persistent storage, and pre-authenticates using your existing credentials. Just click the little icon, and your shell opens right up:</p><div class="image"><img alt="" class="image__image" style="border-radius:0px 0px 0px 0px;border-style:solid;border-width:0px 0px 0px 0px;box-sizing:border-box;border-color:#E5E7EB;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/5ec7e3cf-a53b-4c2b-9eb9-d57af0bbcfc3/image.png?t=1747773451"/></div><p class="paragraph" style="text-align:left;">That environment includes the latest version of the AWS CLI, options for bash or PowerShell, and 1GB of storage, conveniently encrypted with KMS and backed up for you. </p><p class="paragraph" style="text-align:left;">What about credentials? Those are loaded up similar to the way an instance gets an IAM role; we will dig in during the lab. Those credentials also use normal AWS credentials precedence. What’s that? Yet another thing I should probably have covered earlier. Whenever you run a tool, like the AWS CLI or an SDK, it pulls credentials in a particular priority order (unless some a-hole programmer decided to handle it some other way, in which case you <b>do not</b> need their piece of crap tool):</p><ol start="1"><li><p class="paragraph" style="text-align:left;">Direct credentials submitted on the command line or in code as parameters.</p></li><li><p class="paragraph" style="text-align:left;">Environment variables, which we will use today.</p></li><li><p class="paragraph" style="text-align:left;">The AWS credentials file located at <b>~/.aws/credentials</b> (mature operating systems) or <b>%USERPROFILE\.aws\credentials</b> (Windows. Ugh).</p></li><li><p class="paragraph" style="text-align:left;">The AWS config file, in those same locations.</p></li><li><p class="paragraph" style="text-align:left;">Container or instance profile credentials (IAM roles) or <b>CloudShell</b> credentials (which are basically in a metadata service).</p></li></ol><p class="paragraph" style="text-align:left;">The CLI/SDK just checks each spot until it finds credentials, and uses the first set it finds. If you use a defined profile, it will use the first matching profile it finds, again searching in that order. </p><p class="paragraph" style="text-align:left;"><b>CloudShell</b> is great because it never saves your credentials, they aren’t stored where all the nasty malware looks for them, and even if you store (other) credentials yourself they are encrypted. <i>You also can’t SSH or connect to </i><b><i>CloudShell</i></b><i> from outside the console!</i> It’s a browser-based shell. Unlike your computer, an adversary can’t pop a shell with malware and use your credentials while you’re sleeping. </p><p class="paragraph" style="text-align:left;">And for those of you in enterprise environments, you can restrict <b>CloudShell</b> to a VPC. This means it is constrained by all your network security controls (and an attacker can’t just download their attack tools), but it also provides access to your service endpoints (including <b>PrivateLink</b>) in case you need to do network connectivity testing without having to drop an instance into the VPC.</p><h3 class="heading" style="text-align:left;" id="some-prelab-cli-notes">Some pre-lab CLI notes</h3><p class="paragraph" style="text-align:left;">You’ll see the CLI is pretty straightforward to use (<a class="link" href="https://awscli.amazonaws.com/v2/documentation/api/latest/index.html?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=using-the-aws-cli-and-securing-cloudshell" target="_blank" rel="noopener noreferrer nofollow">here’s the reference</a>). You type <span style="font-family:Courier, "Lucida Typewriter", monospace;">aws &lt;service name&gt; --&lt;request parameters&gt;</span>. You can override the persona/identity (by specifying different credentials or choosing a profile) and the region by setting those as parameters. We’ll do both today so you get to see it.</p><p class="paragraph" style="text-align:left;">Here’s an example for running an instance:</p><div class="codeblock"><pre><code>aws ec2 run-instances \
  --image-id ami-0123456789example \
  --count 1 \
  --instance-type t2.micro \
  --key-name MyKeyPair \
  --security-group-ids sg-0123456789example \
  --subnet-id subnet-0123456789example \
  --tag-specifications &#39;ResourceType=instance,Tags=[&#123;Key=Name,Value=MyBasicInstance&#125;]&#39;</code></pre></div><p class="paragraph" style="text-align:left;">Here are my general rules of thumb for how I personally use the options:</p><ul><li><p class="paragraph" style="text-align:left;">To try new things, or quickly enable/disable/build, I usually start in the console. The visual nature helps me wire the process into my head.</p><ul><li><p class="paragraph" style="text-align:left;">Sometimes I use the CLI instead. It’s great for quickly checking the <b>full results of commands,</b> which the console sometimes hides or tucks away in a corner. </p></li><li><p class="paragraph" style="text-align:left;">The CLI is awesome for troubleshooting.</p></li></ul></li><li><p class="paragraph" style="text-align:left;">When I need to create something repeatable, or manage the production version of what I’m building, I use IaC (<b>CloudFormation</b> or <b>Terraform</b>).</p></li><li><p class="paragraph" style="text-align:left;">For automation I use Python. Plenty of people use the CLI instead; it’s a personal preference.</p></li><li><p class="paragraph" style="text-align:left;">If I want to play with some other command line tool and don’t want to install it locally, I might use <b>CloudShell.</b></p></li></ul><p class="paragraph" style="text-align:left;"><b>CloudShell</b> is great. Easy, secure, and a wonderful gateway drug to get out of the console and onto the command line. </p><h3 class="heading" style="text-align:left;" id="todays-key-points-are">Key Lesson Points</h3><ul><li><p class="paragraph" style="text-align:left;"><b>CloudShell</b> is a terminal which runs in your browser.</p></li><li><p class="paragraph" style="text-align:left;">Behind the scenes it’s a container with persistent storage.</p></li><li><p class="paragraph" style="text-align:left;">It’s important to understand credential preference order when using the command line and SDKs.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lab"><b>The Lab</b></h1><p class="paragraph" style="text-align:left;">I knew I wanted to do a <b>CloudShell</b> and CLI lab but it took me a while to figure out something interesting with a security message. Don’t worry, I cracked it!</p><p class="paragraph" style="text-align:left;">Today we’ll launch a <b>CloudShell</b> in our management account. We’ll pull our credentials from the customized metadata service, then <b>AssumeRole</b> into one of our other accounts, and play with the CLI a bit. We’ll also download a file and peek into <b>CloudTrail</b> to see what <b>CloudShell</b> events look like, in case you ever want to build some threat detectors around it.</p><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/ZWnC0jnmOwY" width="100%"></iframe><h2 class="heading" style="text-align:left;" id="stepby-step">Step-by-Step</h2><p class="paragraph" style="text-align:left;">Start in your <b>Sign-in portal &gt; Copy the Account ID for TestAccount1 &gt; Then CloudSLAW &gt; AdministratorAccess.</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/7edacf1a-e2d9-460f-b9bc-3621246125dd/image.png?t=1747856179"/></div><p class="paragraph" style="text-align:left;">Then <b>make sure you are in the Oregon region &gt;</b> <b>launch CloudShell, </b>which is located up on the AWS Console menu bar:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/e8862f7b-8709-4558-967d-ec29d527b304/image.png?t=1747856319"/></div><p class="paragraph" style="text-align:left;">Double-check we are logged in using our role:</p><p class="paragraph" style="text-align:left;"><code>aws sts get-caller-identity</code></p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/9935c2b8-f08f-4fb3-b0d2-e2771b18dd1f/image.png?t=1747856378"/></div><p class="paragraph" style="text-align:left;">Okay, let’s try to grab credentials from the environment variables:</p><p class="paragraph" style="text-align:left;"><code>echo $AWS_ACCESS_KEY_ID</code></p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/66b87b1b-fed4-4cfe-9b83-62bfcc99b963/image.png?t=1747856410"/></div><p class="paragraph" style="text-align:left;">Huh. Well that didn’t work. Let’s see all the environment variables:</p><p class="paragraph" style="text-align:left;"><code>env</code></p><p class="paragraph" style="text-align:left;">Oh, interesting! There’s a lot of cool information in there (like, this is how I know <b>CloudShell</b> is a container… see if you can spot it).</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/3757fda5-8baa-4f12-bd82-7827a457e690/image.png?t=1747856458"/></div><p class="paragraph" style="text-align:left;">What’s that one that says “<b>AWS_CONTAINER_CREDENTIALS_FULL_URI=</b><a class="link" href="https://127.0.0.1:1338/latest/meta-data/container/security-credentials?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=using-the-aws-cli-and-securing-cloudshell" target="_blank" rel="noopener noreferrer nofollow">http://127.0.0.1:1338/latest/meta-data/container/security-credentials</a>”? That looks a lot like the metadata service, but instead of being at 169.254.129.254, it’s at <a class="link" href="http://localhost?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=using-the-aws-cli-and-securing-cloudshell" target="_blank" rel="noopener noreferrer nofollow">localhost</a>. (And is it on port 1338 because it’s one better than 1337?). In case you were wondering, this is <b>not</b> documented anywhere. It’s almost as if AWS doesn’t think you need to use these credentials (you don’t). </p><p class="paragraph" style="text-align:left;">Well, I see an endpoint, and maybe it works like the metadata service, and maybe I can pull credentials? <a class="link" href="https://blog.christophetd.fr/retrieving-aws-security-credentials-from-the-aws-console/?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=using-the-aws-cli-and-securing-cloudshell" target="_blank" rel="noopener noreferrer nofollow">Hat tip to Christophe Tafani-Dereeper for doing all the hard work a few years ago (and, uh, I was a bit surprised he gave me some credit)</a>. Let’s follow the process we use with v2 of the instance metadata service (<a class="link" href="https://slaw.securosis.com/p/experience-danger-mysterious-metadata-service-v1?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=using-the-aws-cli-and-securing-cloudshell" target="_blank" rel="noopener noreferrer nofollow">remember, this is a two-step process</a>):</p><p class="paragraph" style="text-align:left;"><code>TOKEN=$(curl -XPUT localhost:1338/latest/api/token -H &quot;X-aws-ec2-metadata-token-ttl-seconds: 60&quot;)</code></p><p class="paragraph" style="text-align:left;"><code>curl localhost:1338/latest/meta-data/container/security-credentials -H &quot;X-aws-ec2-metadata-token: $TOKEN&quot;</code></p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/c027c4a4-a316-4807-a041-5c05e4ac56ff/image.png?t=1747856508"/></div><p class="paragraph" style="text-align:left;">Whoo hoo, we cracked the Gibson! Well, the… <b>something.</b> </p><p class="paragraph" style="text-align:left;">Okay, let’s shift gears and start using the CLI. To start, let’s assume the <b>OrganizationAccountAccessRole</b> into <b>TestAccount1.</b> This works because we are logged into <b>CloudShell</b> as an administrator in our management account. I’m putting this as one big code block, so you <i>can</i><b> copy and paste the entire thing — you just need to paste in your own account ID and review the warning AWS may show (it’s a browser thing)</b>:</p><div class="codeblock"><pre><code># Replace ACCOUNT_ID with the target member account ID
ACCOUNT_ID=&lt;YOUR ACCOUNT ID&gt;

# Assume the role and store the credentials in a variable
credentials=$(aws sts assume-role \
  --role-arn arn:aws:iam::$ACCOUNT_ID:role/OrganizationAccountAccessRole \
  --role-session-name cloudshell-org-access)

# Extract and export the temporary credentials
export AWS_ACCESS_KEY_ID=$(echo $credentials | jq -r &#39;.Credentials.AccessKeyId&#39;)
export AWS_SECRET_ACCESS_KEY=$(echo $credentials | jq -r &#39;.Credentials.SecretAccessKey&#39;)
export AWS_SESSION_TOKEN=$(echo $credentials | jq -r &#39;.Credentials.SessionToken&#39;)

# Verify you&#39;re now operating in the member account
aws sts get-caller-identity
</code></pre></div><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/bffc5698-f18c-4f1e-b7ba-36570ac50549/image.png?t=1747856611"/></div><p class="paragraph" style="text-align:left;">We’re in! (Note, hacker union rules require that I say “We’re in!” Don’t blame me — I just follow the rules). You can see that we are currently using the <b>OrganizationAccountAccessRole,</b> and it plops <b>CloudShell</b> right into the identity, which means it’s logged.</p><p class="paragraph" style="text-align:left;">Notice that <b>CloudShell</b> emphasizes your current region. That doesn’t stop you from using the CLI to run a command in another region. Let’s play a bit:</p><p class="paragraph" style="text-align:left;"><code>aws cloudformation describe-stacks</code></p><p class="paragraph" style="text-align:left;"><code>aws cloudformation describe-stacks —region us-east-1</code></p><p class="paragraph" style="text-align:left;"><b>Type q to exit those results.</b></p><p class="paragraph" style="text-align:left;">Now for a warning, <b>if you swap regions in the console,</b> that starts a new <b>CloudShell</b> in that region, and you lose your assumed role. This is why knowing how to change the region as a command-line parameter is still important.</p><p class="paragraph" style="text-align:left;"><i>Another cool capability is that your </i><b><i>browser</i></b><i> is still using the credentials you logged in with, but your </i><b><i>shell</i></b><i> is using the assumed credentials. This can be </i><b><i>very</i></b><i> helpful when troubleshooting or testing.</i></p><p class="paragraph" style="text-align:left;">You can also open <b>CloudShell</b> into its own tab. <b>Click the little icon in the upper right corner of the shell:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/d61fa6f7-9b9e-4eb2-a4c8-52a2e9623aa0/image.png?t=1747856725"/></div><p class="paragraph" style="text-align:left;">Okay, now let’s play with files. <b>Get Account Authorization Details</b> pulls down a detailed JSON report of all your IAM permissions in an account. We’ll save the results to a file accessible to our shell:</p><p class="paragraph" style="text-align:left;"><code>aws iam get-account-authorization-details &gt; auth-details.json</code></p><p class="paragraph" style="text-align:left;">Now go to <b>Actions &gt; Download File</b>. You might hit the error/warning in my screenshot — if so just click and follow the instructions to open the shell up in a dedicated tab (this depends on your browser).</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/6cd083a0-9159-4f5d-bd05-54b2a37516c7/image.png?t=1747856800"/></div><p class="paragraph" style="text-align:left;"><b>Type in the file name: auth-details.json:</b></p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/016785c7-f632-46f6-ac4e-fb2ee6f9d0f5/image.png?t=1747856880"/></div><p class="paragraph" style="text-align:left;">To finish up hop over the <b>CloudTrail</b> and take a look at your activities. This will print out recent activity every minute. Pretty cool, eh? </p><div class="codeblock"><pre><code># Poll for recent events every minute
while true; do
  aws cloudtrail lookup-events \
    --lookup-attributes AttributeKey=ReadOnly,AttributeValue=false \
    --start-time $(date -d &#39;2 minutes ago&#39; -u +&quot;%Y-%m-%dT%H:%M:%SZ&quot;) \
    --max-results 10
  sleep 60
done
</code></pre></div><h3 class="heading" style="text-align:left;" id="key-points">Lab Key Points</h3><ul><li><p class="paragraph" style="text-align:left;"><b>CloudShell</b> has its own metadata service for handling credentials, but you should never need to access it directly (it’s undocumented).</p></li><li><p class="paragraph" style="text-align:left;">You can assume roles into other accounts (or the same account) if you have permissions and don’t need to bounce around the console.</p></li><li><p class="paragraph" style="text-align:left;">The CLI is incredibly useful and powerful, and can even run scripts.</p></li><li><p class="paragraph" style="text-align:left;">It even includes Python, along with file upload and download capabilities!</p></li></ul><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=161898bf-14c4-4571-afb8-4f461c680cc5&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Schedule Security Scanning with a Serverless Fanout Pattern</title>
  <description>Running scheduled scans at scale can be challenging, but we&#39;ll knock it out in mere seconds with this cool serverless design pattern.</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/5be47130-aa8f-4036-ae30-ac349eec653a/epic-8.png" length="533865" type="image/png"/>
  <link>https://slaw.securosis.com/p/schedule-security-scanning-with-a-serverless-fanout-pattern</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/schedule-security-scanning-with-a-serverless-fanout-pattern</guid>
  <pubDate>Thu, 08 May 2025 15:00:00 +0000</pubDate>
  <atom:published>2025-05-08T15:00:00Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b><a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=schedule-security-scanning-with-a-serverless-fanout-pattern" target="_blank" rel="noopener noreferrer nofollow">Go Pro for Support and Extras!</a></b></p><p class="paragraph" style="text-align:left;">CloudSLAW is, and always will be, free. But to help cover costs and keep the content up to date we have an optional Patreon. For $10 per month you get access to our support Discord, office hours, exclusive subscriber content (not labs — those are free — just extras) and more. <a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=schedule-security-scanning-with-a-serverless-fanout-pattern" target="_blank" rel="noopener noreferrer nofollow">Check it out!</a></p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;">This is part 9 (the final lab!) of the Advanced Cloud Security Problem Solving series, which I’ve been calling Epic Automation. If you haven’t completed parts 1-6 yet, <span style="color:rgb(44, 129, 229);"><span style="text-decoration:underline;"><i><a class="link" href="https://slaw.securosis.com/p/epic-cloud-security-automation-fixing-the-broken-rcp?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=coding-security-autoremediations&_bhlid=5862c89c317084dd94d8a46aaa8cda2c5e0c9c8d" target="_blank" rel="noopener noreferrer nofollow" style="color: rgb(44, 129, 229)">jump back to the first post in the series</a></i></span></span> and complete all prior posts before trying this one.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Lesson</h1><p class="paragraph" style="text-align:left;">This is, I swear, the absolute last lab in our Advanced Cloud Security Problem Solving series! (Epic Automation fit the title card better).</p><p class="paragraph" style="text-align:left;">By now you’ve probably realized that I, uh, maybe pulled a fast one on you. It seems we’ve spent these 9 labs learning as much about development and cloud architecture patterns as about cloud security. The thing is, these two skills are impossible to separate if you want to work as a cloud security professional. Just as a firewall engineer needs to understand network topologies, technologies, and how to interpret packet captures… on the cloud side we need to understand our own fair share of non-security fundamentals.</p><p class="paragraph" style="text-align:left;">Today’s lab is a very cool one, which I was excited to get up and running. If you recall, <a class="link" href="https://slaw.securosis.com/p/epic-cloud-security-automation-fixing-the-broken-rcp?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=schedule-security-scanning-with-a-serverless-fanout-pattern" target="_blank" rel="noopener noreferrer nofollow">way back in the first lab of this series</a>, we defined our objective: “if a bucket is tagged sensitive, only allow access from within my organization.” To do this we spent 8 labs building out an event-driven architecture to would assess any buckets as their configuration changed, and then enforce our desired settings. The diagram looked like this:</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/c3c25c99-cfd1-4d98-893b-cb62519fa68d/image.png?t=1744744843"/></div><p class="paragraph" style="text-align:left;">So far we’ve built out basically everything except that teeny-tiny little blue box on the left side that says “Time: Every Hour”. And, uh, that little blue box contains a big ol’ can of worms.</p><h2 class="heading" style="text-align:left;" id="scaling-with-a-lambda-fanout-patter">Scaling with a Lambda Fanout Pattern</h2><p class="paragraph" style="text-align:left;">In our little environment we only have one S3 bucket to care about, so running a time-based scan isn’t a big deal. But imagine an organization with many thousands of buckets spread across hundreds of accounts (which I personally have — never mind a big enterprise, a factor of 10 larger than that). Running a scan at that scale, across multiple accounts, would be both massively time consuming and incredibly inefficient.</p><p class="paragraph" style="text-align:left;">We don’t call AWS a “hyperscale cloud provider” for nothing!</p><p class="paragraph" style="text-align:left;">What we have already built is an <i>event-driven architecture pattern</i> that only runs as things change. No changes? Nothing runs. Modify 10 buckets at once? We invoke 10 lambda functions<i> </i>in parallel. This architecture is incredibly scalable and efficient, and nicely shows off the power of on-demand cloud computing. What makes it so efficient is that <i>we know exactly which bucket changed,</i> and our autoremediation lambda <i>only has to look at that one bucket, and only when it changes</i>.</p><p class="paragraph" style="text-align:left;">However, this doesn’t work for time-based scans since we don’t have an event that identifies a changed bucket, so they need to <i>look at every single bucket.</i></p><p class="paragraph" style="text-align:left;">The good news is we have other patterns we can use, and the one we will cover today is a <i>lambda fanout</i>. We will use a series of 3 different lambda functions to identify and then scan every bucket, basically in parallel. Each lambda can trigger other lambdas in a fan pattern to run as much in parallel as possible. Here’s how it works:</p><ul><li><p class="paragraph" style="text-align:left;">We run an <b>EventBridge</b> Rule which triggers our first lambda every 12 hours (I picked this instead of every hour to save costs).</p></li><li><p class="paragraph" style="text-align:left;">That lambda, called <b>enumerate-accounts,</b> takes an environment variable specifying which OU to start in. This is just our Production OU because we defined that as the only place where we want to enforce isolation. <b>enumerate-accounts</b> does two things:</p><ul><li><p class="paragraph" style="text-align:left;">It identifies any child OUs. For each child OU it invokes another version of itself. This continues to fan out until there are no more child OUs. Each of these is a separate invocation running a new copy of the lambda, so it’s incredibly fast.</p></li><li><p class="paragraph" style="text-align:left;">It identifies every account in the current OU. For each account, it invokes the <b>enumerate-tagged-buckets </b>lambda function. So if it finds 37 accounts, <b>enumerate-tagged-buckets</b> runs 37 copies in parallel.</p></li></ul></li><li><p class="paragraph" style="text-align:left;">Our second lambda, <b>enumerate-tagged-buckets,</b> uses a very cool little feature: <i>Resource Group Tagging.</i> Instead of having to scan every bucket to find tagged buckets, <i>we can find all tagged buckets with a single API call!</i> This dramatically improves efficiency and, I hate to admit, I had never used it before this lab.</p><ul><li><p class="paragraph" style="text-align:left;">This creates a list of all buckets tagged with a key of “classification” and value of “sensitive”. </p></li><li><p class="paragraph" style="text-align:left;">We iterate over that list, and invoke our already-created <b>security-auto-s3</b> lambda function once for each bucket. This is the third phase of our fanout and all of these run in parallel.</p><ul><li><p class="paragraph" style="text-align:left;">We directly invoke the function; we don’t need to create a new <b>EventBridge</b> event to trigger it. However, <i>we create a fake event with the account ID and bucket name</i> so we don’t need to update our existing code!</p></li></ul></li></ul></li><li><p class="paragraph" style="text-align:left;"><b>security-auto-s3</b> works exactly the same as if we triggered it from <b>EventBridge,</b> thanks to passing in the <b>synthetic event</b> with the fields it needs to find the bucket.</p></li></ul><p class="paragraph" style="text-align:left;">Even in a large environment this should run in under 30 seconds.</p><p class="paragraph" style="text-align:left;">Cool, eh? This architecture allows us to run a time-based scan with a bunch of parallel resources which don’t even run until the clock triggers them. Then they fan out and invoke the next layer of the stack, as efficiently as possible. Here’s what our 3-tier fanout architecture looks like:</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/02343515-25ba-4a8e-988a-313c5f692fcf/image.png?t=1746562558"/></div><h2 class="heading" style="text-align:left;" id="but-does-it-really-scale-what-else-">But does it really scale? What else do I need to know?</h2><p class="paragraph" style="text-align:left;"><b>While this pattern scales for even massive environments,</b> this is about the simplest possible implementation, and I would modify it if I was operating it in an enterprise:</p><ul><li><p class="paragraph" style="text-align:left;">There is no error handling outside some basics in the functions. And you won’t see those if you aren’t checking the logs.</p><ul><li><p class="paragraph" style="text-align:left;">At a large enough scale you might hit service limits (AWS limits the number of simultaneous API calls on a service), and we don’t handle these errors or attempt retries (well, until 12 hours later).</p></li></ul></li><li><p class="paragraph" style="text-align:left;">There is no state management, which is one of the main ways we manage errors in event-driven architectures.</p></li><li><p class="paragraph" style="text-align:left;">We don’t track or log changes, outside of what you find in CloudTrail logs.</p></li></ul><p class="paragraph" style="text-align:left;">All those are manageable problems, using tools we mix and match like EventBridge, DynamoDB (or ElasticSearch), Simple Queue Service, and Simple Notification Service; and we may get to those someday.</p><p class="paragraph" style="text-align:left;">Or just go buy a commercial tool. This is <b>very</b> similar to how our <a class="link" href="https://defense.firemon.cloud?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=schedule-security-scanning-with-a-serverless-fanout-pattern" target="_blank" rel="noopener noreferrer nofollow">FireMon Cloud Defense platform</a> operates, and most of you in multicloud enterprise environments should strongly consider buying vs. building (and you probably already have something).</p><h3 class="heading" style="text-align:left;" id="todays-key-points-are">Key Lesson Points</h3><ul><li><p class="paragraph" style="text-align:left;">Scheduled scans are more difficult than event-driven triggers since you need to manage scale and respect service limits.</p></li><li><p class="paragraph" style="text-align:left;">A lambda “fanout” pattern scales nearly instantly by running multiple, parallel invocations, which directly match the resources you have.</p></li><li><p class="paragraph" style="text-align:left;">Fanouts work well with different tiers to match scaling requirements.</p><ul><li><p class="paragraph" style="text-align:left;">But don’t forget you might need error handling and state management when using this in an important environment.</p></li></ul></li></ul><h1 class="heading" style="text-align:left;" id="the-lab"><b>The Lab</b></h1><p class="paragraph" style="text-align:left;">This would be a lot to build out completely by hand, so I packaged it all up into a CloudFormation template. There are still a few steps to get it up and running, and I’m including all the code here in the blog post so you can review each piece. We have a few steps to make this work:</p><ul><li><p class="paragraph" style="text-align:left;">We need to delegate administration for AWS Organizations to our <b>SecurityOperations</b> account, so it can read the accounts and OUs. As you will see, we use a restrictive resource policy for only the minimal API calls required.</p></li><li><p class="paragraph" style="text-align:left;">We must deploy the main template, which loads the 2 new lambda functions from my public S3 bucket; we also create 2 new roles and the EventBridge Rule which runs every 12 hours. </p></li><li><p class="paragraph" style="text-align:left;">We finish by updating our <b>SecurityAutoremediation</b> role using an updated template in CloudFormation StackSets. <b>This step must occur last,</b> since it allows our new <b>enumerate-tagged-buckets</b> lambda to use the existing role. If you recall, <a class="link" href="https://slaw.securosis.com/p/secure-a-role-chain-on-both-sides?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=schedule-security-scanning-with-a-serverless-fanout-pattern" target="_blank" rel="noopener noreferrer nofollow">we locked this down in a previous lab</a> so only allowed lambda functions can use the role. </p><ul><li><p class="paragraph" style="text-align:left;">We also add permission to use the <b>tag:GetResources</b> API to find tagged buckets.</p></li></ul></li></ul><p class="paragraph" style="text-align:left;">Since all this deploys at once, here is the code for the <b>enumerate-accounts</b> lambda function which finds all accounts in the current OU, then runs another version of itself if there are any child OUs. This is the first step of our fanout:</p><div class="codeblock"><pre><code>import os
import boto3

org_client = boto3.client(&#39;organizations&#39;)
lambda_client = boto3.client(&#39;lambda&#39;)

def lambda_handler(event, context):
	production_ou = os.environ[&#39;production_ou&#39;]
	account_ids = []

	# Recursively collect all account IDs under the given OU
	def collect_accounts(parent_ou):
		# List accounts directly under this OU
		paginator = org_client.get_paginator(&#39;list_accounts_for_parent&#39;)
		for page in paginator.paginate(ParentId=parent_ou):
			for acct in page[&#39;Accounts&#39;]:
				account_ids.append(acct[&#39;Id&#39;])
		# List child OUs and recurse
		ou_paginator = org_client.get_paginator(&#39;list_organizational_units_for_parent&#39;)
		for ou_page in ou_paginator.paginate(ParentId=parent_ou):
			for ou in ou_page[&#39;OrganizationalUnits&#39;]:
				collect_accounts(ou[&#39;Id&#39;])

	collect_accounts(production_ou)

	# Fan-out: invoke &#39;find-sensitive-buckets&#39; Lambda for each account
	for account_id in account_ids:
		lambda_client.invoke(
			FunctionName=&#39;enumerate-tagged-buckets&#39;,
			InvocationType=&#39;Event&#39;,  # async
			Payload=f&#39;&#123;&#123;&quot;account_id&quot;: &quot;&#123;account_id&#125;&quot;&#125;&#125;&#39;
		)

	return &#123;
		&#39;statusCode&#39;: 200,
		&#39;body&#39;: f&#39;Invoked find-sensitive-buckets for &#123;len(account_ids)&#125; accounts.&#39;
	&#125;
</code></pre></div><p class="paragraph" style="text-align:left;">And here’s the code for <b>enumerate-tagged-buckets</b> which finds all tagged buckets, and then runs <b>security-auto-s3</b> on each of them. This is the second stage of the fanout, and the <b>security-auto-s3</b> is the third stage. Look for the <b>tag:GetResources</b> command — that’s the magic which saves us from having to list all buckets and then look for all the tagged ones:</p><div class="codeblock"><pre><code>import boto3
import os
import json

REGIONS = [&quot;us-east-1&quot;, &quot;us-west-2&quot;]

def assume_role(account_id, region):
	sts = boto3.client(&#39;sts&#39;)
	role_arn = f&quot;arn:aws:iam::&#123;account_id&#125;:role/SecurityOperations/SecurityAutoremediation&quot;
	response = sts.assume_role(
		RoleArn=role_arn,
		RoleSessionName=&quot;FindSensitiveBucketsSession&quot;
	)
	creds = response[&#39;Credentials&#39;]
	return boto3.client(
		&#39;resourcegroupstaggingapi&#39;,
		region_name=region,
		aws_access_key_id=creds[&#39;AccessKeyId&#39;],
		aws_secret_access_key=creds[&#39;SecretAccessKey&#39;],
		aws_session_token=creds[&#39;SessionToken&#39;]
	)

def lambda_handler(event, context):
	account_id = event[&#39;account_id&#39;]
	lambda_client = boto3.client(&#39;lambda&#39;)
	found_buckets = set()

	for region in REGIONS:
		tagging_client = assume_role(account_id, region)
		paginator = tagging_client.get_paginator(&#39;get_resources&#39;)
		for page in paginator.paginate(
			ResourceTypeFilters=[&#39;s3&#39;],
			TagFilters=[&#123;&#39;Key&#39;: &#39;classification&#39;, &#39;Values&#39;: [&#39;sensitive&#39;]&#125;]
		):
			for resource in page[&#39;ResourceTagMappingList&#39;]:
				arn = resource[&#39;ResourceARN&#39;]
				# S3 bucket ARN format: arn:aws:s3:::bucket-name
				bucket_name = arn.split(&quot;:::&quot;)[-1]
				if bucket_name not in found_buckets:
					found_buckets.add(bucket_name)
					# Mimic a CloudTrail CreateBucket event (minimal fields)
					payload = &#123;
						&quot;account&quot;: account_id,
						&quot;detail&quot;: &#123;
							&quot;resources&quot;: [
								&#123;
									&quot;type&quot;: &quot;AWS::S3::Bucket&quot;,
									&quot;ARN&quot;: arn
								&#125;
							],
							&quot;requestParameters&quot;: &#123;
								&quot;bucketName&quot;: bucket_name
							&#125;,
							&quot;eventSource&quot;: &quot;s3.amazonaws.com&quot;,
							&quot;eventName&quot;: &quot;CreateBucket&quot;,
							&quot;recipientAccountId&quot;: account_id,
							&quot;accountId&quot;: account_id
						&#125;
					&#125;
					lambda_client.invoke(
						FunctionName=&quot;security-auto-S3&quot;,
						InvocationType=&quot;Event&quot;,  # async
						Payload=json.dumps(payload)
					)

	return &#123;
		&quot;statusCode&quot;: 200,
		&quot;body&quot;: f&quot;Invoked S3-secure for &#123;len(found_buckets)&#125; sensitive buckets in account &#123;account_id&#125;.&quot;
	&#125;</code></pre></div><p class="paragraph" style="text-align:left;">Okay, let’s jump into the lab:</p><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/jyf8k2aJHyo" width="100%"></iframe><h2 class="heading" style="text-align:left;" id="stepby-step">Step-by-Step</h2><p class="paragraph" style="text-align:left;">Start in your <b>Sign-in portal &gt; CloudSLAW &gt; AdministratorAccess &gt; Organizations</b>. <b>Copy/Paste the </b><i><b>Workloads &gt; Prod OU ID</b></i><b> and the </b><i><b>SecurityOperations Account ID</b></i><i>. </i>You will need both.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/6619d25e-28ac-40c1-af42-938dcfa7e8a5/image.png?t=1746632977"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/12b3079e-a139-47ca-93e6-a4cb8f290782/image.png?t=1746633032"/></div><p class="paragraph" style="text-align:left;">Now we need to delegate administration for Organizations to our <b>SecurityOperations</b> account. Still with <b>Organizations &gt; Settings &gt; Delegate administrator for AWS services &gt; Delegate: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/089ca89f-aab7-4fea-988e-df251bde57ba/image.png?t=1746633245"/></div><p class="paragraph" style="text-align:left;">Then <b>copy this JSON and paste into the window &gt; Replace ACCOUNTID with your </b><i><b>SecurityOperations</b></i><b> account ID:</b></p><div class="codeblock"><pre><code>&#123;
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    &#123;
      &quot;Sid&quot;: &quot;Statement&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: &#123;&quot;AWS&quot;: &quot;arn:aws:iam::ACCOUNTID:root&quot;&#125;,
      &quot;Action&quot;: [
        &quot;organizations:ListAccountsForParent&quot;,
        &quot;organizations:ListOrganizationalUnitsForParent&quot;
      ],
      &quot;Resource&quot;: [
        &quot;*&quot;
      ]
    &#125;
  ]
&#125;</code></pre></div><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ec7bff41-0906-400e-b63f-188190a45daa/image.png?t=1746633450"/></div><p class="paragraph" style="text-align:left;">Notice that we only include 2 permissions. This is all our code needs (for now) to find child accounts and OUs when given a parent. We can walk down the tree, but we can’t climb up. Maybe we’ll change that later, maybe not. I’m kind of whimsical that way.</p><p class="paragraph" style="text-align:left;"><b>Close the tab &gt; Sign-in Portal &gt; SecurityOperations &gt; AdministratorAccess &gt; CloudFormation &gt; Create stack: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/48a980b9-6eda-48f3-9559-99e75e4614e7/image.png?t=1746633694"/></div><p class="paragraph" style="text-align:left;"><b>Specify template</b> <a class="link" href="https://cloudslaw.s3-us-west-2.amazonaws.com/lab57.template?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=schedule-security-scanning-with-a-serverless-fanout-pattern" target="_blank" rel="noopener noreferrer nofollow">https://cloudslaw.s3-us-west-2.amazonaws.com/lab57.template</a><b> and name it S3Scanner &gt; paste your </b><i><b>Prod OU </b></i><b>&gt; click through the rest and Create stack: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/2de9efb5-f644-420e-a5a5-21665677ecc4/image.png?t=1746634018"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/b5ec2162-fa6f-4926-9014-bcaf72ff6c0c/image.png?t=1746634040"/></div><p class="paragraph" style="text-align:left;"><b>Let the stack deploy completely!!!!</b></p><p class="paragraph" style="text-align:left;">This creates:</p><ul><li><p class="paragraph" style="text-align:left;">A role for each lambda function</p></li><li><p class="paragraph" style="text-align:left;">The <b>enumerate-accounts</b> lambda function</p><ul><li><p class="paragraph" style="text-align:left;">With the Prod OU ID as an environment variable. This defines the “top” of the tree to scan. </p></li></ul></li><li><p class="paragraph" style="text-align:left;">The <b>enumerate-tagged-buckets</b> lambda function</p></li><li><p class="paragraph" style="text-align:left;">The <b>EventBridge</b> rule to run every 12 hours</p></li></ul><p class="paragraph" style="text-align:left;">Now <b>we must wait</b> because our next step will add permission for the <b>enumerate-tagged-buckets</b> lambda function to use our role in the target accounts we created for <b>security-auto-s3.</b> Remember we locked that down so only allowed lambda functions could use it? Yep, time to … uh … allow it.</p><p class="paragraph" style="text-align:left;">Did I waste enough time? Good, now we need to go into <b>CloudFormation &gt; StackSets &gt; AutoremediationRole &gt; copy the OU ID:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/bf860181-2bb2-4249-a766-8996a8a7a96a/image.png?t=1746634459"/></div><p class="paragraph" style="text-align:left;">Then go to <b>Actions &gt; Edit StackSet details: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ffd8e63a-b27a-4b08-895f-b3a3ed17092a/image.png?t=1746637992"/></div><p class="paragraph" style="text-align:left;"><b>Replace current template &gt; paste this URL</b> <a class="link" href="https://cloudslaw.s3-us-west-2.amazonaws.com/lab57-role.template?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=schedule-security-scanning-with-a-serverless-fanout-pattern" target="_blank" rel="noopener noreferrer nofollow">https://cloudslaw.s3-us-west-2.amazonaws.com/lab57-role.template</a> <b>&gt; Next</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/01a054ab-2f0a-4b61-aae5-201abe1f4d05/image.png?t=1746638158"/></div><p class="paragraph" style="text-align:left;">This update adds permission for the <b>tag:GetResources</b> action, and allows our new lambda to use the role. </p><p class="paragraph" style="text-align:left;">Click <b>Next</b> on the page with the name, then <b>Paste in the OU ID you just copied &gt; Add all regions &gt; Next &gt; Submit </b>(which is on the next page)<b>: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/b63527d7-8ad7-44b4-b84b-ba2cb2b66229/image.png?t=1746638310"/></div><p class="paragraph" style="text-align:left;">That should roll out quickly; then it’s time to test!</p><p class="paragraph" style="text-align:left;">To test we will run a manual test and review the logs. Go to <b>Lambda &gt; Functions &gt; enumerate-accounts:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/1bd8606a-bfc5-4810-b741-fee63ce562c1/image.png?t=1746638488"/></div><p class="paragraph" style="text-align:left;">Then <b>Test &gt; Test</b> (we don’t need a test event because this is triggered by the clock and doesn’t need any input):</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/18bb887e-8258-4d80-b16a-b6036acb46f1/image.png?t=1746638559"/></div><p class="paragraph" style="text-align:left;">This enumerates all the OUs and accounts, then triggers the <b>enumerate-tagged-buckets</b> function once for every account, then <b>that</b> finds all buckets tagged “sensitive” and runs <b>security-auto-s3.</b> So to see if it works we can skip to the end and check the logs. </p><p class="paragraph" style="text-align:left;">Go to <b>CloudWatch &gt; Log Groups &gt; /aws/lambda/security-auto-s3.</b> Click the most recent one (which should match the current day) and you should see it ran successfully. This thing is so lightning fast that if you don’t see logs by the time you read all this and click around, odds are something went wrong so, uh, try again? (Or ask me for help):</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/a9a9eecb-1a0f-4af8-9de3-110c0b18e771/image.png?t=1746638803"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/5c96953e-8cdd-4f44-b76a-86648705bc1b/image.png?t=1746638897"/></div><h3 class="heading" style="text-align:left;" id="key-points">Lab Key Points</h3><ul><li><p class="paragraph" style="text-align:left;">You can find all tagged resources without enumerating them individually, using <b>tag:GetResources.</b></p></li><li><p class="paragraph" style="text-align:left;">Before updating permissions you need to create the entity that will get the permissions; otherwise AWS will error out.</p></li><li><p class="paragraph" style="text-align:left;">We can manually synthesize an event with the same structure as a CloudTrail event to trigger our existing function without modification.</p></li></ul><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=1ddbff2b-697e-4cb9-b803-5277c0f9ff03&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Coding Security Autoremediations</title>
  <description>For today&#39;s lab we’ll roll up our sleeves, pull out our development tools, and, well... mostly copy and paste, as you learn some techniques for coding security autoremediation.</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/132b61cc-322e-4b26-ba29-faf281648a9a/Copy_of_epic.png" length="660715" type="image/png"/>
  <link>https://slaw.securosis.com/p/coding-security-autoremediations</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/coding-security-autoremediations</guid>
  <pubDate>Thu, 17 Apr 2025 15:00:00 +0000</pubDate>
  <atom:published>2025-04-17T15:00:00Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b>Watch Me Code This Live with AI (Pro Content)!</b></p><p class="paragraph" style="text-align:left;">CloudSLAW is, and always will be, free. But to help cover costs and keep the content up to date we have an optional Patreon. For this lab Pro subscribers got to watch me as I set up my toolchain for writing and testing the code, and how I used AI to help. I was able to knock out all the code in an hour that would previously have taken me half a day. Like all Pro content, the video was recorded and is available to new subscribers <a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=coding-security-autoremediations" target="_blank" rel="noopener noreferrer nofollow">Check it out!</a></p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;">This is part 8 of the Advanced Cloud Security Problem Solving series, which I’ve been abbreviating to Epic Automation. If you haven’t completed parts 1-6 yet, <span style="text-decoration:underline;"><i><a class="link" href="https://slaw.securosis.com/p/epic-cloud-security-automation-fixing-the-broken-rcp?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=coding-security-autoremediations" target="_blank" rel="noopener noreferrer nofollow" style="color: rgb(44, 129, 229)">jump back to the first post in the series</a></i></span> and complete all prior posts before trying this one. </p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Lesson</h1><p class="paragraph" style="text-align:left;">This is it folks, the culmination of months of work! (Or days, for you overachievers blasting through the website). We have everything set up, and today we’ll update permissions and deploy the code to enforce our desired outcome.</p><p class="paragraph" style="text-align:left;">Oh, you forgot that? Me too! Let’s review…</p><p class="paragraph" style="text-align:left;"><i>If an S3 bucket is tagged with a classification of sensitive, only allow access from within our AWS Organization</i>.</p><p class="paragraph" style="text-align:left;">We originally hoped to set this using a Resource Control Policy, but there’s a limitation in IAM and RCP conditions: they don’t support S3 bucket tags. But we (okay, I) decided we could achieve the same objective with an <i>autoremediation guardrail</i> which would trigger whenever anyone tags a bucket or makes permissions changes to a bucket. While policies like SCPs and RCPs are great because they <i>prevent</i> something from happening, they can’t really handle all the complex scenarios we might encounter. For those situations we can use a <i>corrective control</i> which fixes something after the fact… often nearly instantly, thanks to the power of cloud. </p><p class="paragraph" style="text-align:left;">Here’s the logic we built out. We monitor CloudTrail for certain API calls, use them as triggers, assess the bucket, then apply corrective controls to lock it down. </p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/c3c25c99-cfd1-4d98-893b-cb62519fa68d/image.png?t=1744744823"/></div><p class="paragraph" style="text-align:left;">To this point we’ve set triggers, linked them in to run our Lambda function (code) to get the bucket’s information, and established our cross-account access and role chaining permissions.</p><p class="paragraph" style="text-align:left;">All that’s left is the remediation code itself. And really, there isn’t much more to say about it in terms of a general lesson. But here are some points to note:</p><ul><li><p class="paragraph" style="text-align:left;">The main body is <b>lambda_handler.</b> This gets the event and some header information (context).</p></li><li><p class="paragraph" style="text-align:left;">We extract the account ID of the account where the event occurred, and extract the name of the bucket from the event.</p></li><li><p class="paragraph" style="text-align:left;">The function then assumes our target role in the target account, and the <b>get_bucket_info</b> function gathers all the information we want to make our security decisions, including the current tags.</p></li><li><p class="paragraph" style="text-align:left;">If the bucket is tagged with <b>classification:sensitive,</b><i> </i>then go “do things”.</p><ul><li><p class="paragraph" style="text-align:left;">Turn on Block Public Access (BPA), disable ACLs, and check the bucket policy.</p></li><li><p class="paragraph" style="text-align:left;">Retrieve the bucket policy (if one exists) and add a statement to the bucket policy to restrict access to the current organization and AWS services.</p><ul><li><p class="paragraph" style="text-align:left;">For this to work the function needs to know your Organization ID. Since this account <b>(SecurityOperations)</b> does not have permissions for Organizations, it doesn’t have a way to pull the ID. Instead we will set it as an environment variable.</p></li></ul></li></ul></li></ul><p class="paragraph" style="text-align:left;">To make the lab easier to understand, I wrote all the code in a single that you can copy and paste into the console. The code is well-commented and should be easy to follow even if you are new to Python. Start in <b>lambda_handler</b> at the bottom, and you can walk through the logic and the various functions. </p><p class="paragraph" style="text-align:left;">This should work well in a lot of situations, but it doesn’t have any IP address based permissions. <b>This means it blocks all IP-based access, and only allows API-based access!</b> We could write more complex code which could evaluate IP addresses and allow them if they are private-network-only, or from a specific IP address range, but that was beyond our current requirements. Just keep that <b>very important</b> limitation in mind if you use this technique yourself.</p><p class="paragraph" style="text-align:left;">Another quick note on the code: you’ll notice that I don’t rely on any bucket information in the event, other than the bucket name and account ID. Every time I create an autoremediation, I always <i>check the current state of the resource</i> before making any changes. Why? Because life comes at you fast, especially in cloud, and I always want to operate knowing the <b>real-time</b> state of a resource.</p><p class="paragraph" style="text-align:left;">Also, after my first pass I needed to go back through and make sure that the code only makes changes if the expected security isn’t in place. Why? The first version (in one spot) just set the secure condition every time, even if it was already there. But this change API call was picked up as one of our triggers, which ran the lambda again, which made the change again, and… infinite loops in the cloud are fun!</p><p class="paragraph" style="text-align:left;"><i><b>When you play battle bots in the cloud, only Amazon’s accounting department wins!</b></i></p><p class="paragraph" style="text-align:left;">As I mentioned above, it took me about an hour to code up all the remediations with the help of an AI tool. I used our test event to trigger each test, but I specified a target bucket I could safely modify to play with different conditions. You can check out the Pro content in the Patreon for a deeper walkthrough, and to see how I built everything.</p><p class="paragraph" style="text-align:left;">Finally, to make this work we need to update permissions and set that environment variable… both of which are in the lab. </p><h3 class="heading" style="text-align:left;" id="todays-key-points-are">Key Lesson Points</h3><ul><li><p class="paragraph" style="text-align:left;">Always test your code on real resources in a test environment whenever you can. Local testing stacks are great, but don’t totally rely on them.</p></li><li><p class="paragraph" style="text-align:left;">Consider using multiple browsers (or terminal shells) logged into different accounts so you can test without having to bounce around too much.</p><ul><li><p class="paragraph" style="text-align:left;">I show that in the Pro content, but we didn’t really need it for this lab. For deeper testing I write command line scripts instead of using the browser.</p></li></ul></li><li><p class="paragraph" style="text-align:left;">Watch out for infinite loops: make sure your changes don’t continuously trigger your app code. </p><ul><li><p class="paragraph" style="text-align:left;">There’s a different architecture that can handle this a little better which we didn’t use: tracking state with SQS and Dynamo Database. That’s much more complex to set up correctly, and the small amount of re-triggering this architecture performs won’t increase costs. </p></li></ul></li></ul><h1 class="heading" style="text-align:left;" id="the-lab"><b>The Lab</b></h1><p class="paragraph" style="text-align:left;">We will do 3 things in this lab, and I recommend an optional 4th step:</p><ol start="1"><li><p class="paragraph" style="text-align:left;">Get our org ID and set it as an environment variable for our Lambda function.</p></li><li><p class="paragraph" style="text-align:left;">Update our code with the new code I wrote that actually sets the security conditions.</p></li><li><p class="paragraph" style="text-align:left;">Update our automation role’s permissions, now that we’ve finished the code and know exactly what it will do.</p></li><li><p class="paragraph" style="text-align:left;">Make changes to our test bucket and watch the magic work!</p></li></ol><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/DS2Y8mTHkHw" width="100%"></iframe><h2 class="heading" style="text-align:left;" id="stepby-step">Step-by-Step</h2><p class="paragraph" style="text-align:left;">Start in your <b>Sign-in portal &gt; CloudSLAW &gt; AdministratorAccess &gt; Organizations &gt;copy your Organization ID:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/a4722112-4a03-46b5-8291-b9d072ff01f2/image.png?t=1744824995"/></div><p class="paragraph" style="text-align:left;"><b>Close that tab &gt; Sign-in portal &gt; SecurityOperations &gt; AdministratorAccess &gt; Lambda &gt; security-auto-s3: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/4241eee3-23c1-4420-b641-b27d434b56bf/image.png?t=1744825108"/></div><p class="paragraph" style="text-align:left;">Go to <b>Configuration &gt; Environment variables &gt; Edit: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/d7a752a1-e1c9-4366-986c-871ad7309a0b/image.png?t=1744825579"/></div><p class="paragraph" style="text-align:left;">Then <b>Add environment variable </b>(click that once to show the text fields) and <b>Key: org_id; Value: your organization’s ID &gt; Save: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/b46dca62-4a44-4f8b-9964-0d75bf09f992/image.png?t=1744825728"/></div><p class="paragraph" style="text-align:left;">Then go to <b>Code &gt; copy and paste all the code below to replace the existing code &gt; Deploy: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/49a39870-4c01-40b2-b543-c75ae5fc9a63/image.png?t=1744826400"/></div><div class="codeblock"><pre><code>import json
import boto3
import re
import logging
from botocore.exceptions import ClientError
import os

# Set up logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def assume_role(account_id):
    &quot;&quot;&quot;
    Assume the SecurityAutoremediation role in the target account
    &quot;&quot;&quot;
    role_arn = f&quot;arn:aws:iam::&#123;account_id&#125;:role/SecurityOperations/SecurityAutoremediation&quot;
    logger.info(f&quot;Assuming role: &#123;role_arn&#125;&quot;)
    
    sts_client = boto3.client(&#39;sts&#39;)
    
    try:
        response = sts_client.assume_role(
            RoleArn=role_arn,
            RoleSessionName=&quot;S3SecurityAnalysis&quot;
        )
        
        credentials = response[&#39;Credentials&#39;]
        
        return &#123;
            &#39;aws_access_key_id&#39;: credentials[&#39;AccessKeyId&#39;],
            &#39;aws_secret_access_key&#39;: credentials[&#39;SecretAccessKey&#39;],
            &#39;aws_session_token&#39;: credentials[&#39;SessionToken&#39;]
        &#125;
    except Exception as e:
        logger.error(f&quot;Failed to assume role &#123;role_arn&#125;: &#123;str(e)&#125;&quot;)
        raise

def extract_bucket_name(event):
    &quot;&quot;&quot;
    Extract S3 bucket name from event by checking multiple possible locations
    &quot;&quot;&quot;
    # Check in resources array
    if &#39;resources&#39; in event[&#39;detail&#39;] and event[&#39;detail&#39;][&#39;resources&#39;]:
        for resource in event[&#39;detail&#39;][&#39;resources&#39;]:
            if resource.get(&#39;type&#39;) == &#39;AWS::S3::Bucket&#39; and resource.get(&#39;ARN&#39;):
                arn = resource.get(&#39;ARN&#39;)
                bucket_match = re.search(r&#39;arn:aws:s3:::([^/]+)&#39;, arn)
                if bucket_match:
                    return bucket_match.group(1)
    
    # Check in requestParameters
    if &#39;requestParameters&#39; in event[&#39;detail&#39;]:
        req_params = event[&#39;detail&#39;][&#39;requestParameters&#39;]
        if req_params and &#39;bucketName&#39; in req_params:
            return req_params[&#39;bucketName&#39;]
    
    # Check in responseElements
    if &#39;responseElements&#39; in event[&#39;detail&#39;] and event[&#39;detail&#39;][&#39;responseElements&#39;]:
        resp_elements = event[&#39;detail&#39;][&#39;responseElements&#39;]
        # Various response element formats
        if &#39;x-amz-bucket-region&#39; in resp_elements:
            bucket_match = re.search(r&#39;arn:aws:s3:::([^/]+)&#39;, 
                                    str(resp_elements))
            if bucket_match:
                return bucket_match.group(1)
    
    return None

def get_bucket_info(bucket_name, session):
    &quot;&quot;&quot;
    Get comprehensive information about an S3 bucket using the provided session
    &quot;&quot;&quot;
    s3_client = session.client(&#39;s3&#39;)
    sts_client = session.client(&#39;sts&#39;)
    
    # Get the account ID from the assumed role session
    account_id = sts_client.get_caller_identity().get(&#39;Account&#39;)
    
    result = &#123;
        &#39;bucket_name&#39;: bucket_name,
        &#39;arn&#39;: f&#39;arn:aws:s3:::&#123;bucket_name&#125;&#39;,
        &#39;account_id&#39;: account_id,
        &#39;tags&#39;: None,
        &#39;has_bucket_policy&#39;: False,
        &#39;bucket_policy&#39;: None,
        &#39;acls_enabled&#39;: True,  # Default to True, will check if acl is &quot;private&quot;
        &#39;acls&#39;: None,
        &#39;block_public_access_bucket&#39;: None
    &#125;
    
    # Get bucket tags
    try:
        tags_response = s3_client.get_bucket_tagging(Bucket=bucket_name)
        result[&#39;tags&#39;] = tags_response.get(&#39;TagSet&#39;, [])
    except ClientError as e:
        if e.response[&#39;Error&#39;][&#39;Code&#39;] == &#39;NoSuchTagSet&#39;:
            result[&#39;tags&#39;] = []
        else:
            logger.warning(f&quot;Error getting bucket tags: &#123;str(e)&#125;&quot;)
    
    # Get bucket policy
    try:
        policy_response = s3_client.get_bucket_policy(Bucket=bucket_name)
        result[&#39;has_bucket_policy&#39;] = True
        result[&#39;bucket_policy&#39;] = json.loads(policy_response.get(&#39;Policy&#39;, &#39;&#123;&#125;&#39;))
    except ClientError as e:
        if e.response[&#39;Error&#39;][&#39;Code&#39;] == &#39;NoSuchBucketPolicy&#39;:
            result[&#39;has_bucket_policy&#39;] = False
        else:
            logger.warning(f&quot;Error getting bucket policy: &#123;str(e)&#125;&quot;)
    
    # Get bucket ACLs and determine if ACLs are enabled
    try:
        acl_response = s3_client.get_bucket_acl(Bucket=bucket_name)
        result[&#39;acls&#39;] = acl_response.get(&#39;Grants&#39;, [])
        
        # Check bucket ownership controls to determine if ACLs are enabled
        try:
            ownership = s3_client.get_bucket_ownership_controls(Bucket=bucket_name)
            rules = ownership.get(&#39;OwnershipControls&#39;, &#123;&#125;).get(&#39;Rules&#39;, [])
            for rule in rules:
                if rule.get(&#39;ObjectOwnership&#39;) == &#39;BucketOwnerEnforced&#39;:
                    result[&#39;acls_enabled&#39;] = False
                    break
        except ClientError as e:
            if e.response[&#39;Error&#39;][&#39;Code&#39;] != &#39;OwnershipControlsNotFoundError&#39;:
                logger.warning(f&quot;Error getting bucket ownership: &#123;str(e)&#125;&quot;)
    except ClientError as e:
        logger.warning(f&quot;Error getting bucket ACL: &#123;str(e)&#125;&quot;)
    
    # Get block public access settings for bucket
    try:
        bpa_response = s3_client.get_public_access_block(Bucket=bucket_name)
        result[&#39;block_public_access_bucket&#39;] = bpa_response.get(&#39;PublicAccessBlockConfiguration&#39;, &#123;&#125;)
    except ClientError as e:
        if e.response[&#39;Error&#39;][&#39;Code&#39;] != &#39;NoSuchPublicAccessBlockConfiguration&#39;:
            logger.warning(f&quot;Error getting bucket public access block: &#123;str(e)&#125;&quot;)
    
    return result

def remediate_bpa(session: boto3.Session, bucket_name: str) -&gt; dict:
    &quot;&quot;&quot;
    Enable S3 Block Public Access (BPA) for a specified S3 bucket.
    
    Args:
        session (boto3.Session): The boto3 session with assumed role credentials
        bucket_name (str): The name of the S3 bucket
        
    Returns:
        dict: Response from the API call
    &quot;&quot;&quot;
    try:
        s3_client = session.client(&#39;s3&#39;)
        
        response = s3_client.put_public_access_block(
            Bucket=bucket_name,
            PublicAccessBlockConfiguration=&#123;
                &#39;BlockPublicAcls&#39;: True,
                &#39;IgnorePublicAcls&#39;: True,
                &#39;BlockPublicPolicy&#39;: True,
                &#39;RestrictPublicBuckets&#39;: True
            &#125;
        )
        
        logger.info(f&quot;Successfully enabled Block Public Access for bucket: &#123;bucket_name&#125;&quot;)
        return response
        
    except Exception as e:
        logger.error(f&quot;Failed to enable Block Public Access for bucket &#123;bucket_name&#125;: &#123;str(e)&#125;&quot;)
        raise e

def remediate_acls(session: boto3.Session, bucket_name: str) -&gt; dict:
    &quot;&quot;&quot;
    Disable ACLs for a specified S3 bucket by setting ObjectOwnership to BucketOwnerEnforced.
    Only makes changes if ACLs are currently enabled.
    
    Args:
        session (boto3.Session): The boto3 session with assumed role credentials
        bucket_name (str): The name of the S3 bucket
        
    Returns:
        dict: Response from the API call or None if no change needed
    &quot;&quot;&quot;
    try:
        s3_client = session.client(&#39;s3&#39;)
        
        # Check current ownership controls
        try:
            ownership = s3_client.get_bucket_ownership_controls(Bucket=bucket_name)
            rules = ownership.get(&#39;OwnershipControls&#39;, &#123;&#125;).get(&#39;Rules&#39;, [])
            for rule in rules:
                if rule.get(&#39;ObjectOwnership&#39;) == &#39;BucketOwnerEnforced&#39;:
                    logger.info(f&quot;ACLs already disabled for bucket: &#123;bucket_name&#125;&quot;)
                    return None
        except ClientError as e:
            if e.response[&#39;Error&#39;][&#39;Code&#39;] != &#39;OwnershipControlsNotFoundError&#39;:
                raise
        
        # If we get here, either no ownership controls exist or they&#39;re not BucketOwnerEnforced
        response = s3_client.put_bucket_ownership_controls(
            Bucket=bucket_name,
            OwnershipControls=&#123;
                &#39;Rules&#39;: [
                    &#123;
                        &#39;ObjectOwnership&#39;: &#39;BucketOwnerEnforced&#39;
                    &#125;
                ]
            &#125;
        )
        
        logger.info(f&quot;Successfully disabled ACLs for bucket: &#123;bucket_name&#125;&quot;)
        return response
        
    except Exception as e:
        logger.error(f&quot;Failed to disable ACLs for bucket &#123;bucket_name&#125;: &#123;str(e)&#125;&quot;)
        raise e

def remediate_bucket_policy(session: boto3.Session, bucket_name: str, current_policy: dict) -&gt; dict:
    &quot;&quot;&quot;
    Check bucket policy size and add organization restriction for sensitive buckets.
    Allows AWS service principals when acting on behalf of the organization.
    Only updates policy if needed.
    
    Args:
        session (boto3.Session): The boto3 session with assumed role credentials
        bucket_name (str): The name of the S3 bucket
        current_policy (dict): The current bucket policy or empty dict
        
    Returns:
        dict: Response from the API call or None if no change needed
    &quot;&quot;&quot;
    try:
        s3_client = session.client(&#39;s3&#39;)
        org_id = os.environ.get(&#39;org_id&#39;)
        
        if not org_id:
            raise ValueError(&quot;org_id environment variable is not set&quot;)
        
        # Create org deny statement with service principal exception
        org_deny_statement = &#123;
            &quot;Sid&quot;: &quot;DenyAccessOutsideOrg&quot;,
            &quot;Effect&quot;: &quot;Deny&quot;,
            &quot;Principal&quot;: &quot;*&quot;,
            &quot;Action&quot;: &quot;s3:*&quot;,
            &quot;Resource&quot;: [
                f&quot;arn:aws:s3:::&#123;bucket_name&#125;&quot;,
                f&quot;arn:aws:s3:::&#123;bucket_name&#125;/*&quot;
            ],
            &quot;Condition&quot;: &#123;
                &quot;StringNotEquals&quot;: &#123;
                    &quot;aws:PrincipalOrgID&quot;: org_id
                &#125;,
                &quot;StringNotLike&quot;: &#123;
                    &quot;aws:PrincipalArn&quot;: [
                        &quot;arn:aws:iam::*:role/aws-service-role/*&quot;,
                        &quot;arn:aws:iam::*:role/service-role/*&quot;
                    ]
                &#125;,
                &quot;Bool&quot;: &#123;
                    &quot;aws:PrincipalIsAWSService&quot;: &quot;false&quot;
                &#125;
            &#125;
        &#125;
        
        # Check if org restriction already exists
        org_restriction_exists = any(
            statement.get(&#39;Sid&#39;) == &quot;DenyAccessOutsideOrg&quot; and
            statement.get(&#39;Effect&#39;) == &quot;Deny&quot; and
            statement.get(&#39;Condition&#39;, &#123;&#125;).get(&#39;StringNotEquals&#39;, &#123;&#125;).get(&#39;aws:PrincipalOrgID&#39;) == org_id
            for statement in current_policy.get(&#39;Statement&#39;, [])
        )
        
        if org_restriction_exists:
            logger.info(f&quot;Organization restriction already exists in bucket policy for: &#123;bucket_name&#125;&quot;)
            return None
            
        # If we get here, we need to add the org restriction
        # Ensure policy has basic structure
        if not current_policy:
            current_policy = &#123;
                &quot;Version&quot;: &quot;2012-10-17&quot;,
                &quot;Id&quot;: f&quot;SecurityPolicy-&#123;bucket_name&#125;&quot;,
                &quot;Statement&quot;: [org_deny_statement]
            &#125;
        else:
            # Ensure policy has correct version and ID
            current_policy[&#39;Version&#39;] = &quot;2012-10-17&quot;
            if &#39;Id&#39; not in current_policy:
                current_policy[&#39;Id&#39;] = f&quot;SecurityPolicy-&#123;bucket_name&#125;&quot;
            if &#39;Statement&#39; not in current_policy:
                current_policy[&#39;Statement&#39;] = []
            current_policy[&#39;Statement&#39;].append(org_deny_statement)
        
        # Check policy size before applying
        policy_size = len(json.dumps(current_policy))
        if policy_size &gt; 19456:  # 19K in bytes
            logger.error(f&quot;Bucket policy for &#123;bucket_name&#125; is too large (&#123;policy_size&#125; bytes). Cannot add org restriction.&quot;)
            return None
            
        # Apply updated policy
        response = s3_client.put_bucket_policy(
            Bucket=bucket_name,
            Policy=json.dumps(current_policy)
        )
        
        logger.info(f&quot;Successfully added organization restriction to bucket policy for: &#123;bucket_name&#125;&quot;)
        return response
        
    except Exception as e:
        logger.error(f&quot;Failed to update bucket policy for bucket &#123;bucket_name&#125;: &#123;str(e)&#125;&quot;)
        raise e

def lambda_handler(event, context):
    &quot;&quot;&quot;
    Main Lambda handler function
    &quot;&quot;&quot;
    try:
        logger.info(f&quot;Processing event: &#123;json.dumps(event)&#125;&quot;)
        
        # Extract account ID from the event
        if &#39;account&#39; in event:
            account_id = event[&#39;account&#39;]
        else:
            logger.error(&quot;Could not extract account ID from event&quot;)
            return &#123;
                &#39;statusCode&#39;: 400,
                &#39;body&#39;: json.dumps(&#39;Could not extract account ID from event&#39;)
            &#125;
        
        logger.info(f&quot;Extracted account ID: &#123;account_id&#125;&quot;)
        
        # Assume role in the target account
        try:
            credentials = assume_role(account_id)
            session = boto3.Session(**credentials)
        except Exception as e:
            logger.error(f&quot;Failed to create cross-account session: &#123;str(e)&#125;&quot;)
            return &#123;
                &#39;statusCode&#39;: 500,
                &#39;body&#39;: json.dumps(f&#39;Failed to create cross-account session: &#123;str(e)&#125;&#39;)
            &#125;
        
        # For EventBridge events, format is slightly different
        if &#39;detail&#39; in event:
            # Extract bucket name from CloudTrail event
            bucket_name = extract_bucket_name(event)
        else:
            # Direct S3 event format
            bucket_name = extract_bucket_name(&#123;&#39;detail&#39;: event&#125;)
        
        if not bucket_name:
            logger.error(&quot;Could not extract bucket name from event&quot;)
            return &#123;
                &#39;statusCode&#39;: 400,
                &#39;body&#39;: json.dumps(&#39;Could not extract bucket name from event&#39;)
            &#125;
        
        logger.info(f&quot;Extracted bucket name: &#123;bucket_name&#125;&quot;)
        
        # Get information about the bucket using the cross-account session
        bucket_info = get_bucket_info(bucket_name, session)
        logger.info(f&quot;Results: &#123;bucket_info&#125;&quot;)
        
        # Check if bucket has sensitive classification tag
        is_sensitive = any(
            tag.get(&#39;Key&#39;) == &#39;classification&#39; and tag.get(&#39;Value&#39;) == &#39;sensitive&#39;
            for tag in bucket_info.get(&#39;tags&#39;, [])
        )
        
        if is_sensitive:
            logger.info(f&quot;Bucket &#123;bucket_name&#125; is tagged as sensitive&quot;)
            
            # Check if BPA is already fully enabled
            current_bpa = bucket_info.get(&#39;block_public_access_bucket&#39;, &#123;&#125;)
            bpa_fully_enabled = all([
                current_bpa.get(&#39;BlockPublicAcls&#39;, False),
                current_bpa.get(&#39;IgnorePublicAcls&#39;, False),
                current_bpa.get(&#39;BlockPublicPolicy&#39;, False),
                current_bpa.get(&#39;RestrictPublicBuckets&#39;, False)
            ])
            
            if not bpa_fully_enabled:
                logger.info(f&quot;Enabling Block Public Access for sensitive bucket &#123;bucket_name&#125;&quot;)
                remediation_response = remediate_bpa(session, bucket_name)
                bucket_info[&#39;remediation_response&#39;] = remediation_response
            else:
                logger.info(f&quot;Block Public Access already fully enabled for bucket &#123;bucket_name&#125;&quot;)
                
            # Check if ACLs are enabled and disable them if they are
            if bucket_info.get(&#39;acls_enabled&#39;, True):
                logger.info(f&quot;Disabling ACLs for sensitive bucket &#123;bucket_name&#125;&quot;)
                acl_response = remediate_acls(session, bucket_name)
                bucket_info[&#39;acl_remediation_response&#39;] = acl_response
            else:
                logger.info(f&quot;ACLs already disabled for bucket &#123;bucket_name&#125;&quot;)
            
            # Add or update bucket policy with org restrictions
            current_policy = bucket_info.get(&#39;bucket_policy&#39;, &#123;&#125;) if bucket_info.get(&#39;has_bucket_policy&#39;) else &#123;&#125;
            policy_response = remediate_bucket_policy(session, bucket_name, current_policy)
            if policy_response:
                bucket_info[&#39;policy_remediation_response&#39;] = policy_response
        else:
            logger.info(f&quot;Bucket &#123;bucket_name&#125; is not tagged as sensitive&quot;)
            
        return &#123;
            &#39;statusCode&#39;: 200,
            &#39;body&#39;: json.dumps(bucket_info, default=str)
        &#125;
        
    except Exception as e:
        logger.error(f&quot;Error processing event: &#123;str(e)&#125;&quot;)
        return &#123;
            &#39;statusCode&#39;: 500,
            &#39;body&#39;: json.dumps(f&#39;Error: &#123;str(e)&#125;&#39;)
        &#125;</code></pre></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/f12bb93b-46aa-41ae-ad9e-9dd7be539e08/image.png?t=1744826605"/></div><p class="paragraph" style="text-align:left;">The code is pretty well commented. And yes, I used AI for a lot of it (I haz a day job, ya know!). To follow the logic, start in <b>lambda_handler;</b> you can see the flow and how we extract the account ID and the bucket name, then <b>assumerole</b> into the account, then pull the bucket data, then make any remediations if the tag is present.</p><p class="paragraph" style="text-align:left;">You can hit <b>Test</b> now if you want, but even if your bucket is tagged sensitive, not all the remediations will work yet, because we need to fix permissions.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/874b7ac8-e1c9-4a33-b2bc-ae29bcc87869/image.png?t=1744828921"/></div><p class="paragraph" style="text-align:left;">Let’s go fix those permissions! We’ll deploy a new StackSet with an updated template.</p><p class="paragraph" style="text-align:left;"><b>CloudFormation &gt; StackSets &gt; Service-managed &gt; AutoremediationRole: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px 5px 5px 5px;border-style:solid;border-width:2px 2px 2px 2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/62c6b952-196c-4492-a788-1e490a8279b2/image.png?t=1743632141"/></div><p class="paragraph" style="text-align:left;">The first thing we need is the OU ID where the stack is deployed. <b>Copy the OU ID and paste it into a text editor</b> (we’ll also need to copy the URL to the updated template in a second):</p><div class="image"><img alt="" class="image__image" style="border-radius:5px 5px 5px 5px;border-style:solid;border-width:2px 2px 2px 2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/58c3579f-5ad2-48ed-9d36-1a12a8b711fa/image.png?t=1743632409"/></div><p class="paragraph" style="text-align:left;">Then <b>Actions &gt; Edit StackSet details: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px 5px 5px 5px;border-style:solid;border-width:2px 2px 2px 2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/a2bd06ff-b846-4082-a054-bc6b00ed43f8/image.png?t=1743632474"/></div><p class="paragraph" style="text-align:left;">Select <b>Replace current template, </b>then under <b>Specify template, paste in this URL:</b> <a class="link" href="https://cloudslaw.s3-us-west-2.amazonaws.com/lab56.template?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=coding-security-autoremediations" target="_blank" rel="noopener noreferrer nofollow">https://cloudslaw.s3-us-west-2.amazonaws.com/lab56.template</a>, then click <b>Next:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/6fb225c7-e625-4ef2-88aa-48d2adc530ac/image.png?t=1744829227"/></div><p class="paragraph" style="text-align:left;">Click <b>Next on the next 2 screens,</b> then <b>Paste in the OU and select All regions (or just US East (Virginia)).</b> Then <b>Next &gt; Submit: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px 5px 5px 5px;border-style:solid;border-width:2px 2px 2px 2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/f4ab52d1-840a-4ccb-a020-7b95f492a1ef/image.png?t=1743633024"/></div><p class="paragraph" style="text-align:left;">It took me about 3 minutes for the template to update. As a reminder, this new template is <i>the same as the previous two, but with the new permissions added and some unneeded ones removed</i>. </p><p class="paragraph" style="text-align:left;">Now it’s time to test things out. We’ll do this in the <b>Production1</b> account, so we need to switch over to that. </p><p class="paragraph" style="text-align:left;">Start in your <b>Sign-in portal &gt; CloudSLAW &gt; AdministratorAccess.</b> We need to go back to our <b>Production1</b> account and change a tag on our bucket to a sample event to work with. (If you still have one in your email you can use that and skip this part). If you still have your account in your <b>Role history,</b> then just <b>click it.</b> If not here are the screenshots to switch roles (which is just <b>assumerole</b> from your <b>AdministratorAccess</b> role into the <b>OrganizationAccountAccessRole</b> in that account… yep, a simple role chain!).</p><div class="image"><img alt="" class="image__image" style="border-radius:5px 5px 5px 5px;border-style:solid;border-width:2px 2px 2px 2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/f04522de-1205-4b43-a764-aaea8f5b3649/image.png?t=1743628770"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px 5px 5px 5px;border-style:solid;border-width:2px 2px 2px 2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/d945b903-f3ed-4167-a712-2f6e3c7de5a3/image.png?t=1743628832"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px 5px 5px 5px;border-style:solid;border-width:2px 2px 2px 2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/1ec61a7e-c0cc-49d3-8d86-4ebd5cfeca33/image.png?t=1743628901"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px 5px 5px 5px;border-style:solid;border-width:2px 2px 2px 2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/dbbb7290-359c-4701-9a77-778285775b6b/image.png?t=1743628958"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px 5px 5px 5px;border-style:solid;border-width:2px 2px 2px 2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/7bd36ece-d6cb-4775-9773-e7b740525cfc/image.png?t=1743629050"/></div><p class="paragraph" style="text-align:left;">That was just a reminder in case you didn’t have the account in your Role history.</p><p class="paragraph" style="text-align:left;">We need to make two changes to ensure we can test the remediations, then we’ll change the tag on the instance to trigger the autoremediation and see it in action. I’m going to describe what to do and then show key screenshots — this should be easy to follow:</p><ul><li><p class="paragraph" style="text-align:left;"><b>Disable block public access</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ebb44e75-e559-4931-bd01-a501893d5fad/image.png?t=1744829604"/></div></li><li><p class="paragraph" style="text-align:left;"><b>Change Object Ownership to ACLs enabled</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/725aef54-d4d9-419a-86f7-7e92663ad6db/image.png?t=1744829647"/></div></li></ul><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/34d68798-a880-4ed3-a79c-56ce5cd7d328/image.png?t=1744829687"/></div><p class="paragraph" style="text-align:left;">Time to test! Go to <b>Properties &gt; Tags &gt; Edit &gt; add a tag with the key “classification” and value “sensitive”:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/c0c2b80a-9d44-4db4-9c4e-9489a56da1ad/image.png?t=1744829798"/></div><p class="paragraph" style="text-align:left;">Then go back to <b>Permissions </b>and refresh the page in about 15 seconds. You should now see that BPA is enabled, there’s a restrictive bucket policy, and ACLs are disabled!!!</p><p class="paragraph" style="text-align:left;"><b>I’m sorry, this is just really freaking cool! </b><i><b>You have just implemented an advanced, reusable pattern for security autoremediation which operates from a central account!</b></i></p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/4654e593-4003-4516-859e-b6c5951584dd/cf1.jpg?t=1744830094"/></div><h3 class="heading" style="text-align:left;" id="key-points">Lab Key Points</h3><ul><li><p class="paragraph" style="text-align:left;">This is cool</p></li><li><p class="paragraph" style="text-align:left;">I write too much sometimes</p></li></ul><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=d95730c3-a38b-4aaa-b745-95379dc79b70&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Role Chaining and Testing Your Code</title>
  <description>We set up for our role chain; now it&#39;s time to implement it in code, and learn how to test Lambda functions.</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/46450a51-9d68-4afb-9633-7882fad8fa7e/epic-7.png" length="704239" type="image/png"/>
  <link>https://slaw.securosis.com/p/role-chaining-and-testing-your-code</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/role-chaining-and-testing-your-code</guid>
  <pubDate>Thu, 03 Apr 2025 15:00:00 +0000</pubDate>
  <atom:published>2025-04-03T15:00:00Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b><a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=role-chaining-and-testing-your-code" target="_blank" rel="noopener noreferrer nofollow">Go Pro for Support and Extras!</a></b></p><p class="paragraph" style="text-align:left;">CloudSLAW is, and always will be, free. But to help cover costs and keep the content up to date we have an optional Patreon. For $10 per month you get access to our support Discord, office hours, exclusive subscriber content (not labs — those are free — just extras) and more. <a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=role-chaining-and-testing-your-code" target="_blank" rel="noopener noreferrer nofollow">Check it out!</a></p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;">This is part 7 of the Advanced Cloud Security Problem Solving series, which I’ve been abbreviating to Epic Automation. If you haven’t completed parts 1-6 yet, <span style="text-decoration:underline;"><i><a class="link" href="https://slaw.securosis.com/p/epic-cloud-security-automation-fixing-the-broken-rcp?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=role-chaining-and-testing-your-code" target="_blank" rel="noopener noreferrer nofollow" style="color: rgb(44, 129, 229)">jump back to the first post in the series</a></i></span> and complete all prior posts before trying this one. </p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Lesson</h1><p class="paragraph" style="text-align:left;">This lesson is all about the hands-on. We’ve covered the principles, so now it’s time to start implementing. We will focus on two mechanics:</p><ul><li><p class="paragraph" style="text-align:left;">How to implement a role chain in code</p></li><li><p class="paragraph" style="text-align:left;">How to test Lambda function in the console and work through error messages.</p></li></ul><p class="paragraph" style="text-align:left;">Let’s quickly cover each of these, and then get into the meat of this lab.</p><h2 class="heading" style="text-align:left;" id="role-chaining-in-code">Role Chaining in Code</h2><p class="paragraph" style="text-align:left;">As a reminder, a role chain is when you use one role to assume another role. In our last lab we learned how to <a class="link" href="https://slaw.securosis.com/p/secure-a-role-chain-on-both-sides?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=role-chaining-and-testing-your-code" target="_blank" rel="noopener noreferrer nofollow">define permissions to secure a role chain</a>. With that out of the way, everything is in place to assume a role. This isn’t the first time you role chained: in previous labs when we logged into IAM Identity Center and then used the little <i>Switch Role</i> link in the upper-right corner of the console, that was technically chaining from our Identity Center role into <b>OrganizationAccountAccessRole</b> in the target account. But stuff in the console is a bit magical and abstracted — now we’ll see the guts.</p><p class="paragraph" style="text-align:left;">The trick in code is that you are creating a <b>session.</b> You can code in multiple sessions, and then pick which to use when making an API call. Heck, you could set up 8 different sessions, go all Doc Oc, and do different things in different accounts simultaneously.</p><div class="section" style="background-color:#cfe3fa;border-color:#2C81E5;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:0.0px 0.0px 0.0px 0.0px;"><p class="paragraph" style="text-align:center;"><b>The TL;DR on how API calls in AWS actually work</b></p><p class="paragraph" style="text-align:left;">I use the term “session” a lot, and sometimes in the console and code it looks like there is some kind of secure pipe we make all our API calls through… but <b>that is not how it works!!!</b></p><p class="paragraph" style="text-align:left;">When AWS was created, SSL hardware that worked on the scale of even then-baby-AWS was very expensive and not sufficiently viable. To keep AWS API calls secure and private, Amazon just built all the encryption into the request itself. When you make an API call into AWS it uses something called <i>http request signing using Signature 4</i>. </p><p class="paragraph" style="text-align:left;">The API call itself is a self-contained encrypted blob using your access key and secret access key. <i>Think more “secure envelopes with encrypted letters going through the mail”, and less “encrypted phone calls”.</i></p></div><p class="paragraph" style="text-align:left;">We always need a base identity. In our case, that’s the default <b>lambda execution role.</b> If we assign a role to a lambda function, the lambda service just assumes it for us when our function runs. Any API call that doesn’t specify another session uses the default lambda execution role.</p><p class="paragraph" style="text-align:left;">After that, we need to manually assume roles in our code. <i>Assuming a role retrieves temporary credentials used to make API calls</i>. </p><p class="paragraph" style="text-align:left;">Here’s the function in our code to assume a role in another account. Since we use this to assume roles in multiple accounts, but the role name is always the same, this little lambda function just needs the Account ID of the account to jump into; it already knows the role name. <i>Boto3 is the name of the python library for AWS — don’t let that confuse you!</i></p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/3d40dead-cf2f-40fa-8ce8-6a9e6969be83/image.png?t=1743548603"/></div><p class="paragraph" style="text-align:left;">It’s doing the following:</p><ul><li><p class="paragraph" style="text-align:left;">Get an account ID to use when running,</p></li><li><p class="paragraph" style="text-align:left;">Create the full “role_arn”: the ARN of the role we will assume, including the account ID and path/name.</p></li><li><p class="paragraph" style="text-align:left;">Start up a connection (boto3.client) to the security token service (which we use to assume roles).</p></li><li><p class="paragraph" style="text-align:left;">Assume the target role (role_arn) and sets a session name as <b>S3SecurityAnalysis.</b> You can code in whatever session name you want, which shows up in the CloudTrail logs. </p></li><li><p class="paragraph" style="text-align:left;"><b>sts.assumerole</b> returns some stuff; we pull out the temporary credentials (Access Key, Secret Access Key, and Session Token). </p></li><li><p class="paragraph" style="text-align:left;">The function returns those credentials.</p></li></ul><p class="paragraph" style="text-align:left;">That’s the key point: <i>assuming a role retrieves temporary credentials, which we use to make API calls; we still have credentials, they are just ephemeral.</i></p><p class="paragraph" style="text-align:left;">Here’s where it gets a bit tricky. This process gives us temporary credentials, but we need to create the <b>session.</b> What’s that? Basically it’s a container (object) in the code which holds the credentials, region, etc. for the actual API calls. This function doesn’t reconnect with AWS — it just says, “Create a session object that stores the credentials which I can use for API calls.” Remember that a “session” on the AWS side is just “a set of credentials which expire”, not some persistent pipe. </p><p class="paragraph" style="text-align:left;">The “session” object in <b>boto3</b> is just the container, so you don’t need to specify the keys and configuration every time you make an actual API call.</p><div class="codeblock"><pre><code># Assume role in the target account
        try:
            credentials = assume_role(account_id)
            session = boto3.Session(**credentials)
        except Exception as e:
            logger.error(f&quot;Failed to create cross-account session: &#123;str(e)&#125;&quot;)
            return &#123;
                &#39;statusCode&#39;: 500,
                &#39;body&#39;: json.dumps(f&#39;Failed to create cross-account session: &#123;str(e)&#125;&#39;)
            &#125;</code></pre></div><p class="paragraph" style="text-align:left;">Now we have our credentials in a little object we can use for API calls with lines like <span style="color:#006a36;">s3_client = session.client(&#39;s3&#39;)</span> — which sets us up to talk to the S3 service. The <b>boto3</b> Software Development Kit (SDK) converts a command like <span style="color:#006a36;">s3_client.get_bucket_policy(Bucket=bucket_name)</span> into an encrypted request for the bucket policy, packages it up, and sends it to the S3 service. </p><p class="paragraph" style="text-align:left;">Phew. This is one of those situations where I don’t want to overcomplicate or oversimplify. It’s a very elegant system — especially once you go deep into the weeds to see how AWS processes all these events. There is crazy stuff like service, region, and even daily keys under the covers. Let’s just say these requests aren’t just decrypted and shared on the back end of AWS… everything is really locked down at every level.</p><p class="paragraph" style="text-align:left;">Okay, one last attempt to keep it simple:</p><ul><li><p class="paragraph" style="text-align:left;">You start with a base identity — otherwise you can’t even talk to the Security Token Service (STS) to try assuming a role.</p></li><li><p class="paragraph" style="text-align:left;">You make the <b>assumerole</b> API call, which returns a set of temporary credentials you can use for a session (until they expire). </p></li><li><p class="paragraph" style="text-align:left;">The <b>boto3.session</b> API call creates a reusable object in code which holds those credentials and makes it easier to use them. (Among other things, when they expire we only need to update the session object once, instead of in all the clients).</p></li><li><p class="paragraph" style="text-align:left;"><b>session.client(service)</b> sets us up to make an API call to a service. The API call itself gets wrapped up as a secure letter, using the keys retrieved with <b>assumerole.</b></p></li></ul><p class="paragraph" style="text-align:left;">In our code we only have one session, so we call it “session”. I’ve written code where I need to juggle multiple sessions to multiple accounts, and I just name the session so I can track which one I’m using. E.g. “session_logs”, “session_database”, “session_breakthings”.</p><h2 class="heading" style="text-align:left;" id="testing-lambda-code">Testing Lambda Code</h2><p class="paragraph" style="text-align:left;">Wow, this is already longer than I planned.</p><p class="paragraph" style="text-align:left;">Okay… to test a lambda there are the “I’m a real developer” options, and the “I’m a hacker/security person who just slaps stuff together” options.</p><p class="paragraph" style="text-align:left;">We’ll go with the latter. Duh.</p><p class="paragraph" style="text-align:left;">If you are doing full-time and big project dev work, you probably want to use a local testing framework like LocalStack or AWS SAM CLI. For our purposes we will run tests in the console with a built-in capability.</p><p class="paragraph" style="text-align:left;">Last lab we made changes which triggered our function, and then we looked in the logs to see what broke. That is inefficient, and testing in the console with test events is a much better option. So, yeah, we’ll do that.</p><h3 class="heading" style="text-align:left;" id="todays-key-points-are">Key Lesson Points</h3><ul><li><p class="paragraph" style="text-align:left;">When you assume a role, that asks the <b>Security Token Service</b> to give you a set of temporary credentials which work for a set time — we call that a session.</p></li><li><p class="paragraph" style="text-align:left;">In code, you need to specify those credentials for those API calls.</p></li><li><p class="paragraph" style="text-align:left;">To make this easier, the SDKs for different programming languages allow you to create an object called a “session” which holds those credentials, so you don’t need to specify them every time.</p><ul><li><p class="paragraph" style="text-align:left;">The Python library is called <b>Boto3.</b> No, I don’t know why. Yes, I could ask one of my AIs to figure it out. No, I’m not going to.</p></li></ul></li><li><p class="paragraph" style="text-align:left;">You can test lambda functions offline using special tools, but you can also load a test event into the console and use the feature there.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lab"><b>The Lab</b></h1><p class="paragraph" style="text-align:left;">We will:</p><ol start="1"><li><p class="paragraph" style="text-align:left;">Generate an event in our <b>Production1</b> account, which we can use as a test event for our lambda function.</p></li><li><p class="paragraph" style="text-align:left;">Create the test event in the console.</p></li><li><p class="paragraph" style="text-align:left;">Update our function with the new code Rich wrote, which includes the role chain and some API calls to gather information about the S3 bucket. Rich promises he wrote it all himself and didn’t use AI. (Rich lies).</p></li><li><p class="paragraph" style="text-align:left;">Run the test, and then fix 2 problems with the code.</p></li></ol><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/p-UB41wsFOM" width="100%"></iframe><h2 class="heading" style="text-align:left;" id="stepby-step">Step-by-Step</h2><p class="paragraph" style="text-align:left;">Start in your <b>Sign-in portal &gt; CloudSLAW &gt; AdministratorAccess.</b> We need to go back to our <b>Production1</b> account and change a tag on our bucket to a sample event to work with. (If you still have one in your email you can use that, and skip this part). If you still have your account in your <b>Role history</b> then just <b>click it.</b> If not, here are the screenshots to switch roles (which is just <b>assumerole</b> from your <b>AdministratorAccess</b> role into the <b>OrganizationAccountAccessRole</b> in that account… yep, a simple role chain!).</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/f04522de-1205-4b43-a764-aaea8f5b3649/image.png?t=1743628770"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/d945b903-f3ed-4167-a712-2f6e3c7de5a3/image.png?t=1743628832"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/1ec61a7e-c0cc-49d3-8d86-4ebd5cfeca33/image.png?t=1743628901"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/dbbb7290-359c-4701-9a77-778285775b6b/image.png?t=1743628958"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/7bd36ece-d6cb-4775-9773-e7b740525cfc/image.png?t=1743629050"/></div><p class="paragraph" style="text-align:left;">That was just a reminder in case you didn’t have the account in your Role history.</p><p class="paragraph" style="text-align:left;">Okay, let’s <b>go into S3 and change a tag on our bucket.</b> Since we’ve done this already I’ll just shortcut it, showing the main screenshots in order. I mean, you’ve been with me over a year at this point, so I have a lot of confidence in you. 😀 </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/1b37b93d-4524-4427-94af-8ced68bb3c94/image.png?t=1743629332"/></div><p class="paragraph" style="text-align:left;">After you <b>Save changes,</b> go and <b>close the tab</b>. Then <b>check your email for the event</b>. Remember we left the EventBridge rule in place to email you events? It’s almost like I planned some things for once!!! Take that event, copy it, and paste it into a text editor (we won’t edit it, but we need a copy we can get easily). Also, <b>only copy the JSON text!</b> Don’t leave any spaces or anything:</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/5d416a2a-80c7-4db2-936e-0983314fe8ca/image.png?t=1743629603"/></div><p class="paragraph" style="text-align:left;">Now let’s go into our Lambda function and update the code. <b>Sign-in portal &gt; SecurityOperations&gt; AdministratorAccess &gt; Lambda &gt; security-auto-s3</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/981e3f48-fb8b-460e-999a-afb9d1b8b586/image.png?t=1743629968"/></div><p class="paragraph" style="text-align:left;">Click <b>Test,</b> then name it <b>S3Test,</b> and <b>paste in your test event</b>. To make sure it’s a proper event click <b>Format JSON</b> — it’s easy to make mistakes like missing a character, and this helps catch those. Then <b>Save.</b></p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/a297daba-8977-4ad5-b4ea-9c0eedb7fc53/image.png?t=1743630087"/></div><p class="paragraph" style="text-align:left;">We need to swap in our new code. Although I focused on role chaining in the Lesson section, here’s what it all does:</p><ol start="1"><li><p class="paragraph" style="text-align:left;">It pulls the account ID from the event, so it knows which account generated the event.</p></li><li><p class="paragraph" style="text-align:left;">It pulls the bucket ARN from the event, so it knows which bucket changed.</p></li><li><p class="paragraph" style="text-align:left;">In assumes the <b>SecurityAutoremediation</b> role in the identified target account.</p></li><li><p class="paragraph" style="text-align:left;">It gathers some information about the S3 bucket, which we will use later to analyze the security posture.</p></li></ol><p class="paragraph" style="text-align:left;">Click <b>Code.</b> Here’s our new code. <b>Copy this and paste it over the existing code &gt; Deploy:</b></p><div class="codeblock"><pre><code>import json
import boto3
import re
import logging
from botocore.exceptions import ClientError

# Set up logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def assume_role(account_id):
    &quot;&quot;&quot;
    Assume the SecurityAutoremediation role in the target account
    &quot;&quot;&quot;
    role_arn = f&quot;arn:aws:iam::&#123;account_id&#125;:role/SecurityOperations/SecurityAutoremediation&quot;
    logger.info(f&quot;Assuming role: &#123;role_arn&#125;&quot;)
    
    sts_client = boto3.client(&#39;sts&#39;)
    
    try:
        response = sts_client.assume_role(
            RoleArn=role_arn,
            RoleSessionName=&quot;S3SecurityAnalysis&quot;
        )
        
        credentials = response[&#39;Credentials&#39;]
        
        return &#123;
            &#39;aws_access_key_id&#39;: credentials[&#39;AccessKeyId&#39;],
            &#39;aws_secret_access_key&#39;: credentials[&#39;SecretAccessKey&#39;],
            &#39;aws_session_token&#39;: credentials[&#39;SessionToken&#39;]
        &#125;
    except Exception as e:
        logger.error(f&quot;Failed to assume role &#123;role_arn&#125;: &#123;str(e)&#125;&quot;)
        raise

def extract_bucket_name(event):
    &quot;&quot;&quot;
    Extract S3 bucket name from event by checking multiple possible locations
    &quot;&quot;&quot;
    # Check in resources array
    if &#39;resources&#39; in event[&#39;detail&#39;] and event[&#39;detail&#39;][&#39;resources&#39;]:
        for resource in event[&#39;detail&#39;][&#39;resources&#39;]:
            if resource.get(&#39;type&#39;) == &#39;AWS::S3::Bucket&#39; and resource.get(&#39;ARN&#39;):
                arn = resource.get(&#39;ARN&#39;)
                bucket_match = re.search(r&#39;arn:aws:s3:::([^/]+)&#39;, arn)
                if bucket_match:
                    return bucket_match.group(1)
    
    # Check in requestParameters
    if &#39;requestParameters&#39; in event[&#39;detail&#39;]:
        req_params = event[&#39;detail&#39;][&#39;requestParameters&#39;]
        if req_params and &#39;bucketName&#39; in req_params:
            return req_params[&#39;bucketName&#39;]
    
    # Check in responseElements
    if &#39;responseElements&#39; in event[&#39;detail&#39;] and event[&#39;detail&#39;][&#39;responseElements&#39;]:
        resp_elements = event[&#39;detail&#39;][&#39;responseElements&#39;]
        # Various response element formats
        if &#39;x-amz-bucket-region&#39; in resp_elements:
            bucket_match = re.search(r&#39;arn:aws:s3:::([^/]+)&#39;, 
                                    str(resp_elements))
            if bucket_match:
                return bucket_match.group(1)
    
    return None

def get_bucket_info(bucket_name, session):
    &quot;&quot;&quot;
    Get comprehensive information about an S3 bucket using the provided session
    &quot;&quot;&quot;
    s3_client = session.client(&#39;s3&#39;)
    sts_client = session.client(&#39;sts&#39;)
    
    # Get the account ID from the assumed role session
    account_id = sts_client.get_caller_identity().get(&#39;Account&#39;)
    
    result = &#123;
        &#39;bucket_name&#39;: bucket_name,
        &#39;arn&#39;: f&#39;arn:aws:s3:::&#123;bucket_name&#125;&#39;,
        &#39;account_id&#39;: account_id,
        &#39;tags&#39;: None,
        &#39;has_bucket_policy&#39;: False,
        &#39;bucket_policy&#39;: None,
        &#39;acls_enabled&#39;: True,  # Default to True, will check if acl is &quot;private&quot;
        &#39;acls&#39;: None,
        &#39;block_public_access_bucket&#39;: None
    &#125;
    
    # Get bucket tags
    try:
        tags_response = s3_client.get_bucket_tagging(Bucket=bucket_name)
        result[&#39;tags&#39;] = tags_response.get(&#39;TagSet&#39;, [])
    except ClientError as e:
        if e.response[&#39;Error&#39;][&#39;Code&#39;] == &#39;NoSuchTagSet&#39;:
            result[&#39;tags&#39;] = []
        else:
            logger.warning(f&quot;Error getting bucket tags: &#123;str(e)&#125;&quot;)
    
    # Get bucket policy
    try:
        policy_response = s3_client.get_bucket_policy(Bucket=bucket_name)
        result[&#39;has_bucket_policy&#39;] = True
        result[&#39;bucket_policy&#39;] = json.loads(policy_response.get(&#39;Policy&#39;, &#39;&#123;&#125;&#39;))
    except ClientError as e:
        if e.response[&#39;Error&#39;][&#39;Code&#39;] == &#39;NoSuchBucketPolicy&#39;:
            result[&#39;has_bucket_policy&#39;] = False
        else:
            logger.warning(f&quot;Error getting bucket policy: &#123;str(e)&#125;&quot;)
    
    # Get bucket ACLs and determine if ACLs are enabled
    try:
        acl_response = s3_client.get_bucket_acl(Bucket=bucket_name)
        result[&#39;acls&#39;] = acl_response.get(&#39;Grants&#39;, [])
        
        # Check bucket ownership controls to determine if ACLs are enabled
        try:
            ownership = s3_client.get_bucket_ownership_controls(Bucket=bucket_name)
            rules = ownership.get(&#39;OwnershipControls&#39;, &#123;&#125;).get(&#39;Rules&#39;, [])
            for rule in rules:
                if rule.get(&#39;ObjectOwnership&#39;) == &#39;BucketOwnerEnforced&#39;:
                    result[&#39;acls_enabled&#39;] = False
                    break
        except ClientError as e:
            if e.response[&#39;Error&#39;][&#39;Code&#39;] != &#39;OwnershipControlsNotFoundError&#39;:
                logger.warning(f&quot;Error getting bucket ownership: &#123;str(e)&#125;&quot;)
    except ClientError as e:
        logger.warning(f&quot;Error getting bucket ACL: &#123;str(e)&#125;&quot;)
    
    # Get block public access settings for bucket
    try:
        bpa_response = s3_client.get_public_access_block(Bucket=bucket_name)
        result[&#39;block_public_access_bucket&#39;] = bpa_response.get(&#39;PublicAccessBlockConfiguration&#39;, &#123;&#125;)
    except ClientError as e:
        if e.response[&#39;Error&#39;][&#39;Code&#39;] != &#39;NoSuchPublicAccessBlockConfiguration&#39;:
            logger.warning(f&quot;Error getting bucket public access block: &#123;str(e)&#125;&quot;)
    
    return result

def lambda_handler(event, context):
    &quot;&quot;&quot;
    Main Lambda handler function
    &quot;&quot;&quot;
    try:
        logger.info(f&quot;Processing event: &#123;json.dumps(event)&#125;&quot;)
        
        # Extract account ID from the event
        if &#39;account&#39; in event:
            account_id = event[&#39;account&#39;]
        else:
            logger.error(&quot;Could not extract account ID from event&quot;)
            return &#123;
                &#39;statusCode&#39;: 400,
                &#39;body&#39;: json.dumps(&#39;Could not extract account ID from event&#39;)
            &#125;
        
        logger.info(f&quot;Extracted account ID: &#123;account_id&#125;&quot;)
        
        # Assume role in the target account
        try:
            credentials = assume_role(account_id)
            session = boto3.Session(**credentials)
        except Exception as e:
            logger.error(f&quot;Failed to create cross-account session: &#123;str(e)&#125;&quot;)
            return &#123;
                &#39;statusCode&#39;: 500,
                &#39;body&#39;: json.dumps(f&#39;Failed to create cross-account session: &#123;str(e)&#125;&#39;)
            &#125;
        
        # For EventBridge events, format is slightly different
        if &#39;detail&#39; in event:
            # Extract bucket name from CloudTrail event
            bucket_name = extract_bucket_name(event)
        else:
            # Direct S3 event format
            bucket_name = extract_bucket_name(&#123;&#39;detail&#39;: event&#125;)
        
        if not bucket_name:
            logger.error(&quot;Could not extract bucket name from event&quot;)
            return &#123;
                &#39;statusCode&#39;: 400,
                &#39;body&#39;: json.dumps(&#39;Could not extract bucket name from event&#39;)
            &#125;
        
        logger.info(f&quot;Extracted bucket name: &#123;bucket_name&#125;&quot;)
        
        # Get information about the bucket using the cross-account session
        bucket_info = get_bucket_info(bucket_name, session)
        logger.info(f&quot;Results: &#123;bucket_info&#125;&quot;)
        
        return &#123;
            &#39;statusCode&#39;: 200,
            &#39;body&#39;: json.dumps(bucket_info, default=str)
        &#125;
        
    except Exception as e:
        logger.error(f&quot;Error processing event: &#123;str(e)&#125;&quot;)
        return &#123;
            &#39;statusCode&#39;: 500,
            &#39;body&#39;: json.dumps(f&#39;Error: &#123;str(e)&#125;&#39;)
        &#125;</code></pre></div><p class="paragraph" style="text-align:left;">If you are a Pro subscriber, I’ll be holding a code review session after we finish these labs — and if you are reading this a year later, check out the recorded video in the library.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/5173d796-1b40-4160-aa43-2aa565a2e6d7/image.png?t=1743630550"/></div><p class="paragraph" style="text-align:left;">Alrighty, click <b>Test.</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ad01e853-5e0a-4ca9-ab86-3777054e2cf6/image.png?t=1743630673"/></div><p class="paragraph" style="text-align:left;">And it should fail. 😦 </p><p class="paragraph" style="text-align:left;">Okay, it was a timeout. Easy enough to fix! Jump off the Code tab to <b>Configuration</b> <b>&gt; General Configuration &gt; Edit &gt; set the timeout to 30 seconds:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/e700dd69-11bb-4652-8a0b-9a3e3e13b072/image.png?t=1743631012"/></div><p class="paragraph" style="text-align:left;">Here’s where things get a bit tricky. As a heads up, there is a lot of error-handling built into the code. So errors are managed gracefully and the function will run completely, but some errors will only show up in the logs, since they were handled. I deliberately left a common error in the code since — one you will hit a <b>lot</b> over the years. <b>Click Test again, then scroll through the Output:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/12d88bbc-7510-47c1-9532-13509438840e/image.png?t=1743631258"/></div><p class="paragraph" style="text-align:left;">Here’s what happened. As far as the console is concerned, our function works because <i>the internal error handling caught and handled the error</i>. The problem is… we still have an error. In this case we are missing the permission (in the target account) to check the <b>BucketOwnershipControls,</b> which are important to know whether ACLs are enabled (<a class="link" href="https://slaw.securosis.com/p/accidentally-expose-all-your-stuff-on-s3-with-acls?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=role-chaining-and-testing-your-code" target="_blank" rel="noopener noreferrer nofollow">we talked about that a long time ago</a>). </p><p class="paragraph" style="text-align:left;">To fix this, we need to change permissions in the account with that S3 bucket. How? Come on, you got this… <b>StackSets!</b> We just need to update our StackSet to add that permission! In a real enterprise this would be handled using a CI/CD pipeline, but it isn’t hard for us to make the change in the console. And one big difference: since people run through these labs at their own pace, and we need this error for this lab, we will use a new template instead of fixing the old one. </p><p class="paragraph" style="text-align:left;">Personally, this is where I like to open up another tab, so I can keep the function open to run the test again. If you <b>right-click the AWS icon</b> in the upper-right corner, then <b>Open link in new tab,</b> then <b>CloudFormation &gt; StackSets &gt; Service-managed &gt; AutoremediationRole: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/55b6e8fc-09a5-4333-9533-4492b554d28b/image.png?t=1743632076"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/62c6b952-196c-4492-a788-1e490a8279b2/image.png?t=1743632141"/></div><p class="paragraph" style="text-align:left;">The first thing we need is the OU ID where the stack is deployed. <b>Copy the OU ID and paste it into a text editor</b> (since we also need to copy the URL to the updated template in a second):</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/58c3579f-5ad2-48ed-9d36-1a12a8b711fa/image.png?t=1743632409"/></div><p class="paragraph" style="text-align:left;">Then <b>Actions &gt; Edit StackSet details: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/a2bd06ff-b846-4082-a054-bc6b00ed43f8/image.png?t=1743632474"/></div><p class="paragraph" style="text-align:left;">Select <b>Replace current template, </b>then under <b>Specify template paste in this URL:</b> <a class="link" href="https://cloudslaw.s3-us-west-2.amazonaws.com/lab55.template?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=role-chaining-and-testing-your-code" target="_blank" rel="noopener noreferrer nofollow">https://cloudslaw.s3-us-west-2.amazonaws.com/lab55.template</a>, then click <b>Next:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/269cf6cc-5911-4c7c-ac28-8d5f10581e2d/image.png?t=1743632608"/></div><p class="paragraph" style="text-align:left;">Click <b>Next on the next 2 screens,</b> then <b>Paste in the OU and select All regions (or just US East (Virginia).</b> Then <b>Next &gt; Submit: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/f4ab52d1-840a-4ccb-a020-7b95f492a1ef/image.png?t=1743633024"/></div><p class="paragraph" style="text-align:left;">It took me about 3 minutes for the template to update. As a reminder, this new template is <i>exactly the same as the previous one, with just the new permission added</i>. </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/17ac48b7-9cf8-4bcf-a6cc-4dec594df17f/image.png?t=1743633204"/></div><p class="paragraph" style="text-align:left;"><b>Go back to your lambda function &gt; Test &gt; PROFIT!</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ac1b2aba-fd54-4a56-8b31-ad415b254293/image.png?t=1743633273"/></div><p class="paragraph" style="text-align:left;">No more error messages, and the <b>results</b> have all the information we need to make a security decision!</p><p class="paragraph" style="text-align:left;">We are getting close to the end of this series, so the next few labs will be heavy on code. We still need to analyze the state of the buckets and, if needed, lock them down so they can only be accessed from our Organization. We also need to set up our time-based sweep to analyze all buckets, which is very tricky from a performance standpoint. </p><p class="paragraph" style="text-align:left;">I appreciate you sticking with me on this extended series. By the end you should have all the tools you need to write your own enterprise-scale cloud security automation. This is absolutely an advanced skill!</p><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=28fdbf9a-bd01-413a-b88c-eca9cceb5a1e&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Secure a Role Chain on Both Sides</title>
  <description>In this lab we&#39;ll learn how to tightly control our autoremediation role to prevent abuse, and use StackSets to push it out to only where we need it.</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/f288b371-6c0d-43f5-acf3-17c15844e3cf/epic-6.png" length="702570" type="image/png"/>
  <link>https://slaw.securosis.com/p/secure-a-role-chain-on-both-sides</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/secure-a-role-chain-on-both-sides</guid>
  <pubDate>Thu, 20 Mar 2025 15:00:00 +0000</pubDate>
  <atom:published>2025-03-20T15:00:00Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><b><a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=secure-a-role-chain-on-both-sides" target="_blank" rel="noopener noreferrer nofollow">Go Pro for Support and Extras!</a></b></p><p class="paragraph" style="text-align:left;">CloudSLAW is, and always will be, free. But to help cover costs and keep the content up to date we have an optional Patreon. For $10 per month you get access to our support Discord, office hours, exclusive subscriber content (not labs — those are free — just extras) and more. <a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=secure-a-role-chain-on-both-sides" target="_blank" rel="noopener noreferrer nofollow">Check it out!</a></p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;">This is part 6 of the Advanced Cloud Security Problem Solving series, which I’ve been abbreviating to Epic Automation. If you haven’t done parts 1-5 yet, <span style="text-decoration:underline;"><i><a class="link" href="https://slaw.securosis.com/p/epic-cloud-security-automation-fixing-the-broken-rcp?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=secure-a-role-chain-on-both-sides" target="_blank" rel="noopener noreferrer nofollow" style="color: rgb(44, 129, 229)">jump back to the first post in the series</a></i></span> and complete all prior posts before trying this one. </p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Lesson</h1><p class="paragraph" style="text-align:left;">I’m one of those people who, as I get older, definitely suffers the consequences of not sticking to the good habits everyone told us to start when we were younger.</p><p class="paragraph" style="text-align:left;">Yes, I’m talking about flossing.</p><p class="paragraph" style="text-align:left;">You see, the tap water where I grew up was very good and highly fluoridated. I’d never had a single cavity when I started college. TL;DR, I paid the price later in life. You haven’t lived until you’ve been on a work trip to some remote client location, and needed to get an emergency crown thanks to a protein bar.</p><p class="paragraph" style="text-align:left;">And let’s stop pretending that any of you are thinking this analogy is about anything other than IAM. </p><p class="paragraph" style="text-align:left;">Way back in <a class="link" href="https://slaw.securosis.com/p/assume-role-centralized-logging-part-1?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=secure-a-role-chain-on-both-sides" target="_blank" rel="noopener noreferrer nofollow">Assume the Role!</a> we learned about IAM roles and cross-account access. In this series we have already <a class="link" href="https://slaw.securosis.com/p/assume-role-centralized-logging-part-1?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=secure-a-role-chain-on-both-sides" target="_blank" rel="noopener noreferrer nofollow">created a centralized role</a> for our cross-account EventBridge. But I kinda glossed over a couple of details, one of which is both important and often missed when setting up a centralized service, like we are.</p><p class="paragraph" style="text-align:left;">Let’s review the diagram from that first role assumption lab:</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/2d3449bb-ea99-415d-9694-d47af939a98f/image.png?t=1742255425"/></div><p class="paragraph" style="text-align:left;">As a reminder, in that case an IAM user had permission to assume a role in another account. The IAM user needed the <b>sts:AssumeRole</b> permission in their IAM policy, and the remote account needed a Role Trust Policy to allow that IAM user to assume it. <b>You need permission to assume roles, and the specific role needs to allow the identity to assume it.</b><i> </i>You need permissions <b>on both sides.</b> </p><p class="paragraph" style="text-align:left;">Because of how we’ve done most things in the console, these permissions have often been defaulted for us. They have also been a little over-provisioned in some cases. For example we allow <b>sts:AssumeRole</b> but don’t restrict it to only assuming a single role.</p><p class="paragraph" style="text-align:left;">Now it’s time to take things a bit further, especially since we are enabling code (our lambda function) to change things across multiple accounts. There are some interesting things we can do to lock things down tight, and limit the potential for privilege escalation. </p><h2 class="heading" style="text-align:left;" id="locking-down-both-sides-of-a-role-c">Locking down both sides of a role chain</h2><p class="paragraph" style="text-align:left;">To enhance security we will add conditions on both sides of the relationship:</p><ul><li><p class="paragraph" style="text-align:left;">Only our designated lambda function will be able to assume the lambda role. Other functions can’t use it (the default when using the console and in most examples).</p></li><li><p class="paragraph" style="text-align:left;">Our lambda role will only be allowed to assume target roles which have a specific path we will consistently use in every account. (With a wildcard for the account ID).</p></li><li><p class="paragraph" style="text-align:left;">Our lambda role will only be allowed to assume those roles in the specific OU.</p></li><li><p class="paragraph" style="text-align:left;">Our target roles in each account will only allow the centralized lambda role to assume them.</p></li></ul><p class="paragraph" style="text-align:left;">Again, we’ve seen hints of how this works, but today is the day to call out the details. It <b>absolutely</b> takes more time and a bit more knowledge to set up. But it means the privileged role in a bunch of accounts, with permissions to change things, can’t be used by anything other than the expected lambda function, and that function can’t be abused to assume any role other than the intended one. </p><p class="paragraph" style="text-align:left;">Even I often get my neurons tangled up as I work through where I need to put all the right pieces, so here’s a simple way to keep things straight in your head when having roles assume other roles (we call this role chaining — and yes, this also applies to other principals assuming roles, but I want to keep it simple today):</p><p class="paragraph" style="text-align:left;">To do anything, your lambda function first needs its “lambda execution role” which is the role that allows it to do anything. The <i>lambda service</i> performs an <b>AssumeRole</b> for you. Then, for an entity with a role in one account to do things in another account, it needs to assume a role in that other account.</p><p class="paragraph" style="text-align:left;"><i><b>This process of using one role to assume another role is called “role chaining”, and we use it a lot!</b></i></p><p class="paragraph" style="text-align:left;">In terms of what it can do, remember that all IAM <i>permissions policies</i> live in the <i>account where the actions are happening</i>. You can’t just do something like “PutBucketPolicy” from a central account, because permissions policies are attached to the IAM principals, and that principal <i>doesn’t exist in the target account</i> so there is no way to define what it can and can’t do. Your role chains, but your permissions are always limited to whatever is at the <b>end</b> of the chain. This diagram might help you visualize it:</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/215d15bc-b58e-4583-abac-179a7382ae8e/image.png?t=1741995170"/></div><p class="paragraph" style="text-align:left;">Or not — I’m no graphic designer.</p><p class="paragraph" style="text-align:left;">Remember that <b>AssumeRole</b> is a way of creating a session, and the <b>AssumeRole</b> API call returns temporary credentials: an <b>Access Key,</b><i> a </i><b>Secret Access Key,</b> and a <b>Security Token.</b> When you role chain you are gathering these at every step, and your permissions are limited by which permissions are associated with whatever set of credentials you use to make an API call. Obviously those only work in the account where that role lives, <i>and you </i><b><i>never</i></b><i> inherit permissions from anywhere else on the chain!</i> </p><p class="paragraph" style="text-align:left;">Honestly this is easier to see when you are coding, since in code we specify the session used to make the API calls.</p><p class="paragraph" style="text-align:left;">It’s easy to get confused because you need <b>sts:AssumeRole</b> in the <i>permissions policy</i> of the role which wants to assume another role. And we also need <b>sts:AssumeRole</b> in the <i>trust policy</i> of the role being assumed. </p><p class="paragraph" style="text-align:left;"><b>It’s not </b><i><b>that</b></i><b> hard when you think about it: “I need permission to assume roles” and “I give you permission to assume me”.</b></p><p class="paragraph" style="text-align:left;">What’s cool is that we can put restrictions on that source role’s permission policy to assume roles, limiting which roles it can assume, <b>and</b> we can put restrictions on the target role’s trust policy so that only a specific source role can assume it. Both sides are locked down so no one else can shove their way into the relationship. I could have used that in college.</p><p class="paragraph" style="text-align:left;">We will also use the role trust policy to restrict the lambda service so it can only assume this role for our <b>security-auto-s3 lambda</b> function. This ensures that no other lambda functions can use this role to muck around with S3 in other accounts.</p><p class="paragraph" style="text-align:left;">Let’s see what these policies look like. To start, here’s the permissions policy for our source role. </p><div class="codeblock"><pre><code>&#123;
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    &#123;
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: &quot;logs:CreateLogGroup&quot;,
      &quot;Resource&quot;: &quot;arn:aws:logs:us-west-2:730335263677:*&quot;
    &#125;,
    &#123;
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: [
        &quot;logs:CreateLogStream&quot;,
        &quot;logs:PutLogEvents&quot;
      ],
      &quot;Resource&quot;: [
        &quot;arn:aws:logs:us-west-2:730335263677:log-group:/aws/lambda/security-auto-S3:*&quot;
      ]
    &#125;,
    &#123;
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: &quot;sts:AssumeRole&quot;,
      &quot;Resource&quot;: &quot;arn:aws:iam::*:role/SecurityOperations/SecurityAutoremediation&quot;,
      &quot;Condition&quot;: &#123;
        &quot;ForAnyValue:StringLike&quot;: &#123;
          &quot;aws:ResourceOrgPaths&quot;: &quot;&lt;your org path&gt;/*&quot;
        &#125;
      &#125;
    &#125;
  ]
&#125;</code></pre></div><p class="paragraph" style="text-align:left;">The top part is what we started with last week, and just says the lambda function can write its logs to CloudWatch.</p><p class="paragraph" style="text-align:left;">The meat is the <b>sts:AssumeRole</b> part.<b> </b>This says the role can <b>only</b> assume a role named <b>/SecurityOperations/SecurityAutoremediation</b> (as a reminder using paths keeps things cleaner, and we will use that later when we protect this role with an SCP). But it <b>can</b> assume that role in any account where that role exists, thanks to “*” in the <b>Resource </b>path. </p><p class="paragraph" style="text-align:left;">Not only can it only assume that role, but notice that extra condition with <b>ForAnyValue:StringLike</b>? That’s to further restrict it so it can only assume that role in our <b>Workloads</b> Organizational Unit! Even if someone created that role in, say, our Security OU, they still couldn’t assume it. We like things nice and locked down. </p><p class="paragraph" style="text-align:left;">Here’s what the source role’s trust policy looks like. Notice how it only allows the lambda service, and only our named function (thanks to the <b>aws:SourceArn </b>condition key)?</p><div class="codeblock"><pre><code>&#123;
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    &#123;
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: &#123;
        &quot;Service&quot;: &quot;lambda.amazonaws.com&quot;
      &#125;,
      &quot;Action&quot;: &quot;sts:AssumeRole&quot;,
      &quot;Condition&quot;: &#123;
        &quot;StringEquals&quot;: &#123;
          &quot;aws:SourceArn&quot;: &quot;arn:aws:lambda:us-west-2:&lt;SecOps account ID&gt;:function:security-auto-S3&quot;
        &#125;
      &#125;
    &#125;
  ]
&#125;</code></pre></div><p class="paragraph" style="text-align:left;">Now let’s look at the target role. In this case we use the <i>role trust policy</i> to ensure that it can <b>only</b> be used by that one source role, with the ARN hard-coded in. (This is a snippet from the CloudFormation template we will use to create the role in the target accounts. I’m skipping the rest for brevity.)</p><div class="codeblock"><pre><code>Resources:
  SecurityAutoremediationRole:
    Type: &#39;AWS::IAM::Role&#39;
    Properties:
      RoleName: SecurityAutoremediation
      Path: /SecurityOperations/
      AssumeRolePolicyDocument:
        Version: &#39;2012-10-17&#39;
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Sub &#39;arn:aws:iam::$&#123;SecurityOperationsAccountID&#125;:role/lambda-slaw-s3&#39;
            Action: &#39;sts:AssumeRole&#39;</code></pre></div><p class="paragraph" style="text-align:left;">Look at <b>AssumeRolePolicyDocument,</b> which is the trust policy. It has <b>Allow</b> and then requires an ARN: the ARN of the lambda role in our <b>SecurityOperations</b> account (remember that !Sub is you’ll swap in the account ID, since you’ll be running this from my S3 bucket).</p><p class="paragraph" style="text-align:left;">And I’ll skip the permissions policy — you’ll see it in a minute, and it’s just the usual S3 API calls.</p><p class="paragraph" style="text-align:left;">This image shows how it all works together:</p><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/30a1660a-415d-4859-ae1a-3ae9b04acf55/image.png?t=1741997226"/></div><ul><li><p class="paragraph" style="text-align:left;">In the <b>SecurityOperations</b> account:</p><ul><li><p class="paragraph" style="text-align:left;">In the <b>lambda-slaw-s3</b> role we created for our lambda function:</p><ul><li><p class="paragraph" style="text-align:left;">Only the specific lambda function can use this role. The role trust policy only allows the lambda service, when working on behalf of the <b>security-auto-s3</b> lambda function, to <b>sts:AssumeRole.</b></p></li><li><p class="paragraph" style="text-align:left;">When assumed, this returns session credentials to the lambda function.</p></li><li><p class="paragraph" style="text-align:left;">The permission policy on the role which the function is now using only allows the function to assume a role named <b>SecurityAutoremediation</b> in any account in our organization where it exists. But…</p><ul><li><p class="paragraph" style="text-align:left;">We added a condition to restrict it to only assuming this role if it is in our <b>Workloads</b> OU.</p></li></ul></li></ul></li></ul></li><li><p class="paragraph" style="text-align:left;">In the Target Workload account:</p><ul><li><p class="paragraph" style="text-align:left;">We have a <b>SecurityAutoremediation</b> role.</p><ul><li><p class="paragraph" style="text-align:left;">With a role trust policy which only allows our <b>SecurityOperations</b> <b>lambda-slaw-s3</b> role to assume it.</p></li><li><p class="paragraph" style="text-align:left;">With permissions to make certain S3 API calls.</p></li></ul></li></ul></li><li><p class="paragraph" style="text-align:left;">For the lambda function to make an API call on a target account:</p><ul><li><p class="paragraph" style="text-align:left;">The lambda service assumes the <b>lambda-slaw-s3</b> role.</p></li><li><p class="paragraph" style="text-align:left;">This returns session credentials to our running function.</p></li><li><p class="paragraph" style="text-align:left;">The function uses those credentials to assume the <b>SecurityAutoremediation</b> role.</p></li><li><p class="paragraph" style="text-align:left;">That returns a different set of session credentials, which enable the function to make those S3 API calls.</p></li><li><p class="paragraph" style="text-align:left;">The chain breaks whenever any of those sessions expires (the default is 1 hour).</p></li></ul></li></ul><p class="paragraph" style="text-align:left;">This pattern is the flossing of cross-account access. We restrict both sides of the relationship, but in a way which still allows us to work across multiple accounts. This is another reason a good OU structure is so important, and having a good mental model for how to use both role trust policies and permissions policies together.</p><h3 class="heading" style="text-align:left;" id="todays-key-points-are">Key Lesson Points</h3><ul><li><p class="paragraph" style="text-align:left;">Role chaining enables us to use one role to assume another role… and then that role can assume the next role in the chain.</p></li><li><p class="paragraph" style="text-align:left;">We use role chaining to go from one account into other accounts. It’s the basic way to build the IAM for security automation.</p></li><li><p class="paragraph" style="text-align:left;">Trust policies define who or what can use a role, and we can lock that down pretty specifically to prevent the role from being abused.</p></li><li><p class="paragraph" style="text-align:left;">A role’s permissions policies must include <b>sts:AssumeRole</b> if you want to use it to assume other roles. We can also put resource restrictions and conditionals on it, to further lock down both sides of the relationship.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lab"><b>The Lab</b></h1><p class="paragraph" style="text-align:left;">If you managed to read this far, you know what’s coming. We will:</p><ul><li><p class="paragraph" style="text-align:left;">Update the permission policy of our lambda execution role so it can assume roles, but only one role, only in one OU.</p></li><li><p class="paragraph" style="text-align:left;">Update the trust policy of the lambda execution role so it is only usable by our named lambda function and not others.</p></li><li><p class="paragraph" style="text-align:left;">Use StackSets to push out our target/security automation role into our <b>Workloads</b> OU.</p></li></ul><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/I3iK5cFMha8" width="100%"></iframe><h2 class="heading" style="text-align:left;" id="stepby-step">Step-by-Step</h2><p class="paragraph" style="text-align:left;">First we need to build the path to our Workloads OU so we can use it as a condition later. Start in your <b>Sign-in portal &gt; CloudSLAW &gt; AdministratorAccess &gt; Organizations.</b> Then collect your <b>organization ID, the root OU ID,</b> and <b>the Workloads OU ID:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/b145335f-f4d0-4524-b4f5-52f6d37e4a9b/image.png?t=1742404918"/></div><p class="paragraph" style="text-align:left;">Just paste that string someplace — we’ll need it later. When you specify an OU using a global condition key, you need to use the entire path like this: <b>o-0sqvc6dvj1/r-na6s/ou-na6s-ag0wxvj3/*</b></p><p class="paragraph" style="text-align:left;">With that out of the way, let’s start changing our various permissions. Go to your lambda execution role (the one the lambda function will always use): <b>Sign-in portal &gt; SecurityOperations &gt; AdministratorAccess</b> <b>&gt; IAM &gt; Roles &gt; lambda-slaw-s3:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/cc3384cf-668b-4395-91c4-e5d43ac409ce/image.png?t=1742405705"/></div><p class="paragraph" style="text-align:left;">Click <b>Permissions policies &gt; lambda-s3 &gt; Edit:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/02027078-f132-4a96-932f-916e6adfc30d/image.png?t=1742406082"/></div><p class="paragraph" style="text-align:left;"><b>Copy this policy, substitute in your Account ID </b><i><b>twice</b></i><b> and your org path, and paste over the existing policy</b>. As a reminder, you can get your current account ID by clicking in the upper-right corner dropdown in the AWS console.</p><div class="codeblock"><pre><code>&#123;
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    &#123;
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: &quot;logs:CreateLogGroup&quot;,
      &quot;Resource&quot;: &quot;arn:aws:logs:us-west-2:&lt;your account ID&gt;:*&quot;
    &#125;,
    &#123;
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: [
        &quot;logs:CreateLogStream&quot;,
        &quot;logs:PutLogEvents&quot;
      ],
      &quot;Resource&quot;: [
        &quot;arn:aws:logs:us-west-2:&lt;your account ID&gt;:log-group:/aws/lambda/security-auto-S3:*&quot;
      ]
    &#125;,
    &#123;
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: &quot;sts:AssumeRole&quot;,
      &quot;Resource&quot;: &quot;arn:aws:iam::*:role/SecurityOperations/SecurityAutoremediation&quot;,
      &quot;Condition&quot;: &#123;
        &quot;ForAnyValue:StringLike&quot;: &#123;
          &quot;aws:ResourceOrgPaths&quot;: &quot;&lt;your org path&gt;/*&quot;
        &#125;
      &#125;
    &#125;
  ]
&#125;</code></pre></div><p class="paragraph" style="text-align:left;">It will then look like this, <i><b>but with your IDs, not mine!</b></i> Then click <b>Next,</b> and on the next page click <b>Save changes:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/c19784d9-8417-4747-9634-a8310808f22e/image.png?t=1742406353"/></div><p class="paragraph" style="text-align:left;">New you need to adjust the role trust policy. Click <b>Trust relationships &gt; Edit trust policy:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ec4787ff-d753-46a2-8810-6a7ab6876162/image.png?t=1742406977"/></div><p class="paragraph" style="text-align:left;">You’ll need that <b>account ID</b> again, and this time <b>copy this policy, replace the account ID, and then paste the policy into the window &gt; Update policy:</b></p><div class="codeblock"><pre><code>&#123;
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    &#123;
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: &#123;
        &quot;Service&quot;: &quot;lambda.amazonaws.com&quot;
      &#125;,
      &quot;Action&quot;: &quot;sts:AssumeRole&quot;,
      &quot;Condition&quot;: &#123;
        &quot;StringEquals&quot;: &#123;
          &quot;aws:SourceArn&quot;: &quot;arn:aws:lambda:us-west-2:&lt;SecOps account ID&gt;:function:security-auto-S3&quot;
        &#125;
      &#125;
    &#125;
  ]
&#125;</code></pre></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/4a5dca12-d1e9-4ecb-9304-ef6314b43131/image.png?t=1742409815"/></div><p class="paragraph" style="text-align:left;">Now it’s time for the easy part. We’ll use StackSets to push out our SecurityAutomation role, which has permissions to make changes in the accounts. When we do this, we’ve also set it to use the <b>/SecurityOperations/</b> path, which helps keep things organized. </p><p class="paragraph" style="text-align:left;">Go to <b>CloudFormation &gt; StackSets &gt; Service-managed &gt; Create StackSet: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/2d80eace-405c-4d39-b796-4803f4098c21/image.png?t=1742410131"/></div><p class="paragraph" style="text-align:left;"><b>Paste in this template URL and click Next:</b></p><p class="paragraph" style="text-align:left;"> <a class="link" href="https://cloudslaw.s3-us-west-2.amazonaws.com/lab54.template?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=secure-a-role-chain-on-both-sides" target="_blank" rel="noopener noreferrer nofollow">https://cloudslaw.s3-us-west-2.amazonaws.com/lab54.template</a></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/b71310db-dd45-4161-bbc8-1b191bdc0b62/image.png?t=1742410258"/></div><p class="paragraph" style="text-align:left;">Name it <b>AutoremediationRole,</b> provide the <b>description,</b> then <b>Copy & Paste the Account ID, </b>like we’ve been doing. Then click <b>Next: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/efbb1f9a-8c57-4093-a403-61b7888ef985/image.png?t=1742410408"/></div><p class="paragraph" style="text-align:left;"><b>Click the checkbox &gt; Next: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/9afc23f1-46a9-4afa-af38-38b8351631d4/image.png?t=1742410519"/></div><p class="paragraph" style="text-align:left;">Then <b>Deploy to organizational units &gt; Paste in your Workloads OU ID, which you previously copied.</b> Previously we deployed our role for central event collection everywhere, but for autoremediation (which will change things, and could break things) we want to be more cautious.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/11e0bf59-9984-4c6d-a8aa-a1bb0c5328f2/image.png?t=1742419961"/></div><p class="paragraph" style="text-align:left;">Pick <b>US East</b> as the region, then <b>Next.</b> Don’t worry about concurrency — there are only 2 accounts within that OU. </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/be023336-6ba6-44da-b7ab-b3f78e347af9/image.png?t=1742420157"/></div><p class="paragraph" style="text-align:left;">On the next page click <b>Submit.</b> No screenshot for you — it’s, like, a big button in the corner. You can figure out which corner. I have confidence in you.</p><p class="paragraph" style="text-align:left;">It will take a few minutes, but you can click <b>Stack instances</b> to track the deployment and see when it’s finished: </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/23a3cc57-b838-445d-88ed-f03ef693507b/image.png?t=1742421306"/></div><p class="paragraph" style="text-align:left;">If you click <b>Template</b>: as you can see we have a block of permissions for S3 read actions, and a second block for write actions.</p><p class="paragraph" style="text-align:left;">One last point: you’ll notice that we named everything S3 for the lambda and the role over in the <b>SecurityOperations</b> account, but this template creates a generic <b>SecurityAutoremediation</b> role in all target accounts. Why? Because down the road we will add more automated guardrails, but we don’t want to stuff accounts with different roles for each service. We will update this role with more permissions, and then limit what the automation lambda can actually do by inserting an inline policy called a session policy. You’ll see how that works once we get to writing the code.</p><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=0c3ea533-19db-42da-92b9-dc03a0680b8d&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

      <item>
  <title>Serverless Security 101</title>
  <description>In part 5 of our Epic Automation series we&#39;ll learn the basics of Lambda functions and how to use them securely, even for security, because we like security.</description>
      <enclosure url="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/47511c3c-50d5-4fd8-894c-a8adb1012c7c/epic-5.png" length="858340" type="image/png"/>
  <link>https://slaw.securosis.com/p/serverless-security-101</link>
  <guid isPermaLink="true">https://slaw.securosis.com/p/serverless-security-101</guid>
  <pubDate>Thu, 06 Mar 2025 15:00:00 +0000</pubDate>
  <atom:published>2025-03-06T15:00:00Z</atom:published>
    <dc:creator>Rich Mogull</dc:creator>
  <content:encoded><![CDATA[
    <div class='beehiiv'><style>
  .bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
  .bh__table_cell { padding: 5px; background-color: #FFFFFF; }
  .bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
  .bh__table_header { padding: 5px; background-color:#F1F1F1; }
  .bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
</style><div class='beehiiv__body'><div class="section" style="background-color:#89ff56;border-color:#00ad11;border-radius:5px;border-style:solid;border-width:2px;margin:0.0px 0.0px 0.0px 0.0px;padding:4.0px 4.0px 4.0px 4.0px;"><p class="paragraph" style="text-align:center;"><a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=serverless-security-101" target="_blank" rel="noopener noreferrer nofollow"><b>Go Pro for Support and Extras!</b></a></p><p class="paragraph" style="text-align:left;">CloudSLAW is, and always will be, free. But to help cover costs and keep the content up to date we have an optional Patreon. For $10 per month you get access to our support Discord, office hours, exclusive subscriber content (not labs — those are free — just extras) and more. <a class="link" href="https://patreon.com/CloudSLAW?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=serverless-security-101" target="_blank" rel="noopener noreferrer nofollow">Check it out!</a></p></div><h1 class="heading" style="text-align:left;" id="prerequisites">Prerequisites</h1><ul><li><p class="paragraph" style="text-align:left;">This is part 5 of the Advanced Cloud Security Problem Solving series, which I’ve been abbreviating to Epic Automation. If you haven’t done parts 1-4 yet, <a class="link" href="https://slaw.securosis.com/p/epic-cloud-security-automation-fixing-the-broken-rcp?utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=serverless-security-101" target="_blank" rel="noopener noreferrer nofollow">jump back to the first post in the series</a> and complete all prior posts before trying this one.</p></li></ul><h1 class="heading" style="text-align:left;" id="the-lesson">The Lesson</h1><p class="paragraph" style="text-align:left;">Over the past four labs we’ve built out the foundation of our automation platform by wiring together our centralized event bus, using a simple EventBridge rule and SNS to validate that the pipeline works. This week we’ll set up our first Lambda function, which is where all application logic will live.</p><p class="paragraph" style="text-align:left;">Oh, never heard of Lambda functions? Or never worked with them before? Yeah, this is where things get pretty awesome.</p><h2 class="heading" style="text-align:left;" id="serverless-101">Serverless 101</h2><p class="paragraph" style="text-align:left;">Let’s clean up some terminology to start. Everything I’m about to cover is more on the development and operations side, but you can’t secure things you don’t understand.</p><p class="paragraph" style="text-align:left;"><i>Serverless</i> is the term we use for cloud services you use where you don’t manage the underlying servers. Huh? Think of it this way: with EC2 we ran an instance and we handled all the configuration of the software and the operating system. We are responsible for scaling, sizing, and overall management. S3? It’s just a… thing… where we put files, and we can configure it to serve those files like a web server, without installing or managing any web server software. We have no idea what’s under the hood and we never touch an operating system. EC2 isn’t serverless, but by my definition (which not everyone agrees with), S3 is. Especially if you configure it for use as a web server. </p><p class="paragraph" style="text-align:left;">It’s a little confusing because some people (okay, a lot) tend to only use the term serverless when referring to <i>Function as a Service (FaaS).</i> What’s FaaS? It’s a kind of <b><i>workload,</i></b> which is a term we use to describe where code runs. It’s easier to understand what FaaS is if we list the major workload types:</p><ul><li><p class="paragraph" style="text-align:left;"><b>Physical server: </b>If I need to explain what this is you probably skipped ahead a few posts, and should come back after learning the alphabet.</p></li><li><p class="paragraph" style="text-align:left;"><b>Virtual Machine (VM):</b> We run our code/application in a full operating system on a hypervisor. An EC2 instance is a virtual machine.</p></li><li><p class="paragraph" style="text-align:left;"><b>Container:</b> Containers encapsulate an application and its dependencies. They usually run on a VM or physical host, on top of an operating system in a little isolated bubble. Some containers are heavy, very like a full virtual machine, while others are very lightweight (which is how they should be!) Containers are isolated, but less strongly than virtual machines (because modern hypervisors use all sorts of special hardware for performance and isolation, but containers share operating system resources).</p></li><li><p class="paragraph" style="text-align:left;"><b>Serverless Functions/FaaS:</b> You load and run your code in the cloud provider, but you don’t deal with an underlying operating system or even container. Your code is triggered, runs, and then shuts down. It often runs in a container in a VM, but these aren’t good times for Russian doll references.</p></li></ul><p class="paragraph" style="text-align:left;">That’s a key difference between FaaS and other types of workloads: the code you load runs when triggered, not all the time. When Lambda (Amazon’s FaaS, the first on the market) was released I think it timed out in something like 30 seconds. It was very expensive to run it for more than a few seconds. </p><p class="paragraph" style="text-align:left;">Lambda is what we use to build event-driven applications. The function is triggered and passed some data, it does something fast, sends out results, and then resets itself for the next request. Here’s where it gets really cool: you don’t need to worry about scaling or performance (okay, that’s not really true once things get big enough, but stick with me for now). If I send in 1 event on my event bus, it triggers one lambda. If I send in 10,000 events at the same time, 10,000 lambdas run in parallel. I pay for every <i>invocation,</i> but I don’t pay a penny when the lambda is just sitting there without any events.</p><p class="paragraph" style="text-align:left;">Lambda functions are ideal for the kind of application we are building. It’s lightweight, I never need to worry about a server or a container, and I don’t need to patch or maintain anything other than my own code. It doesn’t cost anything when there aren’t events, and for many architectures it’s super cost effective. And yes, you can support complex application logic using multiple lambdas, event buses, message queues, and other components. These designs also scale really <b>really</b> well. If you are going to build a new startup thingy, go event-driven and serverless as much as possible.</p><h2 class="heading" style="text-align:left;" id="lambda-security-101">Lambda Security 101</h2><p class="paragraph" style="text-align:left;">Let’s start with how lambda functions work:</p><ul><li><p class="paragraph" style="text-align:left;">You create a function, and provide your application code and configuration options — we’ll talk more about these later in this lab.</p></li><li><p class="paragraph" style="text-align:left;">Your function uses an IAM role. Every function needs a role, and you want to use least privilege.</p></li><li><p class="paragraph" style="text-align:left;">You trigger the function from the outside. This can be an EventBridge rule, an SNS notification, an SQS message, direct invocation from a URL, from a load balancer, an API gateway, or more. </p></li><li><p class="paragraph" style="text-align:left;">When a function is triggered you pass in the event or data for it to process.</p></li><li><p class="paragraph" style="text-align:left;">The function runs. It uses that IAM role, and you can add in things like environment variables, or even connect it to a VPC if it needs network access.</p><ul><li><p class="paragraph" style="text-align:left;">If you don’t put it on a VPC, it can access the Internet using Amazon’s network. We’ll discuss this in future labs.</p></li></ul></li><li><p class="paragraph" style="text-align:left;">The function sends its output wherever you specify in its code. </p></li><li><p class="paragraph" style="text-align:left;">By default the function logs to CloudWatch Logs.</p></li><li><p class="paragraph" style="text-align:left;">When it’s done it’s done. That function ends execution. Depending on how things are set up and what’s going on at the time, the runtime environment may also tear itself down.</p></li></ul><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/90bf4e9f-9031-4b5a-9f78-d6590ef7b34b/image.png?t=1740863066"/></div><p class="paragraph" style="text-align:left;">Sure, there can be a lot of security nuance with lambda functions, but the fundamentals are pleasantly straightforward. Here are <b>Rich’s 6 Fundamentals of Lambda Security</b><sup><b>(TM)</b></sup><b>:</b></p><ul><li><p class="paragraph" style="text-align:left;">Control who and what can <i>trigger</i> your function. I’ve found some unsecured/unauthenticated ones during assessments.</p></li><li><p class="paragraph" style="text-align:left;">Use a least-privilege <i>IAM role</i> for the function. A few times not only have I found unsecured functions, but they had full-admin IAM roles.</p></li><li><p class="paragraph" style="text-align:left;">Write secure code. Okay, we’ll try not to screw our code up, but in an enterprise you want to use <b>ALL the code scanning tools.</b> And keep your code up to date if you include other libraries which might become vulnerable.</p></li><li><p class="paragraph" style="text-align:left;">Don’t store secrets in your lambda function or the<i> environment variables</i> you configure when you set it up. Pull from a secrets manager instead (specifics will be a future lesson).</p></li><li><p class="paragraph" style="text-align:left;">Write your code to <i>log</i> prolifically. There are frameworks available to make this better.</p></li><li><p class="paragraph" style="text-align:left;">If you need to access internal resources (<i>e.g.,</i> databases) put your lambda on a VPC and don’t use the default AWS network access.</p></li></ul><p class="paragraph" style="text-align:left;">That’s most of it. Our application is relatively simple so we won’t need secrets management or network access, but you’ll see the rest in the lab.</p><h3 class="heading" style="text-align:left;" id="todays-key-points-are">Key Lesson Points</h3><ul><li><p class="paragraph" style="text-align:left;">Serverless functions are a key workload type, especially suited for event-driven applications.</p></li><li><p class="paragraph" style="text-align:left;">Serverless functions still run on servers under the hood, but we don’t need to patch or maintain anything other than our own code.</p></li><li><p class="paragraph" style="text-align:left;">Security focuses on secure code, triggers, IAM, secrets management, logging, and network security.</p><ul><li><p class="paragraph" style="text-align:left;">But, as always, it’s mostly IAM.</p></li></ul></li></ul><h1 class="heading" style="text-align:left;" id="the-lab"><b>The Lab</b></h1><p class="paragraph" style="text-align:left;">For this lab we will implement our first Lambda function and trigger it using the EventBridge infrastructure we already built. The function is super simple and just logs the event to CloudWatch Logs, but that validates our application logic and that all the right pieces are connected. </p><p class="paragraph" style="text-align:left;">In our next labs we’ll start building out the actual coding logic in Lambda and modify our function and its IAM privileges as we go.</p><h2 class="heading" style="text-align:left;" id="video-walkthrough">Video Walkthrough</h2><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="true" class="youtube_embed" frameborder="0" height="100%" src="https://youtube.com/embed/qIQ06p6dJ8I" width="100%"></iframe><h2 class="heading" style="text-align:left;" id="stepby-step">Step-by-Step</h2><p class="paragraph" style="text-align:left;">Go to your IAM Identity Center <b>sign-in portal &gt; SecurityOperations &gt; AdministratorAccess &gt; IAM &gt; Roles &gt; Create role:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/6c6619fa-fd42-41d0-bd84-35fd6ed5b1d5/image.png?t=1740954200"/></div><p class="paragraph" style="text-align:left;">Choose <b>AWS service &gt; Lambda &gt; Lambda &gt; Next</b>. This will create a role for lambda to use. Behind the scenes it’s basically just automation to set up the Role Trust Policy. If we were doing this via API or CLI we would need to write/include that policy ourselves.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/630b6385-b697-48e9-9c60-41f11ed6e576/image.png?t=1740954304"/></div><p class="paragraph" style="text-align:left;"><b>Do not add any permissions!!!</b> Just <b>scroll down and click Next.</b> We will add permissions in a minute using an inline policy, since this is a set of permissions we won’t use anywhere else. It’s also an excuse for me to show you inline policies (which live alongside the role and can’t be used anywhere else) vs. managed policies, which we’ve been using previously.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/1bbfe449-c2a0-4aaf-9805-01d7d584bcd0/image.png?t=1740954540"/></div><p class="paragraph" style="text-align:left;">Use the role name <span style="color:rgb(0, 0, 0);"><a class="link" href="https://us-east-1.console.aws.amazon.com/iam/home?region=us-west-2&utm_source=slaw.securosis.com&utm_medium=newsletter&utm_campaign=serverless-security-101#/roles/details/lambda-slaw-s3" rel="noopener noreferrer nofollow">lambda-slaw-s3</a></span><span style="color:rgb(0, 0, 0);">. Then scroll down and </span><span style="color:rgb(0, 0, 0);"><b>Create role. </b></span><span style="color:rgb(0, 0, 0);">I’m not worried about a description on this one — that name is spiffy on its own.</span></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/33b0ac89-edf2-42a0-9838-9ad83a3628af/image.png?t=1740954653"/></div><p class="paragraph" style="text-align:left;">Now click the <b>lambda-slaw-s3</b> role:</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/ac3160b9-5c52-4681-9f06-a51e0c6eda15/image.png?t=1740954854"/></div><p class="paragraph" style="text-align:left;">Then <b>Add permissions &gt; Create inline policy:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/b21f9c43-90a8-4f1d-adeb-556b9a4a5041/image.png?t=1740954915"/></div><p class="paragraph" style="text-align:left;">Select <b>JSON</b> and then <b>Copy and paste this policy.</b> There are three statements in here, and in a moment we’ll swap in your account ID. This policy is for the Lambda we are about to build. It:</p><ul><li><p class="paragraph" style="text-align:left;">Allows the Lambda function to create a log group in CloudWatch Logs.</p></li><li><p class="paragraph" style="text-align:left;">Allows the function to create a new stream each time a function launches (this is how it works) and to store events in the stream. This is hard-coded to the name we will use for our function when we create it (<b>security-auto-s3,</b> showing my usual lack of creativity).</p></li><li><p class="paragraph" style="text-align:left;">Allows various S3 read API calls. </p></li></ul><p class="paragraph" style="text-align:left;">Anyone notice the flaw in what I’ve done here? As written this onlys allow access to S3 <i>in this account</i>. If you remember, IAM permissions apply only where the policy lives. We’ll actually need to change this up later for cross-account access, and we’ll need to push out a role with CloudFormation StackSets in our other accounts for this function to assume. But for today this is fine, since we aren’t making cross-account calls yet, and it will help us understand that process when we get to that lab (like, probably the next lab).</p><div class="codeblock"><pre><code>&#123;
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        &#123;
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &quot;logs:CreateLogGroup&quot;,
            &quot;Resource&quot;: &quot;arn:aws:logs:us-west-2:&lt;accountID&gt;:*&quot;
        &#125;,
        &#123;
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;logs:CreateLogStream&quot;,
                &quot;logs:PutLogEvents&quot;
            ],
            &quot;Resource&quot;: [
                &quot;arn:aws:logs:us-west-2:&lt;accountID&gt;:log-group:/aws/lambda/security-auto-S3:*&quot;
            ]
        &#125;,
        &#123;
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;s3:GetBucketAcl&quot;,
                &quot;s3:GetBucketPolicy&quot;,
                &quot;s3:GetBucketPolicyStatus&quot;,
                &quot;s3:GetBucketTagging&quot;,
                &quot;s3:GetBucketLocation&quot;,
                &quot;s3:GetBucketPublicAccessBlock&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        &#125;
    ]
&#125;</code></pre></div><div class="image"><img alt="" class="image__image" style="" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/6ed90d4b-2de6-4de4-8f3f-5cf55298e37c/image.png?t=1740955325"/></div><p class="paragraph" style="text-align:left;">Now you need to <b>get your account ID from the upper right corner</b> and <b>paste it to over &lt;accountID&gt; in two places in the policy.</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/9528bf9d-a139-428f-a559-86b9fef4b108/image.png?t=1740955531"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/cf9231ad-3efa-4580-b643-ba10c18c0c98/image.png?t=1740955622"/></div><p class="paragraph" style="text-align:left;">Then <b>Next &gt;</b> name it <b>lambda-S3 &gt; Create policy: </b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/1cd24aae-aea5-4929-b002-e5ec18c482c7/image.png?t=1740955744"/></div><p class="paragraph" style="text-align:left;">Phew! We’re done with IAM (for now). To review, what we just did was create a role for our lambda function. That role can log things and access S3… in the same account. Yeah, my bad, we’ll talk more about this, probably in the next post when we set up cross-account access.</p><p class="paragraph" style="text-align:left;">So where does that leave us? It’s time to create our function! Go to <b>Lambda &gt; Create a function:</b> </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/cab77223-acfe-4f05-a0fc-de7621ecdd06/image.png?t=1740961517"/></div><p class="paragraph" style="text-align:left;">On this next page we set all the core configuration we need:</p><ul><li><p class="paragraph" style="text-align:left;"><b>Function name: security-auto-S3 </b>(case matters — this is hardcoded into our IAM policy).</p></li><li><p class="paragraph" style="text-align:left;"><b>Runtime:</b> <b>Python</b> (3.13 is the current version as of this writing, but whatever is the default python will work).</p></li><li><p class="paragraph" style="text-align:left;"><b>Architecture: amd64.</b> Either architecture works, but AMD in AWS tends to be cheaper with better performance across the board.</p></li><li><p class="paragraph" style="text-align:left;"><b>Permissions &gt; Use an existing role &gt; lambda-slaw-s3</b> to use the role we just created.</p></li><li><p class="paragraph" style="text-align:left;">Then <b>Create function.</b></p></li></ul><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/73f647f4-2ad2-4db0-981f-483137bb5943/image.png?t=1740961613"/></div><p class="paragraph" style="text-align:left;">Before we swap in our code, let’s look at the default python lambda function. </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/127d6f2b-8d25-4a3a-8f67-d37a925f516e/image.png?t=1740962076"/></div><p class="paragraph" style="text-align:left;">I’m not here to teach you how to code, but … I think I need to teach you how to sorta code. Like, just enough to get by. This is very simple: first it loads up the python library to read json because everything in cloud is json (events, policies, etc.) and the odds are nearly nil that you would ever <i>not</i> need this library. </p><p class="paragraph" style="text-align:left;">Then we have the “lambda_handler” function. This is the part of the code which the lambda service will call when triggered. It takes the event and the context, both of which are passed in by EventBridge (or another trigger). Not all your functions will start in this exact way, but it’s exactly what we want for our design.</p><p class="paragraph" style="text-align:left;">Then there’s the “return” statement, which is what the function returns to the service when it’s done running. By default this logs to CloudWatch.</p><p class="paragraph" style="text-align:left;">Okay, so let’s look at our code:</p><div class="codeblock"><pre><code>import json
import logging

# Configure the logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    &quot;&quot;&quot;
    Lambda function that logs the incoming event to the default CloudWatch log stream.
    
    Parameters:
    event (dict): The event data passed to the Lambda function
    context (LambdaContext): The runtime information of the Lambda function
    
    Returns:
    dict: A response with statusCode 200 and the logged event
    &quot;&quot;&quot;
    try:
        # Log the entire event as JSON
        logger.info(f&quot;Received event: &#123;json.dumps(event, default=str)&#125;&quot;)
        
        # Log some context information
        logger.info(f&quot;Function name: &#123;context.function_name&#125;&quot;)
        logger.info(f&quot;Request ID: &#123;context.aws_request_id&#125;&quot;)
        logger.info(f&quot;Remaining time: &#123;context.get_remaining_time_in_millis()&#125; ms&quot;)
        
        return &#123;
            &#39;statusCode&#39;: 200,
            &#39;body&#39;: json.dumps(&#123;
                &#39;message&#39;: &#39;Event successfully logged&#39;,
                &#39;event&#39;: event
            &#125;, default=str)
        &#125;
    except Exception as e:
        # Log any exceptions
        logger.error(f&quot;Error processing event: &#123;str(e)&#125;&quot;)
        return &#123;
            &#39;statusCode&#39;: 500,
            &#39;body&#39;: json.dumps(&#123;
                &#39;message&#39;: &#39;Error processing event&#39;,
                &#39;error&#39;: str(e)
            &#125;)
        &#125;</code></pre></div><p class="paragraph" style="text-align:left;">In our case we add the <b>logging</b> library for better log messages. If we didn’t use that we could just use <b>print</b> statements. Our function handler is the same, taking the event and the context, but then we change things up. We take the event and dump the event right into our log stream (you’ll see this soon enough). Then our exit message is that we successfully logged the event. The try/except are to catch potential errors “try this, and if it fails, then do that”.</p><p class="paragraph" style="text-align:left;">More words, like a bunch more words, but this basically says “log the event, and log if it was a success or failure”. And while this will work for any event, as you’ll see we are only triggering it from our S3 events we built out last week (once we add the trigger).</p><p class="paragraph" style="text-align:left;">Now <b>copy and paste the code into the window,</b> then click <b>Deploy</b> to make it active. Remember, there isn’t any concept of saving — a live function is a live function, so “Deploy” is the right word: we are deploying code. (And Lambda can track versions). </p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/3468ab92-6b51-4c03-abd5-f1fb8e67d2c8/image.png?t=1741020439"/></div><p class="paragraph" style="text-align:left;">If you want to see some other settings which affect security but we aren’t using, check out the video or feel free to look around (just don’t change anything). Your function is all loaded up and ready to run, we just need to set a trigger and test it.</p><p class="paragraph" style="text-align:left;">Got to <b>EventBridge &gt; Rules, </b>choose the <b>SecurityAutomation</b> event bus,<b> S3-security-remediations:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/22aba6d8-0ba7-4357-9880-f800cfce7901/image.png?t=1741020933"/></div><p class="paragraph" style="text-align:left;">One of the cool things about EventBridge rules is that they support multiple targets. Our existing rule sends us an email via SNS, and we can add a second target to trigger our lambda function. For now we’ll keep both because this helps us test — if we get the email but don’t see the log message, we know that the trigger works but the function has an issue.</p><p class="paragraph" style="text-align:left;">Go to <b>Targets &gt; Edit &gt; Add another target:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/05be7f6a-fd33-4dbe-b83f-906f3be44b15/image.png?t=1741021202"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/352b6833-fe1f-4ce3-86bc-66ca229b1bff/image.png?t=1741021265"/></div><p class="paragraph" style="text-align:left;">Then use the following settings:</p><ul><li><p class="paragraph" style="text-align:left;"><b>AWS service</b></p></li><li><p class="paragraph" style="text-align:left;"><b>Lambda function</b></p></li><li><p class="paragraph" style="text-align:left;"><b>security-auto-s3</b></p></li><li><p class="paragraph" style="text-align:left;"><b>Use execution role</b></p></li><li><p class="paragraph" style="text-align:left;"><b>Create a new role for this specific resource</b></p></li></ul><p class="paragraph" style="text-align:left;">If you decide to look at the IAM role created, it has permissions to invoke the lambda. Since this is one AWS service talking to another, you absolutely need this permission.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/2db5d21f-4084-4324-bfdb-e369faad89db/image.png?t=1741021505"/></div><p class="paragraph" style="text-align:left;">Then just click through <b>Next &gt; Next &gt; Update rule.</b></p><p class="paragraph" style="text-align:left;">To save you some pain, if you go into the lambda function there is a section at the top which lists triggers. For whatever reason ours won’t show, and I suspect it’s because we are using a custom event bus. Don’t worry — I tested everything and it works.</p><p class="paragraph" style="text-align:left;">Now <b>close the tab and go back to your sign-in portal &gt; CloudSLAW &gt; Administrator access &gt; Switch role to Production1.</b> We will drop into our <i>Production1 </i>account using the <b>OrganizationAccountAccessRole</b> again to create a tag on our S3 bucket. If you are using the same browser as the last lab you can drop into the account with one click. If not, just review how to switch roles into this account from last week’s lab (I’m worried this is getting so long it might be filtered by mail servers):</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/b57e4d1c-9c55-43b8-b249-8191c5ed4903/image.png?t=1741022105"/></div><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/05048804-029b-4426-b72a-378c523d340b/image.png?t=1741022207"/></div><p class="paragraph" style="text-align:left;">Go to <b>S3 &gt; whatever you called your bucket &gt; Properties &gt; Tags &gt; Edit</b> and <b>add a tag</b>. You can review the screenshots from the last lab if you need — I’m definitely taunting the email delivery gods with this long lab.</p><p class="paragraph" style="text-align:left;">Last step. First of all, you should already have an email triggered by the change. Now we want to go back into our <b>SecurityOperations</b> account to see if our lambda function logged to CloudWatch. So <b>Close the tab &gt; sign in portal &gt; SecurityOperations &gt; AdministratorAccess &gt; CloudWatch &gt; Log groups.</b> You will only see a log group if it worked. Then <b>click your log group:</b></p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/84ac44a0-f178-4e11-a632-f2eade2f80b9/image.png?t=1741022497"/></div><p class="paragraph" style="text-align:left;"><b>It worked!</b> Yeah, this was a lot for one lab, but my video clocked in right at 31 minutes so I’m calling that within the margin of error for my 15-30 minute rule. Now feel free to click any of the log streams and read the messages. Every single function creates its own stream when invoked. When operating at scale you definitely want to keep an eye on costs, but logging is critical for security so I wouldn’t cut corners here. The devs should be paying the bill anyway.</p><div class="image"><img alt="" class="image__image" style="border-radius:5px;border-style:solid;border-width:2px;box-sizing:border-box;border-color:#C0C0C0;" src="https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/78e534b9-ef3e-4d80-8013-0413e6b2d59a/image.png?t=1741022735"/></div><p class="paragraph" style="text-align:left;">Now we have our alerting infrastructure set up, and we know we are passing selected S3 events into our central event bus and lambda. Next up we’ll fix up our permissions for cross-account access, then we’ll have a few labs working through the code to meet our requirements.</p><p class="paragraph" style="text-align:left;">Yes, this series is <b>a lot,</b> but I haven’t seen any good tutorials out there to walk you through every piece of the process to build out autoremediation (or even an event-driven application). Could I have just dumped some CloudFormation on you? Well, yeah, but that makes it tough to learn how to architect and implement for yourself in a different context.</p><p class="paragraph" style="text-align:left;">-Rich</p></div><div class='beehiiv__footer'><br class='beehiiv__footer__break'><hr class='beehiiv__footer__line'><a target="_blank" class="beehiiv__footer_link" style="text-align: center;" href="https://www.beehiiv.com/?utm_campaign=38777298-2c7f-4ed0-a5e4-61e81f2ac951&utm_medium=post_rss&utm_source=cloud_security_lab_a_week_s_l_a_w">Powered by beehiiv</a></div></div>
  ]]></content:encoded>
</item>

  </channel>
</rss>
