<?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: Sesank Munukutla (Naga)</title>
    <description>The latest articles on DEV Community by Sesank Munukutla (Naga) (@sesank_naga_m_01).</description>
    <link>https://dev.to/sesank_naga_m_01</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%2F3764862%2F1c85bbb3-0756-4f3b-9be2-33032604f5e5.png</url>
      <title>DEV Community: Sesank Munukutla (Naga)</title>
      <link>https://dev.to/sesank_naga_m_01</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sesank_naga_m_01"/>
    <language>en</language>
    <item>
      <title>From Compliance to Drift: Building and Breaking ISO 27001 Controls on AWS (Hands-on Evidence)</title>
      <dc:creator>Sesank Munukutla (Naga)</dc:creator>
      <pubDate>Mon, 06 Apr 2026 17:13:07 +0000</pubDate>
      <link>https://dev.to/sesank_naga_m_01/from-compliance-to-drift-building-and-breaking-iso-27001-controls-on-aws-hands-on-evidence-3p7a</link>
      <guid>https://dev.to/sesank_naga_m_01/from-compliance-to-drift-building-and-breaking-iso-27001-controls-on-aws-hands-on-evidence-3p7a</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Most ISO 27001 implementations fail at one critical point: they treat compliance as a &lt;strong&gt;static checklist&lt;/strong&gt;, not a &lt;strong&gt;continuous process&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In reality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Systems drift&lt;/li&gt;
&lt;li&gt;Configurations change&lt;/li&gt;
&lt;li&gt;Controls break silently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This project demonstrates a &lt;strong&gt;real-world compliance lifecycle&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build → Break → Detect → Investigate → Fix → Verify&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform (baseline provisioning)&lt;/li&gt;
&lt;li&gt;AWS Config (continuous compliance)&lt;/li&gt;
&lt;li&gt;Security Hub (aggregation)&lt;/li&gt;
&lt;li&gt;CloudTrail (audit evidence)&lt;/li&gt;
&lt;li&gt;S3 (evidence storage)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;The system was designed as a lightweight continuous compliance engine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Terraform → AWS Config → Security Hub → CloudTrail → S3 Evidence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Terraform establishes a &lt;strong&gt;compliant baseline&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;AWS Config continuously evaluates resources&lt;/li&gt;
&lt;li&gt;Security Hub aggregates findings&lt;/li&gt;
&lt;li&gt;CloudTrail records audit evidence&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Baseline Deployment (Terraform)
&lt;/h2&gt;

&lt;p&gt;Infrastructure was deployed using Terraform, creating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Config (recorder + rules)&lt;/li&gt;
&lt;li&gt;CloudTrail (logging enabled)&lt;/li&gt;
&lt;li&gt;S3 bucket (encrypted evidence storage)&lt;/li&gt;
&lt;li&gt;Security Hub (enabled)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures a &lt;strong&gt;controlled, compliant starting point&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%2Fpze3dmn7p9gp6gfr2n7z.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%2Fpze3dmn7p9gp6gfr2n7z.png" alt="Image01" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Initial Compliance State
&lt;/h2&gt;

&lt;p&gt;After deployment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Config begins evaluation&lt;/li&gt;
&lt;li&gt;Initial state may show &lt;code&gt;INSUFFICIENT_DATA&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Eventually stabilizes into &lt;strong&gt;COMPLIANT / NON_COMPLIANT&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&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%2Fzgrv2jjnxw0pi8lxkna8.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%2Fzgrv2jjnxw0pi8lxkna8.png" alt="Image02" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;A baseline security posture before any drift&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Simulating Real-World Misconfiguration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  (ISO Control A.8.12 – Encryption of Data at Rest)
&lt;/h3&gt;

&lt;p&gt;To simulate drift, an intentional violation was introduced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created an S3 bucket&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Disabled default encryption&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&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%2Fi0dfclpkjfl631u4nv67.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%2Fi0dfclpkjfl631u4nv67.png" alt="Image03" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This represents a common real-world failure:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Data stored without encryption due to misconfiguration&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Detection via AWS Config
&lt;/h2&gt;

