<?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: Lokesh Vangari</title>
    <description>The latest articles on DEV Community by Lokesh Vangari (@lokesh_vangari_a671430724).</description>
    <link>https://dev.to/lokesh_vangari_a671430724</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%2F3562872%2F5b7a33fd-5718-4b50-8ec4-8611a5362ba2.jpg</url>
      <title>DEV Community: Lokesh Vangari</title>
      <link>https://dev.to/lokesh_vangari_a671430724</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lokesh_vangari_a671430724"/>
    <language>en</language>
    <item>
      <title>Creating Multiple AWS EventBridge Rules Using CDKTF and a Config File from S3</title>
      <dc:creator>Lokesh Vangari</dc:creator>
      <pubDate>Fri, 23 Jan 2026 15:07:03 +0000</pubDate>
      <link>https://dev.to/lokesh_vangari_a671430724/creating-multiple-aws-eventbridge-rules-using-cdktf-and-a-config-file-from-s3-2gl4</link>
      <guid>https://dev.to/lokesh_vangari_a671430724/creating-multiple-aws-eventbridge-rules-using-cdktf-and-a-config-file-from-s3-2gl4</guid>
      <description>&lt;p&gt;When working with AWS EventBridge, it’s common to create multiple rules with different schedules, targets, and configurations.&lt;/p&gt;

&lt;p&gt;Initially, I was creating a separate CDKTF function or resource block for each EventBridge rule. This worked, but the code quickly became repetitive and hard to maintain.&lt;/p&gt;

&lt;p&gt;So I moved to a &lt;strong&gt;config-driven approach&lt;/strong&gt;, where EventBridge rules are created dynamically based on a JSON configuration file stored in S3.&lt;/p&gt;

&lt;p&gt;This blog explains the idea, the approach, and why it worked well for my use case.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;As the number of EventBridge rules increased, I faced a few issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repeating the same CDKTF code for every rule&lt;/li&gt;
&lt;li&gt;Small changes required code updates and redeployments&lt;/li&gt;
&lt;li&gt;Hard to manage different schedules and targets cleanly&lt;/li&gt;
&lt;li&gt;Code readability reduced as more rules were added&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted a way to &lt;strong&gt;define EventBridge rules without writing new infrastructure code every time&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;Instead of defining each EventBridge rule in CDKTF, I decided to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store EventBridge configurations in a &lt;strong&gt;JSON file&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Upload that file to &lt;strong&gt;Amazon S3&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Download and read the config file during CDKTF deployment&lt;/li&gt;
&lt;li&gt;Loop through the config and create EventBridge rules dynamically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding a new EventBridge rule only requires updating the JSON file and re-run the deployment steps&lt;/li&gt;
&lt;li&gt;No new CDKTF code is needed for each rule&lt;/li&gt;
&lt;li&gt;Infrastructure logic remains clean and reusable&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  High-Level Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Flow:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CDKTF deployment starts&lt;/li&gt;
&lt;li&gt;ConfigJSON config file is downloaded from S3&lt;/li&gt;
&lt;li&gt;Config file is parsed&lt;/li&gt;
&lt;li&gt;For each entry in the config:

&lt;ul&gt;
&lt;li&gt;An EventBridge rule is created&lt;/li&gt;
&lt;li&gt;Target resources are attached&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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%2F779zeeinqr6bj0b1v5fu.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%2F779zeeinqr6bj0b1v5fu.png" alt=" " width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Configuration File Structure (S3)
&lt;/h2&gt;

&lt;p&gt;The JSON file stored in S3 contains all EventBridge definitions.&lt;/p&gt;

