<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gerardo Castro Arica</title>
    <description>The latest articles on DEV Community by Gerardo Castro Arica (@gerardokaztro).</description>
    <link>https://dev.to/gerardokaztro</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F497606%2Fcd6efd4d-6266-4d67-8bdd-e9733338fb65.png</url>
      <title>DEV Community: Gerardo Castro Arica</title>
      <link>https://dev.to/gerardokaztro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gerardokaztro"/>
    <language>en</language>
    <item>
      <title>Mutable tags. 10,000 pipelines. One credential. — What the Trivy attack taught me about implicit trust</title>
      <dc:creator>Gerardo Castro Arica</dc:creator>
      <pubDate>Fri, 27 Mar 2026 16:25:45 +0000</pubDate>
      <link>https://dev.to/aws-heroes/mutable-tags-10000-pipelines-one-credential-what-the-trivy-attack-taught-me-about-implicit-2308</link>
      <guid>https://dev.to/aws-heroes/mutable-tags-10000-pipelines-one-credential-what-the-trivy-attack-taught-me-about-implicit-2308</guid>
      <description>&lt;p&gt;A few days ago I was designing a GitHub Actions pipeline with security scanning tools. Choosing what to integrate, how to structure it, what permissions to give it — especially for the context of the project I was building it for. The kind of work that feels productive — you're building something that will improve the team's security posture.&lt;/p&gt;

&lt;p&gt;That's when I found out what had happened to Trivy.&lt;/p&gt;

&lt;p&gt;On March 19, 2026, TeamPCP compromised the most widely used open-source vulnerability scanner in the cloud-native ecosystem. They didn't hack a business application. They didn't exploit a vulnerability in production code. They compromised the tool that thousands of organizations use to find vulnerabilities in their own applications. The security scanner became the weapon.&lt;/p&gt;

&lt;p&gt;That made me stop. Not to throw the pipeline away — but to rethink from what principles I was building it.&lt;/p&gt;

&lt;p&gt;This post is not a threat intelligence analysis. I'm a practitioner who is learning more and more about building security pipelines and who reflected on this case. What I found along the way changed how I think about trust in external dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  What exactly happened?
&lt;/h2&gt;

&lt;p&gt;The attack wasn't a single event — it was a multi-phase campaign that lasted several days and started earlier than most people think.&lt;/p&gt;