&lt;p&gt;AWS Config automatically evaluated the resource and flagged:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rule: &lt;code&gt;s3-bucket-server-side-encryption-enabled&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Status: &lt;strong&gt;NON_COMPLIANT&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drilling into the resource:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Specific bucket identified&lt;/li&gt;
&lt;li&gt;Evaluation result recorded&lt;/li&gt;
&lt;li&gt;Timeline shows compliance change&lt;/li&gt;
&lt;/ul&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Continuous monitoring instead of periodic audits&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Aggregation via Security Hub
&lt;/h2&gt;

&lt;p&gt;The same issue is surfaced in Security Hub as a finding.&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%2F5fg4cjg22s6ypxv1khjz.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%2F5fg4cjg22s6ypxv1khjz.png" alt="Image04" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key observations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Findings categorized by severity&lt;/li&gt;
&lt;li&gt;Centralized visibility across services&lt;/li&gt;
&lt;li&gt;Enables prioritization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This layer answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Which issues matter most right now?”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Audit Evidence via CloudTrail
&lt;/h2&gt;

&lt;p&gt;Detection alone is not sufficient — audit requires &lt;strong&gt;traceability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;CloudTrail provides that.&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%2Fa1svojx0rqgzsa9d07io.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%2Fa1svojx0rqgzsa9d07io.png" alt="Image05" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Relevant events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CreateBucket&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;No encryption configuration applied&lt;/li&gt;
&lt;/ul&gt;

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

&lt;blockquote&gt;
&lt;p&gt;The violation originated at resource creation, not later drift&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Remediation Phase
&lt;/h2&gt;

&lt;p&gt;The issue was fixed by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enabling S3 default encryption (SSE-S3)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After remediation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Config → &lt;strong&gt;COMPLIANT&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Timeline updated with state transition&lt;/li&gt;
&lt;/ul&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%2F9tpgxgb8gn74duag1xhj.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%2F9tpgxgb8gn74duag1xhj.png" alt="Image06" width="737" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This completes the full lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NON_COMPLIANT → FIX APPLIED → COMPLIANT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What This Demonstrates
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Compliance is Dynamic
&lt;/h3&gt;

&lt;p&gt;Traditional audits capture a snapshot.&lt;br&gt;
Real systems require &lt;strong&gt;continuous validation&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Terraform ≠ Compliance
&lt;/h3&gt;

&lt;p&gt;Terraform establishes a baseline, but:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Drift happens outside Infrastructure as Code&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  3. Detection vs Evidence
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AWS Config → detects issues&lt;/li&gt;
&lt;li&gt;Security Hub → aggregates&lt;/li&gt;
&lt;li&gt;CloudTrail → proves what happened&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without CloudTrail:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You cannot defend your audit findings&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  4. Real Gap in Organizations
&lt;/h3&gt;

&lt;p&gt;Most organizations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don’t monitor continuously&lt;/li&gt;
&lt;li&gt;Don’t store evidence properly&lt;/li&gt;
&lt;li&gt;Don’t validate remediation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;p&gt;This project moves ISO 27001 from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Documentation-driven compliance&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Operational, continuously validated security posture&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;Compliance is not about passing an audit.&lt;/p&gt;

&lt;p&gt;It’s about being able to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What broke?&lt;/li&gt;
&lt;li&gt;When did it break?&lt;/li&gt;
&lt;li&gt;Who changed it?&lt;/li&gt;
&lt;li&gt;Was it fixed?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This lab proves that with the right tooling,&lt;br&gt;
ISO 27001 controls can be &lt;strong&gt;measured, enforced, and verified in real-time&lt;/strong&gt;.&lt;/p&gt;




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

&lt;p&gt;This was just one control (S3 encryption).&lt;/p&gt;

&lt;p&gt;You can extend this to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IAM MFA enforcement (A.5.23)&lt;/li&gt;
&lt;li&gt;Logging validation (A.8.16)&lt;/li&gt;
&lt;li&gt;Resource tagging (A.8.9)&lt;/li&gt;
&lt;li&gt;Least privilege enforcement&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you’re building in cloud:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Start thinking in terms of &lt;strong&gt;control → detection → evidence → remediation&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s where real security engineering begins.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>beginners</category>
      <category>devops</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Event-Driven EC2 Isolation in AWS: Building a Minimal Cloud SOAR Without Buying One</title>
      <dc:creator>Sesank Munukutla (Naga)</dc:creator>
      <pubDate>Fri, 13 Feb 2026 16:56:02 +0000</pubDate>
      <link>https://dev.to/sesank_naga_m_01/event-driven-ec2-isolation-in-aws-building-a-minimal-cloud-soar-without-buying-one-32aj</link>
      <guid>https://dev.to/sesank_naga_m_01/event-driven-ec2-isolation-in-aws-building-a-minimal-cloud-soar-without-buying-one-32aj</guid>
      <description>&lt;p&gt;Detection without response is operational noise.&lt;/p&gt;