&lt;p&gt;Each entry defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rule name&lt;/li&gt;
&lt;li&gt;Schedule expression&lt;/li&gt;
&lt;li&gt;Input&lt;/li&gt;
&lt;li&gt;Optional metadata
&lt;/li&gt;
&lt;/ul&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The default configuration set"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"values"&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;"cron_schedule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 0 1 1 ? 2000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"cron_enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"input"&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;"job_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&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;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The default configuration set"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"values"&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;"cron_schedule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 0 1 1 ? 2000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"cron_enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"input"&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;"job_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&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;h2&gt;
  
  
  Sample code cdktf code snippet
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;configJSON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;cron_schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;cron_enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;eventConfigs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;configJSON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;eventConfigs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// Skip if cron is disabled&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cron_enabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cloudwatchEventRule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CloudwatchEventRule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;`event-rule-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&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="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;scheduleExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`cron(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cron_schedule&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="na"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cloudwatchEventTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CloudwatchEventTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;`event-target-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&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="na"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rule&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="na"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;TARGET_ARN_HERE&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Lambda / Step Function / Batch&lt;/span&gt;
            &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&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="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Benefits of This Approach
&lt;/h2&gt;

&lt;p&gt;This approach gave me several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Less CDKTF code&lt;/li&gt;
&lt;li&gt;✅ Easier to scale (add 10 or 100 rules easily)&lt;/li&gt;
&lt;li&gt;✅ Configuration changes without code changes&lt;/li&gt;
&lt;li&gt;✅ Better separation of config and infra logic&lt;/li&gt;
&lt;li&gt;✅ Cleaner and more readable CDKTF stack&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Things to Keep in Mind
&lt;/h2&gt;

&lt;p&gt;While this works well, a few things are important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validate the config file before using it&lt;/li&gt;
&lt;li&gt;Handle missing or invalid fields safely&lt;/li&gt;
&lt;li&gt;Control access to the S3 config file&lt;/li&gt;
&lt;li&gt;Keep versioning enabled on the S3 bucket&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Using a config file from S3 to create multiple EventBridge rules dynamically helped me reduce code duplication and made the infrastructure more flexible.&lt;/p&gt;

&lt;p&gt;This pattern works well when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You expect frequent changes in scheduling&lt;/li&gt;
&lt;li&gt;Multiple similar resources need to be managed&lt;/li&gt;
&lt;li&gt;You want infra to be configuration-driven&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach can also be extended to other AWS resources like Lambda, SQS, or Step Functions.&lt;/p&gt;




&lt;p&gt;Thanks for reading! If you found this helpful, let’s connect on LinkedIn and continue sharing knowledge and experiences:&lt;a href="https://www.linkedin.com/in/lokesh-vangari/" rel="noopener noreferrer"&gt;Lokesh Vangari&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lokesh Vangari&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>cicd</category>
      <category>aws</category>
    </item>
    <item>
      <title>☁️ AWS GameDay: From Breach to Fix</title>
      <dc:creator>Lokesh Vangari</dc:creator>
      <pubDate>Thu, 16 Oct 2025 09:10:56 +0000</pubDate>
      <link>https://dev.to/lokesh_vangari_a671430724/aws-gameday-from-breach-to-fix-3o9c</link>
      <guid>https://dev.to/lokesh_vangari_a671430724/aws-gameday-from-breach-to-fix-3o9c</guid>
      <description>&lt;p&gt;Yesterday, I participated in AWS GameDay, a hands-on challenge designed to test real-world cloud problem-solving skills. The scenario was intense — our website, which was hosted on EC2 instances behind an Application Load Balancer (ALB), had been hacked and encrypted by ransomware! The task was to perform forensics on compromised instances, secure the infrastructure, and restore the application without losing valuable data or uptime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The setup consisted of:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;1 Application Load Balancer (ALB)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;1 Auto Scaling Group (ASG) configured to always maintain two healthy EC2 instances&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A website hosted on those EC2 instances, which was now showing an encrypted ransom message&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Initially, our instinct was to detach and replace the EC2 instances behind the ALB. However, the new instances were also being compromised immediately — meaning the attack source was still active. It became clear we needed to isolate, protect, and then restore in a controlled way.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧩 Step-by-Step Solution
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Isolate the Compromised Instances&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first priority was to stop the spread of the attack.&lt;br&gt;
We created a new Security Group with no inbound or outbound rules and then attached it to the compromised EC2 instances.&lt;/p&gt;

&lt;p&gt;This effectively disconnected them from the network — keeping them intact for forensic analysis, while stopping any malicious activity from spreading to the new instances.&lt;/p&gt;