&lt;p&gt;Three weeks before March 19, an automated bot called &lt;code&gt;hackerbot-claw&lt;/code&gt; exploited a misconfigured workflow in the Trivy repository and stole a Personal Access Token. Aqua Security detected the incident and rotated credentials — but the rotation was incomplete. TeamPCP retained access to the credentials that survived. That's what made everything that followed possible. [&lt;a href="https://www.paloaltonetworks.com/blog/cloud-security/trivy-supply-chain-attack/" rel="noopener noreferrer"&gt;Palo Alto Networks&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;On March 19 at 17:43 UTC, using those retained credentials, TeamPCP compromised three components simultaneously:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Trivy binary.&lt;/strong&gt; A malicious version — v0.69.4 — was published across all official distribution channels: GitHub Releases, Docker Hub, GHCR, ECR Public, deb/rpm repositories, and get.trivy.dev. The scanning logic lives in this binary. The other two components are wrappers that invoke it. [&lt;a href="https://www.wiz.io/blog/trivy-compromised-teampcp-supply-chain-attack" rel="noopener noreferrer"&gt;Wiz Research&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;trivy-action.&lt;/strong&gt; The GitHub Action most teams use to integrate Trivy into their pipelines. TeamPCP force-pushed 76 of 77 version tags to point at malicious commits. Pipelines referencing these actions by tag began executing the attacker's code on their next run — with no visible change to the tag name or the releases page. [&lt;a href="https://www.microsoft.com/en-us/security/blog/2026/03/24/detecting-investigating-defending-against-trivy-supply-chain-compromise/" rel="noopener noreferrer"&gt;Microsoft Security Blog&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;setup-trivy.&lt;/strong&gt; The Action that installs the binary. All 7 existing tags were compromised in the same way.&lt;/p&gt;

&lt;p&gt;The payload was an infostealer that harvested SSH keys, cloud tokens, Kubernetes credentials, and pipeline environment variables — all while Trivy ran normally and produced the expected output. To an engineer reviewing the logs, the step appeared successful. [&lt;a href="https://www.sans.org/blog/when-security-scanner-became-weapon-inside-teampcp-supply-chain-campaign" rel="noopener noreferrer"&gt;SANS Institute&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;Exposure windows ranged from 3 to 12 hours depending on the component. Three days later, on March 22, TeamPCP published additional malicious images on Docker Hub — v0.69.5 and v0.69.6 — using separately compromised Docker Hub credentials, extending exposure by another ~10 hours. [&lt;a href="https://www.legitsecurity.com/blog/the-trivy-supply-chain-compromise-what-happened-and-playbooks-to-respond" rel="noopener noreferrer"&gt;Legit Security&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;According to SANS Institute, more than 10,000 CI/CD workflows are reported to have been affected.&lt;/p&gt;

&lt;h2&gt;
  
  
  They didn't exploit code — they exploited trust
&lt;/h2&gt;

&lt;p&gt;When most teams think about an attack, they picture someone finding a buffer overflow, an SQL injection, an RCE. A technical vulnerability in the code that needs to be patched.&lt;/p&gt;

&lt;p&gt;TeamPCP did none of that.&lt;/p&gt;

&lt;p&gt;They didn't find a bug in Trivy. They didn't break any cryptographic algorithm. They exploited something much harder to patch: the implicit trust logic that organizations place in their dependencies. The premise that if a tool comes from the official vendor, through the official channel, with the official tag — it's safe.&lt;/p&gt;

&lt;p&gt;That premise is what failed.&lt;/p&gt;

&lt;p&gt;There's a brutal irony in how the impact was distributed. The most diligent teams — the ones who had integrated Trivy into every PR, every merge, every deploy — were the most exposed. The more disciplined the team was about running their security pipeline, the more times the infostealer ran. The good practice became the attack vector. [&lt;a href="https://www.sans.org/blog/when-security-scanner-became-weapon-inside-teampcp-supply-chain-campaign" rel="noopener noreferrer"&gt;SANS Institute&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;This is not an argument to stop scanning. It's an argument to understand that trusting an external tool without verifying its integrity is exactly the same as trusting a user without verifying their identity. In IAM, nobody accepts that. In dependencies, we accept it all the time.&lt;/p&gt;

&lt;p&gt;The expansion of the attack confirms it. TeamPCP didn't stop at Trivy. On March 23 they compromised Checkmarx KICS — the infrastructure-as-code scanner. On March 24 they reached LiteLLM on PyPI, using credentials stolen from BerriAI's Trivy pipeline. According to SANS Institute, one stolen token propagated across five distinct ecosystems: CI/CD, npm, Docker, PyPI, and AI infrastructure. [&lt;a href="https://arcticwolf.com/resources/blog/teampcp-supply-chain-attack-campaign-targets-trivy-checkmarx-kics-and-litellm-potential-downstream-impact-to-additional-projects/" rel="noopener noreferrer"&gt;Arctic Wolf&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;TeamPCP isn't targeting business applications. It's targeting the security tooling ecosystem — exactly the tools that the most security-conscious teams have integrated into their pipelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mutable tags: not the vector, but the multiplier
&lt;/h2&gt;

&lt;p&gt;When the attack was reported, much of the media focus fell on mutable tags. That makes sense — it's the most striking technical detail. But it's worth understanding exactly what role they played, because they weren't the entry vector — they were what turned a single point of access into a massive problem.&lt;/p&gt;

&lt;p&gt;To understand the difference, three concepts need to be separated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The attack vector&lt;/strong&gt; was the retained credential — the Personal Access Token that survived an incomplete rotation from a prior incident. That credential with write permissions over Aqua Security's repositories is what gave TeamPCP initial access. Without it, nothing that followed would have been possible. [&lt;a href="https://www.paloaltonetworks.com/blog/cloud-security/trivy-supply-chain-attack/" rel="noopener noreferrer"&gt;Palo Alto Networks&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The attack surface&lt;/strong&gt; was the CI/CD pipelines of thousands of organizations referencing Trivy and its GitHub Actions by tag. Every pipeline running &lt;code&gt;uses: aquasecurity/trivy-action@v0.35.0&lt;/code&gt; was an exposed surface — not because it had a vulnerability, but because its trust model depended on the immutability of a tag that wasn't immutable. According to SANS Institute, more than 10,000 CI/CD workflows were part of that surface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mutable tags&lt;/strong&gt; were the multiplier. Once TeamPCP had access, tag mutability allowed them to silently redirect 76 of 77 tags in &lt;code&gt;trivy-action&lt;/code&gt; and all 7 tags in &lt;code&gt;setup-trivy&lt;/code&gt; to malicious commits — with no visible change in names, dates, or release pages. [&lt;a href="https://www.microsoft.com/en-us/security/blog/2026/03/24/detecting-investigating-defending-against-trivy-supply-chain-compromise/" rel="noopener noreferrer"&gt;Microsoft Security Blog&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;Now, the fundamental difference between a tag and a digest.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;tag&lt;/strong&gt; is a named pointer — &lt;code&gt;v0.35.0&lt;/code&gt;, &lt;code&gt;latest&lt;/code&gt;, &lt;code&gt;v0.69.4&lt;/code&gt;. In Git and container registries, that name can be redirected to any commit or image without the name changing. No notification. No alert. No visible change on the releases page.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;digest&lt;/strong&gt; is the SHA256 of the actual content — &lt;code&gt;sha256:a3f8d2c1...&lt;/code&gt;. It's immutable by definition. If the content changes, the digest changes. It can't be forged, it can't be redirected. Referencing a dependency by digest anchors it to a specific verified piece of content.&lt;/p&gt;

&lt;p&gt;Any pipeline referencing those actions by tag began executing malicious code on its next run. Without any visible change. Without any alert. If those same pipelines had referenced the actions by digest, the force-push would have had no effect. The digest would still point to the original commit. The blast radius would have been dramatically smaller. [&lt;a href="https://www.legitsecurity.com/blog/the-trivy-supply-chain-compromise-what-happened-and-playbooks-to-respond" rel="noopener noreferrer"&gt;Legit Security&lt;/a&gt;]&lt;/p&gt;

&lt;h2&gt;
  
  
  Supply chain security = least privilege applied to dependencies
&lt;/h2&gt;

&lt;p&gt;There's a principle any engineer working with AWS knows by heart: least privilege. You don't give a role more permissions than it needs. You don't create access keys when you can use temporary roles. You don't leave a &lt;code&gt;*&lt;/code&gt; in a Resource when you can specify the exact ARN.&lt;/p&gt;

&lt;p&gt;It's the most repeated principle in cloud security. And yet, when it comes to external dependencies, we systematically ignore it.&lt;/p&gt;

&lt;p&gt;When a pipeline runs &lt;code&gt;uses: aquasecurity/trivy-action@v0.35.0&lt;/code&gt;, it's placing full trust in that tag — without verifying its integrity, without anchoring to specific content, without questioning whether the pointer still points to what it pointed to yesterday. It's the equivalent of giving &lt;code&gt;AdministratorAccess&lt;/code&gt; to a role because "it's easier." It works. Until it doesn't.&lt;/p&gt;

&lt;p&gt;Supply chain security isn't a separate domain from the security you already practice. It's the same least privilege principle applied to a different level: your dependencies. The question isn't "do I trust this tool?" — it's "what verification do I have that what I'm executing is exactly what I think it is?"&lt;/p&gt;

&lt;p&gt;The expansion of the attack illustrates why this matters at scale. TeamPCP didn't need to compromise each organization individually. It compromised a central tool in the ecosystem and let implicit trust do the rest. LiteLLM was compromised because its pipeline used Trivy — the infostealer stole the PyPI publishing token, and from there TeamPCP published malicious versions with 3.6 million daily downloads. [&lt;a href="https://www.sans.org/blog/when-security-scanner-became-weapon-inside-teampcp-supply-chain-campaign" rel="noopener noreferrer"&gt;SANS Institute&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;One token. Five compromised ecosystems. That's the geometry of a well-executed supply chain attack.&lt;/p&gt;

&lt;p&gt;What makes this case especially uncomfortable is that the victims weren't careless teams. They were teams that had invested in security, that scanned their pipelines, that used cloud-native tooling. Diligence didn't protect them — because the diligence was applied inside the trust perimeter, not at the perimeter itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution is not to stop using external tools
&lt;/h2&gt;

&lt;p&gt;The wrong conclusion from this incident would be to stop using Trivy, to distrust all open-source tools, or to build everything in-house. That's neither viable nor the right lesson.&lt;/p&gt;

&lt;p&gt;The lesson is that trust in external dependencies needs to be explicit and verifiable — not implicit and assumed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Digest pinning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most concrete and immediate change is to reference GitHub Actions by digest instead of by tag.&lt;/p&gt;

&lt;p&gt;Instead of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aquasecurity/trivy-action@v0.35.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aquasecurity/trivy-action@sha256:57a97c7...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The digest anchors the pipeline to a specific verified piece of content. If someone force-pushes the tag, the pipeline keeps executing the original commit. Tag mutability stops being a problem because the pipeline doesn't depend on the tag. [&lt;a href="https://www.legitsecurity.com/blog/the-trivy-supply-chain-compromise-what-happened-and-playbooks-to-respond" rel="noopener noreferrer"&gt;Legit Security&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;The obvious objection is that this makes updates harder — and it's valid. A fixed digest doesn't update itself. That's where the second part comes in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependabot and Renovate&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Dependabot and Renovate can manage GitHub Actions updates automatically, including digest pinning. When a new verified version is released, they open a PR with the updated digest. The team reviews, approves, and the pipeline updates in a controlled and auditable way.&lt;/p&gt;

&lt;p&gt;The combination closes the loop: digest pinning eliminates exposure to mutable tags, and Dependabot/Renovate eliminates the friction of maintaining digests manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confirmed safe versions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For those using Trivy at the time of the incident, confirmed safe versions are: Trivy binary v0.69.3 or earlier, trivy-action v0.35.0 at commit &lt;code&gt;57a97c7&lt;/code&gt;, setup-trivy v0.2.6 at commit &lt;code&gt;3fb12ec&lt;/code&gt;. Any reference to v0.70.0 in logs should be treated as suspicious — the attacker attempted to publish that version but was stopped before the tag was pushed. [&lt;a href="https://www.legitsecurity.com/blog/the-trivy-supply-chain-compromise-what-happened-and-playbooks-to-respond" rel="noopener noreferrer"&gt;Legit Security&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secrets: less is more&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One thing this incident makes clear is the problem of pipelines that inherit more secrets than they need. If a scanning job doesn't need production credentials, it shouldn't have them — regardless of whether they're temporary or static. Applying least privilege to what secrets get passed to each job reduces the blast radius when a tool is compromised.&lt;/p&gt;

&lt;p&gt;Immediately rotate any credential that was exposed to a pipeline that ran compromised versions of Trivy between March 19 and March 22. That includes GitHub tokens, cloud credentials, registry tokens, SSH keys, and database passwords. [&lt;a href="https://www.legitsecurity.com/blog/the-trivy-supply-chain-compromise-what-happened-and-playbooks-to-respond" rel="noopener noreferrer"&gt;Legit Security&lt;/a&gt;]&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing — what happened in LATAM
&lt;/h2&gt;

&lt;p&gt;All of the above reflection could remain theoretical if it weren't for someone in our community who lived this in real time.&lt;/p&gt;

&lt;p&gt;Alejandro Castañeda, AWS Community Builder and Cloud Engineer from Colombia, shared on LinkedIn what happened to his team. His pipeline was compromised. The infostealer ran on a self-hosted runner inside his EKS cluster and sent approximately 80KB to the attacker's server. To put that in perspective: 80KB is enough to carry away all the secrets from a complete pipeline.&lt;/p&gt;

&lt;p&gt;And yet, they reviewed CloudTrail top to bottom and the result was zero lateral movement. No API calls from the attacker's IP. No IAM user creation, no policy changes, no resource access.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because Alejandro's team had made an architecture decision ahead of time, when they were defining the foundations of their infrastructure: OIDC with IAM Roles instead of static access keys. Their pipelines assumed temporary roles that expired in minutes. By the time the attacker had the credentials in hand, they were already worthless. And the role's trust policy only allowed use from GitHub — so even if they hadn't expired, they couldn't be used from anywhere else.&lt;/p&gt;

&lt;p&gt;If they had had static access keys stored as GitHub secrets, the story would be completely different. The attacker would have had access to ECR to inject malicious images into production containers, and to Secrets Manager to read database credentials and financial service tokens.&lt;/p&gt;

&lt;p&gt;The difference between "our secrets were stolen and nothing happened" and a real disaster was an architecture decision that nobody made thinking about this specific attack. They made it because it was the right way to build.&lt;/p&gt;

&lt;p&gt;That's what I take from this incident. Not the list of affected versions — that changes. Not the name of the threat actor — that changes too. What doesn't change is the principle: design so that when someone compromises a dependency, they don't find anything useful on the other side.&lt;/p&gt;

&lt;p&gt;Security isn't about building a perfect wall. It's about making sure that when someone jumps it, there's nothing useful on the other side.&lt;/p&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Gerardo Castro&lt;/strong&gt; is an AWS Security Hero and Cloud Security Engineer focused on LATAM. Founder and Lead Organizer of the AWS Security Users Group LatAm. He believes the best way to learn cloud security is by building real things — not memorizing frameworks. He writes about what he builds, what he finds, and what he learns along the way.&lt;/p&gt;

&lt;p&gt;🔗 GitHub: &lt;a href="https://github.com/gerardokaztro" rel="noopener noreferrer"&gt;gerardokaztro&lt;/a&gt;&lt;br&gt;
🔗 LinkedIn: &lt;a href="https://linkedin.com/in/gerardokaztro" rel="noopener noreferrer"&gt;gerardokaztro&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>devsecops</category>
      <category>aws</category>
    </item>
    <item>
      <title>I automated an AWS Security Maturity Model recommendation across 40 accounts — design decisions included</title>
      <dc:creator>Gerardo Castro Arica</dc:creator>
      <pubDate>Wed, 25 Mar 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/i-automated-an-aws-security-maturity-model-recommendation-across-40-accounts-design-decisions-nm6</link>
      <guid>https://dev.to/aws-heroes/i-automated-an-aws-security-maturity-model-recommendation-across-40-accounts-design-decisions-nm6</guid>
      <description>&lt;p&gt;The &lt;a href="https://maturitymodel.security.aws.dev/" rel="noopener noreferrer"&gt;AWS Security Maturity Model&lt;/a&gt; has a recommendation in Phase 1 — Quick Wins that seems trivial: assign a security contact in each account of your AWS Organization.&lt;/p&gt;

&lt;p&gt;It's not glamorous. It doesn't have a complex architecture diagram. It doesn't require enabling any new service. It's literally filling out a form with a name, an email, and a phone number.&lt;/p&gt;

&lt;p&gt;And yet, in most organizations I work with in LATAM, it isn't done.&lt;/p&gt;

&lt;p&gt;Not because nobody knows about it — but because in environments with dozens of accounts, "filling out a form" becomes a manual process that depends on someone remembering, having access, and doing it correctly in each account. And when Control Tower provisions a new account, that process starts from scratch all over again.&lt;/p&gt;

&lt;p&gt;The question that kicked off this project was simple: why am I doing this by hand?&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;An active AWS Organization isn't static. New projects arrive, development environments get created, teams join. With Control Tower, provisioning a new account takes minutes — and that's exactly what you want. The problem is what happens after provisioning.&lt;/p&gt;

&lt;p&gt;Every new account is born without a security contact. AWS uses that contact to send critical alerts — abuse notifications, credential compromises, active vulnerabilities in your resources. If it isn't configured, those alerts go to the root account email, which in most cases nobody actively monitors.&lt;/p&gt;

&lt;p&gt;In an organization with 10 accounts, you can manage it by hand. With 20, you start missing things. With 40, it's systematically inconsistent.&lt;/p&gt;

&lt;p&gt;The maturity model is clear about this: it's not an advanced recommendation. It's in Phase 1. It's baseline. It's what you should have solved before anything else. And "solved" doesn't mean doing it once for the accounts that exist today — it means any account created tomorrow also has it, automatically, without anyone having to remember.&lt;/p&gt;

&lt;p&gt;That requires automation. Not a runbook, not a checklist — real automation that reacts to the right event and doesn't depend on human intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;The most important design decision in this project isn't the Lambda — it's where it lives and how it gets triggered.&lt;/p&gt;

&lt;p&gt;The obvious temptation is simple: create an EventBridge rule in the Management account that invokes the Lambda directly. It works. But it creates coupling you'll regret later: the Management account knows a Lambda exists, knows where it is, and needs permissions to invoke it cross-account. Every new consumer of that event requires touching the Management account.&lt;/p&gt;

&lt;p&gt;The right solution is a &lt;strong&gt;Custom Event Bus in the Security account&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Control Tower (Management)
  → EventBridge Rule
  → Custom Event Bus (Security)
  → EventBridge Rule
  → Lambda
  → account API (all accounts)
  → Slack notification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flow is: Management publishes the event to the Bus. Security has its own rules that decide what to do with it. Management knows nothing about the Lambda — it only knows there's a Bus to send events to.&lt;/p&gt;

&lt;p&gt;What you gain from this is real extensibility. The Custom Event Bus in Security becomes the central point for organization events. When tomorrow you want to react to the same event in a different way — send to a SIEM, trigger another automation, notify a different channel — you add a rule to the Bus. The Management account isn't touched. That's the kind of decision that seems like overhead at first and that you appreciate when the system grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  CreateManagedAccount, not CreateAccount
&lt;/h2&gt;

&lt;p&gt;When Control Tower provisions an account, AWS emits two distinct events at different moments in the process. Confusing them is one of the easiest mistakes to make — and one of the hardest to diagnose because both seem correct on paper.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CreateAccount&lt;/code&gt; is emitted when the creation process &lt;em&gt;begins&lt;/em&gt;. At that point, the account exists in Organizations but Control Tower hasn't finished configuring it yet. If you trigger the Lambda with that event, you attempt to assign the security contact on an account that isn't fully accessible yet. The result is an error that looks like a permissions problem — and one that will have you spending time reviewing IAM policies that are perfectly fine.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CreateManagedAccount&lt;/code&gt; is emitted when Control Tower &lt;em&gt;finishes&lt;/em&gt; provisioning. The account is ready, the roles are deployed, you can operate on it. This is the correct event.&lt;/p&gt;

&lt;p&gt;But there's a second, subtler trap. The event's &lt;code&gt;detail-type&lt;/code&gt; isn't &lt;code&gt;AWS Control Tower via CloudTrail&lt;/code&gt; as you might assume — it's &lt;code&gt;AWS Service Event via CloudTrail&lt;/code&gt;. This detail isn't well documented and there's only one way to discover it: opening CloudTrail, finding the actual event emitted when CT provisioned an account, and reading the full JSON.&lt;/p&gt;

&lt;p&gt;The correct EventBridge rule looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"aws.controltower"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"AWS Service Event via CloudTrail"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"eventName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"CreateManagedAccount"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use &lt;code&gt;CreateAccount&lt;/code&gt; or the wrong &lt;code&gt;detail-type&lt;/code&gt;, the rule never fires — or fires at the wrong moment. In both cases, the security contact isn't assigned and there's no visible error telling you why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lambda and idempotency
&lt;/h2&gt;

&lt;p&gt;The most important behavior of this Lambda isn't what it does when it finds an account without a contact — it's what it does when it finds an account that already has the correct one.&lt;/p&gt;

&lt;p&gt;It does nothing.&lt;/p&gt;

&lt;p&gt;That seems obvious, but it has concrete design implications. The Lambda doesn't blindly call &lt;code&gt;PutAlternateContact&lt;/code&gt; on every account. It first calls &lt;code&gt;GetAlternateContact&lt;/code&gt;, reads the current value, compares it to the expected one, and only acts if there's a real difference. If the contact exists and is correct, the account is marked as "unchanged" and the process moves on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_contact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_management&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unchanged&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;continue&lt;/span&gt;

&lt;span class="nf"&gt;set_contact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_management&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assigned&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the Lambda idempotent: you can run it a hundred times with the same result. There's no risk of overwriting a contact someone updated manually in a specific account — if the values match, it doesn't touch anything.&lt;/p&gt;

&lt;p&gt;Idempotency also solves the problem of existing accounts. A reactive trigger that only responds to &lt;code&gt;CreateManagedAccount&lt;/code&gt; covers new accounts, but not the 40 that already existed before deploying the tool. The solution is to run the Lambda once across the entire organization on the first deploy — same code, no additional logic, because idempotency guarantees it won't break anything that's already correctly configured.&lt;/p&gt;

&lt;p&gt;The summary of each execution arrives via Slack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Security Contact Enforcer
Accounts processed: 40
Assigned: 3
Updated: 1
Unchanged: 36
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the message you want to see. Thirty-six accounts that were already fine — and four that needed attention and no longer do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The account API and its traps
&lt;/h2&gt;

&lt;p&gt;The Lambda is simple. The AWS Account API is not.&lt;/p&gt;

&lt;p&gt;There are three behaviors of the &lt;code&gt;account&lt;/code&gt; API that aren't well documented, that don't generate descriptive errors when ignored, and that only appear when you deploy in a real environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The API is global — it only works in us-east-1&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The AWS &lt;code&gt;account&lt;/code&gt; service is a global API. That means it's not available in every region — only in &lt;code&gt;us-east-1&lt;/code&gt;. If your Lambda lives in &lt;code&gt;us-east-2&lt;/code&gt; or &lt;code&gt;sa-east-1&lt;/code&gt; and you create the boto3 client without specifying a region, the call fails with &lt;code&gt;AccessDeniedException&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The error is confusing because it looks like an IAM permissions problem. You can spend hours reviewing policies that are perfectly fine before realizing the issue is the region. The solution is explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;account_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line. But one you only know you need after you've needed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Management account doesn't accept AccountId&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For member accounts, the API accepts an &lt;code&gt;AccountId&lt;/code&gt; parameter that indicates which account to operate on. For the Management account, that parameter can't be passed — the call must be made without it, in standalone mode.&lt;/p&gt;

&lt;p&gt;If you pass the Management account's &lt;code&gt;AccountId&lt;/code&gt;, the API returns an error. If there's no logic to bifurcate behavior based on account type, the Lambda will silently fail on the Management account or skip it without warning.&lt;/p&gt;

&lt;p&gt;The solution was adding an &lt;code&gt;is_management&lt;/code&gt; parameter to the &lt;code&gt;get_contact()&lt;/code&gt; and &lt;code&gt;set_contact()&lt;/code&gt; functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_contact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_management&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_management&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_alternate_contact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AlternateContactType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SECURITY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_alternate_contact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;AccountId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;AlternateContactType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SECURITY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Trusted Access must be enabled once&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before you can call &lt;code&gt;account:GetAlternateContact&lt;/code&gt; or &lt;code&gt;account:PutAlternateContact&lt;/code&gt; from an account other than Management, you need to enable Trusted Access between AWS Organizations and the Account Management service. Without this step, the API returns &lt;code&gt;AccessDeniedException&lt;/code&gt; even if the role has all the correct permissions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws organizations enable-aws-service-access &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-principal&lt;/span&gt; account.amazonaws.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--profile&lt;/span&gt; YOUR-MANAGEMENT-PROFILE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a one-time step that isn't in the Terraform flow — it must be done manually before the first deploy. If it isn't documented, it's the kind of prerequisite that costs hours to whoever tries to reproduce the project from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Makefile as the only path
&lt;/h2&gt;

&lt;p&gt;Lambda with ZIP has a silent trap that's easy to ignore until it costs you real time.&lt;/p&gt;

&lt;p&gt;When you update the Python code in &lt;code&gt;src/security_contact_enforcer.py&lt;/code&gt; and build the ZIP manually, Lambda keeps running the old code. Without any error. Without any warning. It simply executes the previous version as if nothing changed.&lt;/p&gt;

&lt;p&gt;What happens is that the code Lambda executes isn't the file you edited directly — it's what's inside &lt;code&gt;src/package/&lt;/code&gt;, the directory that gets packaged into the ZIP. If you update the source but forget to copy it to &lt;code&gt;package/&lt;/code&gt; before repackaging, the "successful" Lambda deploy contains outdated code.&lt;/p&gt;

&lt;p&gt;In a manual flow with multiple steps, that oversight is inevitable. The solution is to eliminate the manual flow.&lt;/p&gt;

&lt;p&gt;The Makefile turns the update into a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;update&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;cp &lt;/span&gt;src/security_contact_enforcer.py src/package/
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;.zip
    &lt;span class="nb"&gt;cd &lt;/span&gt;src/package &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; zip &lt;span class="nt"&gt;-r&lt;/span&gt; ../../function.zip .
    aws lambda update-function-code &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--function-name&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;FUNCTION_NAME&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--zip-file&lt;/span&gt; fileb://function.zip &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--profile&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;SECURITY_PROFILE&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;AWS_REGION&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. You edit &lt;code&gt;src/security_contact_enforcer.py&lt;/code&gt;, run &lt;code&gt;make update&lt;/code&gt;, and the full cycle — copy, package, deploy — happens in order without any steps that can be skipped.&lt;/p&gt;

&lt;p&gt;The Makefile isn't an optimization — it's the only safe way to update the Lambda. When there's only one correct path, there's no room for human error.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to validate it without creating an account
&lt;/h2&gt;

&lt;p&gt;Validating that the system works end-to-end has two independent parts. Confusing them leads to incomplete tests or waiting for Control Tower to provision a real account every time you want to verify something.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validate the Lambda logic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this, you don't need to create any account. Manually delete the security contact from an existing account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws account delete-alternate-contact &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--alternate-contact-type&lt;/span&gt; SECURITY &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--account-id&lt;/span&gt; YOUR-ACCOUNT-ID &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--profile&lt;/span&gt; YOUR-MANAGEMENT-PROFILE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then invoke the Lambda directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda invoke &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--function-name&lt;/span&gt; security-contact-enforcer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--payload&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cli-binary-format&lt;/span&gt; raw-in-base64-out &lt;span class="se"&gt;\&lt;/span&gt;
  response.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--profile&lt;/span&gt; YOUR-SECURITY-PROFILE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; YOUR-REGION

&lt;span class="nb"&gt;cat &lt;/span&gt;response.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the result shows &lt;code&gt;Assigned: 1, Unchanged: N-1&lt;/code&gt; — the complete logic is validated. The Lambda traversed all accounts, detected the one without a contact, fixed it, and left the rest untouched. All without touching EventBridge or Control Tower.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validate the EventBridge trigger&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This validation is independent and requires the real event. The only way to do it is to create an account from Control Tower and verify in CloudWatch Logs that the Lambda fired automatically upon receiving the &lt;code&gt;CreateManagedAccount&lt;/code&gt; event.&lt;/p&gt;

&lt;p&gt;These are two distinct tests that validate different things. The first confirms the business logic works. The second confirms the trigger responds to the correct event. You need both — but you don't need to do them together or in that order.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;Running the Lambda for the first time against an organization with 40 active accounts produced this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Security Contact Enforcer
Accounts processed: 40
Assigned: 31
Updated: 4
Unchanged: 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn43swebkyxyu3d3l9ngz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn43swebkyxyu3d3l9ngz.png" alt="Slack Alert"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thirty-one accounts without a security contact. Not because nobody cared about security — but because nobody had built the mechanism to guarantee it systematically. The 4 updated ones had a contact configured with outdated information, from an email that no longer existed or someone who had already left the team.&lt;/p&gt;

&lt;p&gt;Only 5 accounts were correctly configured.&lt;/p&gt;

&lt;p&gt;That number isn't unusual. It's what you find when you audit this control in organizations that have been on AWS for years without baseline automation. The problem isn't negligence — it's that without automation, consistency depends on someone remembering to do something manually at the right moment, every time a new account gets created.&lt;/p&gt;

&lt;p&gt;Since the deploy, every account Control Tower provisions has a security contact assigned before the team that requested it finishes configuring their first resources. No tickets, no runbooks, no human intervention.&lt;/p&gt;

&lt;p&gt;The operational cost of maintaining this control is now zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The AWS Security Maturity Model has dozens of controls. Some are complex, costly, and require weeks of planning. This one isn't.&lt;/p&gt;

&lt;p&gt;Assigning a security contact is Phase 1 — Quick Wins. It's the first thing you should have solved. And yet, in practice, it's one of the most frequently omitted controls in multi-account organizations because nobody built the mechanism to guarantee it continuously.&lt;/p&gt;

&lt;p&gt;What this project demonstrates isn't that automation is hard — it's exactly the opposite. A Lambda, a Custom Event Bus, Terraform and a Makefile are enough to turn a manual, error-prone process into a control that works on its own, forever, without anyone having to remember.&lt;/p&gt;

&lt;p&gt;The traps that appeared along the way — the wrong Control Tower event, the global API that only lives in us-east-1, the special behavior of the Management account, the Lambda ZIP that silently deployed old code — aren't well documented anywhere. They appear when you deploy in production with real accounts. That's why they're documented here.&lt;/p&gt;

&lt;p&gt;If you're building AWS security in LATAM with the resources you have, I hope this saves you the hours it cost to discover.&lt;/p&gt;

&lt;p&gt;The repository is on GitHub with all the code, the IaC in Terraform, and the complete README.&lt;/p&gt;

&lt;p&gt;🔗 GitHub: &lt;a href="https://github.com/gerardokaztro/security-contact-enforcer" rel="noopener noreferrer"&gt;gerardokaztro/security-contact-enforcer&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Gerardo Castro&lt;/strong&gt; is an AWS Security Hero and Cloud Security Engineer focused on LATAM. Founder and Lead Organizer of the AWS Security Users Group LatAm. He believes the best way to learn cloud security is by building real things — not memorizing frameworks. He writes about what he builds, what he finds, and what he learns along the way.&lt;/p&gt;

&lt;p&gt;🔗 GitHub: &lt;a href="https://github.com/gerardokaztro" rel="noopener noreferrer"&gt;gerardokaztro&lt;/a&gt;&lt;br&gt;
🔗 LinkedIn: &lt;a href="https://linkedin.com/in/gerardokaztro" rel="noopener noreferrer"&gt;gerardokaztro&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>cloud</category>
      <category>python</category>
    </item>
    <item>
      <title>My manager asked if it could run itself. Here's how I automated iam-audit with Fargate, EventBridge and Terraform (Part 3)</title>
      <dc:creator>Gerardo Castro Arica</dc:creator>
      <pubDate>Tue, 24 Mar 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/my-manager-asked-if-it-could-run-itself-heres-how-i-automated-iam-audit-with-fargate-eventbridge-10ak</link>
      <guid>https://dev.to/aws-heroes/my-manager-asked-if-it-could-run-itself-heres-how-i-automated-iam-audit-with-fargate-eventbridge-10ak</guid>
      <description>&lt;p&gt;A few weeks ago my manager asked me a question that seemed simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Can it be scheduled to arrive on its own every week?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The script was already scanning more than 20 AWS accounts. It was already detecting Access Keys from 2018 still active in production. It was already generating a dashboard that any CISO could read without opening a spreadsheet. Technically, the work was done.&lt;/p&gt;

&lt;p&gt;But "the work was done" meant someone had to remember to run it. Someone had to have Docker installed, credentials configured, and free time on a Monday morning. On a security team with multiple open fronts, that "someone" is exactly the link that breaks.&lt;/p&gt;

&lt;p&gt;Automation wasn't a cosmetic improvement. It was the step that turned a tool into a service.&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint that defines the architecture
&lt;/h2&gt;

&lt;p&gt;The first decision wasn't technical — it was about constraints.&lt;/p&gt;

&lt;p&gt;The report needs to run once a week. It takes minutes. When it's done, there's nothing to keep alive. Paying for infrastructure that sits idle 99.9% of the time isn't just a cost problem — it's a design problem.&lt;/p&gt;

&lt;p&gt;With that clear, the options narrow themselves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lambda?&lt;/strong&gt; The 15-minute execution limit is the problem. In an Organization with many accounts, the script can take longer — and a silent timeout halfway through an audit is worse than not running at all. Lambda is designed for millisecond-to-minute workloads, not for audit processes that traverse dozens of accounts in sequence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ECS Service?&lt;/strong&gt; A Service is designed for processes that run indefinitely — an API, a worker listening to a queue. Keeping a Service alive for a weekly job is exactly the antipattern we wanted to avoid. You pay for availability you'll never use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EC2?&lt;/strong&gt; More attack surface, more OS management, more base cost. Discarded.&lt;/p&gt;

&lt;p&gt;The right answer is &lt;strong&gt;ECS Fargate Task&lt;/strong&gt; — no Service, no persistent instances. A Task is ephemeral by design: it spins up, executes, and disappears. Nothing is running between executions. Nothing to patch, monitor, or pay for when not in use.&lt;/p&gt;

&lt;p&gt;That's FinOps applied to security: the cheapest architecture isn't the one with fewer features — it's the one that doesn't spend on what it doesn't need.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;The complete flow has four steps and no unnecessary pieces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EventBridge Scheduler&lt;/strong&gt; fires an event every Monday at 9am Lima time (&lt;code&gt;cron(0 14 ? * MON *)&lt;/code&gt; — UTC-5). No server waiting, no process sleeping. EventBridge simply remembers it has something to do, and does it.&lt;/p&gt;

&lt;p&gt;That event spins up an &lt;strong&gt;ECS Fargate Task&lt;/strong&gt; in the Security account. The task runs the iam-audit Docker image — the same one you were running locally with a single command in Part 2 — but now on AWS, with an assigned IAM role, no hardcoded credentials, no human intervention.&lt;/p&gt;

&lt;p&gt;When the task finishes, it uploads the report to a dedicated &lt;strong&gt;S3 bucket&lt;/strong&gt;. The bucket has a 90-day lifecycle policy — older reports are deleted automatically. No indefinite accumulation, no silently growing costs.&lt;/p&gt;

&lt;p&gt;The last step is notification. The task generates a &lt;strong&gt;presigned URL&lt;/strong&gt; valid for 48 hours and sends it via Slack. Whoever receives the message has two days to open the dashboard — after that the link expires. The report never leaves your AWS account; what travels through Slack is only the temporary access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EventBridge Scheduler (Monday 9am Lima)
        │
        ▼
ECS Fargate Task
  └─ image: gerardokaztro/iam-audit
  └─ role: iam-audit-task-role
  └─ secret: Slack webhook URL (Secrets Manager)
        │
        ├──▶ S3 bucket (report + presigned URL 48h)
        │
        └──▶ Slack (presigned URL)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything lives in the &lt;strong&gt;Security account&lt;/strong&gt; of the Organization. Not in the management account, not in an application account. The Security account is the right place for tools that touch the entire organization — isolated, with controlled access, audited separately.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to deploy it in a different account, you can do so without touching anything structural. Just adjust the configuration values — bucket name, SSO profile, environment variables — and Terraform handles the rest the same way.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F002vfjtpf7pibdtilwo0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F002vfjtpf7pibdtilwo0.png" alt="Architecture Diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform and the partial backend
&lt;/h2&gt;

&lt;p&gt;The entire stack is defined in Terraform. But before writing a single resource, there's a language limitation that needs to be understood — and if you don't know it, it leads you straight to hardcoding things that shouldn't be in code.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;backend&lt;/code&gt; block in Terraform initializes before the variable system. That means this &lt;strong&gt;doesn't work&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state_bucket&lt;/span&gt;  &lt;span class="c1"&gt;# ❌ not valid&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_region&lt;/span&gt;    &lt;span class="c1"&gt;# ❌ not valid&lt;/span&gt;
    &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_profile&lt;/span&gt;   &lt;span class="c1"&gt;# ❌ not valid&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform rejects it at &lt;code&gt;init&lt;/code&gt;. The variables simply don't exist yet at that point in the lifecycle.&lt;/p&gt;

&lt;p&gt;The obvious solution — and the wrong one — is to hardcode the values directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-state-bucket"&lt;/span&gt;  &lt;span class="c1"&gt;# ❌ now it's in the repo&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
    &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-sso-profile"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works. But if the repo is public, you just exposed your state bucket name and SSO profile. And if the repo is private today, it might not be tomorrow.&lt;/p&gt;

&lt;p&gt;The correct solution is the &lt;strong&gt;partial backend&lt;/strong&gt;: leave the block empty in &lt;code&gt;main.tf&lt;/code&gt; and pass the values in a separate file that goes in &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;main.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;backend.hcl&lt;/code&gt; (in &lt;code&gt;.gitignore&lt;/code&gt;, never in the repo):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;  &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"your-state-bucket"&lt;/span&gt;
&lt;span class="nx"&gt;region&lt;/span&gt;  &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"your-sso-profile"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the init looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init &lt;span class="nt"&gt;-backend-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;backend.hcl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repo includes a &lt;code&gt;backend.hcl.example&lt;/code&gt; with the structure and example values. Whoever clones the project copies the file, fills in their values, and runs init. No friction, no exposed secrets.&lt;/p&gt;

&lt;p&gt;This isn't a workaround — it's the pattern Terraform recommends exactly for this case. The language limitation, turned into a security best practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Task Definition and secrets
&lt;/h2&gt;

&lt;p&gt;When you define an ECS Task Definition in Terraform, you have two ways to pass values to the container: &lt;code&gt;environment&lt;/code&gt; and &lt;code&gt;secrets&lt;/code&gt;. They seem equivalent. They're not.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;environment&lt;/code&gt; passes the value directly as an environment variable — visible in plain text in the ECS console, in the task logs, and in any &lt;code&gt;describe-task-definition&lt;/code&gt; that someone with account access runs.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;secrets&lt;/code&gt; does something different: it tells the task to fetch the value from &lt;strong&gt;AWS Secrets Manager&lt;/strong&gt; at execution time, inject it as an environment variable in memory, and never write it anywhere. The value doesn't appear in the task definition. It doesn't appear in the logs. It doesn't appear in the console.&lt;/p&gt;

&lt;p&gt;The Slack webhook URL is exactly the kind of value that shouldn't be in &lt;code&gt;environment&lt;/code&gt;. Anyone with that URL can send messages to your Slack channel on behalf of the system — with no additional authentication, no traceability. It's a credential, not a configuration.&lt;/p&gt;

&lt;p&gt;In the Task Definition it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;secrets&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SLACK_WEBHOOK_URL"&lt;/span&gt;
    &lt;span class="nx"&gt;valueFrom&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_secretsmanager_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slack_webhook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value is created once in Secrets Manager and Terraform only references the ARN. The container receives the variable at runtime — the Python code reads it with &lt;code&gt;os.environ["SLACK_WEBHOOK_URL"]&lt;/code&gt; like any environment variable, but it was never exposed in any definition.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwsfvqmpyr0jz5zjayc27.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwsfvqmpyr0jz5zjayc27.png" alt="Task Definition"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The task's IAM role
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;iam-audit-task-role&lt;/code&gt; is the role the container assumes at runtime. It's the equivalent of the AWS profile you were using locally in the first two posts — but now it's a role with permissions explicitly defined in Terraform, no long-lived credentials, no &lt;code&gt;~/.aws&lt;/code&gt; to mount.&lt;/p&gt;

&lt;p&gt;What the task needs to function is exactly this and nothing more:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# List accounts in the Organization&lt;/span&gt;
&lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;effect&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
  &lt;span class="nx"&gt;actions&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"organizations:ListAccounts"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Assume the audit role in each member account&lt;/span&gt;
&lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;effect&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
  &lt;span class="nx"&gt;actions&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::*:role/iam-audit-role"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Upload the report to the S3 bucket&lt;/span&gt;
&lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;effect&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
  &lt;span class="nx"&gt;actions&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"${aws_s3_bucket.reports.arn}/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Read the Slack secret&lt;/span&gt;
&lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;effect&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
  &lt;span class="nx"&gt;actions&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"secretsmanager:GetSecretValue"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_secretsmanager_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slack_webhook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;*&lt;/code&gt; in resources where it isn't necessary. No &lt;code&gt;AdministratorAccess&lt;/code&gt; because "it's easier." Each permission has a specific reason and a scoped target.&lt;/p&gt;

&lt;p&gt;There's something worth noting: this is the role of the tool that audits least privilege across the entire organization. If that role had excessive permissions, we'd be auditing a principle we don't apply at home. Consistency isn't just aesthetic — it's what makes the project credible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;Every Monday at 9am, without anyone doing anything, this arrives in Slack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🔍 iam-audit | Weekly report
Organization: more than 20 accounts audited
📊 View dashboard → https://s3.amazonaws.com/...?X-Amz-Expires=172800
⏳ Link valid for 48 hours
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing to run. Nothing to remember. No engineer who had to remember on a Monday morning that this tool existed.&lt;/p&gt;

&lt;p&gt;The Fargate Task spun up, audited, uploaded the report, generated the presigned URL, notified, and disappeared. The total cost of that execution is cents — literally. An ephemeral task that runs minutes per week doesn't generate a visible line in the monthly billing.&lt;/p&gt;

&lt;p&gt;That's what it means to automate well: not just that it runs on its own, but that it runs on its own &lt;strong&gt;without leaving infrastructure or cost behind&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a security team in LATAM operating on a tight budget with multiple open fronts, this isn't a minor detail. It's the difference between a tool that gets used and a tool that gets forgotten.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqj542rr3rm111fk3h108.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqj542rr3rm111fk3h108.png" alt="Slack Alert"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Three posts. Three versions of the same problem.&lt;/p&gt;

&lt;p&gt;The first was a question: who has access, with what credentials, and since when? The answer was a Python script that traversed the entire Organization in minutes and surfaced what nobody was looking at.&lt;/p&gt;

&lt;p&gt;The second was a tension: the data was there, but it didn't communicate to all audiences. The answer was a dashboard anyone could read, root account detection, and a Docker image that eliminated the friction of running it.&lt;/p&gt;

&lt;p&gt;The third was an operational constraint: someone had to run it. The answer was turning the tool into a service — ephemeral, automated, secure, and with a cost that doesn't justify a line in the budget.&lt;/p&gt;

&lt;p&gt;Visibility. Communication. Automation.&lt;/p&gt;

&lt;p&gt;That's what we built. Not with commercial platforms, not with an enterprise budget, not with a team of ten people. With Python, Docker, Terraform, and the right design decisions.&lt;/p&gt;

&lt;p&gt;If you're building AWS security in LATAM with the resources you have — not the ones you wish you had — I hope this series gave you something concrete to take with you. Not a framework to memorize. A tool you can run today.&lt;/p&gt;

&lt;p&gt;The repository is on GitHub. The image is on Docker Hub. The IaC is in Terraform. All open, all documented, all yours.&lt;/p&gt;

&lt;p&gt;🔗 GitHub: &lt;a href="https://github.com/gerardokaztro/iam-audit" rel="noopener noreferrer"&gt;gerardokaztro/iam-audit&lt;/a&gt;&lt;br&gt;
🐳 Docker Hub: &lt;a href="https://hub.docker.com/r/gerardokaztro/iam-audit" rel="noopener noreferrer"&gt;gerardokaztro/iam-audit&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Gerardo Castro&lt;/strong&gt; is an AWS Security Hero and Cloud Security Engineer focused on LATAM. Founder and Lead Organizer of the AWS Security Users Group LatAm. He believes the best way to learn cloud security is by building real things — not memorizing frameworks. He writes about what he builds, what he finds, and what he learns along the way.&lt;/p&gt;

&lt;p&gt;🔗 GitHub: &lt;a href="https://github.com/gerardokaztro" rel="noopener noreferrer"&gt;gerardokaztro&lt;/a&gt;&lt;br&gt;
🔗 LinkedIn: &lt;a href="https://linkedin.com/in/gerardokaztro" rel="noopener noreferrer"&gt;gerardokaztro&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>python</category>
      <category>boto3</category>
    </item>
    <item>
      <title>OpenClaw on AWS Lightsail — Threat Model Alignment: OWASP, MITRE ATLAS, and the Gap No Framework Anticipated (Part 3)</title>
      <dc:creator>Gerardo Castro Arica</dc:creator>
      <pubDate>Mon, 23 Mar 2026 06:22:14 +0000</pubDate>
      <link>https://dev.to/aws-heroes/openclaw-on-aws-lightsail-threat-model-alignment-owasp-mitre-atlas-and-the-gap-no-framework-1mdn</link>
      <guid>https://dev.to/aws-heroes/openclaw-on-aws-lightsail-threat-model-alignment-owasp-mitre-atlas-and-the-gap-no-framework-1mdn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Part 3 of the series:&lt;/strong&gt; In Part 1 we audited the initial OpenClaw setup on AWS Lightsail — outdated kernel, the &lt;code&gt;gateway + allow&lt;/code&gt; combination as a critical attack chain, and the Gateway Token exposed in plaintext. In Part 2 we went deep into the full dashboard — channels, agents, cron jobs, logs, and configuration panels. If you haven't read the previous parts, the full index is at the end of this post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note on findings:&lt;/strong&gt; Vectors #7, #8, and #10 identified in Part 2 are pending live validation. The framework mapping in this post confirms that the vector exists and has documented precedent in similar systems — not that it was executed on this specific OpenClaw instance. Controlled environment validation is coming in Part 4.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;References used in this post:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://trust.openclaw.ai/trust/threatmodel" rel="noopener noreferrer"&gt;MITRE ATLAS — OpenClaw Official Threat Model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/" rel="noopener noreferrer"&gt;OWASP Top 10 for Agentic Applications 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/ai/security/agentic-ai-scoping-matrix/" rel="noopener noreferrer"&gt;AWS Agentic AI Security Scoping Matrix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/ai/security/generative-ai-scoping-matrix/" rel="noopener noreferrer"&gt;AWS Generative AI Security Scoping Matrix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/security/the-agentic-ai-security-scoping-matrix-a-framework-for-securing-autonomous-ai-systems/" rel="noopener noreferrer"&gt;AWS Blog — Agentic AI Security Scoping Matrix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/aws-samples/sample-OpenClaw-on-AWS-with-Bedrock" rel="noopener noreferrer"&gt;AWS Official Repo — sample-OpenClaw-on-AWS-with-Bedrock&lt;/a&gt; &lt;em&gt;(contains the explicit acknowledgment of the open prompt injection gap)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The starting point
&lt;/h2&gt;

&lt;p&gt;In the first two parts of this series we audited OpenClaw deployed on AWS Lightsail — not as a user, but as a Cloud Security Engineer with the goal of mapping the attack surface that deployment opens.&lt;/p&gt;

&lt;p&gt;The result was a list of 13 findings distributed across three layers that don't always appear together in the same analysis:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IaaS Layer&lt;/strong&gt; — what Lightsail brings by default and the operator inherits without necessarily knowing it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Blueprint with outdated kernel and libraries&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;IPv6 enabled by default&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Apache2 without documented hardening&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Application Layer&lt;/strong&gt; — what OpenClaw exposes as a system, regardless of where it runs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Gateway Token in plaintext on the dashboard&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;No granular access control on channels&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Indirect prompt injection via external channels&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Memory poisoning via unvalidated context&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;53 skills active by default — opt-out model&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;No permission inheritance model in agent chains&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Config and Debug exposed via Gateway Token&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Intersection Layer&lt;/strong&gt; — where operator configuration activates vectors that no application framework anticipates alone:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;exec_host_policy: gateway&lt;/code&gt; + &lt;code&gt;shell_approval: allow&lt;/code&gt; = no isolation&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Cron Jobs as persistence and defense evasion vector&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Local logs without external export&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These three layers are not a design accident — they are the result of a deploy decision. Someone saw the OpenClaw blueprint on Lightsail, clicked, and inherited an attack surface that no application threat model fully contemplates, because none was designed to do so.&lt;/p&gt;

&lt;p&gt;The question this post answers: what does each existing security framework cover, where do these 13 findings map, and what is left without a home?&lt;/p&gt;

&lt;h2&gt;
  
  
  MITRE ATLAS — OpenClaw's official threat model
&lt;/h2&gt;

&lt;p&gt;MITRE ATLAS (Adversarial Threat Landscape for AI Systems) is the reference framework for modeling threats against artificial intelligence systems. It works with the same logic as MITRE ATT&amp;amp;CK — tactics, techniques, and procedures — but applied to the ML and AI ecosystem.&lt;/p&gt;

&lt;p&gt;The OpenClaw team adopted it as the basis for their official threat model, available at &lt;code&gt;trust.openclaw.ai/threatmodel&lt;/code&gt;. The result is a matrix of 37 threats distributed across 8 tactics, with 6 classified as critical.&lt;/p&gt;

&lt;p&gt;Before mapping the findings, there is something important to understand about the scope of this threat model: &lt;strong&gt;it was designed to model threats against OpenClaw as a system — its skills, its gateway, its execution model, its channels.&lt;/strong&gt; It was not designed to model what happens when someone deploys OpenClaw on a Lightsail VPS with an outdated kernel. That distinction is not a criticism — it is the correct scope for an application threat model.&lt;/p&gt;

&lt;p&gt;With that in mind, the mapping:&lt;/p&gt;

&lt;h3&gt;
  
  
  What ATLAS anticipated — and matches the findings
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;ATLAS Threat&lt;/th&gt;
&lt;th&gt;Tactic&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#7 Indirect prompt injection&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;T-EXEC-002&lt;/code&gt; Indirect Prompt Injection&lt;/td&gt;
&lt;td&gt;Execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#8 Memory poisoning&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;T-PERSIST-005&lt;/code&gt; Prompt Injection Memory Poisoning&lt;/td&gt;
&lt;td&gt;Persistence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#9 53 skills opt-out&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;T-ACCESS-004&lt;/code&gt; Malicious Skill as Entry Point&lt;/td&gt;
&lt;td&gt;Initial Access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#11 Cron Jobs persistence&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;T-EVADE-004&lt;/code&gt; Staged Payload Delivery&lt;/td&gt;
&lt;td&gt;Defense Evasion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#13 Config/Debug exposed&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;T-DISC-002/003/004&lt;/code&gt; Session/Prompt/Env Enumeration&lt;/td&gt;
&lt;td&gt;Discovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#3 Gateway Token&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;T-ACCESS-003&lt;/code&gt; Token Theft&lt;/td&gt;
&lt;td&gt;Initial Access&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These findings have a home in ATLAS. The framework anticipated them, assigned a technique, and placed them in an attack chain.&lt;/p&gt;

&lt;h3&gt;
  
  
  What ATLAS does not cover — and why
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Why it falls outside&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#1 Outdated kernel&lt;/td&gt;
&lt;td&gt;Operator infrastructure — out of scope by design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#4 IPv6 enabled&lt;/td&gt;
&lt;td&gt;Operator network configuration — out of scope by design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#5 Apache2 without hardening&lt;/td&gt;
&lt;td&gt;Operator responsibility — out of scope by design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#2 exec_host_policy + allow&lt;/td&gt;
&lt;td&gt;Intersection between operator configuration and application design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#6 No access control on channels&lt;/td&gt;
&lt;td&gt;ATLAS documents &lt;code&gt;AllowFrom&lt;/code&gt; as a trust boundary, but does not model the gap when that control is simply not configured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#10 Permission inheritance&lt;/td&gt;
&lt;td&gt;No threat models privilege escalation between agents with different configurations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#12 Local logs&lt;/td&gt;
&lt;td&gt;Completely absent from the threat model&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The most interesting finding from the mapping:&lt;/strong&gt; Finding #2 best illustrates the gap between application threat model and deploy reality. OpenClaw documents &lt;code&gt;exec_host_policy&lt;/code&gt; as a configuration the operator controls. ATLAS models &lt;code&gt;T-EXEC-004 Exec Approval Bypass&lt;/code&gt; as a threat. But neither explicitly models what happens when the operator activates the most permissive combination by default — which is exactly what the Lightsail blueprint does.&lt;/p&gt;

&lt;p&gt;The threat model assumes the operator makes informed decisions. The blueprint assumes the operator wants simplicity. The intersection of those two assumptions is Finding #2.&lt;/p&gt;

&lt;h2&gt;
  
  
  OWASP Top 10 for Agentic Applications 2026
&lt;/h2&gt;

&lt;p&gt;In December 2025, OWASP published the first security framework specific to agentic applications. It is not an extension of the Top 10 for LLMs — it is an independent list, built on the recognition that autonomous agents have a fundamentally different threat model than a chatbot.&lt;/p&gt;

&lt;p&gt;The central difference: an LLM that fails produces an incorrect response. An agent that fails executes incorrect actions on real systems.&lt;/p&gt;

&lt;p&gt;The 10 framework categories:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AG01&lt;/td&gt;
&lt;td&gt;Prompt Injection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AG02&lt;/td&gt;
&lt;td&gt;Memory Poisoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AG03&lt;/td&gt;
&lt;td&gt;Tool Misuse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AG04&lt;/td&gt;
&lt;td&gt;Privilege Escalation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AG05&lt;/td&gt;
&lt;td&gt;Unsafe Agent Chaining&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AG06&lt;/td&gt;
&lt;td&gt;Resource Exhaustion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AG07&lt;/td&gt;
&lt;td&gt;Data Exfiltration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AG08&lt;/td&gt;
&lt;td&gt;Uncontrolled Agent Spawning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AG09&lt;/td&gt;
&lt;td&gt;Over-Permissioned Agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AG10&lt;/td&gt;
&lt;td&gt;Excessive Trust of Agent Output&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The mapping with the findings
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;OWASP Category&lt;/th&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#7 Indirect prompt injection&lt;/td&gt;
&lt;td&gt;AG01 Prompt Injection&lt;/td&gt;
&lt;td&gt;Direct coverage — OWASP distinguishes direct and indirect prompt injection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#8 Memory poisoning&lt;/td&gt;
&lt;td&gt;AG02 Memory Poisoning&lt;/td&gt;
&lt;td&gt;Direct coverage — and in OpenClaw memory is plain Markdown files on disk, which amplifies the vector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#9 53 skills opt-out&lt;/td&gt;
&lt;td&gt;AG03 Tool Misuse + AG09 Over-Permissioned Agents&lt;/td&gt;
&lt;td&gt;Double mapping — the opt-out model inverts the principle of least privilege&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#10 Permission inheritance&lt;/td&gt;
&lt;td&gt;AG04 Privilege Escalation + AG05 Unsafe Agent Chaining&lt;/td&gt;
&lt;td&gt;Double mapping — the absence of documentation on permission inheritance is exactly AG05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#11 Cron Jobs&lt;/td&gt;
&lt;td&gt;AG08 Uncontrolled Agent Spawning&lt;/td&gt;
&lt;td&gt;Partial — Cron Jobs don't spawn new agents, but create unsupervised autonomous executions with the same effect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#6 No access control on channels&lt;/td&gt;
&lt;td&gt;AG09 Over-Permissioned Agents&lt;/td&gt;
&lt;td&gt;A channel without access control is equivalent to an agent with excessive permissions over its input surface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#2 exec_host_policy + allow&lt;/td&gt;
&lt;td&gt;AG03 Tool Misuse&lt;/td&gt;
&lt;td&gt;Partial — OWASP models tool abuse, but not the deploy configuration that eliminates the sandbox&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What OWASP does not cover
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Why it falls outside&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#1 Outdated kernel&lt;/td&gt;
&lt;td&gt;Infrastructure — out of scope by design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#4 IPv6&lt;/td&gt;
&lt;td&gt;Infrastructure — out of scope by design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#5 Apache2&lt;/td&gt;
&lt;td&gt;Infrastructure — out of scope by design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#12 Local logs&lt;/td&gt;
&lt;td&gt;OWASP has no observability category — the absence of external logging has no home&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#13 Config/Debug exposed&lt;/td&gt;
&lt;td&gt;OWASP touches it tangentially in AG07 (exfiltration) but does not model the exposure of internal panels via a shared token&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The most revealing finding from the OWASP mapping:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finding #9 maps simultaneously to AG03 and AG09 — Tool Misuse and Over-Permissioned Agents. That is not an editorial coincidence. It is the direct consequence of the opt-out model: when everything is enabled by default, each unnecessarily active skill is simultaneously an excessive permission and a potential abuse surface.&lt;/p&gt;

&lt;p&gt;OWASP anticipated it conceptually. What it did not anticipate is that a real and popular system would implement it in reverse — not "enable what you need" but "disable what you don't want." The difference is philosophical but the consequences are operational.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Agentic AI Security Scoping Matrix
&lt;/h2&gt;

&lt;p&gt;AWS published two complementary frameworks that coexist and reference each other.&lt;/p&gt;

&lt;p&gt;The first is the &lt;strong&gt;Generative AI Security Scoping Matrix&lt;/strong&gt; — 5 scopes based on how much ownership the organization has over the model. OpenClaw on Lightsail falls in &lt;strong&gt;Scope 3&lt;/strong&gt;: building a custom application using a pre-trained model via API. The model is not trained or fine-tuned — it is invoked. Security responsibilities in Scope 3 are divided between AWS (the model, Bedrock infrastructure) and the operator (the application, the integration, the data passed to it).&lt;/p&gt;

&lt;p&gt;The second is the &lt;strong&gt;Agentic AI Security Scoping Matrix&lt;/strong&gt; — published in November 2025, specifically designed for autonomous systems. It categorizes four scopes based on two dimensions: level of agency (what actions the system can take) and level of autonomy (how much human oversight exists).&lt;/p&gt;

&lt;p&gt;This second matrix speaks most directly to the findings in this series.&lt;/p&gt;

&lt;h3&gt;
  
  
  What scope does OpenClaw operate in with the audited configuration?
&lt;/h3&gt;

&lt;p&gt;The default configuration of the Lightsail blueprint — &lt;code&gt;exec_host_policy: gateway&lt;/code&gt; + &lt;code&gt;shell_approval: allow&lt;/code&gt; — places OpenClaw in &lt;strong&gt;Scope 4: Full Agency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;AWS's definition for Scope 4 is clear: systems that self-initiate, operate continuously with minimal human supervision, and can execute complex workflows autonomously. OpenClaw's Cron Jobs are exactly this — scheduled tasks that execute without operator intervention.&lt;/p&gt;

&lt;p&gt;The problem is not that Scope 4 exists. The problem is that &lt;strong&gt;Scope 4 requires the most sophisticated controls in the framework&lt;/strong&gt; — continuous behavioral monitoring, automatic circuit breakers, guaranteed rollback mechanisms, real-time anomaly detection. And the Lightsail blueprint does not configure any of them.&lt;/p&gt;

&lt;p&gt;It is the same pattern identified with MITRE ATLAS: the system arrives configured to operate with maximum autonomy, but without the controls that autonomy requires.&lt;/p&gt;

&lt;h3&gt;
  
  
  The mapping with the 6 security dimensions
&lt;/h3&gt;

&lt;p&gt;The Agentic AI Security Scoping Matrix organizes controls across six dimensions. This is where the findings find their most precise placement:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identity Context&lt;/strong&gt; — user, service, and agent identity management.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#3 Gateway Token in plaintext&lt;/td&gt;
&lt;td&gt;The token is the gateway's only authentication mechanism — no rotation, no scope, no expiration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#6 No granular access on channels&lt;/td&gt;
&lt;td&gt;The framework requires appropriate authentication per scope level. In Scope 4, any channel participant can instruct the agent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Data, Memory &amp;amp; State Protection&lt;/strong&gt; — persistent memory security and memory poisoning prevention.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#8 Memory poisoning&lt;/td&gt;
&lt;td&gt;AWS explicitly names memory poisoning as a critical vector in this dimension. In OpenClaw memory is Markdown files on disk — no integrity validation, no encryption&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is the finding with the most direct coverage in the entire matrix. AWS anticipated it, named it, and described exactly the risk. OpenClaw's implementation materializes it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit &amp;amp; Logging&lt;/strong&gt; — complete action traceability and reasoning chain capture.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#12 Local logs without export&lt;/td&gt;
&lt;td&gt;The framework requires tamper-resistant logs, especially in Scope 4. Modifiable local logs are the opposite of what Scope 4 requires&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#11 Cron Jobs as persistence&lt;/td&gt;
&lt;td&gt;Without external logging, a malicious task can execute and erase its trail before anyone detects it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Agent &amp;amp; LLM Controls&lt;/strong&gt; — guardrails, behavioral monitoring, sandboxing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#2 exec_host_policy + allow&lt;/td&gt;
&lt;td&gt;Eliminates the sandbox. The framework requires containerization and resource quotas in Scope 4 — the default configuration does exactly the opposite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#7 Indirect prompt injection&lt;/td&gt;
&lt;td&gt;The framework mentions behavioral monitoring as a control. Without it, an indirect injection can execute undetected&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Agency Perimeters &amp;amp; Policies&lt;/strong&gt; — operational boundaries and dynamic constraint evaluation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#9 53 skills opt-out&lt;/td&gt;
&lt;td&gt;The framework is explicit: agents must operate within the limits of their designed purpose. 53 skills active by default is the opposite — maximum surface, minimum restriction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#10 Permission inheritance&lt;/td&gt;
&lt;td&gt;The framework does not specify how permissions should propagate in agent chains — that gap is Finding #10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Orchestration&lt;/strong&gt; — agent-to-system interaction management, tool access, execution flow control.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#10 Permission inheritance&lt;/td&gt;
&lt;td&gt;The Orchestration dimension discusses inter-agent coordination protocols, but does not define the permission inheritance model in delegation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#11 Cron Jobs&lt;/td&gt;
&lt;td&gt;The framework mentions transaction management and rollback mechanisms — Cron Jobs have neither&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What the matrix does not cover
&lt;/h3&gt;

&lt;p&gt;The Agentic AI Security Scoping Matrix implicitly assumes deployment is on AWS managed services — Bedrock, AgentCore, Lambda. It says nothing about what happens when the blueprint arrives with an outdated kernel, Apache without hardening, or IPv6 enabled by default.&lt;/p&gt;

&lt;p&gt;That is not a criticism of the framework — it is the correct scope. AWS designed this matrix for the application and orchestration layer, not for the IaaS layer. The operator who chooses Lightsail inherits a layer of responsibilities that neither matrix contemplates.&lt;/p&gt;

&lt;p&gt;That layer is unmapped territory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap — what no framework anticipated
&lt;/h2&gt;

&lt;p&gt;Three frameworks reviewed. One designed by the OpenClaw team itself, one by OWASP, one by AWS. All three do their job well. And all three leave the same territory uncovered.&lt;/p&gt;

&lt;p&gt;Before naming it, it is worth seeing the pattern:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;MITRE ATLAS&lt;/th&gt;
&lt;th&gt;OWASP Agentic&lt;/th&gt;
&lt;th&gt;AWS Scoping Matrix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#1 Outdated kernel&lt;/td&gt;
&lt;td&gt;❌ out of scope&lt;/td&gt;
&lt;td&gt;❌ out of scope&lt;/td&gt;
&lt;td&gt;❌ out of scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#2 exec_host_policy + allow&lt;/td&gt;
&lt;td&gt;⚠️ partial&lt;/td&gt;
&lt;td&gt;⚠️ partial&lt;/td&gt;
&lt;td&gt;⚠️ partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#3 Gateway Token&lt;/td&gt;
&lt;td&gt;✅ T-ACCESS-003&lt;/td&gt;
&lt;td&gt;⚠️ tangential&lt;/td&gt;
&lt;td&gt;✅ Identity Context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#4 IPv6&lt;/td&gt;
&lt;td&gt;❌ out of scope&lt;/td&gt;
&lt;td&gt;❌ out of scope&lt;/td&gt;
&lt;td&gt;❌ out of scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#5 Apache2&lt;/td&gt;
&lt;td&gt;❌ out of scope&lt;/td&gt;
&lt;td&gt;❌ out of scope&lt;/td&gt;
&lt;td&gt;❌ out of scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#6 No channel access&lt;/td&gt;
&lt;td&gt;⚠️ partial&lt;/td&gt;
&lt;td&gt;✅ AG09&lt;/td&gt;
&lt;td&gt;✅ Identity Context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#7 Prompt injection&lt;/td&gt;
&lt;td&gt;✅ T-EXEC-002&lt;/td&gt;
&lt;td&gt;✅ AG01&lt;/td&gt;
&lt;td&gt;⚠️ mentioned, not resolved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#8 Memory poisoning&lt;/td&gt;
&lt;td&gt;✅ T-PERSIST-005&lt;/td&gt;
&lt;td&gt;✅ AG02&lt;/td&gt;
&lt;td&gt;✅ Data &amp;amp; Memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#9 53 skills opt-out&lt;/td&gt;
&lt;td&gt;✅ T-ACCESS-004&lt;/td&gt;
&lt;td&gt;✅ AG03+AG09&lt;/td&gt;
&lt;td&gt;✅ Agency Perimeters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#10 Permission inheritance&lt;/td&gt;
&lt;td&gt;❌ absent&lt;/td&gt;
&lt;td&gt;✅ AG04+AG05&lt;/td&gt;
&lt;td&gt;⚠️ partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#11 Cron Jobs&lt;/td&gt;
&lt;td&gt;⚠️ partial&lt;/td&gt;
&lt;td&gt;⚠️ partial&lt;/td&gt;
&lt;td&gt;⚠️ partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#12 Local logs&lt;/td&gt;
&lt;td&gt;❌ absent&lt;/td&gt;
&lt;td&gt;❌ absent&lt;/td&gt;
&lt;td&gt;✅ Audit &amp;amp; Logging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#13 Config/Debug&lt;/td&gt;
&lt;td&gt;⚠️ partial&lt;/td&gt;
&lt;td&gt;⚠️ tangential&lt;/td&gt;
&lt;td&gt;⚠️ tangential&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pattern is clear. Findings from the &lt;strong&gt;application layer&lt;/strong&gt; have a home in at least one of the three frameworks. Findings from the &lt;strong&gt;IaaS layer&lt;/strong&gt; have no home in any of them. And findings from the &lt;strong&gt;intersection layer&lt;/strong&gt; have partial coverage in all — no framework captures them completely because none was designed to see both layers at the same time.&lt;/p&gt;

&lt;h3&gt;
  
  
  The gap has a name
&lt;/h3&gt;

&lt;p&gt;What is missing is not one more finding. It is a layer of analysis that existing frameworks do not contemplate by design: &lt;strong&gt;the attack surface opened by the decision to deploy on IaaS when the system being deployed is an autonomous agent.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An application threat model assumes that infrastructure is the operator's responsibility. An infrastructure framework assumes that the application running on top is the developer's responsibility. Neither models the intersection — the point where operator configuration activates vectors that the application threat model did not anticipate, and vice versa.&lt;/p&gt;

&lt;p&gt;Finding #2 is the clearest example: &lt;code&gt;exec_host_policy: gateway + shell_approval: allow&lt;/code&gt; is an operator configuration decision that eliminates the agent's only isolation mechanism. It is not an OpenClaw bug — it is a documented option. It is not an operator error — it is the blueprint's default value. Responsibility is distributed in a way that no framework captures in a single place.&lt;/p&gt;

&lt;h3&gt;
  
  
  The validation that was not expected
&lt;/h3&gt;

&lt;p&gt;This is not just an independent observation. The official AWS repository — &lt;code&gt;sample-OpenClaw-on-AWS-with-Bedrock&lt;/code&gt; — documents it explicitly in its README:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Plan A is soft enforcement — the LLM can theoretically be bypassed via prompt injection. Plan E catches what Plan A misses. For hard enforcement via AgentCore Gateway MCP mode, see Roadmap."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AWS, in its own reference repository for deploying OpenClaw, acknowledges that prompt injection is not resolved and places it on the roadmap. This is not an inferred gap — it is a gap the AWS technical team documented while building the solution.&lt;/p&gt;

&lt;p&gt;And prompt injection is Finding #7 — the critical vector identified in Part 2, which none of the three frameworks fully resolves in the context of an IaaS deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this means for anyone deploying OpenClaw today
&lt;/h3&gt;

&lt;p&gt;Deploying OpenClaw on Lightsail following the official blueprint means operating an agent at &lt;strong&gt;Scope 4 of the AWS Agentic AI Security Scoping Matrix&lt;/strong&gt; — maximum autonomy — without the controls Scope 4 requires, on infrastructure that no agentic security framework models, with a prompt injection vector that the AWS team itself acknowledges as pending work.&lt;/p&gt;

&lt;p&gt;OpenClaw is not broken. Lightsail is not insecure. The combination of both on the default configuration opens an attack surface that requires an analysis that does not yet exist as a unified framework.&lt;/p&gt;

&lt;p&gt;That is what comes next.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Three frameworks analyzed. Thirteen findings mapped. A gap identified with real evidence.&lt;/p&gt;

&lt;p&gt;Something important about several findings mapped in this post: findings #7, #8, and #10 are marked as risk vectors identified but not executed live. The framework mapping confirms the vector exists and has documented precedent. Validation that it works in this specific implementation comes in Part 4.&lt;/p&gt;

&lt;p&gt;And the demo may reveal something that static analysis did not show. That is not a warning — it is the method. An autonomous agent cannot be audited solely from the dashboard. At some point you have to see what it does when something goes wrong.&lt;/p&gt;

&lt;p&gt;Part 4 closes the series with what frameworks cannot replace: live evidence. Prompt injection executed in a controlled environment, validation of pending findings, and the first elements of something that does not yet exist in LATAM: a unified framework for evaluating the security posture of an autonomous agent considering all three layers together.&lt;/p&gt;

&lt;p&gt;Not a whitepaper. Not a checklist. Something built from real findings, on a real system, deployed on real infrastructure.&lt;/p&gt;

&lt;p&gt;If you arrived here without reading Part 1 and Part 2, the full series index is here:&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://dev.to/posts/despliegue-openclaw-aws-hallazgos-cloud-security-engineer"&gt;Part 1 — Secure setup and first infrastructure findings&lt;/a&gt;&lt;br&gt;
→ &lt;a href="https://dev.to/posts/openclaw-channels-agents-cronjobs-superficie-ataque"&gt;Part 2 — Full dashboard: channels, agents, cron jobs, and logs&lt;/a&gt;&lt;br&gt;
→ Part 3 — this post&lt;br&gt;
→ Part 4 — coming soon&lt;/p&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Gerardo Castro&lt;/strong&gt; is an AWS Security Hero and Cloud Security Engineer focused on LATAM. Founder and Lead Organizer of the AWS Security Users Group LatAm. He believes the best way to learn cloud security is by building real things — not memorizing frameworks. He writes about what he builds, what he finds, and what he learns along the way.&lt;/p&gt;

&lt;p&gt;🔗 LinkedIn: &lt;a href="https://linkedin.com/in/gerardokaztro" rel="noopener noreferrer"&gt;gerardokaztro&lt;/a&gt;&lt;br&gt;
🔗 Blog: &lt;a href="https://roadtocloudsec.la" rel="noopener noreferrer"&gt;roadtocloudsec.la&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>aws</category>
      <category>security</category>
      <category>openclaw</category>
    </item>
    <item>
      <title>I Kept Auditing OpenClaw on AWS Lightsail: 53 Default Skills, No Channel Access Controls, Deletable Logs (Part 2)</title>
      <dc:creator>Gerardo Castro Arica</dc:creator>
      <pubDate>Thu, 12 Mar 2026 14:55:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/i-kept-auditing-openclaw-on-aws-lightsail-53-default-skills-no-channel-access-controls-deletable-37e8</link>
      <guid>https://dev.to/aws-heroes/i-kept-auditing-openclaw-on-aws-lightsail-53-default-skills-no-channel-access-controls-deletable-37e8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Part 2 of a series:&lt;/strong&gt; In Part 1 we audited the initial OpenClaw setup on AWS Lightsail — outdated kernel, the &lt;code&gt;gateway + allow&lt;/code&gt; attack chain, and the Gateway Token exposed in plaintext. If you haven't read it, &lt;a href="https://dev.to/posts/despliegue-openclaw-aws-hallazgos-cloud-security-engineer"&gt;start there&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Part 1 I closed with a warning: secure setup is the starting point, not the destination.&lt;/p&gt;

&lt;p&gt;Once the server is patched, the firewall restricted, and security settings reviewed — there's still the entire dashboard to explore. And the OpenClaw dashboard isn't just a UI. It's a map of attack surfaces: each section has its own security implications, its own trust model, its own blast radius if something goes wrong.&lt;/p&gt;

&lt;p&gt;Channels, Agents, Cron Jobs, Nodes, Logs, Config and Debug. I reviewed them all.&lt;/p&gt;

&lt;p&gt;What I found isn't catastrophic — OpenClaw isn't broken. But it's also not production-ready with the default configuration. And there are design decisions every team should understand before connecting this agent to their messaging channels or infrastructure.&lt;/p&gt;

&lt;p&gt;That's what we're looking at in this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Channels: the input vector that comes from outside
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a Channel in OpenClaw
&lt;/h3&gt;

&lt;p&gt;A Channel is a messaging integration — WhatsApp, Telegram, Discord, Slack, Signal, Google Chat. Once configured, any message that arrives through that channel becomes a prompt for the agent.&lt;/p&gt;

&lt;p&gt;The UI makes it look simple: channel name, API token, webhook URL. Five minutes and it's running.&lt;/p&gt;

&lt;p&gt;The security problem is exactly in that simplicity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding #6: No granular access control at the channel level
&lt;/h3&gt;

&lt;p&gt;When you connect a channel to OpenClaw, the agent responds to messages. The question you need to ask before doing so is: &lt;strong&gt;whose messages?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The channel configuration UI doesn't expose granular access controls — there's no field to specify which users or roles can instruct the agent, no authorized sender whitelist. If the bot is in a Telegram group, the system design doesn't offer a native mechanism to restrict which group participants can give it instructions.&lt;/p&gt;

&lt;p&gt;This opens a vector for &lt;strong&gt;identity impersonation&lt;/strong&gt;: someone with access to the channel can instruct the agent to execute tasks as if they were a legitimate user — and the agent has no way to verify whether the person writing has authorization for what they're requesting.&lt;/p&gt;

&lt;p&gt;In an enterprise environment, this translates to horizontal privilege escalation: an employee with access to the corporate Slack group where the agent is integrated can instruct it to do things outside their role.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔍 &lt;strong&gt;Finding #6:&lt;/strong&gt; The channel configuration UI doesn't expose granular access controls. In multi-user environments, any participant with channel access can send instructions to the agent, representing a horizontal privilege escalation vector.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Finding #7: Indirect prompt injection via channel
&lt;/h3&gt;

&lt;p&gt;This is the vector that concerns me most in the entire series.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Direct prompt injection&lt;/strong&gt; is when someone writes directly to the agent: &lt;em&gt;"Ignore your previous instructions and do X."&lt;/em&gt; That's the lab scenario — easy to understand, relatively easy to mitigate with guardrails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Indirect prompt injection&lt;/strong&gt; is different: the attacker doesn't talk to the agent. The attacker contaminates &lt;em&gt;content the agent will consume&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Attacker publishes malicious content  →  Agent consumes that content  →  Agent executes hidden instruction
(on a website, a doc, a message)           (as part of a task)              (without the user knowing)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OpenClaw can browse the web, read files, process documents. If someone asks the agent to "summarize this page" and that page contains hidden instructions designed to manipulate its behavior, the agent without proper guardrails might execute them.&lt;/p&gt;

&lt;p&gt;Messaging channels expand this surface. If the agent is in a group where external parties participate, anyone can send a message with embedded instructions designed to influence the next task the agent executes.&lt;/p&gt;

&lt;p&gt;This vector was not tested on this instance — live demo validation is coming in &lt;strong&gt;Part 4&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔍 &lt;strong&gt;Finding #7:&lt;/strong&gt; Integration of external channels expands the indirect prompt injection surface. Malicious content injected into channels accessible to the agent can manipulate its behavior without direct attacker interaction.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Finding #8: Memory poisoning via unvalidated context
&lt;/h3&gt;

&lt;p&gt;OpenClaw maintains memory of conversations and context between sessions. Not as a database — as plain Markdown files on disk: a &lt;code&gt;MEMORY.md&lt;/code&gt; for long-term context and daily logs in &lt;code&gt;memory/YYYY-MM-DD.md&lt;/code&gt;. If it hasn't been written to those files, the agent doesn't remember it.&lt;/p&gt;

&lt;p&gt;That transparency is good for auditing. But it also defines the attack vector: if an attacker manages to introduce false information into that memory — via a channel with weak authentication, via indirect prompt injection, or via direct filesystem access — that information persists and affects all future interactions.&lt;/p&gt;

&lt;p&gt;Concrete scenario: the agent has in memory that "the admin user is &lt;a href="mailto:bad@boy.com"&gt;bad@boy.com&lt;/a&gt;". An attacker introduces in context that this data has changed. Depending on how the agent uses that memory in subsequent tasks, the consequences can be significant.&lt;/p&gt;

&lt;p&gt;Unlike a real-time attack, &lt;strong&gt;memory poisoning is persistent&lt;/strong&gt; — the effects continue even after the attacker has lost access to the system.&lt;/p&gt;

&lt;p&gt;Validation of this vector is also coming in &lt;strong&gt;Part 4&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔍 &lt;strong&gt;Finding #8:&lt;/strong&gt; OpenClaw's memory consists of Markdown files on disk, with no integrity validation mechanisms. Malicious information introduced into the memory context can persistently affect agent behavior, even after the attacker loses access.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Agents: the default permissions problem
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What are Agents in OpenClaw
&lt;/h3&gt;

&lt;p&gt;OpenClaw allows configuring agents with different models, enabled skills, and system instructions. An agent can also delegate tasks to another agent.&lt;/p&gt;

&lt;p&gt;From a productivity perspective, that's powerful. From a security perspective, it introduces questions that modern frameworks are still trying to resolve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding #9: 53 skills active by default — opt-out model, not opt-in
&lt;/h3&gt;

&lt;p&gt;OpenClaw installs &lt;strong&gt;53 bundled skills&lt;/strong&gt; as part of the system. The default behavior isn't "nothing until you enable it": it's &lt;strong&gt;everything active unless you explicitly disable it&lt;/strong&gt;. Skills activate automatically if the corresponding CLI tool is available on the system.&lt;/p&gt;

&lt;p&gt;When reviewing the &lt;code&gt;main&lt;/code&gt; agent in the dashboard, I confirmed 50 skills enabled — the difference from 53 is explained by skills whose dependent binaries aren't installed on this Lightsail instance.&lt;/p&gt;

&lt;p&gt;Among the capabilities available by default: code execution, filesystem access, web browsing, email sending, external API interaction, process management.&lt;/p&gt;

&lt;p&gt;The problem isn't the number — it's the model. The principle of least privilege says a system should only have access to what it needs to fulfill its function. If your agent is configured to answer questions about internal documentation, it doesn't need the ability to send emails or full filesystem access.&lt;/p&gt;

&lt;p&gt;Every unnecessarily active skill is additional attack surface — either because an attacker uses it after compromising the agent, or because the agent itself acts unexpectedly in response to an ambiguous instruction.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔍 &lt;strong&gt;Finding #9:&lt;/strong&gt; OpenClaw installs 53 bundled skills with an opt-out model: everything active by default, the operator must explicitly disable what they don't need. This inverts the principle of least privilege and maximizes blast radius under any compromise or unexpected behavior.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Finding #10: Privilege escalation in agent chains
&lt;/h3&gt;

&lt;p&gt;When Agent A delegates a task to Agent B, what permissions does Agent B have to execute it?&lt;/p&gt;

&lt;p&gt;OpenClaw's official documentation doesn't define a permission inheritance model between agents — it's not specified which restrictions from the origin agent propagate to the destination agent in a delegation chain. That means the operator has no visibility into how trust propagates through the system.&lt;/p&gt;

&lt;p&gt;The risk derived from that gap: if Agent A has strict restrictions but Agent B has more permissive configurations, an attacker who manages to get Agent A to delegate to Agent B could bypass the first agent's controls. The pattern is analogous to privilege escalation in Unix systems — you don't compromise root directly, you compromise a process with sudo that can execute what you need.&lt;/p&gt;

&lt;p&gt;This is an analysis vector, not a proven finding. Validation is coming in &lt;strong&gt;Part 4&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔍 &lt;strong&gt;Finding #10:&lt;/strong&gt; OpenClaw doesn't document a permission inheritance model between agents. The absence of specification on how trust propagates in delegation chains opens a privilege escalation vector between agents with different security configurations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cron Jobs: the automation nobody supervises
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What are Cron Jobs in OpenClaw
&lt;/h3&gt;

&lt;p&gt;If you've ever scheduled a task on Linux with &lt;code&gt;cron&lt;/code&gt;, the idea is the same. In OpenClaw you can tell the agent: &lt;em&gt;"run this task every day at 8am"&lt;/em&gt;, and it does — without you being present.&lt;/p&gt;

&lt;p&gt;The UI is simple and intuitive. That's part of the problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding #11: Cron Jobs as a persistence and defense evasion vector
&lt;/h3&gt;

&lt;p&gt;An attacker who gains access to the OpenClaw dashboard — via a compromised Gateway Token, for example — can create a Cron Job that establishes persistence, evades detection, or gradually exfiltrates data.&lt;/p&gt;

&lt;p&gt;The persistence vector is direct: a scheduled task that executes an outbound connection every hour maintains access to the system even if the original vector was closed. The defense evasion one too: tasks that periodically delete or modify logs make a compromise much harder to detect.&lt;/p&gt;

&lt;p&gt;The most concerning aspect is that &lt;strong&gt;a malicious task is visually indistinguishable from legitimate use&lt;/strong&gt; in the Cron Jobs dashboard list. There's no visible difference between "check system status every hour" and a persistence task.&lt;/p&gt;

&lt;p&gt;OpenClaw has a Logs section in the dashboard, but we didn't validate whether it records Cron Job creation or with what level of detail — that's something we'll examine in the &lt;strong&gt;Part 4&lt;/strong&gt; demo.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔍 &lt;strong&gt;Finding #11:&lt;/strong&gt; Cron Jobs can be used as a post-compromise persistence and defense evasion mechanism. There's no visual distinction between legitimate and malicious tasks in the UI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Nodes: distributing execution, distributing risk
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What are Nodes in OpenClaw
&lt;/h3&gt;

&lt;p&gt;Nodes allow distributing agent execution across multiple machines. Instead of everything running on a single instance, you can have independent nodes executing tasks in parallel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observation on Nodes
&lt;/h3&gt;

&lt;p&gt;Each node has its own exec approvals configuration — independent from the global configuration. You can have a node with &lt;code&gt;ask&lt;/code&gt; (the agent asks permission before executing) and another with &lt;code&gt;allow&lt;/code&gt; (executes freely), running simultaneously in the same system.&lt;/p&gt;

&lt;p&gt;This has an interesting security implication: &lt;strong&gt;blast radius from a compromise is contained to the affected node&lt;/strong&gt;. If an attacker compromises one node, they don't necessarily have access to the others.&lt;/p&gt;

&lt;p&gt;But it also works in reverse: if a node has more permissive configurations than the rest, that node becomes the weakest link in the chain — the path of least resistance for an attacker.&lt;/p&gt;

&lt;p&gt;We didn't identify a concrete finding here because the behavior is by design. What is clear is that in a multi-node system, the security posture of the entire system is determined by the most permissive node, not the average.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logs: the record that can disappear
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What are Logs in OpenClaw
&lt;/h3&gt;

&lt;p&gt;OpenClaw has a Logs section in the dashboard that shows agent activity and allows downloading them locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding #12: Local logs without external export
&lt;/h3&gt;

&lt;p&gt;The problem isn't that logs exist — the problem is where they live.&lt;/p&gt;

&lt;p&gt;OpenClaw's logs are stored locally on the server. That has two direct security implications.&lt;/p&gt;

&lt;p&gt;First: if the agent has &lt;code&gt;exec host policy&lt;/code&gt; set to &lt;code&gt;gateway&lt;/code&gt; — the attack chain we saw in Part 1 — any process with filesystem access can modify or delete them. An attacker who compromises the server can clean up evidence of their activity before anyone detects it.&lt;/p&gt;

&lt;p&gt;Second: if the instance fails, logs are lost with it. No historical visibility, no event correlation between sessions, no way to reconstruct what the agent did last week.&lt;/p&gt;

&lt;p&gt;The obvious countermeasure is shipping logs to an external, immutable destination — CloudWatch Logs on AWS, for example — before they can be altered.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔍 &lt;strong&gt;Finding #12:&lt;/strong&gt; OpenClaw's logs are exclusively local. Combined with an agent with filesystem access, this facilitates post-compromise defense evasion and eliminates historical audit capability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Config and Debug: internal information within token reach
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What are these panels
&lt;/h3&gt;

&lt;p&gt;The OpenClaw dashboard includes two panels that aren't for end users — they're for whoever administers the system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Config&lt;/strong&gt; shows the agent configuration: model, paths, timeouts, gateway parameters. &lt;strong&gt;Debug&lt;/strong&gt; shows real-time internal diagnostics: component status, errors, internal system paths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding #13: Internal information exposure via Gateway Token
&lt;/h3&gt;

&lt;p&gt;Anyone who has the Gateway Token can access these panels.&lt;/p&gt;

&lt;p&gt;The problem isn't new — we already saw it in Part 1: the token is visible in plaintext in the dashboard. But what we hadn't dimensioned is what information gets exposed if that token leaks.&lt;/p&gt;

&lt;p&gt;With access to Config and Debug, an attacker has before executing any action: the complete map of how the agent is configured, the internal system paths, real-time status of each component, and error messages with internal architecture details.&lt;/p&gt;

&lt;p&gt;That's not an execution vulnerability — it's free reconnaissance. And reconnaissance is the first step of any targeted attack.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔍 &lt;strong&gt;Finding #13:&lt;/strong&gt; Config and Debug panels expose internal system information to any Gateway Token holder. Combined with a token that's never been rotated, this is equivalent to permanent read access to the agent's internal state.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Findings summary
&lt;/h2&gt;

&lt;p&gt;Combining findings from Part 1 and this Part 2, the complete map so far:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Surface&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Blueprint with outdated kernel and libraries&lt;/td&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;exec host policy: gateway&lt;/code&gt; + &lt;code&gt;shell approval: allow&lt;/code&gt; = no isolation&lt;/td&gt;
&lt;td&gt;Security Settings&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Gateway Token in plaintext on dashboard&lt;/td&gt;
&lt;td&gt;Authentication&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;IPv6 enabled by default&lt;/td&gt;
&lt;td&gt;Network&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Apache2 without documented hardening&lt;/td&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;No granular access control on channels&lt;/td&gt;
&lt;td&gt;Channels&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Indirect prompt injection via external channels&lt;/td&gt;
&lt;td&gt;Channels / Agent&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Memory poisoning via unvalidated context&lt;/td&gt;
&lt;td&gt;Agent&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;53 skills active by default — opt-out model&lt;/td&gt;
&lt;td&gt;Agent&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;No permission inheritance model in agent chains&lt;/td&gt;
&lt;td&gt;Agents&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Cron Jobs as persistence and defense evasion vector&lt;/td&gt;
&lt;td&gt;Cron Jobs&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Local logs without external export&lt;/td&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Config and Debug panels exposed via Gateway Token&lt;/td&gt;
&lt;td&gt;Dashboard&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Thirteen findings. Two critical, most of them high.&lt;/p&gt;

&lt;p&gt;But a list of findings without structure is just noise. The next step is to give them a framework — understand where each one falls on the threat map of the agentic AI ecosystem, and how well existing frameworks anticipated them.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 3&lt;/strong&gt; I'll map all these findings against three references:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OWASP Top 10 for Agentic Applications 2026&lt;/strong&gt; — the first framework specific to autonomous agents, published in December 2025.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Agentic AI Security Scoping Matrix&lt;/strong&gt; — AWS's framework for categorizing and prioritizing security controls in agentic AI systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenClaw's official threat model&lt;/strong&gt; — based on MITRE ATLAS, with 37 threats documented by the team itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This won't be a framework walkthrough. I'll show where each finding lands on the map, what the frameworks anticipated, and — more interesting — what they didn't.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 4&lt;/strong&gt; we get hands-on: a live demo putting findings to the test in a controlled environment. Real prompt injection, attack chain validation, and the first foundations of something that doesn't yet exist in LATAM: a security maturity model for agentic AI systems.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Did you find any of these surfaces in your own deploy? Leave a comment — I'd love to know how they look in different environments.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Gerardo Castro&lt;/strong&gt; is an AWS Security Hero and Cloud Security Engineer focused on LATAM. Founder and Lead Organizer of the AWS Security Users Group LatAm. He believes the best way to learn cloud security is by building real things — not memorizing frameworks. He writes about what he builds, what he finds, and what he learns along the way.&lt;/p&gt;

&lt;p&gt;🔗 LinkedIn: &lt;a href="https://linkedin.com/in/gerardokaztro" rel="noopener noreferrer"&gt;gerardokaztro&lt;/a&gt;&lt;br&gt;
🔗 Blog: &lt;a href="https://roadtocloudsec.la" rel="noopener noreferrer"&gt;roadtocloudsec.la&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>openclaw</category>
      <category>security</category>
      <category>ai</category>
    </item>
    <item>
      <title>The script worked. The CISO needed something else. iam-audit v2: interactive dashboard, root account detection and Docker.</title>
      <dc:creator>Gerardo Castro Arica</dc:creator>
      <pubDate>Tue, 10 Mar 2026 02:07:25 +0000</pubDate>
      <link>https://dev.to/aws-heroes/the-script-worked-the-ciso-needed-something-else-iam-audit-v2-interactive-dashboard-root-3dc0</link>
      <guid>https://dev.to/aws-heroes/the-script-worked-the-ciso-needed-something-else-iam-audit-v2-interactive-dashboard-root-3dc0</guid>
      <description>&lt;p&gt;&lt;em&gt;From raw CSVs to a visual dashboard anyone can run with a single Docker command — and how each feature came from a real need.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The hook
&lt;/h2&gt;

&lt;p&gt;The first script worked. It iterated through 20+ AWS accounts, assumed roles with minimum privilege, and produced two CSVs with everything you needed to know about Access Keys and remediation events. Technically correct. Complete. Useful.&lt;/p&gt;

&lt;p&gt;But when the time came to share the results with the team, I realized something uncomfortable: &lt;strong&gt;a CSV is an answer for someone who knows how to ask the right questions.&lt;/strong&gt; For everyone else, it's noise.&lt;/p&gt;

&lt;p&gt;The engineer opens the CSV and sees data. The CISO opens the CSV and sees rows. It's the same file — but it's not the same experience.&lt;/p&gt;

&lt;p&gt;That led me to a question that changed the direction of the project: what if findings could be viewed, filtered, and understood without opening a single spreadsheet?&lt;/p&gt;

&lt;h2&gt;
  
  
  The context
&lt;/h2&gt;

&lt;p&gt;When you work in security in enterprise environments, you quickly learn that technical findings have two distinct lives. The first is the technical life — raw data, fields, values, dates. The second is the executive life — risk, impact, urgency. The problem is that most tools only speak one of those two languages.&lt;/p&gt;

&lt;p&gt;A CSV speaks the first. And that's fine — for the CloudSec Engineer who needs to triage, filter by account, sort by key age, and export for a ticket. But for the CISO who needs to understand the security posture of the organization in 30 seconds, a CSV is a barrier, not an answer.&lt;/p&gt;

&lt;p&gt;This tension isn't new. Commercial security tools have been solving it for years — CSPMs like Wiz or Prisma Cloud have polished executive dashboards. But not every team has budget for those platforms, and even less so in LATAM where many organizations are still building their security baseline.&lt;/p&gt;

&lt;p&gt;With that clear, the next step was obvious: the data was already there. What was missing was the presentation layer — no cost, no external dependencies, no friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 1: The dashboard
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;From data to visibility: the HTML dashboard&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The decision to use pure HTML was intentional. I could have used Grafana, QuickSight, or any visualization tool — but they all have the same problem: they require infrastructure, configuration, or an account on some service. An HTML file requires nothing. You open it in any browser, on any machine, without installing anything.&lt;/p&gt;

&lt;p&gt;The mechanism is simple: the script generates the CSVs as usual, but before finishing it embeds them directly inside the HTML as strings. The dashboard parses them in the browser with JavaScript and converts them into interactive widgets. No backend, no database, no dependencies — a single self-contained file.&lt;/p&gt;

&lt;p&gt;An important warning: the generated HTML contains real data from your organization. Treat it with the same sensitivity as a CSV with account and user information — don't share it through unsecured channels, don't upload it to public repositories, and make sure the &lt;code&gt;output/&lt;/code&gt; directory is in your &lt;code&gt;.gitignore&lt;/code&gt;. The script already handles this, but it's worth keeping in mind.&lt;/p&gt;

&lt;p&gt;The dashboard has four main views:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Executive summary&lt;/strong&gt; — total accounts audited, total Access Keys, active vs inactive keys, users without MFA&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk scoring per user&lt;/strong&gt; — automatic prioritization based on key age, active status, and absence of MFA&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remediation trend&lt;/strong&gt; — CloudTrail events chart over time, to see if the team is actually remediating&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Findings table&lt;/strong&gt; — filterable by account, status, and risk level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The filters are global — when you filter by account, all widgets update simultaneously. That's what makes it useful for a presentation: you can show the status of a specific account in seconds, without exporting anything or switching tools.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9o7hs86jhpfc9l5ytnq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9o7hs86jhpfc9l5ytnq.png" alt="Interactive dashboard generated by iam-audit v2" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Interactive dashboard generated by iam-audit v2"&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 2: Root account detection
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The account everyone ignores: root account detection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's a user in every AWS account that doesn't appear in any IAM Users listing. It doesn't have mandatory MFA by default. It generates no alerts if someone uses it. And it has unrestricted access to absolutely everything in the account. Unlike any other IAM user, its permissions cannot be reduced with identity policies — though certain actions can be restricted at the organizational level through SCPs. AWS Control Tower includes two specific preventive guardrails for this: one that prevents enabling Access Keys on the root account, and another that prevents executing actions as root. To detect whether MFA is enabled, there's also a detective guardrail — &lt;em&gt;Detect Whether MFA for the Root User is Enabled&lt;/em&gt; — implemented via AWS Config Rule. However, none of these mechanisms give you consolidated visibility of root status across all your accounts in one place. That's exactly the gap this feature solves.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://maturitymodel.security.aws.dev/en/model/" rel="noopener noreferrer"&gt;AWS Security Maturity Model v2&lt;/a&gt; is explicit about this. In its &lt;strong&gt;Phase 1 — Quick Wins&lt;/strong&gt;, within the Identity and Access Management domain, one of the fundamental controls is &lt;em&gt;Root Account Protection&lt;/em&gt; — ensuring the root account has active MFA, no active Access Keys, and that its usage is monitored. This isn't an advanced recommendation. It's baseline. It's the first thing you should have resolved before anything else.&lt;/p&gt;

&lt;p&gt;Adding this control to the script was a logical decision: if you were already auditing IAM Users in each account, not auditing the root account meant leaving the most critical finding out of the report.&lt;/p&gt;

&lt;p&gt;What the script audits per account:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MFA enabled&lt;/strong&gt; — whether the root account has active MFA or not&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active Access Keys&lt;/strong&gt; — whether any Access Key associated with root exists, which AWS explicitly recommends avoiding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Last login&lt;/strong&gt; — cross-referenced with &lt;code&gt;ConsoleLogin&lt;/code&gt; CloudTrail events filtered by root identity, to detect if anyone has used the root account recently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This last point is the most valuable for a CISO: it's not just knowing whether root has MFA — it's knowing whether someone used it in the last 90 days. That's what turns a technical finding into a real risk conversation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgniom3jlqs4xivc7g4x3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgniom3jlqs4xivc7g4x3.png" alt="Root account detection widget — MFA, Access Keys and last login per account" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Root account detection widget — MFA, Access Keys and last login per account"&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 3: Docker
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;One command. No installation required. That's what was missing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The script was already auditing. The dashboard was already visualizing. But there was a step that remained an invisible barrier: to run it, you needed Python installed, boto3 installed, and the repo cloned. For a CloudSec Engineer that's trivial. For someone who wants to evaluate the tool quickly, or for a team that doesn't want to manage Python dependencies on their machines, that's enough friction to not even try.&lt;/p&gt;

&lt;p&gt;Docker eliminates that friction entirely.&lt;/p&gt;

&lt;p&gt;The decision to dockerize wasn't about technology — it was about accessibility. If the goal was to build something any team in LATAM could use regardless of their local stack, the image had to be the unit of distribution. Not the repo, not the requirements.txt, not the installation instructions. The image.&lt;/p&gt;

&lt;p&gt;The result is this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; ~/.aws:/root/.aws &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/output:/app/output &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 8000:8000 &lt;span class="se"&gt;\&lt;/span&gt;
  gerardokaztro/iam-audit &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--profile&lt;/span&gt; YOUR-AWS-PROFILE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role&lt;/span&gt; YOUR-AUDIT-ROLE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Docker downloads the image from Docker Hub, mounts your local AWS credentials, runs the audit, generates the dashboard, and serves it at &lt;code&gt;http://localhost:8000&lt;/code&gt;. When you're done, &lt;code&gt;Ctrl+C&lt;/code&gt; and that's it — no residue, no dependencies installed on your machine.&lt;/p&gt;

&lt;p&gt;Two design decisions worth mentioning:&lt;/p&gt;

&lt;p&gt;The first is credential mounting. Instead of passing Access Keys as environment variables — which would be exactly the opposite of what the script audits — the container mounts the &lt;code&gt;~/.aws&lt;/code&gt; directory from your local machine. Your credentials are never hardcoded, never in the image, and expire if you use roles with &lt;code&gt;aws sso login&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The second is the output volume. The &lt;code&gt;output/&lt;/code&gt; directory is mounted as an external volume, which means the generated files — CSVs and HTML dashboard — stay on your local machine even after the container is destroyed. Without a volume, you'd lose the reports when you hit &lt;code&gt;Ctrl+C&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The image is published on Docker Hub at &lt;code&gt;gerardokaztro/iam-audit&lt;/code&gt; and is built automatically from the Dockerfile in the repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrpn5pedohy7npbc7euz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrpn5pedohy7npbc7euz.png" alt="From a terminal command to a dashboard in the browser" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"From a terminal command to a dashboard in the browser"&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The findings
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What the dashboard shows when you run it in production&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Running iam-audit v2 against the same AWS Organization from the first post produced something qualitatively different from the original CSV. Not because the data was different — but because the presentation completely changed the conversation.&lt;/p&gt;

&lt;p&gt;The executive summary of the dashboard showed the state of the organization at a glance:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Accounts audited&lt;/td&gt;
&lt;td&gt;20+ active accounts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access Keys found&lt;/td&gt;
&lt;td&gt;Dozens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oldest key&lt;/td&gt;
&lt;td&gt;Created in 2018 — active in production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Users without MFA + console access&lt;/td&gt;
&lt;td&gt;Dozens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root accounts without MFA&lt;/td&gt;
&lt;td&gt;Several&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root accounts with active Access Keys&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total execution time&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The risk scoring widget was the most useful for prioritizing remediation. Seeing which users combined old key + no MFA + active console access — all in a single filterable view by account — is what turns a report into an action plan.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82fxvj8dio9ekx5w3x8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82fxvj8dio9ekx5w3x8g.png" alt="Executive dashboard view — organization status at a glance" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Risk scoring per user — automatic findings prioritization"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The root account widget had the biggest impact for the executive conversation. Not because there were critical findings in every account — but because for the first time there was an immediate visual answer to the question: what's the root status across the entire organization? Before this feature, that question had no consolidated answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcr6oj8yzp4c5dx0xcwlj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcr6oj8yzp4c5dx0xcwlj.png" alt="Root account status per account — MFA, Access Keys and last login" width="800" height="91"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Root account status per account — MFA, Access Keys and last login"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The most striking data point wasn't technical — it was process-related. The remediation trend in CloudTrail showed that &lt;code&gt;DeleteAccessKey&lt;/code&gt; events increased significantly after sharing the first report. The script didn't just find the problem — it created the mechanism to measure whether it was being solved.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw44wj6h1o35nsyxrxbkf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw44wj6h1o35nsyxrxbkf.png" alt="Remediation trend via CloudTrail — DeleteAccessKey events increased after the first report" width="800" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Remediation trend via CloudTrail — DeleteAccessKey events increased after the first report"&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The closing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What I learned building this&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That tools evolve when you use them in production with real people. The script from the first post was technically correct — but the reality of the field kept pushing it toward something more complete. The dashboard was born because a CSV didn't communicate for all audiences. Root account detection was born because the ASMM required it and no one had it solved in a consolidated way. Docker was born because accessibility matters as much as functionality.&lt;/p&gt;

&lt;p&gt;None of those decisions came from a planned roadmap. They came from using the tool, listening to what was missing, and building the next layer.&lt;/p&gt;

&lt;p&gt;That's exactly what I want to document in Road to CloudSec LATAM — not the polished final result, but the real process of iterative construction. The design decisions, the mistakes, the adjustments. Because that's what actually teaches.&lt;/p&gt;

&lt;p&gt;If you want to run iam-audit v2 in your own AWS Organization, everything you need is in the repository. No cost, no additional infrastructure, no dependencies beyond Docker and an audit role with minimum privilege.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; ~/.aws:/root/.aws &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/output:/app/output &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 8000:8000 &lt;span class="se"&gt;\&lt;/span&gt;
  gerardokaztro/iam-audit &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--profile&lt;/span&gt; YOUR-AWS-PROFILE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role&lt;/span&gt; YOUR-AUDIT-ROLE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🔗 GitHub: &lt;a href="https://github.com/gerardokaztro/iam-audit" rel="noopener noreferrer"&gt;github.com/gerardokaztro/iam-audit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run it, find something interesting, or have feedback — reach out. The CloudSec community in LATAM is built by sharing what works in real environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Gerardo Castro&lt;/strong&gt; is an AWS Security Hero and Cloud Security Engineer focused on LATAM. He believes the best way to learn cloud security is by building real things — not memorizing frameworks. He writes about what he builds, what he finds, and what he learns along the way.&lt;/p&gt;

&lt;p&gt;🔗 LinkedIn: &lt;a href="https://linkedin.com/in/gerardokaztro" rel="noopener noreferrer"&gt;linkedin.com/in/gerardokaztro&lt;/a&gt;&lt;br&gt;
🔗 Blog: &lt;a href="https://roadtocloudsec.la" rel="noopener noreferrer"&gt;roadtocloudsec.la&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>python</category>
      <category>security</category>
      <category>docker</category>
    </item>
    <item>
      <title>A 2018 Access Key. Still Active in Production. Here's the Python Script That Found It Across an Entire AWS Organization.</title>
      <dc:creator>Gerardo Castro Arica</dc:creator>
      <pubDate>Sat, 07 Mar 2026 10:00:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/a-2018-access-key-still-active-in-production-heres-the-python-script-that-found-it-across-an-3pd3</link>
      <guid>https://dev.to/aws-heroes/a-2018-access-key-still-active-in-production-heres-the-python-script-that-found-it-across-an-3pd3</guid>
      <description>&lt;p&gt;A few weeks ago I sat down to review the IAM state of a multi-account AWS Organization. It wasn't a formal audit with weeks of planning. It was the simple question every Cloud Security Engineer should be able to answer at any time:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who has access, with what credentials, and since when?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer surprised me. Not because it was hard to find — but because of how easy it was to automate, and what came to light when I did.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Context
&lt;/h2&gt;

&lt;p&gt;Organizations that have been running on AWS for years accumulate security debt without realizing it. They start with one account, then two, then a team requests its own environment, another project comes along, and suddenly you have an AWS Organization with dozens of accounts, each with its own IAM history.&lt;/p&gt;

&lt;p&gt;The problem isn't scale — it's visibility. Or rather, the lack of it.&lt;/p&gt;

&lt;p&gt;In multi-account environments, nobody has a consolidated view of who has what access. Infrastructure teams know what they deployed. Development teams know what they needed at the time. But the Access Keys created three years ago for an integration process that no longer exists, that were never rotated, that are still active — those don't show up in any dashboard. They don't generate alerts. They don't bother anyone. They just wait.&lt;/p&gt;

&lt;p&gt;That's exactly what I found. An active AWS Organization, with multiple production accounts, and credentials that had gone years without being centrally audited. Not because the team was careless — but because nobody had built the mechanism to see them all at once.&lt;/p&gt;

&lt;p&gt;I decided to automate that search.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Risk
&lt;/h2&gt;

&lt;p&gt;IAM Access Keys are long-lived credentials. Unlike IAM roles — which generate temporary credentials that expire automatically — an Access Key has no expiration date. If you create one today and never rotate it, it's still valid in 2030.&lt;/p&gt;

&lt;p&gt;That makes them one of the most common attack vectors in AWS environments. Not because they're insecure by design, but because time works against them. A key that's been active for years silently accumulates risk:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It may have been exposed in a code repository without anyone noticing&lt;/li&gt;
&lt;li&gt;It may be in the hands of an employee who no longer works at the organization&lt;/li&gt;
&lt;li&gt;It may have permissions granted for a one-time project that were never reviewed&lt;/li&gt;
&lt;li&gt;It may have been used by an attacker for months — and without rotation, nobody knows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://maturitymodel.security.aws.dev/en/model/" rel="noopener noreferrer"&gt;AWS Security Maturity Model v2&lt;/a&gt; is precise about this. In its &lt;strong&gt;Phase 1 — Quick Wins&lt;/strong&gt;, within the Identity and Access Management domain, one of the key controls is &lt;em&gt;Multi-Factor Authentication&lt;/em&gt; — ensuring all users with console access have MFA enabled. In &lt;strong&gt;Phase 2 — Foundational&lt;/strong&gt;, the model goes further with &lt;em&gt;Use Temporary Credentials&lt;/em&gt; — the recommendation to migrate toward IAM roles and short-lived credentials, moving away from long-term Access Keys.&lt;/p&gt;

&lt;p&gt;The script audits both controls in a single run: which users have active Access Keys, how long they've been active, and whether they have MFA configured. An active key from 2018 combined with a user without MFA isn't just technical debt — it's an attack surface that's been open for years.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Design decisions before code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before writing a single line, I made three decisions that define how the script works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First decision: cross-account via STS, not IAM users.&lt;/strong&gt;&lt;br&gt;
The obvious temptation is to create an IAM user with audit permissions in each account. But that solves a visibility problem by creating exactly the problem we're trying to eliminate — more long-lived credentials. The right solution is &lt;code&gt;sts:AssumeRole&lt;/code&gt;: the script assumes an existing role in each account, gets temporary credentials, audits, and the credentials expire on their own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second decision: start from AWS Organizations.&lt;/strong&gt;&lt;br&gt;
Instead of maintaining a manual list of accounts, the script queries the Organization directly and gets all active accounts in real time. If a new account is created tomorrow, the next run includes it automatically. No lists to maintain, no accounts slipping through the cracks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third decision: always paginate.&lt;/strong&gt;&lt;br&gt;
IAM APIs paginate. If you don't use &lt;code&gt;get_paginator()&lt;/code&gt;, in an account with many users you'll only see the first results and never know it. It's the kind of silent bug that destroys the reliability of an audit.&lt;/p&gt;

&lt;p&gt;With those three decisions clear, the code practically writes itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The main flow&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;org_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;organizations&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Get all active accounts from the Organization
&lt;/span&gt;    &lt;span class="n"&gt;accounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_accounts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org_client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;all_findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Auditing account: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;assume_role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SecurityAudit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;iam_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;iam&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;aws_access_key_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AccessKeyId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;aws_secret_access_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SecretAccessKey&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;aws_session_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SessionToken&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_iam_users_with_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iam_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;all_findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error in account &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;all_findings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One account fails — the script continues. That's intentional: in a real Organization you'll find accounts where the role isn't deployed, or where permissions differ. The &lt;code&gt;try/except&lt;/code&gt; ensures an error in one account doesn't stop the entire audit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The heart of the script: what we audit per user&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_iam_users_with_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iam_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;paginator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iam_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_paginator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;list_users&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;paginator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Users&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="c1"&gt;# User's Access Keys
&lt;/span&gt;            &lt;span class="n"&gt;keys_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iam_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_access_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UserName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

            &lt;span class="c1"&gt;# MFA status
&lt;/span&gt;            &lt;span class="n"&gt;mfa_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iam_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_mfa_devices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UserName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;mfa_devices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mfa_response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;MFADevices&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

            &lt;span class="c1"&gt;# Console access
&lt;/span&gt;            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;iam_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_login_profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UserName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                &lt;span class="n"&gt;password_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Configured&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;iam_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NoSuchEntityException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;password_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Not configured&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;keys_response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AccessKeyMetadata&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="n"&gt;last_used_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iam_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_access_key_last_used&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;AccessKeyId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AccessKeyId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;last_used&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;last_used_response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AccessKeyLastUsed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

                &lt;span class="n"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;account_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UserName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;password_status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;password_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;access_key_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AccessKeyId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;created_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CreateDate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;last_used_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_used&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;LastUsedDate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Never used&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;service_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;last_used&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ServiceName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;N/A&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mfa_status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Virtual&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;virtual&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SerialNumber&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                                   &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;mfa_devices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hardware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mfa_devices&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;None&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;findings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each user the script captures: all their Access Keys with status and creation date, when each key was last used and for what service, whether they have MFA active and of what type, and whether they have console access configured. All of that in a single pass per account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to run it&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python iam_audit.py &lt;span class="nt"&gt;--profile&lt;/span&gt; your-mgmt-profile &lt;span class="nt"&gt;--role&lt;/span&gt; OrganizationAccountAccessRole
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script needs two things: an AWS profile with access to the Organization's management account, and a role deployed in each member account that allows assumption from the management account.&lt;/p&gt;

&lt;p&gt;For the management account, the IAM policy is minimal by design — only the strictly necessary permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"organizations:ListAccounts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::*:role/ROLE-NAME-IN-CHILD-ACCOUNTS"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing more. The role in the management account doesn't touch IAM directly — all auditing happens through the role it assumes in each child account. If you use AWS Control Tower, the &lt;code&gt;AWSControlTowerExecution&lt;/code&gt; role already exists in all accounts and you can use it as a starting point, though in production I recommend creating a dedicated audit role with read-only permissions over IAM.&lt;/p&gt;

&lt;p&gt;This isn't a minor detail — it's the least privilege principle applied to the tool that audits least privilege. The script's own attack surface is reduced to the minimum.&lt;/p&gt;

&lt;p&gt;The script doesn't just audit the current state — it also reconstructs the timeline. Once you find the findings and start remediating, you need to be able to answer a second question: &lt;strong&gt;how are we progressing?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For that, the script queries CloudTrail in each account and extracts key IAM activity events: &lt;code&gt;DeleteAccessKey&lt;/code&gt;, &lt;code&gt;CreateAccessKey&lt;/code&gt;, &lt;code&gt;DeleteUser&lt;/code&gt;. This lets you see over time whether the team is actually remediating — or whether the findings stayed in a report nobody acted on.&lt;/p&gt;

&lt;p&gt;The final output is two CSV files: one with all IAM findings, and another with those CloudTrail events that build the remediation trend.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Findings
&lt;/h2&gt;

&lt;p&gt;I ran the script against an active AWS Organization in the region. Here's what I found:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Accounts audited&lt;/td&gt;
&lt;td&gt;More than 20 active accounts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access Keys found&lt;/td&gt;
&lt;td&gt;Dozens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oldest key&lt;/td&gt;
&lt;td&gt;Created in 2018 — active in production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Users without MFA + console access&lt;/td&gt;
&lt;td&gt;Dozens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total execution time&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frapvknm083cqkf9q11c3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frapvknm083cqkf9q11c3.png" alt="Terminal screenshot — actual script output auditing the Organization account by account" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two accounts weren't audited — the audit role wasn't deployed in them. That in itself is a finding: if you can't audit an account, you can't know what's inside.&lt;/p&gt;

&lt;p&gt;The most impactful data point isn't the volume — it's the age. An Access Key created in 2018 and still active in production means it survived everything. Not because someone protected it — but because nobody saw it.&lt;/p&gt;

&lt;p&gt;The script saw it in minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Do You Do With This Now?
&lt;/h2&gt;

&lt;p&gt;If you have an AWS Organization, you can run this script today. You don't need a SIEM, you don't need a dedicated security team, you don't need a special budget. You need Python, boto3, and an audit role with least privilege in your accounts.&lt;/p&gt;

&lt;p&gt;What you do need is the willingness to see what's there.&lt;/p&gt;

&lt;p&gt;The most uncomfortable result of an audit isn't the technical finding — it's realizing that the information was always there, waiting for someone to systematically look for it. Old Access Keys don't show up in any dashboard by default. They don't generate alerts. They don't bother anyone. They just wait.&lt;/p&gt;

&lt;p&gt;Automating visibility is the first step of any serious security program. Not the most glamorous, but the most honest.&lt;/p&gt;

&lt;p&gt;The script generates two CSVs. The natural next step is converting that data into a visual dashboard — risk widgets per account, remediation trend over time, consolidated MFA status. That's coming in the next post.&lt;/p&gt;

&lt;p&gt;The repository with the complete code is on GitHub — link below. If you use it, find something interesting, or have feedback, reach out. The CloudSec community in LATAM is built by sharing what works in real environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Gerardo Castro&lt;/strong&gt; is an AWS Security Hero and Cloud Security Engineer focused on LATAM. He believes the best way to learn cloud security is by building real things — not memorizing frameworks. He writes about what he builds, what he finds, and what he learns along the way.&lt;/p&gt;

&lt;p&gt;🔗 GitHub: &lt;a href="https://github.com/gerardokaztro/iam-audit" rel="noopener noreferrer"&gt;gerardokaztro&lt;/a&gt;&lt;br&gt;
🔗 LinkedIn: &lt;a href="https://linkedin.com/in/gerardokaztro" rel="noopener noreferrer"&gt;gerardokaztro&lt;/a&gt;&lt;br&gt;
🔗 Original post (Spanish): &lt;a href="https://roadtocloudsec.hashnode.dev/encontre-access-key-2018-activa-produccion-python-boto3" rel="noopener noreferrer"&gt;roadtocloudsec.hashnode.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>python</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Deployed OpenClaw on AWS and Here's What I Found as a Cloud Security Engineer (Part 1)</title>
      <dc:creator>Gerardo Castro Arica</dc:creator>
      <pubDate>Thu, 05 Mar 2026 21:32:03 +0000</pubDate>
      <link>https://dev.to/aws-heroes/i-deployed-openclaw-on-aws-and-heres-what-i-found-as-a-cloud-security-engineer-3p9i</link>
      <guid>https://dev.to/aws-heroes/i-deployed-openclaw-on-aws-and-heres-what-i-found-as-a-cloud-security-engineer-3p9i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Part 1 of a series:&lt;/strong&gt; Secure setup, real findings, and attack surface analysis of an autonomous AI agent on AWS Lightsail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AWS just announced the general availability of &lt;strong&gt;OpenClaw on Amazon Lightsail&lt;/strong&gt; — an open-source, self-hosted autonomous AI agent that connects to WhatsApp, Telegram, Discord, and executes tasks independently: running code, managing files, browsing the web.&lt;/p&gt;

&lt;p&gt;The community is fired up testing it. So did I — but with a &lt;strong&gt;Cloud Security Engineer&lt;/strong&gt; hat on.&lt;/p&gt;

&lt;p&gt;This post isn't about how to use OpenClaw as an assistant. It's about &lt;strong&gt;what I found while setting it up from a security perspective&lt;/strong&gt;, what decisions I made, and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  What exactly is OpenClaw?
&lt;/h2&gt;

&lt;p&gt;Before talking security, let's align on concepts.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;LLM&lt;/strong&gt; (like Claude or GPT) receives a prompt and returns text. That's it.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;autonomous agent&lt;/strong&gt; is different: it has access to tools (terminal, browser, APIs), decides the order in which to use them, interprets the results, and acts — without you directing each step. The LLM is the "brain," but the agent is the full system that operates in the real world.&lt;/p&gt;

&lt;p&gt;OpenClaw is exactly that: an agent running on your server, using an LLM (in this case via &lt;strong&gt;Amazon Bedrock&lt;/strong&gt;) as its reasoning engine, capable of executing tasks autonomously through messaging channels.&lt;/p&gt;

&lt;p&gt;That autonomy is what makes it powerful. And also what makes it interesting from a security standpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lightsail Setup
&lt;/h2&gt;

&lt;p&gt;AWS packaged OpenClaw as a Lightsail blueprint — meaning you can have an instance running in minutes with no manual configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I saw in the wizard (and what caught my attention)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The SSH keypair:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lightsail gives you two options: let AWS generate the keypair, or upload your own.&lt;/p&gt;

&lt;p&gt;The security recommendation is clear: &lt;strong&gt;generate your own locally&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"openclaw-sandbox"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why? When AWS generates the keypair, the private key is created on their servers and travels to you for download. That transmission moment is an avoidable risk. If you generate it yourself, the private key &lt;strong&gt;never leaves your machine&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The analogy: the public key is the padlock you put on the server. The private key is the key only you hold. Anyone can see the padlock, but no one else can open it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Remember:&lt;/strong&gt; always run &lt;code&gt;chmod 400&lt;/code&gt; on your private key. If it has &lt;code&gt;644&lt;/code&gt; permissions, other system users can read it — and the SSH client itself will warn you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The firewall (Security Group):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By default, Lightsail opened ports 80, 443, and 22 to &lt;code&gt;0.0.0.0/0&lt;/code&gt; — any IP in the world.&lt;/p&gt;

&lt;p&gt;For a personal sandbox that's unnecessary. I changed all three rules to restrict them &lt;strong&gt;to my IP only&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl ifconfig.me  &lt;span class="c"&gt;# get your public IP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Less exposed attack surface = less blast radius if something goes wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  First Finding: Outdated OS
&lt;/h2&gt;

&lt;p&gt;When I connected via SSH, the welcome message was straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;44 updates can be applied immediately.
31 of these updates are standard security updates.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AWS blueprint shipped with &lt;strong&gt;31 unpatched security updates&lt;/strong&gt;. Including a kernel patch and &lt;code&gt;intel-microcode&lt;/code&gt; update.&lt;/p&gt;

&lt;p&gt;Why does the kernel matter? Because it's the core of the OS — it controls memory, processes, and permissions. Known kernel vulnerabilities like &lt;strong&gt;Dirty Pipe&lt;/strong&gt; or &lt;strong&gt;Spectre/Meltdown&lt;/strong&gt; allow privilege escalation or reading memory from other processes.&lt;/p&gt;

&lt;p&gt;On a server running Docker containers (like OpenClaw), an unpatched kernel can be exploited for a &lt;strong&gt;container escape&lt;/strong&gt; — breaking out of the isolated container and taking full control of the host.&lt;/p&gt;

&lt;p&gt;The fix is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;🔍 &lt;strong&gt;Finding #1:&lt;/strong&gt; OpenClaw blueprint on Lightsail deployed with outdated kernel and system libraries. Any client using it in production without applying patches is exposed to known CVEs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Most Interesting Part: OpenClaw Security Settings
&lt;/h2&gt;

&lt;p&gt;After setup, OpenClaw presents 5 security configurations. This is where things get interesting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. File &amp;amp; folder protection     → Status: ✓ Protected
2. Browser remote control       → Status: ✓ Disabled
3. Exec host policy             → Status: ~ Not set (defaults to sandbox)
4. Shell command approval       → Status: ~ Unrestricted
5. Access token                 → Status: ~ Never rotated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setting 3: Exec host policy
&lt;/h3&gt;

&lt;p&gt;This controls &lt;strong&gt;where&lt;/strong&gt; the agent executes shell commands.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sandbox&lt;/code&gt;&lt;/strong&gt; (default): commands run inside an isolated Docker container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gateway&lt;/code&gt;&lt;/strong&gt;: commands run directly on the server OS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The blast radius difference is enormous. With &lt;code&gt;gateway&lt;/code&gt;, if the agent is compromised, the attacker has direct access to the filesystem, environment variables, the IAM role — everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting 4: Shell command approval
&lt;/h3&gt;

&lt;p&gt;This controls &lt;strong&gt;whether the agent asks for permission&lt;/strong&gt; before executing a command.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;deny&lt;/code&gt;&lt;/strong&gt;: blocks all shell commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;allow&lt;/code&gt;&lt;/strong&gt;: executes them freely without asking.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The attack chain you can't ignore
&lt;/h3&gt;

&lt;p&gt;Here's the most dangerous scenario, and it's completely possible if you don't configure things properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exec host policy: gateway  +  shell command approval: allow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; the agent can execute any command directly on the server, without isolation and without asking for permission.&lt;/p&gt;

&lt;p&gt;Add a &lt;strong&gt;prompt injection&lt;/strong&gt; attack on top of this — where someone inserts malicious instructions into content the agent consumes (an email, a file, a web page) — and the attacker can take control of the server without you noticing.&lt;/p&gt;

&lt;p&gt;This chaining of weak configurations is called &lt;strong&gt;attack chaining&lt;/strong&gt;, and it's exactly the type of analysis that needs to happen before putting an agent into production.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔍 &lt;strong&gt;Finding #2:&lt;/strong&gt; The combination of &lt;code&gt;exec host policy: gateway&lt;/code&gt; + &lt;code&gt;shell command approval: allow&lt;/code&gt; eliminates all agent isolation layers. In production, this represents a critical risk.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Setting 5: Gateway Token visible in the dashboard
&lt;/h3&gt;

&lt;p&gt;The Gateway Token is the agent's "password" — whoever has it controls OpenClaw completely.&lt;/p&gt;

&lt;p&gt;The problem: &lt;strong&gt;it's displayed in plaintext in the dashboard&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Any screenshot of the dashboard, any screen recording, anyone looking over your shoulder — compromises access to the agent.&lt;/p&gt;

&lt;p&gt;AWS recommends rotating it frequently and not hardcoding it in configuration files. But the fact that it's visible by default in the UI is a design consideration worth noting.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔍 &lt;strong&gt;Finding #3:&lt;/strong&gt; The Gateway Token is displayed in plaintext in the dashboard. Combined with an internet-exposed dashboard, this represents direct credential exposure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  IPv6 Enabled by Default
&lt;/h2&gt;

&lt;p&gt;A detail that flies under the radar: the blueprint comes with &lt;strong&gt;dual-stack (IPv4 + IPv6)&lt;/strong&gt; enabled by default.&lt;/p&gt;

&lt;p&gt;The security problem: many firewall rules are written with IPv4 in mind. IPv6 traffic can go unnoticed if you're not reviewing both protocol families in your controls.&lt;/p&gt;

&lt;p&gt;If you're not using IPv6, disable it. Unnecessary attack surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Installed in This Blueprint?
&lt;/h2&gt;

&lt;p&gt;Among the services restarted after &lt;code&gt;apt upgrade&lt;/code&gt;, an interesting one appeared:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;systemctl restart apache2.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The OpenClaw dashboard runs on &lt;strong&gt;Apache2&lt;/strong&gt;. That means Apache is another attack surface to consider — with its own CVEs and configurations to review.&lt;/p&gt;

&lt;p&gt;An attacker familiar with Apache vulnerabilities could attempt to bypass gateway authentication without ever needing the token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Findings Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Blueprint deployed with outdated kernel and system libraries (31 pending security updates)&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gateway&lt;/code&gt; + &lt;code&gt;allow&lt;/code&gt; combination eliminates isolation and command approval&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Gateway Token displayed in plaintext in the dashboard&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;IPv6 enabled by default with no option to disable it in the wizard&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Apache2 as web server with no documented hardening&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Secure Configuration Recommendations
&lt;/h2&gt;

&lt;p&gt;If you're deploying OpenClaw in a real environment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Generate your keypair locally&lt;/strong&gt; — never let the provider generate it for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restrict the firewall&lt;/strong&gt; to known IPs from the very beginning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply patches immediately&lt;/strong&gt; after deploy — don't trust that the blueprint is up to date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep &lt;code&gt;exec host policy&lt;/code&gt; on &lt;code&gt;sandbox&lt;/code&gt;&lt;/strong&gt; — Docker isolation is your first line of defense.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set &lt;code&gt;shell command approval&lt;/code&gt; to &lt;code&gt;deny&lt;/code&gt;&lt;/strong&gt; or require explicit approval — human in the loop for irreversible actions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotate the Gateway Token&lt;/strong&gt; regularly and never expose it in screenshots.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disable IPv6&lt;/strong&gt; if you're not using it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't expose the dashboard to the internet&lt;/strong&gt; — if you need remote access, use a VPN or secure tunnel.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Autonomous AI agents are arriving in production at companies across LATAM. OpenClaw is just the first of many.&lt;/p&gt;

&lt;p&gt;Most teams deploying them aren't thinking about attack surface, blast radius from a misconfiguration, or how a prompt injection can chain with a misconfiguration to fully compromise a server.&lt;/p&gt;

&lt;p&gt;That gap is exactly where Cloud Security Engineers need to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;Part 2&lt;/strong&gt; I'll explore the surfaces we didn't touch today: Channels (WhatsApp/Telegram integrations), Agents, Cron Jobs, and how each one expands the system's attack surface.&lt;/p&gt;

&lt;p&gt;I'll also run a full threat model using the &lt;strong&gt;OWASP Top 10 for Agentic Applications 2026&lt;/strong&gt; and the &lt;strong&gt;AWS Agentic AI Security Scoping Matrix&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article is part of the &lt;strong&gt;Road to CloudSec LATAM&lt;/strong&gt; series. Original version in Spanish on Hashnode.&lt;/p&gt;

&lt;p&gt;Have questions or found something different in your deployment? Drop a comment — I'm building the complete threat model for Part 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;Gerardo Castro is an AWS Security Hero and Cloud Security Engineer focused on LATAM. He believes the best way to learn cloud security is by building real things — not memorizing frameworks. He writes about what he builds, what he finds, and what he learns along the way.&lt;/p&gt;

&lt;p&gt;🔗 GitHub: &lt;a href="https://github.com/gerardokaztro" rel="noopener noreferrer"&gt;https://github.com/gerardokaztro&lt;/a&gt;&lt;br&gt;
🔗 LinkedIn: &lt;a href="https://linkedin.com/in/gerardokaztro" rel="noopener noreferrer"&gt;https://linkedin.com/in/gerardokaztro&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>openclaw</category>
      <category>ai</category>
      <category>security</category>
    </item>
    <item>
      <title>Cómo visualizar los hallazgos de Amazon GuardDuty en MS Teams</title>
      <dc:creator>Gerardo Castro Arica</dc:creator>
      <pubDate>Fri, 03 Mar 2023 19:27:11 +0000</pubDate>
      <link>https://dev.to/aws-builders/como-visualizar-los-hallazgos-de-amazon-guardduty-en-ms-teams-9e5</link>
      <guid>https://dev.to/aws-builders/como-visualizar-los-hallazgos-de-amazon-guardduty-en-ms-teams-9e5</guid>
      <description>&lt;p&gt;En este blog post te mostraré como enviar los hallazgos de Amazon GuardDuty a un canal de mensajería como Microsoft Teams en un formato de tarjeta que contenga información relevante del hallazgo para que el equipo de seguridad pueda tomar acción inmediata.&lt;/p&gt;

&lt;p&gt;Puede usar esta integración para comprender mejor los hallazgos de Amazon GuardDuty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enfoque para visualizar los resultados de Amazon GuardDuty
&lt;/h2&gt;

&lt;p&gt;Como se muestra en la Figura 1, hay cuatro pasos de alto nivel para entender como funciona la solución.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ky3YKHgQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ak5ytpgq0l71zyj2dx9y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ky3YKHgQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ak5ytpgq0l71zyj2dx9y.png" alt="Figura 1: Pasos para visualizar hallazgos de Amazon GuardDuty" width="800" height="98"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Centralizar los hallazgos de Amazon GuardDuty
&lt;/h3&gt;

&lt;p&gt;Puede centralizar los hallazgos de Amazon GuardDuty en una única cuenta como Security Audit para que desde aquí se pueda implementar el resto de componentes de la solución. De esa manera, puedes usar esta solución para recibir todos los hallazgos detectados en tus cargas de trabajo, sin importar en que cuenta o región se haya detectado.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Recopilar los hallazgos con Amazon EventBridge
&lt;/h3&gt;

&lt;p&gt;Amazon EventBridge es un servicio muy útil al momento de recopilar eventos de algún otro servicio y redireccionarlos hacia un target especifico como una función Lambda, un tópico SNS, un servicio ETL como Kinesis Data Firehose, entre otros.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Ejecución del código de una función Lambda
&lt;/h3&gt;

&lt;p&gt;Cada vez que la función lambda reciba un hallazgo de Amazon GuardDuty invocara el código que tiene configurado para hacer el envío de este hallazgo a un canal de mensajería como MS Teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Visualizar el hallazgo
&lt;/h3&gt;

&lt;p&gt;En la Figura 2, podemos ver un ejemplo de como el equipo de segurida recibirán los hallazgos de Amazon GuardDuty en su canal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NqjwzfOk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gy45hpmrqo30gzpakehn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NqjwzfOk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gy45hpmrqo30gzpakehn.png" alt="Figura 2: Visualización del hallazgo" width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Descripción general del diseño
&lt;/h2&gt;

&lt;p&gt;Este diagrama de arquitectura representa a alto nivel los servicios a usar y la interacción entre ellos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_OsgAt0q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yki9l7oh8i6vz07vvpu9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_OsgAt0q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yki9l7oh8i6vz07vvpu9.png" alt="Figura 3: Descripción general del ddiseño" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En el diagrama de la figura 3, vamos a explicar el paso a paso:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Dentro de un esquema de múltiples cuentas AWS, conviene empezar a delegar la administración de algunos servicios por sobre el resto de cuentas, como es el caso de Amazon GuardDuty. Hacer esto te permite centralizar todos los hallazgos de Amazon GuarDuty de todas las cuentas donde tengas habilitado este servicio. Y delegar una única cuenta, en este caso la Security Audit para gestionar Los hallazgos desde un único punto.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cada hallazgo detectado por Amazon GuardDuty, es recopilado por una regla basada en eventos de Amazon EventBridge y redirigirá estos eventos (antes hallazgos) hacia una función lambda.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;La función lambda va a desencadenar la invocación del código por cada evento que reciba de la regla configurada en EventBridge.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recibirás una notificación en tu canal de MS Teams cada vez que se detecte un hallazgo, el cual se configura por medio de un incoming webhook.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Beneficios de la solución:
&lt;/h2&gt;

&lt;p&gt;Al implementar esta solución, puede lograr los siguientes beneficios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Ahorra tiempo al analizar un hallazgos de manera rápida y sencilla en lugar de hacerlo desde la misma consola de Amazon GuardDuty. En un esquema múltiples cuentas, no será efectivo hacer la búsqueda entre 1000 posibles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Concentrarse en los hallazgos que generen mayor interés, por ejemplo, puedes solo recibir hallazgos de severidad HIGH, o de algún Treath Purpose como Cryptomining, o sobre algun recurso critico como una instancia EC2, un bucket S3, entre otros.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Obtener una vista rápida de los detalles del hallazgo, como saber cual es el indicador de compromiso del atacante o cual es el recursos afectado, incluso saber a que cuenta y región pertenece el hallazgo.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pre-requisitos:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Instalar Git&lt;/li&gt;
&lt;li&gt;Instalar Visual Studio Code, o algún otro de tu preferencia&lt;/li&gt;
&lt;li&gt;Por si no lo tienes, instalar Zip&lt;/li&gt;
&lt;li&gt;Instalar el cliente de MS Teams&lt;/li&gt;
&lt;li&gt;Por ultimo, tener cuenta en Github y MS teams&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tutorial
&lt;/h2&gt;

&lt;p&gt;Puedes hacer uso de esta solución tanto en un entorno de múltiples cuentas o una cuenta standalone. Es decir, si solamente tienes activado Amazon GuardDuty en una única cuenta, y quieres usar esta solución, puedes hacerlo, va a funcionar de todas maneras.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lo primero que haremos,&lt;/strong&gt; será crear o usar un canal de MS Teams para recibir los hallazgos, y configuraremos un incoming webhook.&lt;/p&gt;

&lt;p&gt;Si vamos a crear un canal, estos son los pasos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elegir un nombre y descripción de tu preferencia.&lt;/li&gt;
&lt;li&gt;La privacidad del canal, puede ser tanto pública o privada, va a depender de tus necesidades.&lt;/li&gt;
&lt;li&gt;Dar clic en crear.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Rh9j5EMD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nonsyxqjwrs8a53u0vko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Rh9j5EMD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nonsyxqjwrs8a53u0vko.png" alt="crear canal en ms teams" width="585" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Una vez tengas el canal creado, ve a configuraciones del canal, y luego a conectores:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1aeeK-6M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q0ekswxqpu1pmchhtu2g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1aeeK-6M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q0ekswxqpu1pmchhtu2g.png" alt="configurar un conector en ms teams" width="328" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Acto seguido, donde dice "Webhook entrante" o "Incoming Webhook" dar clic en configurar:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UfkGM2sw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8vednl0mboagc01b8bqd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UfkGM2sw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8vednl0mboagc01b8bqd.png" alt="configurar un iw" width="585" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Luego, en los detalles de la configuración del Webhook, coloca un nombre, agrega un icono y finalmente da clic en "Crear"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gSscMyU8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wjgcdv58tzksv7spt1m1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gSscMyU8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wjgcdv58tzksv7spt1m1.png" alt="configurar iw pasos finales" width="753" height="633"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inmediatamente el conector te va a brindar la url del webhook, cópiala en algún lugar, vamos a usar más tarde.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lo segundo que haremos&lt;/strong&gt; será crear una función lambda. Esta función debe ser creada en la misma cuenta donde residen los hallazgos de Amazon GuardDuty.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elije un nombre, yo usare &lt;strong&gt;"alert-guardduty-teams"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Usa los mismos valores que muestro en la imagen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Mdf2pIUK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ny4yunzqo58b3mnhkf6c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Mdf2pIUK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ny4yunzqo58b3mnhkf6c.png" alt="Crear la función de lambda" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cambiemos los valores por defecto que usa lambda function a estos nuevos:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lYCh2UlU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jtzuerxswrekegq0as4c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lYCh2UlU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jtzuerxswrekegq0as4c.png" alt="configuración de lambda" width="800" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En la opción de &lt;strong&gt;Enviroment Variables&lt;/strong&gt; (variables de entorno), agreguemos lo siguiente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SEVERITY_THRESHOLD = coloca aquí el nivel de severidad de las amenazas que deben ser notificadas&lt;/li&gt;
&lt;li&gt;TEAMS_WEBHOOK_URL = coloca aquí la url del incoming webhook que generaste en el primer paso.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--J6rAetUL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nrbfey29cu1pbi5w6i2k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--J6rAetUL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nrbfey29cu1pbi5w6i2k.png" alt="Variables de entorno" width="800" height="129"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lo tercero que haremos&lt;/strong&gt; será crear una regla en EventBridge, no te preocupes por el código ahora mismo, ya lo veremos.&lt;/p&gt;

&lt;p&gt;Para crear una regla basada en eventos, ir a Amazon CloudWatch, y luego hacer clic en Rule:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ERIat-_m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z8l95hy67jydw3ivekip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ERIat-_m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z8l95hy67jydw3ivekip.png" alt="Ir a CloudWatch Rule" width="260" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para crear y configurar una regla, consta de 3 pasos importantes:&lt;/p&gt;

&lt;p&gt;Paso 1:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elegir un nombre.&lt;/li&gt;
&lt;li&gt;Agrega una descripción.&lt;/li&gt;
&lt;li&gt;Todo lo demás por defecto, como en la imagen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NuvtDHzk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2pvznx17snby831fz22n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NuvtDHzk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2pvznx17snby831fz22n.png" alt="crear regla paso 1" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paso 2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Las opciones de "Event Source" y "Sample Event" las dejaremos por defecto.&lt;/li&gt;
&lt;li&gt;En la opción de "Creation Method" elegir "Use pattern form".&lt;/li&gt;
&lt;li&gt;En la opción de "Event pattern".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--17JVFpPO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x6cix7by1zaomwiizptf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--17JVFpPO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x6cix7by1zaomwiizptf.png" alt="crear regla paso 2a" width="800" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5rVs9Ddp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u6ir7ayx5idy79h62s8w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5rVs9Ddp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u6ir7ayx5idy79h62s8w.png" alt="crear regla 2b" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paso 3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Target type: AWS Services&lt;/li&gt;
&lt;li&gt;Select a target: Lambda function&lt;/li&gt;
&lt;li&gt;En Function, elegir la función lambda creada en el segundo paso.&lt;/li&gt;
&lt;li&gt;Todo lo demás por defecto&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GpFiPwit--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdizrznitq5w0adwe1pb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GpFiPwit--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdizrznitq5w0adwe1pb.png" alt="crear regla 3" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ahora si, vamos a por el código 💪&lt;/p&gt;

&lt;p&gt;Deberás clonar en tu máquina local, este repositorio alojado en Github que contiene el código que usaremos.&lt;/p&gt;

&lt;p&gt;No olvides dejar tu ⭐ al repositorio, también hazle fork 🔂 para que puedas customizarlo a tus necesidades y envíame un PR así contribuimos juntos a la comunidad Open Source.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/gerardokaztro"&gt;
        gerardokaztro
      &lt;/a&gt; / &lt;a href="https://github.com/gerardokaztro/guardduty-to-msteams"&gt;
        guardduty-to-msteams
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Cómo enviar los hallazgos de Amazon GuardDuty hacia MS Teams.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Cómo visualizar los hallazgos de Amazon GuardDuty en MS Teams&lt;/h1&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/4099d06104f7e333862fe2a4150f4cb1b47ccf332a51aedae021bc01f359aba5/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f6779343568706d72716f3330677a70616b65686e2e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/4099d06104f7e333862fe2a4150f4cb1b47ccf332a51aedae021bc01f359aba5/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f6779343568706d72716f3330677a70616b65686e2e706e67" alt="Visualización del hallazgo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;En este blog post te mostraré como enviar los hallazgos de Amazon GuardDuty a un canal de mensajería como Microsoft Teams en un formato de tarjeta que contenga información relevante del hallazgo para que el equipo de seguridad pueda tomar acción inmediata.&lt;/p&gt;
&lt;p&gt;Puede usar esta integración para comprender mejor los hallazgos de Amazon GuardDuty.&lt;/p&gt;
&lt;h2&gt;
Enfoque para visualizar los resultados de Amazon GuardDuty&lt;/h2&gt;
&lt;p&gt;Como se muestra en la Figura 1, hay cuatro pasos de alto nivel para entender como funciona la solución.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/553ffa0fbdd0a6fdbd9ca24ad529e8ffb7c4e4f7c142f8514b24c5a07e5da8b4/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f616b357974706771306c37317a796a32647839792e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/553ffa0fbdd0a6fdbd9ca24ad529e8ffb7c4e4f7c142f8514b24c5a07e5da8b4/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f616b357974706771306c37317a796a32647839792e706e67" alt="Figura 1: Pasos para visualizar hallazgos de Amazon GuardDuty"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
1. Centralizar los hallazgos de Amazon GuardDuty&lt;/h3&gt;
&lt;p&gt;Puede centralizar los hallazgos de Amazon GuardDuty en una única cuenta como Security Audit para que desde aquí se pueda implementar el resto de componentes de la solución. De esa manera, puedes usar esta solución para recibir todos los hallazgos detectados en tus cargas de trabajo, sin importar en que cuenta o…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/gerardokaztro/guardduty-to-msteams"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Una vez tengas el repositorio en tu local, busca el archivo alerts-guardduty-teams.zip y súbelo a tu función lambda.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aCdS7fNf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zx8d5pocn5l0rjj5i4ol.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aCdS7fNf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zx8d5pocn5l0rjj5i4ol.png" alt="cargar zip" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finalmente, modifica el lambda handler usando el nombre del archivo python comprimido&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ccgr-Aew--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ecq9l3x3uir0p0qi86we.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ccgr-Aew--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ecq9l3x3uir0p0qi86we.png" alt="lambda handler" width="800" height="109"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mcZ3LGzT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/goosjqc535gvo5yh7kzw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mcZ3LGzT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/goosjqc535gvo5yh7kzw.png" alt="actualizar lambda handler" width="800" height="715"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Si quieres hacer alguna modificación al código, te recomiendo hacerlo de manera local, luego comprimes el archivo python usando zip y lo subes a la función lambda.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finalmente, empieza a recibir los hallazgos:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9GKnJ9pQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztekhu1k2tg72zzvk5bz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9GKnJ9pQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztekhu1k2tg72zzvk5bz.png" alt="ms teams" width="440" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Te invito a compartir este blog, reaccionar y dejar un comentario acerca de que tan útil encuentras contenido como este. y te dejamos todas nuestras redes para que puedas seguirnos 🤝&lt;/p&gt;

&lt;p&gt;🌎 CONTACTO:&lt;/p&gt;

&lt;p&gt;🌐 Website: &lt;a href="https://bit.ly/awssecuritylatam"&gt;https://bit.ly/awssecuritylatam&lt;/a&gt;&lt;br&gt;
🤝 Meetup: &lt;a href="https://bit.ly/comunidad-awsseclatam"&gt;https://bit.ly/comunidad-awsseclatam&lt;/a&gt;&lt;br&gt;
🔗 LinkedIn: &lt;a href="https://bit.ly/linkedin-awsseclatam"&gt;https://bit.ly/linkedin-awsseclatam&lt;/a&gt;&lt;br&gt;
🗣️ Slack: &lt;a href="https://bit.ly/slack-awsseclatam"&gt;https://bit.ly/slack-awsseclatam&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔥 MÁS CONTENIDO:&lt;/p&gt;

&lt;p&gt;📺 Youtube: &lt;a href="https://bit.ly/youtube-awsseclatam"&gt;https://bit.ly/youtube-awsseclatam&lt;/a&gt;&lt;br&gt;
📺 Twitch: &lt;a href="https://bit.ly/twitch-awsseclatam"&gt;https://bit.ly/twitch-awsseclatam&lt;/a&gt;&lt;br&gt;
🤳 &lt;a href="https://bit.ly/instagram-awsseclatam"&gt;https://bit.ly/instagram-awsseclatam&lt;/a&gt;&lt;br&gt;
✍ &lt;a href="https://bit.ly/blogs-awsseclatam"&gt;https://bit.ly/blogs-awsseclatam&lt;/a&gt;&lt;br&gt;
🚨 Si quieres estar enterado de todo nuestros contenidos, no olvides suscribirte, y ¡compartir!&lt;/p&gt;

&lt;p&gt;⚠️ Si alguno de nuestros enlaces no funciona, no dudes en reportarlo.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Cómo lograr un gobierno de múltiples cuentas a escala con AWS Control Tower - Parte 2</title>
      <dc:creator>Gerardo Castro Arica</dc:creator>
      <pubDate>Fri, 17 Feb 2023 05:07:15 +0000</pubDate>
      <link>https://dev.to/aws-builders/como-lograr-un-gobierno-de-multiples-cuentas-a-escala-con-aws-control-tower-parte-2-5af7</link>
      <guid>https://dev.to/aws-builders/como-lograr-un-gobierno-de-multiples-cuentas-a-escala-con-aws-control-tower-parte-2-5af7</guid>
      <description>&lt;p&gt;En la &lt;a href="https://dev.to/aws-builders/como-lograr-un-gobierno-de-multiples-cuentas-a-escala-con-aws-control-tower-parte-1-1iko"&gt;primera parte&lt;/a&gt; de esta serie, entendimos cuales son los beneficios que se obtienen al &lt;strong&gt;hacer uso de una estrategia de múltiples cuentas AWS&lt;/strong&gt; por sobre el uso de una cuenta única.&lt;/p&gt;

&lt;p&gt;Descubrimos que haciendo uso de &lt;strong&gt;AWS Control Tower&lt;/strong&gt; se obtiene el &lt;strong&gt;aprovisionamiento automatizado de cuentas AWS con una configuración básica consistente,&lt;/strong&gt; simplificando el gobierno, y el cumplimiento de múltiples cuentas con modelos prescriptivos y prácticas recomendadas.&lt;/p&gt;

&lt;p&gt;Como resumen, al llegar a este blog, &lt;em&gt;ya hemos aprendido a activar AWS Control Tower y a configurarlo adecuadamente&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;En esta segunda parte, nos enfocaremos a, &lt;strong&gt;¿Cuál es la estrategia de gobierno que puedo lograr con AWS Control Tower de acuerdo a los intereses y lineamientos de mi organización?,&lt;/strong&gt; por supuesto que, &lt;strong&gt;cada organización tiene intereses y objetivos diferentes,&lt;/strong&gt; hay organizaciones que están interesadas en el &lt;strong&gt;cumplimiento de estándares como la ISO 270001 y PCI&lt;/strong&gt;, otras en cambio a &lt;em&gt;estándares como GDPR, NIST e incluso HIPAA.&lt;/em&gt; Todo depende del &lt;em&gt;rubro de la organización, de la residencia de sus datos, del tipo de datos que almacenen y procesen sean propios o de clientes, entre otros factores.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Para profundizar en el tema de este blog, usaremos como escenario ser una organización del rubro de servicios financieros (Banca, Fintech, Neo Bank, Payment Processors, Corredora de seguros, etc) donde como lineamientos de la organización es el cumplimiento de estándares como PCI, ISO 27001 y NIST. Veremos a continuación como AWS Control Tower nos ayuda a cumplir con nuestros objetivos.&lt;/p&gt;

&lt;h2&gt;
  
  
  PASO 1: Cómo organizar mis cargas de trabajo mediante Unidades Organizativas
&lt;/h2&gt;

&lt;p&gt;Cuando tenemos cargas de trabajo para diferentes propósitos y entornos, es una recomendación general organizarlas en OUs diferentes. A continuación te explico cuales son las OUs que deberías tener dentro de tu AWS Organization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- OU Workloads:&lt;/strong&gt;&lt;br&gt;
Esta OU puede subdividirse en otras OUs para organizar mejor tus cargas de trabajo por entornos (Dev, QAS y PRD), por país operación (Perú, Argentina, Colombia, Chile, etc), por tipo de unidad de negocio (Banca, Seguro, Corredora), por tipo de carga de trabajo (Inteligencia de Negocios, Seguridad, Machine Learning, Serverless, etc).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- OU Sandbox:&lt;/strong&gt;&lt;br&gt;
Las cuentas dentro de esta OU deben estar aisladas del resto del ecosistema pues se requieren permisos menos restrictivos ya que su propósito es solo de experimentación, tal y como su nombre lo indica. Debemos asegurarnos que las cuentas dentro de esta OU se encuentren completamente aisladas de las otras OUs tanto a nivel de autenticación y red. Es muy útil que se apliquen los guardrails o service control policies (SCP) necesarios para evitar que “por simple aprendizaje” se termine levantando recursos de manera indiscriminada y que generen un alto costo.&lt;br&gt;
Les recomiendo este &lt;a href="https://aws.amazon.com/es/blogs/aws-spanish/gestion-automatica-de-recursos-efimeros-para-pruebas-usando-tecnologia-sin-servidor/" rel="noopener noreferrer"&gt;blogpost&lt;/a&gt; donde encontrarán una forma automatizada con buenas prácticas incorporadas para operar dicha OU y tener un buen control sobre el presupuesto de la cuenta.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- OU Shared:&lt;/strong&gt;&lt;br&gt;
Aquí se pueden desplegar servicios compartidos por el resto de cuentas, por ejemplo, el uso de algún firewall IPS/IDS como AWS Network Firewall con un modelo de despliegue de salida hacia internet centralizada, el uso de VPNs como Site to Site o SSL. También puede centralizar la implementación de AWS Transit Gateway, RAM. Asi como casos donde se implementan y comparten servicios de directorio (AD), resolución de nombre de dominio (DNS) y cuentas de herramientas de desarrollo y ciclo de vida del software (SDLC) repositorio de código fuente, repositorio de artefactos, pipelines de CI/CD, servicios de infraestructura cómo código, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- OU Security:&lt;/strong&gt;&lt;br&gt;
En esta OU, se despliegan las herramientas de seguridad que el equipo de seguridad usaría para monitorear el entorno y asegurarse de que no haya brechas de seguridad y que tengan las herramientas necesarias para remediar cualquiera de estas acciones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- OU Suspended:&lt;/strong&gt;&lt;br&gt;
Si tienes cuentas que ya no son necesarias y quieres mantenerla en una OU un tanto especifica, puedes incluirla en esta OU. No está demás mencionar que es necesario que a esta OU se les asocie  una SCP que deniegue cualquier tipo de acción. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- OU Policy Staging:&lt;/strong&gt;&lt;br&gt;
Recomiendo que antes de aplicar los guardrails o SCPs a una cuenta de producción, estos hayan sido probado antes en alguna cuenta para pruebas (no confundir con un ambiente sandbox).Tener una OU para ensayar políticas, puede resultar útil.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fehw56ajc0n0ku2w181sf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fehw56ajc0n0ku2w181sf.png" alt="OUs" width="383" height="131"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  PASO 2: Identificar y habilitar los controles (guardrails)
&lt;/h2&gt;

&lt;p&gt;Una vez separada tus cargas de trabajo mediante la &lt;strong&gt;segmentación de cuentas y unidades organizativas&lt;/strong&gt; y como organización cuyo rubro es de servicios financieros, necesitamos controles que nos permitan cumplir o en su efecto estar alineados a los estándares que necesitamos.&lt;/p&gt;
&lt;h3&gt;
  
  
  AWS Control Tower Controls Library
&lt;/h3&gt;

&lt;p&gt;Esta feature nos permite usar los diferentes tipos de controles que podemos encontrar agrupados por categoría.&lt;/p&gt;

&lt;p&gt;Repasemos, &lt;strong&gt;¿Qué es un control o guardrail en AWS Control Tower?&lt;/strong&gt;&lt;br&gt;
👉 &lt;strong&gt;Un control es una regla de alto nivel que nos permite aplicar una definición de gobierno&lt;/strong&gt; sobre una unidad organizacional y posteriormente una cuenta o grupo de cuentas. &lt;strong&gt;Estos controles van a tener la misión de mejorar una postura de seguridad enfocada a objetivos de controles&lt;/strong&gt; que pueden ser: &lt;em&gt;logging monitoring, protección de datos entre otros, gestión de las vulnerabilidades, mínimo privilegio,&lt;/em&gt; entre otros.&lt;/p&gt;

&lt;p&gt;Dentro de esta característica, vamos a conocer cuáles son, cómo funcionan y qué tipos existen, hasta aplicar una estrategia de controles en nuestro ambiente de múltiples cuentas AWS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F887e2msmfhr4gs9ks4yo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F887e2msmfhr4gs9ks4yo.png" alt="All Controls Library" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Control Behavior: ¿Cómo es que funcionan?
&lt;/h3&gt;

&lt;p&gt;Repasemos nuevamente, existen 3 tipos de comportamientos para los controles:&lt;br&gt;
1️⃣ Preventivos&lt;br&gt;
2️⃣ Detectivos&lt;br&gt;
3️⃣ Proactivos&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frqk6ofbzz7ytur0e6h3l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frqk6ofbzz7ytur0e6h3l.png" alt="Controls Behavior" width="748" height="198"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Controles Preventivos
&lt;/h4&gt;

&lt;p&gt;Previenen acciones dentro de nuestra infraestructura en AWS. Estos controles funcionan con SCP manejada por Control Tower, y deniegan acciones específicas relacionadas con el tipo de control que queremos aplicar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4joncpwbte7h42msa312.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4joncpwbte7h42msa312.png" alt="Preventive control" width="469" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Los controles preventivos, garantizan que la infraestructura esté en compliance con ese control o requerimiento específico, ya que no permite por medio de la SCP que se despliega recursos o que se haga alguna configuración inadecuada.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flw20t7hjfhvx84ynvw72.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flw20t7hjfhvx84ynvw72.png" alt="Controles Preventivos" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Otra característica de este tipo de controles es que su estado es &lt;strong&gt;“enforced”&lt;/strong&gt; o &lt;strong&gt;“not enabled".&lt;/strong&gt; Por lo que si queremos saber si estamos en cumplimiento con un control específico, solo basta con mirar el estado del control preventivo.&lt;/p&gt;
&lt;h5&gt;
  
  
  Ejemplo de un control preventivo
&lt;/h5&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": "*",
            "Resource": "*",
            "Effect": "Deny",
            "Condition": {
                "StringLike": {
                    "aws:PrincipalArn": [
                        "arn:aws:iam::*:root"
                    ]
                }
            }
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Este guardrail &lt;strong&gt;(por detrás se aplica una SCP)&lt;/strong&gt; es un control que previene que se realicen acciones en las cuentas AWS con el usuario root ya que se considera que el uso de este usuario privilegiado sobre la cuenta son para acciones de alto nivel y no para tareas del día a día, es decir, si tus acciones son de desplegar infra, cómo crear recursos en EC2, S3 entre otros, &lt;strong&gt;por favor, no uses la cuenta root.&lt;/strong&gt; Y como parte del equipo de seguridad, si lo que &lt;strong&gt;requiero es prevenir este tipo de acciones y estar 100% en cumplimiento,&lt;/strong&gt; pues este control preventivo de ejemplo es el ideal para responder.&lt;/p&gt;
&lt;h4&gt;
  
  
  Controles Detectivos
&lt;/h4&gt;

&lt;p&gt;Nos permiten &lt;strong&gt;detectar cambios en la configuración actual&lt;/strong&gt; de nuestra infraestructura e &lt;strong&gt;identifica si estos cambios se alinean o no con la postura de seguridad de la organización.&lt;/strong&gt; Estos controles &lt;strong&gt;no garantizan&lt;/strong&gt; qué todos nuestros recursos estén en compliance a diferencia de los controles preventivos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzld9hyatgnzycthhldtw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzld9hyatgnzycthhldtw.png" alt="Detective Control" width="503" height="135"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Los controles detectivos funcionan con &lt;strong&gt;Config Rules,&lt;/strong&gt; que nos permite &lt;strong&gt;conocer los cambios de configuración de los elementos en los recursos&lt;/strong&gt; que estén desplegados y cuyos resultados son aprovechados por Control Tower.&lt;/p&gt;

&lt;p&gt;En este tipo de controles, tenemos 3 estados que son &lt;strong&gt;“Clear”, “In Violation”&lt;/strong&gt; y &lt;strong&gt;“not enabled”.&lt;/strong&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  Ejemplo de un control detectivo
&lt;/h5&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: "2010-09-09"
Description: ""
Resources:
  ConfigRule:
    Type: "AWS::Config::ConfigRule"
    Properties:
      ConfigRuleName: "s3-bucket-public-read-prohibited"
      Scope:
        ComplianceResourceTypes:
          - "AWS::S3::Bucket"
      Description: "A Config rule that checks that your Amazon S3 buckets do not allow public read access. If an Amazon S3 bucket policy or bucket ACL allows public read access, the bucket is noncompliant."
      Source:
        Owner: "AWS"
        SourceIdentifier: "S3_BUCKET_PUBLIC_READ_PROHIBITED"
Parameters: {}
Metadata: {}
Conditions: {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Este guardrail (por detrás se aplica una config rule) es un control que detecta cuando un bucket S3 cambia su estado de configuración, pasando de haber sido un recurso privado a uno público.&lt;/p&gt;
&lt;h4&gt;
  
  
  Controles Proactivos:
&lt;/h4&gt;

&lt;p&gt;Nos ayudan a identificar la infraestructura que está a punto de desplegarse en la cuenta AWS, dando como resultado si los recursos a aprovisionarse serán compliance o no. Este análisis es realizado en la misma definición del código o template por el que se aprovisiona la infraestructura. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbbqytqnlnw7k6mkwy6w1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbbqytqnlnw7k6mkwy6w1.png" alt="Proactive control" width="204" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cabe mencionar que funcionan utilizando cloudformation hooks, que precisamente es para identificar configuraciones específicas en recursos a desplegar. Un punto a considerar sobre este tipo de controles, es que solo es aplicable por recursos que son provisionados por AWS Cloudformation, aun no soporta recursos desplegados por Terraform, Pulumi, CDK, entre otros.&lt;/p&gt;

&lt;p&gt;💡 TIP: Si usas StackSets (característica que permite desplegar infraestructura en múltiples cuentas) también podrás hacer uso de los controles proactivos.&lt;/p&gt;
&lt;h4&gt;
  
  
  Categorías de controles y Frameworks
&lt;/h4&gt;

&lt;p&gt;Los controles en AWS Control Tower, se agrupan de la siguiente manera:&lt;/p&gt;
&lt;h5&gt;
  
  
  Agrupados por servicios de AWS:
&lt;/h5&gt;

&lt;p&gt;Indican los servicios sobre los que se aplican los controles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxwcygf7ipj3fq51jb5td.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxwcygf7ipj3fq51jb5td.png" alt="Services" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  Agrupados por objetivos de control:
&lt;/h5&gt;

&lt;p&gt;Indican en qué épica de seguridad nos ayudan a mejorar como: limitar el acceso a la red, administrar los secretos, impulsar el mínimo privilegio, administrar las vulnerabilidades, entre muchos otros.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fylke3anurhfro78tsti4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fylke3anurhfro78tsti4.png" alt="Controls Objective" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  Agrupados por Framework:
&lt;/h5&gt;

&lt;p&gt;Nos muestran 3 categorías que nos ayudan a estar en cumplimiento respecto a las buenas prácticas que recomienda AWS con referencia a NIST, PCI y CIS AWS Benchmark.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq6f001j0zqrvj0dxs37t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq6f001j0zqrvj0dxs37t.png" alt="Framework" width="800" height="230"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  PASO 3: Recomendaciones para habilitar controles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Revisa cada uno de los controles antes de activarlos en una OU (y por consecuente en las cuentas). Es necesario tener una clara comprensión sobre los controles que necesites habilitar y que este entendimiento sea compartido entre los equipos de seguridad, desarrollo y operaciones.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si quieres conocer el detalle de un control en especifico, desplázate hacia a la pestaña &lt;strong&gt;“About”&lt;/strong&gt; y encontraras información útil del control.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm3yg75obxs8716aqweiu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm3yg75obxs8716aqweiu.png" alt="controls details" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Es importante que se tenga definido que objetivos se quieren mejorar en la organización, es decir, cuales son esas políticas de seguridad a las que se necesita estar alineado. De esta forma, en Controls Library podrás los controles que te ayuden a mejorar la postura de esa política.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por ejemplo, si están utilizando Amazon S3 y quieren averiguar todos los controles relacionados a este servicio y cumplir con el control objetivo de “protección de datos” pueden hacerlo de esta manera:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F483zvi5jh2b4wvv0jf8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F483zvi5jh2b4wvv0jf8e.png" alt="protection data policy" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  PASO 4: Conclusión
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Identifica los controles que quieres habilitar

&lt;ul&gt;
&lt;li&gt;Recuerda profundizar sobre el control: definición, tipo, comportamiento y referencia.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Despliega los controles aplicando buenas prácticas

&lt;ul&gt;
&lt;li&gt;Recuerda tener una &lt;strong&gt;OU Policy Stagging&lt;/strong&gt; con la finalidad de probar aquí los guardrails (controles) para luego ir pasando de a poco hacia OU productivas.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Monitorea el estado de los controles habilitados

&lt;ul&gt;
&lt;li&gt;Importante monitorear los controles de tipo detectivo, ya que ellos te dirán que recursos se encuentran "In Violation", quiere decir, recursos non-compliance, identifica esos recursos y remédialos.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;💡 TIP: Usa esta sintaxis en tu AWS CLI o AWS CloudShell para identificar los controles que tengas habilitados en alguna OU especifica.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws controltower list-enabled-controls --target-identifier &amp;lt;OU Id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ℹ️ Si quieres aprender mas sobre AWS Control Tower - Controls Library, te invito a ver este vídeo, recomendádselo&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/y3cg3aKvLmY"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Te invito a compartir este blog, reaccionar y dejar un comentario de que te tan útil encuentras contenido como este. y te dejamos todas nuestras redes para que puedas seguirnos 🤝&lt;/p&gt;

&lt;p&gt;🌎 CONTACTO:&lt;/p&gt;

&lt;p&gt;🌐 Website: &lt;a href="https://bit.ly/awssecuritylatam" rel="noopener noreferrer"&gt;https://bit.ly/awssecuritylatam&lt;/a&gt;&lt;br&gt;
🤝 Meetup: &lt;a href="https://bit.ly/comunidad-awsseclatam" rel="noopener noreferrer"&gt;https://bit.ly/comunidad-awsseclatam&lt;/a&gt;&lt;br&gt;
🔗 LinkedIn: &lt;a href="https://bit.ly/linkedin-awsseclatam" rel="noopener noreferrer"&gt;https://bit.ly/linkedin-awsseclatam&lt;/a&gt;&lt;br&gt;
🗣️ Slack: &lt;a href="https://bit.ly/slack-awsseclatam" rel="noopener noreferrer"&gt;https://bit.ly/slack-awsseclatam&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔥 MÁS CONTENIDO:&lt;/p&gt;

&lt;p&gt;📺 Youtube: &lt;a href="https://bit.ly/youtube-awsseclatam" rel="noopener noreferrer"&gt;https://bit.ly/youtube-awsseclatam&lt;/a&gt;&lt;br&gt;
📺 Twitch: &lt;a href="https://bit.ly/twitch-awsseclatam" rel="noopener noreferrer"&gt;https://bit.ly/twitch-awsseclatam&lt;/a&gt;&lt;br&gt;
🤳 &lt;a href="https://bit.ly/instagram-awsseclatam" rel="noopener noreferrer"&gt;https://bit.ly/instagram-awsseclatam&lt;/a&gt;&lt;br&gt;
✍ &lt;a href="https://bit.ly/blogs-awsseclatam" rel="noopener noreferrer"&gt;https://bit.ly/blogs-awsseclatam&lt;/a&gt;&lt;br&gt;
🚨 Si quieres estar enterado de todo nuestros contenidos, no olvides suscribirte, y ¡compartir!&lt;/p&gt;

&lt;p&gt;⚠️ Si alguno de nuestros enlaces no funciona, no dudes en reportarlo.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>security</category>
      <category>spanish</category>
    </item>
    <item>
      <title>Cómo lograr un gobierno de múltiples cuentas a escala con AWS Control Tower - Parte 1</title>
      <dc:creator>Gerardo Castro Arica</dc:creator>
      <pubDate>Tue, 07 Feb 2023 19:43:31 +0000</pubDate>
      <link>https://dev.to/aws-builders/como-lograr-un-gobierno-de-multiples-cuentas-a-escala-con-aws-control-tower-parte-1-1iko</link>
      <guid>https://dev.to/aws-builders/como-lograr-un-gobierno-de-multiples-cuentas-a-escala-con-aws-control-tower-parte-1-1iko</guid>
      <description>&lt;p&gt;Hoy en día, los clientes de las industrias reguladas enfrentan el desafío de definir y hacer cumplir los controles necesarios para cumplir con los requisitos de cumplimiento y seguridad, en la mayoría de casos, es posible que las organizaciones también deban cumplir con marcos y estándares como ISO 27001 y NIST 800-53.&lt;/p&gt;

&lt;p&gt;Mantener la seguridad y la gobernabilidad en un modelo de múltiples cuentas podría ser un desafío para algunas organizaciones. Sin controles preventivos estructurados aplicados a escala (&lt;strong&gt;guardrails&lt;/strong&gt;) y la aplicación de configuraciones básicas (&lt;strong&gt;Baseline&lt;/strong&gt;), la solución de problemas y la mitigación de riesgos puede requerir un esfuerzo mayor al esperado.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;¿Qué es una cuenta AWS?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Una cuenta AWS representa un límite de seguridad que aísla nuestros recursos y cargas de trabajo frente a las cargas de otros clientes de AWS, aunque hagamos uso de la misma región, la misma zona de disponibilidad e incluso el mismo centro de datos.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;¿Qué estrategia me conviene usar: Cuenta única o Multi-Cuenta?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Si tu propósito es hacer uso de una cuenta con fines de aprendizaje, pruebas, sandbox, entonces te recomendaría solo tener una única cuenta AWS para que sea más sencilla de administrar.&lt;/p&gt;

&lt;p&gt;Pero, si vas a utilizar la nube para tu empresa u organización, para alguna carga de trabajo productiva, se recomienda un esquema multi-cuentas, por las siguientes razones: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Para separar tu infraestructura en entornos como producción, testing y desarrollo.&lt;/li&gt;
&lt;li&gt;Si desde un punto de vista temprano, sabes que vas a gestionar una infraestructura a escala, para separar las cargas de trabajo y que un despliegue de una unidad de negocio no afecte las cargas de otra unidad. &lt;/li&gt;
&lt;li&gt;Para  simplificar la separación de costos Unidades de negocio o proyecto, en cada cuenta.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Entonces, &lt;em&gt;¿Cómo podemos gestionar a escala consistentemente múltiples cuentas de AWS?&lt;/em&gt; Usando &lt;strong&gt;AWS Control Tower,&lt;/strong&gt; el mismo que ya fué &lt;a href="https://aws.amazon.com/es/about-aws/whats-new/2019/06/aws-control-tower-is-now-generally-available/" rel="noopener noreferrer"&gt;anunciado de manera global en 2019&lt;/a&gt; para los clientes de AWS. Este servicio automatiza el aprovisionamiento de cuentas con una configuración básica consistente, simplifica la gobernanza, y el cumplimiento de múltiples cuentas con modelos prescriptivos y prácticas recomendadas.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Introducción&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;En este blog post, les mostraré cómo activar AWS Control Tower, asumiré que es la primera vez que usaras este servicio, iré paso a paso para que no te pierdas de nada. Si ya tienes el conocimiento básico o configuraste el servicio, te invito a ver &lt;a href="https://bit.ly/controltower-p2d" rel="noopener noreferrer"&gt;la parte 2 de esta serie.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Descripción general del servicio:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;AWS Control Tower configura tres cuentas de referencia (&lt;strong&gt;Management Account, Log Archive y Security Audit&lt;/strong&gt;) que proporcionan entornos dedicados para funciones especializadas dentro de su organización.&lt;/p&gt;

&lt;p&gt;Les dejo una breve descripción de cada cuenta de referencia:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Management Account&lt;/strong&gt; contiene información de facturación para cada recurso en su landing zone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log Archive&lt;/strong&gt; proporciona a su equipo el acceso a la información de registro (logs) de todas sus cuentas asociadas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Audit&lt;/strong&gt; proporciona a su equipo el acceso a la información de auditoría que AWS Control Tower pone a su disposición, principalmente por motivos de seguridad y cumplimiento.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Características adicionales:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Landing zone:&lt;/strong&gt; Es el entorno general de las múltiples cuentas que AWS Control Tower configura por nosotros, a partir de una cuenta de AWS nueva.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controls:&lt;/strong&gt; Tambien conocido como guardrail, es una regla de alto nivel que proporciona un gobierno continuo para nuestro entorno general de AWS.

&lt;ul&gt;
&lt;li&gt;Existen tres tipos de controles:

&lt;ul&gt;
&lt;li&gt;preventivo&lt;/li&gt;
&lt;li&gt;detectivo&lt;/li&gt;
&lt;li&gt;proactivo&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Se aplican tres categorías de orientación a los controles: 

&lt;ul&gt;
&lt;li&gt;mandatorios&lt;/li&gt;
&lt;li&gt;fuertemente recomendados&lt;/li&gt;
&lt;li&gt;electivos.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Account Factory:&lt;/strong&gt; es una plantilla de cuenta configurable que ayuda a estandarizar el aprovisionamiento de nuevas cuentas con configuraciones de cuenta preaprobadas.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Dashboard:&lt;/strong&gt; ofrece una supervisión continua de su landing zone a su equipo de administradores de la nube central.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Cómo Activar AWS Control Tower:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Desde su cuenta root (o &lt;strong&gt;Management Account&lt;/strong&gt;), ir al servicio de AWS Control Tower y dar clic en &lt;strong&gt;“Set Up Landing Zone”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F71358youj9l48ljwr1y9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F71358youj9l48ljwr1y9.png" alt="Dashboard de AWS Control Tower" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS Control Tower verificará ciertos requisitos como el no tener activado Cloudtrail y Config a nivel Organizacional para determinar si tu cuenta root esta lista para ser configurada. De no cumplir con algunos de estos requisitos, te aparecerá un mensaje así:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fparn45q3lykbwe0xto5b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fparn45q3lykbwe0xto5b.png" alt="Mensaje de advertencia" width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Es importante corregir los requisitos fallidos para poder darle &lt;strong&gt;“Rechek”&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Paso 1: Revisión de precios y Selección de regiones&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frty5935yxwosh8yi97sq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frty5935yxwosh8yi97sq.png" alt="Paso 1 - AWS control Tower Setup" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Home Region:&lt;/strong&gt; Esta es la región predeterminada donde se aprovisionan los recursos de sus cuentas compartidas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deny Region Control&lt;/strong&gt;: El control de denegación de región es único, porque se aplica a su landing zone como un todo, en lugar de a cualquier unidad organizativa específica. Si aún no tiene idea de que regiones denegar, puede dejar esta opción en “not enabled” ya que puede cambiarse después.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Additional AWS Region:&lt;/strong&gt; Permite seleccionar las regiones que estarán bajo el gobierno de AWS Control Tower adicional al “Home Region” seleccionado. Aún podrá aprovisionar sus recursos en las regiones no seleccionadas, pero debe tener en cuenta que no estarán bajo el gobierno de AWS Control Tower. También puede configurar esta opción más tarde.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Paso 2: Configurar unidades organizativas (OU)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Las mejores prácticas de AWS para un entorno bien diseñado recomiendan que debe separar sus recursos y cargas de trabajo en varias cuentas de AWS y que se agrupan (a menudo) en unidades organizativas (OU) con fines de gobierno y control.&lt;/p&gt;

&lt;p&gt;AWS Control Tower configura automáticamente algunas de estas unidades organizativas:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Foundational OU:&lt;/strong&gt; que contiene 3 cuentas compartidas: Management Account, Log Archive y Security Audit (también conocida como Audit).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additional OU:&lt;/strong&gt; para ayudar a configurar una estrategia multi-cuentas, es recomendable crear una OU secundaria al configurar la landing Zone. Esta OU se puede utilizar para almacenar cualquier cuenta de producción o desarrollo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vh0lmuwoog3i3ilxq2c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vh0lmuwoog3i3ilxq2c.png" alt="Paso 2 - AWS control Tower Setup" width="800" height="615"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Paso 3: Configurar cuentas compartidas&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;AWS Control Tower requiere dos direcciones de correo electrónico únicas que aún no estén asociadas con una cuenta de AWS. Cada una de estas dos direcciones de correo electrónico se convierte en una bandeja de entrada colaborativa, lo que significa que cada una se convierte en una cuenta de correo electrónico compartida, a la que pueden acceder usuarios específicos de su empresa que realizan trabajos relacionados con AWS Control Tower.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fao73203tm29k8xntj238.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fao73203tm29k8xntj238.png" alt="Paso 3 - AWS control Tower Setup" width="800" height="881"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Paso 4: Configurar CloudTrail y encriptación&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;AWS CloudTrail es un servicio que registra continuamente las actividades de su cuenta de AWS y está habilitado de forma predeterminada para los trails (rastreo) de CloudTrail a nivel de organización.&lt;/p&gt;

&lt;p&gt;Los trails a nivel de organización agrega registros para todas las cuentas en la organización de AWS, incluidas las cuentas que no están gobernadas por AWS Control Tower. Puede cambiar esta configuración más adelante para evitar cargos adicionales de CloudTrail en las cuentas que no estén bajo el gobierno de AWS Control Tower.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkua6tawvvs2isbpq2za9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkua6tawvvs2isbpq2za9.png" alt="Paso 4 - AWS control Tower Setup" width="800" height="527"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;De manera opcional, en la sección de &lt;strong&gt;“Log configuration for Amazon S3”&lt;/strong&gt; puede configurar el tiempo de retención del bucket de archivos de registro de Amazon S3 y el tiempo de retención de los registros para acceder al depósito.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzfpavn3t0g7rf4hcoiff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzfpavn3t0g7rf4hcoiff.png" alt="Log Configuration for S3" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Y en la sección de &lt;strong&gt;"KMS Encryption"&lt;/strong&gt; puede seleccionar una llave KMS existente o crear una nueva que cifre los registros almacenados en el bucket que AWS Control Tower crea para almacenar los registros de Cloudtrail a nivel Organizacional. Tenga en cuenta que esta llave deberá tener permisos sobre CloudTrail. Esta opción está deshabilitada de manera predeterminada y puede configurarse más adelante.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ldp1ayz7bbze9mn7a46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ldp1ayz7bbze9mn7a46.png" alt="KMS encryption" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Paso 5: Revisar y configurar la landing zone&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Finalmente, después de toda la configuración de los pasos previos, deberás marcar la casilla de verificación y dar clic en “Set Up landing zone”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fei9anduxmx0rxdljfclz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fei9anduxmx0rxdljfclz.png" alt="Paso 5 - AWS control Tower Setup" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;El proceso que toma esta configuración dura aproximadamente 1 hora (60 minutos).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvly05t5yqkvmijs6jvw5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvly05t5yqkvmijs6jvw5.png" alt="Aprovisionamiento del Landing Zone" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Pantallas o secciones adicionales:&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;La consola ofrece algunas acciones recomendadas:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9d4osg5xirsdmsdrgq1p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9d4osg5xirsdmsdrgq1p.png" alt="recomended actions" width="800" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En este punto, se pueden ver y habilitar los controles obligatorios, opcionales y fuertemente recomendados:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhvtbagzc584niswrbvx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhvtbagzc584niswrbvx.png" alt="security controls" width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Puedo ver las unidades organizativas (OU) y las cuentas, y el estado de cumplimiento de cada una (con respecto a las medidas de seguridad):&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn7bybka8gjyx1q5qe3t7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn7bybka8gjyx1q5qe3t7.png" alt="compliance account" width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hasta aquí, ya tenemos activado AWS Control Tower. En la parte 2 de este blog, te mostraré algunas tareas operacionales que puedes hacer para administrar de manera eficiente este servicio.&lt;/p&gt;

&lt;p&gt;Te invito a compartir este blog, reaccionar y dejar un comentario de que te tan útil encuentras contenido como este. y te dejamos todas nuestras redes para que puedas seguirnos 🤝 &lt;/p&gt;

&lt;p&gt;🌎 CONTACTO:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌐 Website: &lt;a href="https://bit.ly/awssecuritylatam" rel="noopener noreferrer"&gt;https://bit.ly/awssecuritylatam&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🤝 Meetup: &lt;a href="https://bit.ly/comunidad-awsseclatam" rel="noopener noreferrer"&gt;https://bit.ly/comunidad-awsseclatam&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🔗 LinkedIn: &lt;a href="https://bit.ly/linkedin-awsseclatam" rel="noopener noreferrer"&gt;https://bit.ly/linkedin-awsseclatam&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🗣️ Slack: &lt;a href="https://bit.ly/slack-awsseclatam" rel="noopener noreferrer"&gt;https://bit.ly/slack-awsseclatam&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔥 MÁS CONTENIDO:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📺 Youtube: &lt;a href="https://bit.ly/youtube-awsseclatam" rel="noopener noreferrer"&gt;https://bit.ly/youtube-awsseclatam&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📺 Twitch: &lt;a href="https://bit.ly/twitch-awsseclatam" rel="noopener noreferrer"&gt;https://bit.ly/twitch-awsseclatam&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🤳 &lt;a href="https://bit.ly/instagram-awsseclatam" rel="noopener noreferrer"&gt;https://bit.ly/instagram-awsseclatam&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✍ &lt;a href="https://bit.ly/blogs-awsseclatam" rel="noopener noreferrer"&gt;https://bit.ly/blogs-awsseclatam&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🚨 Si quieres estar enterado de todo nuestros contenidos, no olvides suscribirte, y ¡compartir!&lt;/p&gt;

&lt;p&gt;⚠️ Si alguno de nuestros enlaces no funciona, no dudes en reportarlo.&lt;/p&gt;

</description>
      <category>writing</category>
      <category>offers</category>
      <category>devto</category>
    </item>
    <item>
      <title>AWS Re:Invent 2021 #Reaction Final Part</title>
      <dc:creator>Gerardo Castro Arica</dc:creator>
      <pubDate>Sat, 04 Dec 2021 17:05:48 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-reinvent-2021-reaction-final-part-1m65</link>
      <guid>https://dev.to/aws-builders/aws-reinvent-2021-reaction-final-part-1m65</guid>
      <description>&lt;p&gt;AWS re: Invent 2021 has come to an end. It has been a week of total ecstasy. A week has passed since I published this post at the pre-inaugural midnight madness moment:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/aws-builders" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2794%2F88da75b6-aadd-4ea1-8083-ae2dfca8be94.png" alt="AWS Community Builders " width="350" height="350"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F497606%2Fcd6efd4d-6266-4d67-8bdd-e9733338fb65.png" alt="" width="400" height="400"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/aws-builders/aws-reinvent-2021-reaction-5e9c" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;AWS Re:Invent 2021 #Reaction&lt;/h2&gt;
      &lt;h3&gt;Gerardo Castro Arica for AWS Community Builders  ・ Nov 29 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#aws&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#discuss&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#news&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;Now, let me tell you a summary of the brightest and most outstanding moments that happened for me during Werner's keynote.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keynote with Dr. Werner Vogels&lt;/strong&gt;&lt;br&gt;
Besides having had one of the best intros I have ever seen, listening to Werner's keynote was like going back in time, the beginnings of many services such as EC2 among others were remembered, since 2006. If you see the intro in detail, We will see references to the past like that "box" and we see that Werner remembers when he announced Lambda for the first time. Why so much nod to the past?&lt;/p&gt;

&lt;p&gt;After the nod to the past about EC2 instances, boom! nod to the future with EC2 M1 Mac Instances, what will come next? kind of InstancesVerse? we won't know yet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm53rmr3jmxo6xlas3snl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm53rmr3jmxo6xlas3snl.png" alt="EC2 M1 Mac Instances" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Other of the best announcements of this keynote is the launch of 30 new AWS Local Zones, including 2 AWS Local Zones in LATAM. This after highlighting how AWS has been working to offer a better user experience with very low latencies thanks to the large number of regions, and not to mention the more than 216 Edge Locations that it now has.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4jxv0uzloceg75yaqjvt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4jxv0uzloceg75yaqjvt.png" alt="AWS Local Zones" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our much-loved Amplify Studio comes with a huge upgrade. Now it allows us to create applications in a matter of hour, with a nice UI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjweaih4n7p55hanmv9u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjweaih4n7p55hanmv9u.png" alt="AWS Amplify Studio" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What I liked the most about this new release is its integration with the very powerful design tool &lt;a href="//figma.com"&gt;Figma&lt;/a&gt;. That is, you have the development of your application without the need to throw code, and with a single click, you export all that UI to Figma for prototyping. What more can you ask? Yes, I am a fan of Figma.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxwlh3wk6pdgnfr4oty9l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxwlh3wk6pdgnfr4oty9l.png" alt="Amplify Studio and Figma" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Following that line "Dev", because now SDK is compatible with runtime like Swift, Kotlin and Rust.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F212wqp8guquekldkck7c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F212wqp8guquekldkck7c.png" alt="SDKs" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sustainability pillar&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7lkry9qdfss8qa2a3awj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7lkry9qdfss8qa2a3awj.png" alt="Pilar de sostenibilidad" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Goodbye StackOverFlow, Welcome AWS re:Post&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fny6jj5g9jomxbt4jfao1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fny6jj5g9jomxbt4jfao1.png" alt="AWS re:Post" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You asked for this. Basically, this is your fault.&lt;br&gt;
By Werner Vogels.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vvhnewh00vvf3m4k2vv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vvhnewh00vvf3m4k2vv.png" alt="AWS Services" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want to see the complete Keynote, this is the video:&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/8_Xs8Ik0h1w"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>aws</category>
      <category>discuss</category>
      <category>news</category>
    </item>
  </channel>
</rss>