&lt;p&gt;GuardDuty alerts are valuable — but if a human has to read, decide, and manually isolate an instance, your blast radius window is still open.&lt;/p&gt;

&lt;p&gt;I wanted high-confidence findings to trigger automatic containment.&lt;/p&gt;

&lt;p&gt;So I built a minimal AWS-native SOAR pipeline.&lt;/p&gt;

&lt;p&gt;No third-party tooling.&lt;br&gt;&lt;br&gt;
No overengineering.&lt;br&gt;&lt;br&gt;
Just deterministic, event-driven response.&lt;/p&gt;


&lt;h1&gt;
  
  
  🎯 Objective
&lt;/h1&gt;

&lt;p&gt;Build an automated containment workflow that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Responds only to high-severity GuardDuty findings&lt;/li&gt;
&lt;li&gt;Automatically isolates compromised EC2 instances&lt;/li&gt;
&lt;li&gt;Preserves forensic access&lt;/li&gt;
&lt;li&gt;Avoids recursive execution&lt;/li&gt;
&lt;li&gt;Is observable and debuggable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All event-driven. No polling. No manual trigger.&lt;/p&gt;


&lt;h1&gt;
  
  
  🏗 Architecture Overview
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;GuardDuty Finding&lt;/span&gt;
&lt;span class="s"&gt;↓&lt;/span&gt;
&lt;span class="s"&gt;EventBridge Rule (severity &amp;gt;= 7)&lt;/span&gt;
&lt;span class="s"&gt;↓&lt;/span&gt;
&lt;span class="s"&gt;Lambda Function (Isolation Logic)&lt;/span&gt;
&lt;span class="s"&gt;↓&lt;/span&gt;
&lt;span class="s"&gt;Modify EC2 Security Group → Quarantine SG&lt;/span&gt;
&lt;span class="s"&gt;↓&lt;/span&gt;
&lt;span class="s"&gt;SNS Notification (Visibility Layer)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Minimal. Deterministic. Cheap.&lt;/p&gt;


&lt;h1&gt;
  
  
  Filtering at the Event Layer (Not Inside Lambda)
&lt;/h1&gt;

&lt;p&gt;Instead of checking severity inside the Lambda function, I filtered directly in EventBridge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduces unnecessary Lambda invocations&lt;/li&gt;
&lt;li&gt;Makes response criteria explicit&lt;/li&gt;
&lt;li&gt;Improves audit clarity&lt;/li&gt;
&lt;li&gt;Lowers operational cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example event pattern:&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;"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;"GuardDuty Finding"&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;"severity"&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;"numeric"&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;"&amp;gt;="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&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="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;Only high-confidence findings trigger automation.&lt;/p&gt;

&lt;p&gt;Everything else remains visible — but not auto-remediated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quarantine Security Group Design
&lt;/h2&gt;

&lt;p&gt;Containment is not termination.&lt;/p&gt;

&lt;p&gt;Terminating an instance destroys forensic evidence.&lt;/p&gt;

&lt;p&gt;My quarantine security group:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;❌ No outbound internet&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;❌ No inbound from public IP ranges&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Allow only SOC bastion IP&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Allow forensic collection host&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Optional: allow VPC Flow Logs / monitoring endpoint&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is isolation with controlled investigation access.&lt;/p&gt;

&lt;p&gt;Isolation Logic (Lambda Example)&lt;/p&gt;

&lt;p&gt;Core logic:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;ec2&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;ec2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;isolate_instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quarantine_sg_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;modify_instance_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;InstanceId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;quarantine_sg_id&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;Additional safeguards added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Check instance state before modification&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tag instance Quarantined=true&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Exit if already isolated&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Log original security groups for rollback&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Containment must be idempotent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotency: Preventing Recursive Triggers
&lt;/h2&gt;