&lt;p&gt;👉 After this step, the site naturally went down temporarily, as the instances were no longer reachable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Enable AWS WAF for Protection and Monitoring&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, we implemented AWS Web Application Firewall (WAF) to add an extra layer of protection.&lt;br&gt;
WAF helps monitor and block suspicious traffic before it reaches your application.&lt;/p&gt;

&lt;p&gt;We configured it with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Managed rule groups (to protect against common web exploits)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Custom rules to block specific IPs and patterns we noticed in malicious traffic logs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This ensured that when new instances were launched, they wouldn’t be instantly compromised again.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Replace the Compromised Instances&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With protection in place, it was safe to restore the website.&lt;br&gt;
We navigated to the Auto Scaling Group (ASG) and used the “Detach and Replace” option.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Removed the compromised instances&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Launched fresh, healthy EC2 instances&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Registered them with the ALB&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the new instances were up, the website came back online securely and began serving traffic normally.&lt;/p&gt;

&lt;h2&gt;
  
  
  ✅ Final Outcome
&lt;/h2&gt;

&lt;p&gt;By following this structured approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We preserved one compromised instance for forensic investigation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We blocked the attack vector using AWS WAF.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We restored normal website operations using Auto Scaling automation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was a great learning experience in incident response, security hardening, and AWS operational best practices.&lt;/p&gt;

&lt;p&gt;AWS GameDay not only tested our technical knowledge but also emphasized the importance of process, containment, and prevention in real-world security scenarios.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ec2</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Making AWS Event-bridge Cron Expressions Configurable with Azure DevOps and CDKTF</title>
      <dc:creator>Lokesh Vangari</dc:creator>
      <pubDate>Tue, 14 Oct 2025 05:09:19 +0000</pubDate>
      <link>https://dev.to/lokesh_vangari_a671430724/making-aws-event-bridge-cron-expressions-configurable-with-azure-devops-and-cdktf-3hh4</link>
      <guid>https://dev.to/lokesh_vangari_a671430724/making-aws-event-bridge-cron-expressions-configurable-with-azure-devops-and-cdktf-3hh4</guid>
      <description>&lt;p&gt;I wanted to parameterize the AWS EventBridge cron expression in my Azure DevOps pipeline using a Library Variable Group. However, the deployments kept failing whenever we used that particular variable.&lt;/p&gt;

&lt;p&gt;After checking the logs, I found that the cron expression value wasn’t being passed correctly to the deployment stage. It turned out that the issue was caused by special characters and spaces in the expression (such as parentheses, asterisks *, and question marks ?) that were being misinterpreted during YAML parsing.&lt;/p&gt;




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

&lt;p&gt;To fix this, I changed the variable value in Azure DevOps from&lt;br&gt;
&lt;code&gt;cron(0 8 * * ? *)&lt;/code&gt;  to  &lt;code&gt;cron(0-8-*-*-?-*)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then, in my CDKTF (TypeScript) code, I wrote a small helper function to convert the hyphens (-) back to spaces before using it in the AWS EventBridge configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;configureCronString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/-/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// replaces all '-' with space&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;Later&lt;/strong&gt;, I had to update the cron expression to a new value: &lt;code&gt;cron(0 13 ? * 2-6 *)&lt;/code&gt;&lt;br&gt;
This time, I realized that using - (hyphen) as a delimiter was not safe because it’s a valid character inside cron expressions (for specifying ranges like 2-6).&lt;/p&gt;

&lt;p&gt;So, after a bit of testing, I found a better delimiter that doesn’t conflict with cron syntax — the pipe &lt;code&gt;('|')&lt;/code&gt; symbol.&lt;/p&gt;

&lt;p&gt;I modified the variable value in the library to: &lt;code&gt;cron(0|13|?|*|2-6|*)&lt;/code&gt; And updated the helper method accordingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;configureCronString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&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;p&gt;This approach resolved the issue completely and allowed me to deploy the AWS EventBridge rule successfully using CDKTF with dynamic cron expressions from Azure DevOps.&lt;/p&gt;

&lt;p&gt;— Lokesh Vangari&lt;/p&gt;

</description>
      <category>aws</category>
      <category>terraform</category>
      <category>cicd</category>
    </item>
  </channel>
</rss>