&lt;p&gt;When Lambda modifies security groups, CloudTrail events may fire.&lt;/p&gt;

&lt;p&gt;Without safeguards, you risk infinite loops.&lt;/p&gt;

&lt;p&gt;Mitigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Tag check before modification&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Structured event filtering&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explicit function logging&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DLQ configured for failure cases&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automation that can repeat blindly is dangerous.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Modes I Modeled
&lt;/h2&gt;

&lt;p&gt;Automation amplifies mistakes.&lt;/p&gt;

&lt;p&gt;I explicitly accounted for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;IAM permission drift&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Partial security group modification&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Concurrent findings on same instance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cross-region GuardDuty setup&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;High-volume alert bursts&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mitigations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Dead Letter Queue&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lambda concurrency limits&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CloudWatch error metrics + alarms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explicit structured logs (JSON format)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Permission boundary controls&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automation without observability becomes silent failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Impact
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;MTTR from minutes to seconds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Human triage fatigue&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Decision bottlenecks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inconsistent containment actions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the real improvement was consistency.&lt;/p&gt;

&lt;p&gt;Humans improvise during incidents.&lt;br&gt;
Code executes predictably.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-Offs &amp;amp; Risks
&lt;/h2&gt;

&lt;p&gt;Auto-isolating compute is not trivial.&lt;/p&gt;

&lt;p&gt;You must consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;False positives at high severity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Production-critical workloads&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stateful applications&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Already-compromised lateral movement&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multi-account architecture&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Severity threshold tuning took longer than writing the Lambda function.&lt;/p&gt;

&lt;p&gt;That surprised me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Detection maturity does not equal response maturity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Event-driven architecture scales better than polling remediation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Idempotency is mandatory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multi-account containment becomes architecture work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automation exposes operational blind spots you didn’t know existed.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Next Iterations
&lt;/h2&gt;

&lt;p&gt;If I evolve this into a more mature Cloud SOAR pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Step Functions for multi-stage workflows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automated EBS snapshot before isolation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Memory capture integration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Slack/Jira enrichment with context&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cross-account orchestration via AWS Organizations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GuardDuty central delegated admin integration&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, it becomes a response framework — not a script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;You don’t need a commercial SOAR platform to start automating response.&lt;/p&gt;

&lt;p&gt;Start with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Deterministic triggers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Guardrails&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Observability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explicit blast radius control&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If detection isn’t wired to action, it’s just telemetry.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloudsecurity</category>
      <category>devsecops</category>
      <category>incidentresponse</category>
    </item>
    <item>
      <title>How to Implement Just-In-Time SSH Access for AWS EC2 (Stop Leaving Port 22 Open!)</title>
      <dc:creator>Sesank Munukutla (Naga)</dc:creator>
      <pubDate>Wed, 11 Feb 2026 16:53:55 +0000</pubDate>
      <link>https://dev.to/sesank_naga_m_01/how-to-implement-just-in-time-ssh-access-for-aws-ec2-stop-leaving-port-22-open-4p8m</link>
      <guid>https://dev.to/sesank_naga_m_01/how-to-implement-just-in-time-ssh-access-for-aws-ec2-stop-leaving-port-22-open-4p8m</guid>
      <description>&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Most EC2 instances have permanent SSH access. Port 22 is open to the world. Long-lived keys floating around. Security groups that nobody remembers creating.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The reality:&lt;/strong&gt; You get brute-force attempts within hours. Keys get leaked. Forgotten access stays forever.&lt;/p&gt;

&lt;p&gt;I spent last week implementing Just-In-Time (JIT) access for EC2, and it completely changed how I think about server access. Here's what I built and what I learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ How to eliminate permanent SSH access to EC2&lt;/li&gt;
&lt;li&gt;✅ Build auto-expiring access with native AWS services (no third-party tools)&lt;/li&gt;
&lt;li&gt;✅ Set up full audit trails with CloudTrail&lt;/li&gt;
&lt;li&gt;✅ Use Lambda to automate security group changes&lt;/li&gt;
&lt;li&gt;✅ Apply least-privilege IAM design&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why JIT Access Matters
&lt;/h2&gt;

&lt;p&gt;Permanent SSH access to EC2 is one of the &lt;strong&gt;most common and dangerous&lt;/strong&gt; cloud misconfigurations.&lt;/p&gt;

&lt;p&gt;Here's what happens when you leave SSH open:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🚨 Open port 22 = instant target for attackers&lt;/li&gt;
&lt;li&gt;🚨 Long-lived SSH keys = credential sprawl&lt;/li&gt;
&lt;li&gt;🚨 Forgotten security group rules = standing access nobody uses&lt;/li&gt;
&lt;li&gt;🚨 No audit trail = "who logged in last month?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;JIT access solves this:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSH access granted &lt;strong&gt;only when needed&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Access is &lt;strong&gt;time-bound&lt;/strong&gt; (expires automatically)&lt;/li&gt;
&lt;li&gt;Revocation is &lt;strong&gt;automatic&lt;/strong&gt; (no manual cleanup)&lt;/li&gt;
&lt;li&gt;Every action is &lt;strong&gt;fully auditable&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;The solution uses only native AWS services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amazon EC2&lt;/strong&gt; – The target instance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda&lt;/strong&gt; – JIT automation engine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM&lt;/strong&gt; – Least-privilege roles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudTrail&lt;/strong&gt; – Audit evidence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch&lt;/strong&gt; – Execution logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Design principle:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;No standing access. Access exists only while the automation is running.&lt;/em&gt;&lt;/p&gt;
&lt;h2&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%2F8f0ekmhby6eyu70gw79q.png" alt="Architecture Overview" width="720" height="525"&gt;
&lt;/h2&gt;
&lt;h2&gt;
  
  
  Step 1: Lock Down the EC2 Instance (Baseline)
&lt;/h2&gt;

&lt;p&gt;Start with &lt;strong&gt;zero access&lt;/strong&gt; by default.&lt;/p&gt;

&lt;p&gt;The EC2 instance is launched with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;No inbound SSH rules&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;No 0.0.0.0/0 on port 22&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ Only necessary outbound access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Eliminates permanent exposure&lt;/li&gt;
&lt;li&gt;Forces all access through controlled mechanisms&lt;/li&gt;
&lt;li&gt;Creates a secure baseline&lt;/li&gt;
&lt;/ul&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%2Faslj70c1kxf5pbgdm70o.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%2Faslj70c1kxf5pbgdm70o.png" alt="EC2 Security Group with no inbound SSH rules" width="800" height="287"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Security group showing zero SSH access by default - no port 22 open&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 2: EC2 IAM Role – SSM Only
&lt;/h2&gt;

&lt;p&gt;The EC2 instance gets an IAM role with &lt;strong&gt;only&lt;/strong&gt; the SSM managed policy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key point:&lt;/strong&gt; The instance &lt;strong&gt;cannot&lt;/strong&gt; modify its own security group.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Self-escalation attacks&lt;/li&gt;
&lt;li&gt;Instance-level privilege creep&lt;/li&gt;
&lt;li&gt;Unauthorised security group changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Separation of duties is critical here.&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%2Fti5yvvgaru6y1gj3l8lc.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%2Fti5yvvgaru6y1gj3l8lc.png" alt="IAM Role attached to EC2 instance with SSM policy only" width="800" height="386"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;EC2 instance role with only SSM permissions - cannot modify security groups&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 3: CloudTrail for Auditability
&lt;/h2&gt;

&lt;p&gt;CloudTrail is configured to log &lt;strong&gt;management events only&lt;/strong&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;✅ Security group changes&lt;/li&gt;
&lt;li&gt;✅ Who made the change&lt;/li&gt;
&lt;li&gt;✅ When it happened&lt;/li&gt;
&lt;li&gt;✅ What was modified&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why not data events?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JIT access modifies infrastructure, not data&lt;/li&gt;
&lt;li&gt;Precision logging beats noisy logging&lt;/li&gt;
&lt;li&gt;Lower costs, better signal-to-noise ratio&lt;/li&gt;
&lt;/ul&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%2Fy2i1p1c7krsw8p1e13uf.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%2Fy2i1p1c7krsw8p1e13uf.png" alt="CloudTrail configured for management events only" width="800" height="334"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;CloudTrail logging configuration - management events capture security group changes&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 4: JIT Lambda Function
&lt;/h2&gt;

&lt;p&gt;The core automation: a Lambda function that controls the access lifecycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Grant&lt;/strong&gt; – Add SSH rule for a single IP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wait&lt;/strong&gt; – Hold access for configured duration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revoke&lt;/strong&gt; – Remove the rule automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Lambda pseudo-code:&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;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Read configuration from environment variables
&lt;/span&gt;    &lt;span class="n"&gt;security_group_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SECURITY_GROUP_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;allowed_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ALLOWED_IP&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DURATION&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# seconds
&lt;/span&gt;
    &lt;span class="c1"&gt;# Grant SSH access
&lt;/span&gt;    &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorize_security_group_ingress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;GroupId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;security_group_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;IpPermissions&lt;/span&gt;&lt;span class="o"&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;IpProtocol&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;tcp&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;FromPort&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ToPort&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;IpRanges&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CidrIp&lt;/span&gt;&lt;span class="sh"&gt;'&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;allowed_ip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/32&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="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Wait for configured duration
&lt;/span&gt;    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Revoke SSH access automatically
&lt;/span&gt;    &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revoke_security_group_ingress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;GroupId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;security_group_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;IpPermissions&lt;/span&gt;&lt;span class="o"&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;IpProtocol&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;tcp&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;FromPort&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ToPort&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;IpRanges&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CidrIp&lt;/span&gt;&lt;span class="sh"&gt;'&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;allowed_ip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/32&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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Lambda Configuration via Environment Variables
&lt;/h2&gt;

&lt;p&gt;All JIT behavior is externalized:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SECURITY_GROUP_ID&lt;/code&gt; – Which security group to modify&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ALLOWED_IP&lt;/code&gt; – Which IP gets access&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DURATION&lt;/code&gt; – How long access lasts (seconds)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why environment variables?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ No hardcoding&lt;/li&gt;
&lt;li&gt;✅ Easy to audit&lt;/li&gt;
&lt;li&gt;✅ Safe to change duration without code changes&lt;/li&gt;
&lt;li&gt;✅ Works across environments (dev/staging/prod)&lt;/li&gt;
&lt;/ul&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%2Fu71xtwn8p98kzl3c2a4l.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%2Fu71xtwn8p98kzl3c2a4l.png" alt="Lambda environment variables: SECURITY_GROUP_ID, ALLOWED_IP, DURATION" width="800" height="375"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Lambda configuration externalized - no secrets hardcoded in the function&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 6: Least-Privilege Lambda Execution Role
&lt;/h2&gt;

&lt;p&gt;The Lambda execution role gets &lt;strong&gt;only&lt;/strong&gt; the permissions needed:&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="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ec2:AuthorizeSecurityGroupIngress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ec2:RevokeSecurityGroupIngress"&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;"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:ec2:*:*:security-group/sg-xxxxxxxxx"&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;&lt;strong&gt;This enforces:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Separation of duties&lt;/li&gt;
&lt;li&gt;Minimal blast radius&lt;/li&gt;
&lt;li&gt;Defense in depth&lt;/li&gt;
&lt;/ul&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%2F3e0yku9cuyo3tmjg0usr.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%2F3e0yku9cuyo3tmjg0usr.png" alt="Lambda execution role with least-privilege IAM policy" width="800" height="378"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;IAM policy allowing only security group ingress modifications - nothing else&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 7: JIT Execution (Grant → Revoke)
&lt;/h2&gt;

&lt;p&gt;When you execute the Lambda:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timeline:&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;00:00 – Lambda starts
00:01 – SSH access GRANTED for 203.0.113.45/32
05:00 – Access active (you can SSH in)
10:00 – SSH access REVOKED automatically
10:01 – Lambda completes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key log evidence:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ SSH access granted (CloudWatch)&lt;/li&gt;
&lt;li&gt;✅ SSH access revoked (CloudWatch)&lt;/li&gt;
&lt;li&gt;✅ Execution duration ≈ configured timeout&lt;/li&gt;
&lt;li&gt;✅ No manual intervention&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This confirms true JIT behavior, not manual cleanup.&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%2F9djtx6jn5yxryxugevb3.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%2F9djtx6jn5yxryxugevb3.png" alt="CloudWatch logs showing SSH access granted and then revoked automatically" width="800" height="386"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The smoking gun: Logs prove access was granted at 00:01 and auto-revoked at 10:00&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 8: CloudTrail Proof (Audit Evidence)
&lt;/h2&gt;

&lt;p&gt;CloudTrail records both sides of the access lifecycle:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grant event:&lt;/strong&gt;&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;"eventName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AuthorizeSecurityGroupIngress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"userIdentity"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AssumedRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"principalId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AROAXXXXXXXXX:jit-lambda-function"&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;"requestParameters"&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;"groupId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sg-xxxxxxxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ipPermissions"&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;"items"&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;"ipProtocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"fromPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"toPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ipRanges"&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;"203.0.113.45/32"&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="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;&lt;strong&gt;Revoke event:&lt;/strong&gt;&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;"eventName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RevokeSecurityGroupIngress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"userIdentity"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AssumedRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"principalId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AROAXXXXXXXXX:jit-lambda-function"&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;&lt;strong&gt;What this proves:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📊 Who made the change (Lambda role)&lt;/li&gt;
&lt;li&gt;📊 What was changed (security group rule)&lt;/li&gt;
&lt;li&gt;📊 When it happened (timestamp)&lt;/li&gt;
&lt;li&gt;📊 That it was revoked (not forgotten)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This is audit-grade evidence, not just console screenshots.&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%2F4f36qyb4kajvm4ir937m.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%2F4f36qyb4kajvm4ir937m.png" alt="CloudTrail event showing AuthorizeSecurityGroupIngress" width="800" height="354"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;CloudTrail proof #1: Lambda granted SSH access for specific IP at exact timestamp&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Project Demonstrates
&lt;/h2&gt;

&lt;p&gt;✅ &lt;strong&gt;No standing SSH access&lt;/strong&gt; – Zero permanent exposure&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Time-bound, IP-restricted&lt;/strong&gt; – Access expires automatically&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Automatic revocation&lt;/strong&gt; – Enforced by code, not humans&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Full auditability&lt;/strong&gt; – CloudTrail logging for compliance&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Least-privilege IAM&lt;/strong&gt; – Minimal permissions everywhere  &lt;/p&gt;




&lt;h2&gt;
  
  
  The 5 Mistakes I Made (So You Don't Have To)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Forgot to add CloudWatch Logs permissions&lt;/strong&gt; – Lambda couldn't log execution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Used &lt;code&gt;0.0.0.0/0&lt;/code&gt; in testing&lt;/strong&gt; – Defeated the entire point&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Didn't externalize duration&lt;/strong&gt; – Had to redeploy Lambda to change timing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set timeout too short&lt;/strong&gt; – Lambda terminated before revoking access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Didn't test CloudTrail delay&lt;/strong&gt; – Events take 5-15 minutes to appear&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Security isn't about blocking access — it's about granting the right access, for the right time, with proof.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This project shows how JIT access can be implemented using native AWS services without introducing operational complexity or permanent risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No third-party tools. No manual cleanup. Just automatic, auditable, time-bound access.&lt;/strong&gt;&lt;/p&gt;




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

&lt;p&gt;In the next post, I'll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integrating this with AWS Systems Manager Session Manager (no SSH at all!)&lt;/li&gt;
&lt;li&gt;Adding Slack notifications when access is granted&lt;/li&gt;
&lt;li&gt;Building a self-service portal for developers&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Have you implemented JIT access in your environment? What challenges did you face?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Drop a comment below or connect with me on &lt;a href="https://www.linkedin.com/in/suryasesank" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Series: AWS Security Projects
&lt;/h3&gt;

&lt;p&gt;This is part of my Friday Security Projects series, where I build hands-on cloud security projects focused on real-world failures and defence-in-depth design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Previous projects:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project 5: Zero-Trust EC2 Access with IAM, SSM, and GuardDuty&lt;/li&gt;
&lt;li&gt;Project 4: Eliminating SSH with AWS Systems Manager&lt;/li&gt;
&lt;li&gt;Project 3: Implementing Security Controls in Production&lt;/li&gt;
&lt;li&gt;Project 2: Security Design Trade-offs&lt;/li&gt;
&lt;li&gt;Project 1: Building a Secure Cloud Baseline&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>aws</category>
      <category>security</category>
      <category>cybersecurity</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
