<?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: Emma Moinat</title>
    <description>The latest articles on DEV Community by Emma Moinat (@emmamoinat).</description>
    <link>https://dev.to/emmamoinat</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%2F956050%2Faf2d42ae-8b95-47b2-91f9-7e350c16cabf.jpeg</url>
      <title>DEV Community: Emma Moinat</title>
      <link>https://dev.to/emmamoinat</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/emmamoinat"/>
    <language>en</language>
    <item>
      <title>CSV Imports to DynamoDB at Scale</title>
      <dc:creator>Emma Moinat</dc:creator>
      <pubDate>Mon, 05 May 2025 07:20:25 +0000</pubDate>
      <link>https://dev.to/aws-builders/csv-imports-to-dynamodb-at-scale-4lki</link>
      <guid>https://dev.to/aws-builders/csv-imports-to-dynamodb-at-scale-4lki</guid>
      <description>&lt;p&gt;I recently had to populate a DynamoDB table with over &lt;strong&gt;740,000&lt;/strong&gt; items as part of a migration project. I tried three different approaches to see what would give me the best mix of speed, cost, and operational sanity. I’m sharing what I learned here in case you’re facing the same question: &lt;strong&gt;“What’s the best way to load a large amount of data into DynamoDB?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spoiler: Step Functions with batched Lambda invocations is the winner — but the path to that conclusion was full of interesting surprises.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before You Go Too Far...
&lt;/h2&gt;

&lt;p&gt;If your data is stored in &lt;strong&gt;S3&lt;/strong&gt; as a &lt;strong&gt;CSV&lt;/strong&gt; or &lt;strong&gt;JSON&lt;/strong&gt; file, and you're looking for a &lt;strong&gt;simple, no-code solution&lt;/strong&gt; to load it directly into DynamoDB, AWS offers an &lt;strong&gt;out-of-the-box option&lt;/strong&gt;. You can use the &lt;strong&gt;DynamoDB Data Import&lt;/strong&gt; feature from the S3 console to create a table and populate it from your S3 bucket with minimal effort.&lt;/p&gt;

&lt;p&gt;This feature is ideal if you don't need custom pipelines or complex data transformations. You simply upload your data, configure the table, and let DynamoDB handle the rest.&lt;/p&gt;

&lt;p&gt;For more details on this feature, check out the official documentation: &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/S3DataImport.HowItWorks.html" rel="noopener noreferrer"&gt;DynamoDB S3 Data Import&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If that fits your use case, this post might be more than you need — feel free to stop here.&lt;/strong&gt;&lt;/p&gt;




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

&lt;p&gt;I needed to insert &lt;strong&gt;740,000 items&lt;/strong&gt; into DynamoDB from a &lt;strong&gt;CSV file&lt;/strong&gt; as part of a migration. Each item was under 1KB, which meant write capacity usage stayed predictable and efficient. I tested:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An &lt;strong&gt;AWS Glue Job&lt;/strong&gt; using Apache Spark.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Step Function&lt;/strong&gt; with distributed map writing items directly via &lt;code&gt;PutItem&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Step Function&lt;/strong&gt; batching 1,000 items per state, passed to a Lambda using &lt;code&gt;BatchWriteItem&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s what I found.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benchmark Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Time to Load 740K Items&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Glue Job&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~12 minutes&lt;/td&gt;
&lt;td&gt;~$2.1&lt;/td&gt;
&lt;td&gt;Good for large files &amp;amp; data transformation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Step Function + Direct DynamoDB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~120 minutes&lt;/td&gt;
&lt;td&gt;~$18.5&lt;/td&gt;
&lt;td&gt;Every item is a state transition — ouch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Step Function + Lambda Batches&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~5 minutes&lt;/td&gt;
&lt;td&gt;~$1.78&lt;/td&gt;
&lt;td&gt;Fastest and cheapest with high configurability&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Option 1: Glue Job
&lt;/h2&gt;

&lt;p&gt;Glue is great when you’re dealing with S3-based batch inputs or doing big ETL-style transformations. I used 10 Data Processing Units (DPUs), and the job finished in about &lt;strong&gt;12 minutes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Summary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⏩ Fast &amp;amp; Scalable &lt;/li&gt;
&lt;li&gt;⚙️ Great for data transformation&lt;/li&gt;
&lt;li&gt;💰 Charged for minimum run time of 1-minute&lt;/li&gt;
&lt;li&gt;🤓 Some Apache Spark knowledge required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s a solid option for large datasets, especially if you already have some experience with Glue. Note that for smaller datasets, you'll still be charged for at least 1-minute processing time, even if the job finishes in under a minute.&lt;/p&gt;




&lt;h2&gt;
  
  
  Option 2: Step Function with Direct DynamoDB Writes
&lt;/h2&gt;

&lt;p&gt;This was by far the simplest implementation — just feed the items into a distributed map and call &lt;code&gt;PutItem&lt;/code&gt; on each one. You can also easily configure the concurrency of this step, but be careful about your DynamoDB table's write capacity, or you could get throttled.&lt;/p&gt;

&lt;p&gt;Unfortunately, there’s no simple way to batch the items up and use &lt;code&gt;BatchWriteItem&lt;/code&gt; directly. Serverless Land has created a workflow to do it, which you can find &lt;a href="https://serverlessland.com/workflows/batch-write-to-dynamodb" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I tested a similar approach, but it was &lt;strong&gt;slower&lt;/strong&gt; for large datasets due to more state transitions. I imagine for a small enough dataset, this could be a great solution.&lt;/p&gt;

&lt;p&gt;However, with the large dataset in my case, it was &lt;strong&gt;painfully slow&lt;/strong&gt; and &lt;strong&gt;surprisingly expensive&lt;/strong&gt;. Even just loading the CSV file from S3 took a long time, plus I hit issues with the state payload input/output being too large.&lt;/p&gt;

&lt;p&gt;Summary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🕓 Took more than &lt;strong&gt;2 hours&lt;/strong&gt; to write the data&lt;/li&gt;
&lt;li&gt;💸 Cost almost &lt;strong&gt;$20&lt;/strong&gt; because each item is a Step Function state transition ($0.025 per 1,000 transitions adds up quickly)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple? Yes. Scalable? Not really.&lt;/p&gt;




&lt;h2&gt;
  
  
  Option 3: Step Function with Lambda Batches
&lt;/h2&gt;

&lt;p&gt;Here’s where things got good. I batched the input into &lt;strong&gt;chunks of 1,000 items&lt;/strong&gt; using Step Functions' distributed map, then handed each batch off to a Lambda. That Lambda used &lt;code&gt;BatchWriteItem&lt;/code&gt; in a loop to write in chunks of 25 (the max per batch write). I ran the Lambda task with a concurrency of 20, but you can adjust this based on your table's write capacity units.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;🚀 Completed in &lt;strong&gt;~5 minutes&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;💵 Cost only &lt;strong&gt;$1.78 total&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🔁 Batch size of 1,000 kept SFN transitions and Lambda invocations low&lt;/li&gt;
&lt;li&gt;🛠 Full control over retries, unprocessed items, and logging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Lambda had &lt;strong&gt;2GB of memory&lt;/strong&gt; and finished each batch in &lt;strong&gt;~100ms&lt;/strong&gt;. Total compute cost was therefore very small.&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%2Fd3e6yygrqk1q3gypxw8l.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%2Fd3e6yygrqk1q3gypxw8l.png" alt="cost summary" width="178" height="181"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Final Take
&lt;/h2&gt;

&lt;p&gt;If you’re bulk-loading a DynamoDB table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use Step Functions + Lambda + batch writes&lt;/strong&gt; if you want the best combo of speed, cost, and control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Glue&lt;/strong&gt; if your data is already in S3 or you need transformations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid direct DynamoDB tasks in Step Functions&lt;/strong&gt; unless your item count is small.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sweet spot seems to be letting Step Functions handle parallelism and letting Lambda do the writing in batches.&lt;/p&gt;

&lt;p&gt;This setup scaled cleanly to over 740K items with no issues and minimal cost.&lt;/p&gt;




&lt;h2&gt;
  
  
  Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  DynamoDB Write Capacity Considerations
&lt;/h3&gt;

&lt;p&gt;In terms of DynamoDB write capacity, I ended up using &lt;strong&gt;on-demand&lt;/strong&gt; mode and just made sure I retried enough times when being throttled — and I &lt;em&gt;was&lt;/em&gt; throttled. If you want, you can set the table to have appropriate &lt;strong&gt;provisioned write capacity&lt;/strong&gt;. Here's a rough way to calculate the units:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Required WCUs = (Items per second) × (Item size in KB, rounded up)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, in my case, I am running 20 Lambdas concurrently, each writing batches of 25 items. Each batch write takes around &lt;strong&gt;100ms&lt;/strong&gt;. This means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Items per second = 250 items per second × 20 concurrency = 5000 items per second
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, applying the formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Required WCUs = 5000 (items per second) × 1 (item size in KB)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, a &lt;strong&gt;write capacity of 5000 WCUs&lt;/strong&gt; would have been ideal to avoid throttling.&lt;/p&gt;

&lt;p&gt;In my case, i.e. with &lt;strong&gt;on-demand&lt;/strong&gt; I was throttled quite a bit at first but as the capacity scaled up things settled:&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%2F1677p9k8isdpoozvvccy.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%2F1677p9k8isdpoozvvccy.png" alt="write units and throttling" width="800" height="193"&gt;&lt;/a&gt;&lt;br&gt;
Noting here that I ran this in 2 batches of ~300k&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Don’t underestimate the per-transition cost of Step Functions.&lt;/li&gt;
&lt;li&gt;DynamoDB is a beast.&lt;/li&gt;
&lt;li&gt;Batch everything.&lt;/li&gt;
&lt;li&gt;Lambda + Step Functions = powerful combo when tuned right.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hope this helps you avoid a few hours of experimenting. 🙂&lt;/p&gt;

&lt;p&gt;Stay tuned for a follow-up post which will include all the code I used achieve this!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Lessons Learned from Migrating to Amazon Cognito</title>
      <dc:creator>Emma Moinat</dc:creator>
      <pubDate>Fri, 08 Nov 2024 08:00:04 +0000</pubDate>
      <link>https://dev.to/aws-builders/lessons-learned-from-migrating-to-amazon-cognito-1ilg</link>
      <guid>https://dev.to/aws-builders/lessons-learned-from-migrating-to-amazon-cognito-1ilg</guid>
      <description>&lt;p&gt;Migrating a user authentication system to Amazon Cognito (or any provider) is a complex process that requires meticulous planning and attention to detail. Throughout our journey, we uncovered critical insights into configuration, best practices, and potential challenges. In this guide, we share the most valuable lessons learnt to help you navigate your Cognito migration smoothly and avoid common pitfalls.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. &lt;strong&gt;Configuration is Key: Get It Right the First Time&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Some settings in Amazon Cognito’s user pool configuration are set in stone once selected. This includes critical options like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sign-in Options&lt;/strong&gt; (e.g., email, phone number)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Username Case Sensitivity&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Required Attributes&lt;/strong&gt; (such as phone numbers, email, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Lesson:
&lt;/h4&gt;

&lt;p&gt;These settings can’t be modified post-creation, so you’ll have to create a new user pool if changes are needed later. This can be disruptive, especially if you’ve already started your migration or have active users.&lt;/p&gt;

&lt;p&gt;For example, making a phone number a required attribute may seem harmless—until you encounter partners who can’t provide one. This led to a complete reset of our migration. If you’re uncertain about making an attribute required, consider leaving it optional to maintain flexibility.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. &lt;strong&gt;Mind the Hosted UI Domain Switch&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When using Cognito’s Hosted UI, Cognito will assign a default domain, which you can replace with a custom one later. This sounds simple enough, but it’s essential to be aware of how this switch affects user login behaviours. Many users rely on password managers, and any shift in the domain will cause them to lose their saved login autofills, leading to login difficulties and frustrated users.&lt;/p&gt;

&lt;h4&gt;
  
  
  Cautionary Tale:
&lt;/h4&gt;

&lt;p&gt;A migration was performed without addressing this, resulting in significant login issues. Without observability or tracking in place, the problem wasn’t apparent until trading numbers dropped, causing a scramble to troubleshoot. To avoid this, ensure your new Hosted UI matches your previous login flow’s domain to keep user login habits uninterrupted. Also, ensure you have monitoring and observability in place early so you can catch issues quickly. AWS provides a metric for login success rate so this is a good place to start.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. &lt;strong&gt;The Password Policy Paradox&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Cognito offers robust control over password policies, from length to special characters, allowing for a highly secure environment. However, this flexibility can sometimes become a limitation.&lt;/p&gt;

&lt;h4&gt;
  
  
  Key Points:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Password Requirements&lt;/strong&gt;: If you’re migrating users with passwords from a legacy system, know that Cognito’s password policy won’t retroactively enforce stricter requirements. Users with passwords not meeting the new criteria will still be allowed, so setting &lt;code&gt;RESET_REQUIRED&lt;/code&gt; on their status might be necessary to prompt a password change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Special Character Compatibility&lt;/strong&gt;: Cognito’s allowed characters don’t include symbols like &lt;code&gt;£&lt;/code&gt;, which can be problematic if users in legacy systems used them. During migration, mismatches in allowed characters can lead to login failures, forcing preemptive password changes on your users.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Advice:
&lt;/h4&gt;

&lt;p&gt;To prepare for migration, align your legacy system’s sign-up/registration password policy with the intended Cognito password requirements as early as possible. This ensures that new passwords are migration-ready. Avoid enforcing password requirements during login to prevent access issues. Instead, set up monitoring in your legacy system to detect any passwords containing special characters not supported by Cognito. If a significant percentage of passwords conflict, consider prompting users to update their passwords before migration. Alternatively, you may issue temporary passwords for affected accounts as outlined below.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. &lt;strong&gt;User Migration Strategies: Choosing the Right Path&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Migrating users from an existing system into Cognito can follow various strategies, each with its trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lambda-Based User Migration Trigger&lt;/strong&gt;: One of the most effective approaches, this trigger allows you to migrate users only when they log in. This method is seamless for active users, while inactive accounts remain untouched, reducing unnecessary migration. You will likely need to keep the trigger active for several months to capture most of the active user base. This method will not check the user's password against your Cognito password policy, so will allow all users to be migrated. As mentioned above, you might want to enforce a password reset for these users. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Be careful with this trigger as the event includes the plaintext password, so we don't want to see any &lt;code&gt;logger.info(event)&lt;/code&gt; in this Lambda, or ever for that matter 😉.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Temporary Passwords for Unmigrated Users&lt;/strong&gt;: For users who don’t log in during the migration period, consider issuing temporary passwords. This is an effective fallback that ensures no user is left behind, though it requires sending reset instructions and user follow-up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Passwordless Authentication Options&lt;/strong&gt;: To reduce dependency on legacy password compatibility, explore passwordless methods such as one-time codes or magic links. These can simplify the user experience and reduce migration hurdles by minimising password management complexities. We explored this approach thoroughly but ultimately decided against it, as it altered the user experience too much.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  5. &lt;strong&gt;Addressing Mobile App Complexity and Version Control&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Mobile app releases add a unique layer of complexity during migrations, particularly if the app’s authentication flow relies on the legacy system. Unlike web applications, which can deploy updates quickly, mobile app changes depend on users updating their app versions. This makes it essential to plan for backward compatibility. &lt;/p&gt;

&lt;p&gt;If you change authentication flows or UI domains, coordinate with mobile release schedules to ensure users on older versions aren’t locked out. Consider keeping the legacy system active until the majority of mobile users have updated, or providing fallback authentication methods for those on previous versions. A smooth transition often requires close collaboration with your mobile development team to minimise user disruption.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. &lt;strong&gt;User Communication and Support Planning&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Considering the potential impacts on user behaviour and login experiences, a solid communication strategy can ease the transition for users. You may want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send emails or in-app notifications ahead of time, informing users of upcoming changes.&lt;/li&gt;
&lt;li&gt;Prepare FAQs or help guides on password resets, updating saved passwords, or handling two-factor authentication.&lt;/li&gt;
&lt;li&gt;Brief your support teams on possible issues and train them to handle potential login and password-reset queries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Providing clear, proactive communication can minimise user frustration and reduce the volume of support tickets that may arise during the transition.&lt;/p&gt;




&lt;h3&gt;
  
  
  In Summary
&lt;/h3&gt;

&lt;p&gt;Migrating to Amazon Cognito requires a precise configuration approach and a close understanding of how each setting affects the user experience. The key lessons from our experience are to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set configurations thoughtfully, as they’re often permanent.&lt;/li&gt;
&lt;li&gt;Align Hosted UI domains to maintain password manager compatibility.&lt;/li&gt;
&lt;li&gt;Establish strong but migration-friendly password policies.&lt;/li&gt;
&lt;li&gt;Prepare for mobile complexities by planning around app update cycles.&lt;/li&gt;
&lt;li&gt;Develop a user communication and support strategy to ease the transition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By applying these insights and setting up vigilant monitoring, you can minimise disruptions and ensure a successful migration to Amazon Cognito.&lt;/p&gt;

&lt;p&gt;For a more in-depth guide on Cognito user migration approaches, see this post from &lt;a href="https://theburningmonk.com/2024/02/whats-the-best-way-to-migrate-cognito-users-to-a-new-user-pool/" rel="noopener noreferrer"&gt;The Burning Monk&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For an honest—though perhaps slightly biased—review of Cognito and the migration process, check out this article from &lt;a href="https://fusionauth.io/blog/how-to-migrate-from-cognito" rel="noopener noreferrer"&gt;FusionAuth&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>migration</category>
      <category>programming</category>
      <category>learning</category>
    </item>
    <item>
      <title>Building Auth0 Actions in TypeScript</title>
      <dc:creator>Emma Moinat</dc:creator>
      <pubDate>Thu, 05 Sep 2024 06:16:24 +0000</pubDate>
      <link>https://dev.to/emmamoinat/building-auth0-actions-in-typescript-20p0</link>
      <guid>https://dev.to/emmamoinat/building-auth0-actions-in-typescript-20p0</guid>
      <description>&lt;h2&gt;
  
  
  Auth0 Actions
&lt;/h2&gt;

&lt;p&gt;In 2022 Auth0 announced &lt;a href="https://auth0.com/blog/introducing-auth0-actions/" rel="noopener noreferrer"&gt;Auth0 Actions&lt;/a&gt; as the successor to both Auth0 Rules and Hooks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Auth0 Actions provides a unified view across secure, tenant-specific, self-contained functions that allow you to customize the behavior of Auth0. Each action is bound to a specific triggering event on the Auth0 platform, which executes custom code when that event is produced at runtime.&lt;br&gt;
&lt;a href="https://auth0.com/blog/actions-now-generally-available/" rel="noopener noreferrer"&gt;Auth0 blog post about GA of Actions&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you have used or are still using Rules or Hooks you will likely have experienced their limitations. Here is a quick comparison of Hooks and Rules with Actions:&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%2Fhw3cn5yyr24v0ert95hd.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%2Fhw3cn5yyr24v0ert95hd.png" alt="Comparison between Rules, Hooks and Actions" width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It feels like Actions has it all! Actions offers a smooth developer experience, thanks to its intuitive drag-and-drop flow editor, powerful Monaco code editor and handy version control. Well done Auth0!&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure as Code (IaC)
&lt;/h2&gt;

&lt;p&gt;While online code editors and drag-and-drop flow editors are convenient, they often fall short when it comes to complex deployments. We need the ability to deploy Auth0 configurations repeatedly, perhaps to different tenants or accounts. Enter IaC.&lt;/p&gt;

&lt;p&gt;As this blog post is mostly to discuss Actions and how to handle deploying them from Typescript, we won't delve too deep into the pros and cons of different IaC providers. However, to just mention a few of your options, if you love CDK, then there is a great construct &lt;a href="https://constructs.dev/packages/@flit/cdk-auth0" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Otherwise, there is a Terraform provider &lt;a href="https://registry.terraform.io/providers/auth0/auth0/latest/docs" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In our case we are using the Terraform provider but most of the following steps you can also make use of in the case of the CDK construct.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing An Action
&lt;/h3&gt;

&lt;p&gt;Let's take an example of the Post Login action, and imagine we want to deny someone access if their email is not verified. &lt;/p&gt;

&lt;h4&gt;
  
  
  Approach 1: Auth0 UI
&lt;/h4&gt;

&lt;p&gt;Editing in the UI you would create something like this:&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%2Faof3oov4fn6ebtnror7s.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%2Faof3oov4fn6ebtnror7s.png" alt="Post Login Action Editor" width="800" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the drag-and-drop workflow looking something like this:&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%2Fqupgek8dsz13syw4ti83.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%2Fqupgek8dsz13syw4ti83.png" alt="Drag and drop workflow" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Approach 2: Terraform Inline Code
&lt;/h4&gt;

&lt;p&gt;We want to reproduce this configuration in our IaC. Out of the box you can write inline code as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "auth0_action" "post_login_action" {
  name    = "PostLoginAction"
  runtime = "node18"
  deploy  = true
  code    = &amp;lt;&amp;lt;-EOT
  /**
   * Handler that will be called during the execution of a PostLogin flow.
   *
   * @param {Event} event - Details about the user and the context in which they are logging in.
   * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
   */
   exports.onExecutePostLogin = async (event, api) =&amp;gt; {
     if (!event.user.email_verified) {
        api.access.deny('Please verify your email address to continue.');
     }
   };
  EOT

  supported_triggers {
    id      = "post-login"
    version = "v3"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, inline code is not ideal for many reasons, some of these are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Maintainability&lt;/strong&gt;: As the codebase grows, maintaining large blocks of inline code within Terraform configurations can become cumbersome. It makes the Terraform files longer and harder to read, increasing the risk of errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Version Control&lt;/strong&gt;: Managing code across different environments is easier when it's stored in separate files with proper version control. Inline code makes it difficult to track changes independently from the Terraform configuration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reusability&lt;/strong&gt;: Inline code is tied to a specific Terraform resource, limiting its reuse across multiple resources or projects. External files can be imported wherever needed, promoting code reuse.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt;: Inline code is harder to test in isolation. By keeping the code in separate files, you can easily run unit tests and other checks before deploying it through Terraform.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Type Safety and Tooling&lt;/strong&gt;: When using TypeScript or other languages that compile to JavaScript, you benefit from type checking, better editor support, and more robust development tools. Inline JavaScript in Terraform doesn't allow for this enhanced development workflow.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Using external files for your code, with Terraform's &lt;code&gt;file&lt;/code&gt; function or similar, resolves these issues by separating concerns, improving maintainability, and allowing better integration with development tools.&lt;/p&gt;

&lt;p&gt;Thankfully terraform offers a &lt;code&gt;file&lt;/code&gt; helper function. With this helper you simply pass in a path and it will go and pull the contents of that file in as a string. &lt;/p&gt;

&lt;p&gt;With this &lt;a href="https://developer.hashicorp.com/terraform/language/functions/file" rel="noopener noreferrer"&gt;&lt;code&gt;file&lt;/code&gt; helper&lt;/a&gt; you could then target a JavaScript file as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "auth0_action" "post_login_action" {
  name    = "PostLoginAction"
  runtime = "node18"
  deploy  = true
  code    = file("${path.module}/path/to/post-login-action.js")

  supported_triggers {
    id      = "post-login"
    version = "v3"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is also a valid approach, but what we really want is to be able to write our code in TypeScript and have it transpile into the JavaScript we need. Enter &lt;a href="https://rollupjs.org/" rel="noopener noreferrer"&gt;Rollup&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript to JavaScript
&lt;/h2&gt;

&lt;p&gt;Auth0 Actions are very specific in how the code needs to look so finding the right Rollup config (&lt;code&gt;rollup.config.js&lt;/code&gt;) took some time but with perseverance we got there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import typescript from '@rollup/plugin-typescript';

export default {
  input: ["src/post-login-action.ts"],
  output: {
    strict: false,
    format: "cjs",
    dir: "dist",
  },
  external: [], // here you can add any external dependencies
  plugins: [
    typescript({ module: 'es6' })
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that you will of course need to install both &lt;code&gt;rollup&lt;/code&gt; and &lt;code&gt;@rollup/plugin-typescript&lt;/code&gt; using your package manager.&lt;/p&gt;

&lt;p&gt;Thanks to a &lt;a href="https://community.auth0.com/t/compiling-actions-from-multiple-source-files/76224" rel="noopener noreferrer"&gt;thread in the Auth0 Community for this config&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This now enables us to write (and test 😍) the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type PostLoginAPI = {
  access: { deny: (message: string) =&amp;gt; void };
};

type Event = {
  user: { email_verified: boolean };
  client: { name: string };
};

export const onExecutePostLogin = async (event: Event, api: PostLoginAPI) =&amp;gt; {
  if (!event.user.email_verified) {
    api.access.deny('Please verify your email address to continue.');
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately Auth0 currently lacks public type definitions for &lt;code&gt;Event&lt;/code&gt; and &lt;code&gt;PostLoginAPI&lt;/code&gt;, so I've implemented custom types. I hope Auth0 will release these types in the future, as they would greatly simplify and enhance the type safety of this code.&lt;/p&gt;

&lt;p&gt;Once your code is ready you can run &lt;code&gt;rollup -c&lt;/code&gt; to transpile your TS code. Finally your terraform resource definition would point to your &lt;code&gt;dist&lt;/code&gt; folder where the JS code will be output to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "auth0_action" "post_login_action" {
  name    = "PostLoginAction"
  runtime = "node18"
  deploy  = true
  code    = file("${path.module}/path/to/dist/post-login-action.js")

  supported_triggers {
    id      = "post-login"
    version = "v3"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So that's it, your TypeScript code is ready to be deployed as Auth0 Actions. &lt;/p&gt;

&lt;h2&gt;
  
  
  Considerations
&lt;/h2&gt;

&lt;p&gt;You need to ensure your Action code is built before you attempt a terrform plan or apply. In our case we are using &lt;a href="https://terragrunt.gruntwork.io/" rel="noopener noreferrer"&gt;terragrunt&lt;/a&gt; which has a helpful &lt;code&gt;before_hook&lt;/code&gt;, setup in the &lt;code&gt;terragrunt.hcl&lt;/code&gt; file as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  ...

  before_hook "before_hook" {
    commands     = ["apply", "plan"]
    execute      = ["bash", "../path/to/pre-build.sh"]
  }
}

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

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;pre-build.sh&lt;/code&gt; is a simple script that runs our Action's build command, in our case &lt;code&gt;npm run build&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are other options out there for pre-plan or pre-apply hooks, it is not essential that you use terragrunt, although, I do recommend checking it out. &lt;/p&gt;

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

&lt;p&gt;In summary, Auth0 Actions bring a powerful upgrade to the way we customise and extend Auth0, replacing the older Rules and Hooks with a more flexible and unified platform. &lt;/p&gt;

&lt;p&gt;By leveraging TypeScript and tools like Rollup, we can maintain type safety and modular code while deploying through Infrastructure as Code (IaC) solutions like Terraform. This approach not only enhances our ability to manage complex deployments but also improves the overall developer experience. &lt;/p&gt;

&lt;p&gt;With Actions, you can efficiently create, test, and deploy secure, tenant-specific functions, making it easier to tailor Auth0 to your specific needs. If you have any questions, please leave a comment. &lt;br&gt;
Thanks for following along!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>typescript</category>
      <category>terraform</category>
      <category>node</category>
    </item>
    <item>
      <title>Cognito Inception: How to add Cognito as OIDC Identity Provider in Cognito</title>
      <dc:creator>Emma Moinat</dc:creator>
      <pubDate>Sat, 15 Jun 2024 08:19:12 +0000</pubDate>
      <link>https://dev.to/aws-builders/cognito-inception-how-to-add-cognito-as-oidc-identity-provider-in-cognito-1bk1</link>
      <guid>https://dev.to/aws-builders/cognito-inception-how-to-add-cognito-as-oidc-identity-provider-in-cognito-1bk1</guid>
      <description>&lt;h2&gt;
  
  
  What?
&lt;/h2&gt;

&lt;p&gt;Amazon Cognito is an identity platform for web and mobile apps. With Amazon Cognito, you can authenticate and authorise users from a built-in user directory, from your enterprise directory, or from consumer identity providers like Google and Facebook.&lt;/p&gt;

&lt;p&gt;This post will look at how to setup AWS Cognito to use an OpenID Connect (OIDC) identity provider of another Cognito user pool.&lt;/p&gt;

&lt;p&gt;Open ID Connect (OIDC) is an authentication protocol built on top of OAuth 2.0. It is designed to verify an existing account (identity of an end user) by a third party application using an Identity Provider site (IDP). It complements OAuth 2.0 which is an authorisation protocol.&lt;/p&gt;

&lt;p&gt;In this case we are using Cognito as the IDP but you could replace this with many other providers like Salesforce, Github or Azure AD etc. etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;You might wonder why you would want to integrate 2 Cognitos? 🤔&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%2F42vkagobkpf637rmrnmh.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%2F42vkagobkpf637rmrnmh.png" alt="Login screen with multiple identity providers" width="800" height="591"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this Cognito hosted UI login screen you can see various authentication options are offered, including an alternative Cognito user pool. There's even an option to log directly into this Cognito user pool, which is all configurable.&lt;/p&gt;

&lt;p&gt;Integrating two Cognito user pools can be beneficial if you have a product linked to a Cognito user pool and a customer who has their own Cognito user pool with their user base. This setup allows the customer's user base to access your product without needing to migrate users to your product's user pool.&lt;/p&gt;

&lt;p&gt;These 2 Cognito user pools can exist in different accounts and regions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not?
&lt;/h2&gt;

&lt;p&gt;I feel obliged to mention before you go any further with this setup that it will cost you!&lt;/p&gt;

&lt;p&gt;Cognito generally is known to be an inexpensive alternative to many other auth providers with one of the major benefits being that there is a free tier of 50,000 monthly active users per account or per AWS organisation. However, this is only the case for users who sign in directly to the user pool or through a social identity provider. So what about users who log in through an OIDC federation like this example. Well... &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For users federated through SAML 2.0 or an OpenID Connect (OIDC) identity provider, Amazon Cognito user pools has a free tier of 50 MAUs per account or per AWS organization.&lt;br&gt;
For users who sign in through SAML or OIDC federation, the price for MAUs above the 50 MAU free tier is $0.015.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/cognito/pricing/" rel="noopener noreferrer"&gt;Cognito pricing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would recommend doing a quick estimate of the cost of this approach for your use case. Head over to &lt;a href="https://calculator.aws/#/createCalculator/Cognito" rel="noopener noreferrer"&gt;AWS' pricing calculator&lt;/a&gt; before you continue any further as you may be surprised by the price. And if you never come back to this blog I will understand why! 😂 &lt;/p&gt;

&lt;h2&gt;
  
  
  How?
&lt;/h2&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%2Fztnxdtl019m18ficscv7.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%2Fztnxdtl019m18ficscv7.png" alt="Auth Flow between 2 Cognito Pools" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can see each step in the authentication process. This is a very standard flow when using an external OIDC provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's set it up
&lt;/h2&gt;

&lt;p&gt;To keep things clear, we'll refer to the Cognito with the user base as the "Customer user pool" and the other one as the "Product user pool". Our product will first interact with its own user pool (Product user pool) before being redirected to the Customer user pool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customer User Pool
&lt;/h3&gt;

&lt;p&gt;In this tutorial we will look at how to set this up from A to Z but in reality the Customer user pool may already exist with its user base. In that case you may just need to create a new client in your existing customer user pool so you can skip some of the following steps.&lt;/p&gt;

&lt;p&gt;Let's first set up the Cognito user pool with the user base (i.e. the customer's user pool). &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Head to AWS Cognito and click &lt;code&gt;Create user pool&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Provider types&lt;/code&gt; to be only &lt;code&gt;Cognito user pool&lt;/code&gt; and sign-in options to be whatever suits your use case (I chose email):&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%2Fqnas1fut08oe9e2oi0tt.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%2Fqnas1fut08oe9e2oi0tt.png" alt="Sign-in config screenshot" width="800" height="690"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Follow through the next steps setting up your password policy, MFA, User account recovery and Sign-up experience as you desire.&lt;/li&gt;
&lt;li&gt;On the &lt;code&gt;Integrate your app&lt;/code&gt; page enter your desired user pool name.&lt;/li&gt;
&lt;li&gt;Tick &lt;code&gt;Use the Cognito Hosted UI&lt;/code&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%2Fh9gz2m5j81mdgqioal87.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%2Fh9gz2m5j81mdgqioal87.png" alt="user pool config" width="800" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select the domain setup you want but using a cognito domain is fine if you don't have a custom domain.&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%2F0vnkiev23cr5pevh9woe.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%2F0vnkiev23cr5pevh9woe.png" alt="Domain config" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up the client app as follows:&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%2Fxtc4rmmzcr8xdjxs8za3.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%2Fxtc4rmmzcr8xdjxs8za3.png" alt="client settings" width="800" height="786"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice here I have generated a &lt;strong&gt;client secret&lt;/strong&gt; - in this case we need a secret to use this client later as an identity provider. If you don't include it at setup time then you will have to create a new client as this cannot be changed after creation. &lt;/p&gt;

&lt;p&gt;Also for now I have entered a placeholder allowed callback url of &lt;code&gt;https://example.com&lt;/code&gt; but we will come back to change this later.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the &lt;code&gt;Advanced app client settings&lt;/code&gt; you can leave everything as it is except adjust the scope as follows:&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%2F1cx4egtdvennfqb7l1xk.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%2F1cx4egtdvennfqb7l1xk.png" alt="scopes screenshot" width="800" height="163"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Review and create your user pool!&lt;/li&gt;
&lt;li&gt;Let's get a user added to this customer's user base when we are still in the area. Keep note of the user's details as you will need them later of course.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Product User Pool
&lt;/h3&gt;

&lt;p&gt;Let's set up the "Product" Cognito user pool, i.e. the instance that your product will interact directly with. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Head to AWS Cognito and click &lt;code&gt;Create user pool&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;On the &lt;code&gt;Configure sign-in experience&lt;/code&gt; screen select &lt;code&gt;Federated identity providers&lt;/code&gt; as an option and the sign-in options whatever suits you:&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%2Fdwr86hjee3lplb62c7nf.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%2Fdwr86hjee3lplb62c7nf.png" alt="Configure sign-in experience config screenshot" width="800" height="674"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For &lt;code&gt;Federated sign-in options&lt;/code&gt; tick &lt;code&gt;OpenID Connect (OIDC)&lt;/code&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%2Fihh33xcv3gfmld5yuz53.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%2Fihh33xcv3gfmld5yuz53.png" alt="Federated sign-in options config screenshot" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Follow through the next steps setting up your password policy, MFA, User account recovery and Sign-up experience as you desire.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next you will be presented with a &lt;code&gt;Connect federated identity providers&lt;/code&gt; screen - this is where the magic happens. Here fill in the client id and client secret from your &lt;strong&gt;customer's&lt;/strong&gt; user pool's app client. (i.e. the client app we created in the steps above)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll find those details in the &lt;code&gt;App Integration&lt;/code&gt; tab of your Customer's user pool and then selecting the client you created:&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%2Fcl8pjzfdyhhuuo031cqg.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%2Fcl8pjzfdyhhuuo031cqg.png" alt="Customer a's user pool's details" width="533" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter them as follows (where the provider name will be what is displayed to the user in the hosted UI later):&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%2F6i682ahn2caprdlrtzsy.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%2F6i682ahn2caprdlrtzsy.png" alt="Adding customer client details to product cognito user pool" width="800" height="722"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep &lt;code&gt;Attribute request method&lt;/code&gt; as &lt;code&gt;GET&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Setup the issuer url where the url will be: &lt;code&gt;https://cognito-idp.{region}.amazonaws.com/{customerUserPoolId}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;email&lt;/code&gt; attribute and &lt;code&gt;email_verified&lt;/code&gt; as shown here:&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%2Fogxcpexy1jqtgmydqbnz.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%2Fogxcpexy1jqtgmydqbnz.png" alt="Federated config" width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can add as many other attributes as you want or need here. Each attribute in a user pool with match exactly to the same attribute in the other user pool, logically.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name your user pool, for example, product-user-pool.&lt;/li&gt;
&lt;li&gt;Setup your app client as you require. It is not required at this point to generate a client secret for this user pool. You can add one if you want but I wouldn't recommend it if you plan to use this user pool in a webapp or mobile app etc.&lt;/li&gt;
&lt;li&gt;In the advanced settings, ensure the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set the &lt;code&gt;Identity providers&lt;/code&gt; to include your newly created IDP:&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%2Fbh3q748590afnd2ld3lo.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%2Fbh3q748590afnd2ld3lo.png" alt="idp config" width="700" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you do not want the user to be able to log in directly to your product user pool via the hosted UI, here you can remove the option of Cognito user pool and have the IDP as the only option.&lt;/p&gt;

&lt;p&gt;Set the scopes to match what we set in the other user pool and in the Identity Provider:&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%2Fxc9o95au639y5teq4y5o.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%2Fxc9o95au639y5teq4y5o.png" alt="scope config" width="797" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Review and create your second user pool!&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Final integration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;One last step, we need to go to the Customer user pool and adjust the allowed callbacks for the client.&lt;/li&gt;
&lt;li&gt;Head to the &lt;code&gt;App integration&lt;/code&gt; tab and then click into your client and go to the hosted UI settings.&lt;/li&gt;
&lt;li&gt;Set the allowed callbacks to be the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;https://{productCognitoDomain}/oauth2/idpresponse&lt;/code&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%2F919dwi4e6et0e5ai4kb6.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%2F919dwi4e6et0e5ai4kb6.png" alt="callback url config" width="719" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;Now if you head to the Hosted UI of the Product user pool you will see this:&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%2Faw7b16degummw8owzih9.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%2Faw7b16degummw8owzih9.png" alt="Product user pool" width="761" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you click on the button to login to the customer's user pool you will see this:&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%2Fkvfvb2vkisq118hxbong.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%2Fkvfvb2vkisq118hxbong.png" alt="customer's user pool" width="410" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you look at the url you can see you are on the customer's user pool hosted UI. &lt;/p&gt;

&lt;p&gt;You can now log in with the details you set up earlier in the customer's user pool. You are then redirected to the product user pool's redirect url, authenticated and all. Magic. 🪄&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;Thanks to Daniel Kim and his original post which you can read here &lt;a href="https://dev.to/namuny/using-cognito-user-pool-as-an-openid-connect-provider-4n9a"&gt;Using Cognito User Pool as an OpenID Connect Provider&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>tutorial</category>
      <category>security</category>
    </item>
    <item>
      <title>Lambda Persistent Storage with EFS using CDK</title>
      <dc:creator>Emma Moinat</dc:creator>
      <pubDate>Fri, 22 Dec 2023 17:56:57 +0000</pubDate>
      <link>https://dev.to/aws-builders/lambda-persistent-storage-with-efs-using-cdk-48ie</link>
      <guid>https://dev.to/aws-builders/lambda-persistent-storage-with-efs-using-cdk-48ie</guid>
      <description>&lt;p&gt;This tutorial is a quick run through how to set up persistent storage for a lambdas using CDK. You might wonder why you would want to do that but I will show you some use cases below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Elastic File System
&lt;/h2&gt;

&lt;p&gt;The AWS service I will be using for this is the Elastic File System (EFS). When setting up EFS you will need to choose a throughput mode and a performance mode. Your choice will depend on your use case so please take some time to consider what is best for you. Find more details &lt;a href="https://docs.aws.amazon.com/efs/latest/ug/performance.html" rel="noopener noreferrer"&gt;here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example I am using the recommended throughput mode of &lt;code&gt;Elastic&lt;/code&gt; and the performance mode of &lt;code&gt;General Purpose&lt;/code&gt;. &lt;a href="https://docs.aws.amazon.com/efs/latest/ug/managing-throughput.html" rel="noopener noreferrer"&gt;You can change the throughput mode later if really needed&lt;/a&gt; but performance mode changes would require migration so let's try to avoid that!&lt;/p&gt;

&lt;p&gt;Here we have our file system, which we are deploying inside our Virtual Private Cloud (VPC):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const fileSystem = new FileSystem(this, "FileSystem", {
  vpc: vpc,
  performanceMode: PerformanceMode.GENERAL_PURPOSE,
  throughputMode: ThroughputMode.ELASTIC
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also set properties like encryption or removal policy so take time to consider what setup is best for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Access Point
&lt;/h2&gt;

&lt;p&gt;What we need now is an access point for our lambda to mount to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const accessPoint = fileSystem.addAccessPoint("EfsAccessPoint", {
  createAcl: {
  ownerGid: "1001",
  ownerUid: "1001",
  permissions: "750"
},
  path: "/lambda",
  posixUser: {
    gid: "1001",
    uid: "1001"
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting the &lt;code&gt;path&lt;/code&gt; property above is setting the path on the EFS file system to expose as the root directory to the client using this access point. If not set it will default to &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For more details on the posix user setup check &lt;a href="https://repost.aws/knowledge-center/efs-mount-with-lambda-function" rel="noopener noreferrer"&gt;this&lt;/a&gt; out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lambda Function
&lt;/h2&gt;

&lt;p&gt;We now have all we need to hook up a lambda function to EFS, so here is how we do that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new Function(this, "EfsLambdaFunction", {
  runtime: Runtime.NODEJS_20_X,
  code: Code.fromAsset("lambda-code"),
  handler: "index.handler",
  vpc: vpc, // lambda must be in the same VPC as the file system
  filesystem: LambdaFileSystem.fromEfsAccessPoint(accessPoint, "/mnt/some-folder")
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course the important line here is:&lt;br&gt;
&lt;code&gt;filesystem: LambdaFileSystem.fromEfsAccessPoint(accessPoint, "/mnt/some-folder")&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;This is setting the lambda's access point and the mount path within that access point. &lt;/p&gt;

&lt;p&gt;The mount path must start with the folder &lt;code&gt;mnt&lt;/code&gt; and have a subfolder after that, but this can really be anything you wish. This value of &lt;code&gt;/mnt/some-folder&lt;/code&gt; is going to be very important to your lambda as this is the only folder it can access, if you try to access any file outside the folder &lt;code&gt;/mnt/some-folder/&lt;/code&gt; you will get a permission denied error.&lt;/p&gt;

&lt;p&gt;What can be worth doing is passing this mount path value in as an environment variable to your lambda so you don't have to hard code it into the lambda's code. That way if you were ever to change this value you wouldn't have to change your lambda's code. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const mountPath = "/mnt/some-folder";

new Function(this, "EfsLambdaFunction", {
  runtime: Runtime.NODEJS_20_X,
  code: Code.fromAsset("lambda-code"),
  handler: "index.handler",
  vpc: vpc,
  filesystem: LambdaFileSystem.fromEfsAccessPoint(accessPoint, mountPath),
  environment: {
    EFS_PATH: mountPath
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way you can access files in your lambda using this &lt;code&gt;EFS_PATH&lt;/code&gt; variable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Generative AI (Large Language Models)
&lt;/h3&gt;

&lt;p&gt;In my case, the reason I even considered this approach to lambda storage was on a recent Generative AI project. &lt;/p&gt;

&lt;p&gt;We wanted to add some Large Language Model guardrails to our project. This required pulling in a dependency, namely, &lt;a href="https://llm-guard.com/" rel="noopener noreferrer"&gt;LLM Guard&lt;/a&gt;, which needs to pull in around 2.5GB of data in order to run some checks against a range of AI models. &lt;/p&gt;

&lt;p&gt;Of course lambdas can handle 2.5GB with its ephemeral (temporary) storage (as we all know this can now be set as high as 10GB). The issue for us was really the performance. If the lambda goes cold, then a request comes in, the user would have to wait for the lambda to pull in all the models before getting an answer. This is a terrible user experience taking around 2 minutes to give a response. Switching to EFS got this down to around 20 seconds, which of course is still not ideal but it is a step in the right direction. &lt;/p&gt;

&lt;h3&gt;
  
  
  Other Possible Use Cases
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Large dependencies&lt;/strong&gt; - Sometimes pulling in large dependencies can actually cause a timeout in your lambda's init phase. A workaround is to install the dependencies on EFS so then the lambda doesn't need to install it each time. Here are a few walkthroughs of this from AWS:&lt;br&gt;
&lt;a href="https://aws.amazon.com/blogs/compute/using-amazon-efs-for-aws-lambda-in-your-serverless-applications/" rel="noopener noreferrer"&gt;Node example&lt;/a&gt;&lt;br&gt;
&lt;a href="https://aws.amazon.com/blogs/aws/new-a-shared-file-system-for-your-lambda-functions/" rel="noopener noreferrer"&gt;Python example&lt;/a&gt;&lt;br&gt;
I might look into this for my own use case too!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Processing images or videos&lt;/strong&gt; - A common use case for lambdas, using EFS provides an efficient option to perform these tasks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zipping and unzipping large files&lt;/strong&gt; - Some workflows require large zip files for initialisation. With EFS your files can remain unzipped, ready to use.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Machine Learning workloads&lt;/strong&gt; - AI was already mentioned above in my own use case but worth throwing ML into the list anyway. Many machine learning models depend on large reference data files such as models or libraries. Storing these in EFS will help these task to be much more performant!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Alternatives
&lt;/h2&gt;

&lt;p&gt;I feel it is worth mentioning that there are more options for lambda storage than EFS. Here is a comparison provided by 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%2F9h0qee0z22el6sr5g9ov.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%2F9h0qee0z22el6sr5g9ov.png" alt="Lambda storage comparison" width="780" height="626"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see S3 and lambda layers also mentioned here!&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Again
&lt;/h2&gt;

&lt;p&gt;Just for clarity, here is all the code thrown together in a simple stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {FileSystem, PerformanceMode, ThroughputMode} from "aws-cdk-lib/aws-efs";
import {Vpc} from "aws-cdk-lib/aws-ec2";
import {Runtime, Function, FileSystem as LambdaFileSystem, Code} from "aws-cdk-lib/aws-lambda";
import {Stack} from "aws-cdk-lib";
import {Construct} from "constructs";

export class EfsLambdaStack extends Stack {
  constructor(scope: Construct) {
    super(scope, "EfsLambdaStack");

    const vpc = new Vpc(this, "Vpc");

    const fileSystem = new FileSystem(this, "FileSystem", {
      vpc: vpc,
      performanceMode: PerformanceMode.GENERAL_PURPOSE,
      throughputMode: ThroughputMode.ELASTIC
    });

    const accessPoint = fileSystem.addAccessPoint("EfsAccessPoint", {
      createAcl: {
        ownerGid: "1001",
        ownerUid: "1001",
        permissions: "750"
      },
      path: "/lambda",
      posixUser: {
        gid: "1001",
        uid: "1001"
      }
    });

    const mountPath = "/mnt/some-folder";

    new Function(this, "EfsLambdaFunction", {
      runtime: Runtime.NODEJS_20_X,
      code: Code.fromAsset("lambda-code"),
      handler: "index.handler",
      vpc: vpc,
      filesystem: LambdaFileSystem.fromEfsAccessPoint(accessPoint, mountPath),
      environment: {
        EFS_PATH: mountPath
      }
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks for stopping by! Let me know your use cases for persistent lambda storage in the comments! 💁🏻‍♀️&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>tutorial</category>
      <category>typescript</category>
    </item>
    <item>
      <title>AWS Custom Resource using CDK</title>
      <dc:creator>Emma Moinat</dc:creator>
      <pubDate>Wed, 04 Oct 2023 08:16:14 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-custom-resource-using-cdk-387k</link>
      <guid>https://dev.to/aws-builders/aws-custom-resource-using-cdk-387k</guid>
      <description>&lt;p&gt;If you are new to AWS' Cloud Development Kit (CDK), here's a quick explanation of what exactly it is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The AWS Cloud Development Kit (CDK) is a software development framework that allows you to define and provision cloud infrastructure using familiar programming languages such as TypeScript, Python, and Java.&lt;/p&gt;

&lt;p&gt;Traditionally, infrastructure provisioning has been done using templates or scripts that are difficult to read and understand. CDK simplifies this process by allowing you to define your infrastructure in code, using the same programming constructs that you use to build applications.&lt;/p&gt;

&lt;p&gt;With CDK, you can define your infrastructure as a series of reusable components called "constructs." These constructs can be shared across your organization and easily reused in multiple projects.&lt;/p&gt;

&lt;p&gt;Overall, AWS CDK makes it easier and faster to build and manage cloud infrastructure, with less room for error and greater potential for reuse.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thanks ChatGPT for that great explanation! 😂&lt;/p&gt;

&lt;p&gt;So, now we have the basics of what CDK is and what it does for us, I want to look at building up a Custom Resource.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why build a Custom Resource?
&lt;/h2&gt;

&lt;p&gt;Custom Resources is a very useful feature of CDK that allows you to define and manage resources that are not available as a &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html" rel="noopener noreferrer"&gt;resource type in AWS CloudFormation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are a few different use cases for such a thing, these include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Provisioning resources not supported by CDK&lt;/li&gt;
&lt;li&gt;Implementing custom logic/configuration&lt;/li&gt;
&lt;li&gt;Integrating with third-party services&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Using resource types for third-party resources provides you a way to reliably manage these resources using a single tool, without having to resort to time-consuming and error-prone methods like manual configuration or custom scripts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-types.html" rel="noopener noreferrer"&gt;AWS Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using a Custom Resource allows us to automate the creation, deletion and updating of these resources across multiple environments as and when we need.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to build a Custom Resource?
&lt;/h2&gt;

&lt;p&gt;To build a Custom Resource we need 3 things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Lambda function for &lt;code&gt;onEvent&lt;/code&gt; handling &lt;code&gt;create&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt; and &lt;code&gt;update&lt;/code&gt; events.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;Provider&lt;/code&gt; which points the Custom Resource to the lambda.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;CustomResource&lt;/code&gt; itself with any props you need for your third party resource or otherwise.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can also provide an additional Lambda function to handle &lt;code&gt;isComplete&lt;/code&gt;. &lt;br&gt;
This is used when the lifecycle operation cannot be completed immediately. &lt;br&gt;
The &lt;code&gt;isComplete&lt;/code&gt; handler will be retried asynchronously after &lt;code&gt;onEvent&lt;/code&gt; until it returns &lt;code&gt;{ IsComplete: true }&lt;/code&gt;, or until it times out.&lt;/p&gt;
&lt;h3&gt;
  
  
  onEvent Lambda
&lt;/h3&gt;

&lt;p&gt;This function will be invoked for all resource lifecycle operations (Create/Update/Delete). &lt;/p&gt;

&lt;p&gt;Here is how this handler might look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {
  CloudFormationCustomResourceCreateEvent,
  CloudFormationCustomResourceDeleteEvent,
  CloudFormationCustomResourceEvent,
  CloudFormationCustomResourceResponse,
  CloudFormationCustomResourceUpdateEvent,
} from "aws-lambda";

export const handler = async (event: CloudFormationCustomResourceEvent): Promise&amp;lt;CloudFormationCustomResourceResponse&amp;gt; =&amp;gt; {
  switch (event.RequestType) {
    case "Create":
      return await createSomeResource(event as CloudFormationCustomResourceCreateEvent);
    case "Update":
      return await updateSomeResource(event as CloudFormationCustomResourceUpdateEvent);
    case "Delete":
      return await deleteSomeResource(event as CloudFormationCustomResourceDeleteEvent);
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each of these cases, we want to respond according.&lt;/p&gt;

&lt;p&gt;In other words, for a &lt;code&gt;Create&lt;/code&gt; event we would want to, well, create something. &lt;code&gt;Update&lt;/code&gt; we might want to delete something and create a new item if our use case doesn't offer a way to directly update the resource. For &lt;code&gt;Delete&lt;/code&gt;, we should be deleting our resource. Pretty straight forward.&lt;/p&gt;

&lt;p&gt;If you were, for example, using this Custom Resource to integrate with a third party service, you may want to make some specific API calls for creating, updating and deleting.&lt;/p&gt;

&lt;h4&gt;
  
  
  Understanding Lifecycle Events
&lt;/h4&gt;

&lt;p&gt;It is important to understand how these events will be handled by CloudFormation.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;onEvent&lt;/code&gt; returns successfully, CloudFormation will show you the nice green tick of &lt;code&gt;CREATE_COMPLETE&lt;/code&gt; for the &lt;code&gt;CustomResource&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, if &lt;code&gt;onEvent&lt;/code&gt; throws an error, CloudFormation will let you know something went wrong and the CDK deploy will fail.&lt;/p&gt;

&lt;p&gt;There are some important cases to think about when errors occur:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do you need to tidy up some other resources if some step fails in your create flow?&lt;/li&gt;
&lt;li&gt;What happens if you hit the delete event but there is nothing to delete?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Should be noted, if a &lt;code&gt;Delete&lt;/code&gt; event happens to fail, CloudFormation will just abandon that resource moving forward.&lt;/p&gt;

&lt;p&gt;You can find more detail on &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.custom_resources-readme.html#important-cases-to-handle" rel="noopener noreferrer"&gt;these cases here&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Lambda in CDK
&lt;/h4&gt;

&lt;p&gt;A super simple way to declare this Lambda in CDK is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;readonly onEventHandlerFunction = new NodejsFunction(this, "CustomResourceOnEventHandlerFunction", {
  timeout: Duration.seconds(30),
  runtime: Runtime.NODEJS_18_X,
  entry: "/path/to/CustomResourceOnEventHandler.ts"
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-lambda-nodejs.NodejsFunction.html" rel="noopener noreferrer"&gt;NodejsFunction&lt;/a&gt; creates a Node.js Lambda function bundled using &lt;code&gt;esbuild&lt;/code&gt;. This means you can directly pass in your TypeScript file. Cool, right?&lt;/p&gt;

&lt;h3&gt;
  
  
  Provider
&lt;/h3&gt;

&lt;p&gt;We just need to tell the &lt;code&gt;Provider&lt;/code&gt; (from &lt;code&gt;aws-cdk-lib/custom-resources&lt;/code&gt;) to point the custom resource to the above function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;readonly customResourceProvider = new Provider(this, "CustomResourceProvider", {
  onEventHandler: this.onEventHandlerFunction,
  logRetention: RetentionDays.ONE_DAY
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Custom Resource
&lt;/h3&gt;

&lt;p&gt;Finally, we just need to tell the Custom Resource who its provider is and give it a type starting with &lt;code&gt;Custom::&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;readonly resource = new CustomResource(this, "YourCustomResource", {
  serviceToken: this.customResourceProvider.serviceToken,
  properties: {...this.props, id: this.id},
  resourceType: "Custom::YourCustomResource",
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;Bringing these 3 things together, you will create a class similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {Construct} from "constructs";
import {Provider} from "aws-cdk-lib/custom-resources";
import {RetentionDays} from "aws-cdk-lib/aws-logs";
import {CustomResource, Duration} from "aws-cdk-lib";
import {NodejsFunction} from "aws-cdk-lib/aws-lambda-nodejs";
import {SomeProps} from "../models/SomeProps";
import {Runtime} from "aws-cdk-lib/aws-lambda";

export class YourCustomResource extends Construct {
  constructor(private scope: Construct, private id: string, private props: Omit&amp;lt;SomeProps, "id"&amp;gt;) {
    super(scope, id);
  };

  readonly onEventHandlerFunction = new NodejsFunction(this, "CustomResourceOnEventHandlerFunction", {
    timeout: Duration.seconds(30),
    runtime: Runtime.NODEJS_18_X,
    entry: "/path/to/CustomResourceOnEventHandler.ts"
  });

  readonly customResourceProvider = new Provider(this, "CustomResourceProvider", {
    onEventHandler: this.onEventHandlerFunction,
    logRetention: RetentionDays.ONE_DAY
  });

  readonly resource = new CustomResource(this, "YourCustomResource", {
    serviceToken: this.customResourceProvider.serviceToken,
    properties: {...this.props, id: this.id},
    resourceType: "Custom::YourCustomResource",
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see here we are passing through all the props from the &lt;code&gt;YourCustomResource&lt;/code&gt; into the Custom Resource.&lt;/p&gt;

&lt;p&gt;In the example of making API calls using the Custom Resource we might need something like an API Key to be passed through from our CDK stack into the resource.&lt;/p&gt;

&lt;p&gt;Using this &lt;code&gt;YourCustomResource&lt;/code&gt; class you can now build up something like this in one of your CDK Stacks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;readonly exampleCustomResource = new YourCustomResource(this, "YourCustomResourceExample", {
    enabled: true,
    apiKey: "api-key", // This prop could differ per environment
    name: "Example Custom Resource"
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your CDK diff for this stack would look something like this:&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%2Fqmb78zdwaxy41vf7c8oi.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%2Fqmb78zdwaxy41vf7c8oi.png" alt="CDK diff for Custom Resource" width="273" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we have an understanding of how to build up the infrastructure for this Custom Resource, we now just need to determine how the &lt;code&gt;onEvent&lt;/code&gt; Lambda will handle each of the lifecycle events. &lt;/p&gt;

&lt;p&gt;This depends on your use case but the possibilities are endless really! &lt;/p&gt;

&lt;p&gt;This part I will leave up to you as it is very specific per scenario, but I hope this has helped you on your journey of getting a &lt;code&gt;CustomResource&lt;/code&gt; up and running.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>tutorial</category>
      <category>cloud</category>
      <category>typescript</category>
    </item>
    <item>
      <title>What's Next for CDK? 👀</title>
      <dc:creator>Emma Moinat</dc:creator>
      <pubDate>Mon, 02 Oct 2023 11:48:43 +0000</pubDate>
      <link>https://dev.to/aws-builders/whats-next-for-cdk-2580</link>
      <guid>https://dev.to/aws-builders/whats-next-for-cdk-2580</guid>
      <description>&lt;p&gt;Last week we had the yearly CDK Day where we saw many talks from all around the world. If you missed it you can find the schedule and links to all the talks &lt;a href="https://www.cdkday.com/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One of the talks was from the CDK team where they talked about the recent improvements of CDK and what is coming next. You can find the talk here:&lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=qlUR5jVBC6c&amp;amp;t=9830s" rel="noopener noreferrer"&gt;Meet the CDK team&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recent Improvements
&lt;/h2&gt;

&lt;p&gt;Some enhancements from this year were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Improved L2 Construct Coverage ⛱️&lt;/li&gt;
&lt;li&gt;Policy Validation at Synth Time 🫶&lt;/li&gt;
&lt;li&gt;Improved Permissions Boundaries via Bootstrap 🔐&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/app-staging-synthesizer-alpha-readme.html" rel="noopener noreferrer"&gt;App Staging Synthesizer&lt;/a&gt; 🎛️&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h3&gt;
  
  
  New Channel
&lt;/h3&gt;

&lt;p&gt;CDK are launching a new YouTube channel called &lt;a href="https://youtube.com/@CDK-Live" rel="noopener noreferrer"&gt;CDK Live&lt;/a&gt;. This will not only be about hearing from AWS but hearing from the community too. &lt;/p&gt;

&lt;p&gt;This will become the go to source for anything related to AWS CDK. The channel will be a blend of tutorials, deep dives and interviews with industry experts. 🆒&lt;/p&gt;

&lt;h3&gt;
  
  
  Improvements
&lt;/h3&gt;

&lt;p&gt;From listening to their users, the CDK team have flagged the areas they wish to improve by the end of this year and beyond.&lt;/p&gt;

&lt;h4&gt;
  
  
  Speed
&lt;/h4&gt;

&lt;p&gt;CDK is slow, this we can all agree on. This is mostly caused by CloudFormation. &lt;/p&gt;

&lt;p&gt;You'll be glad to hear that the CloudFormation and the CDK teams are working together to improve this. &lt;/p&gt;

&lt;p&gt;According to the CDK team, we can expect some big performance improvements before the end of the year. 🎉&lt;/p&gt;

&lt;h4&gt;
  
  
  Migration
&lt;/h4&gt;

&lt;p&gt;If you have existing cloud resources it is currently quite challenging to shift these resources into CDK.&lt;/p&gt;

&lt;p&gt;The CDK team will be introducing a &lt;code&gt;cdk migrate&lt;/code&gt; capability.&lt;/p&gt;

&lt;p&gt;The basic principle here is to take a CloudFormation template and autogenerate some CDK code.&lt;/p&gt;

&lt;p&gt;This is actually &lt;a href="https://github.com/aws/aws-cdk/commit/3f1f974b1c17003e1cb8c7a39eb6ef64bfe9a06a" rel="noopener noreferrer"&gt;already available&lt;/a&gt; but is still an experimental feature so there are no guarantees about the outcome or stability of the functionality.&lt;/p&gt;

&lt;h4&gt;
  
  
  Refactoring
&lt;/h4&gt;

&lt;p&gt;This was the most voted &lt;a href="https://github.com/aws/aws-cdk-rfcs/issues/162" rel="noopener noreferrer"&gt;RFC&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;As developers, we want to be able to refactor the CDK code and for CDK to be smart enough to not just recreate resources from scratch but to relocate them, for example, into another stack.&lt;/p&gt;

&lt;p&gt;This will potentially be available officially at the start of next year. 🤞&lt;/p&gt;




&lt;p&gt;These are some of the main areas the CDK team will be focusing on in the upcoming months but of course, as always, there will be improvements for construct coverage, and many other enhancements.&lt;/p&gt;

&lt;p&gt;I hope you are also excited for what is to come next for CDK.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>cloud</category>
      <category>community</category>
    </item>
    <item>
      <title>AWS CDK: Principle of Least Privilege</title>
      <dc:creator>Emma Moinat</dc:creator>
      <pubDate>Fri, 29 Sep 2023 14:51:10 +0000</pubDate>
      <link>https://dev.to/aws-builders/cdk-principle-of-least-privilege-45i4</link>
      <guid>https://dev.to/aws-builders/cdk-principle-of-least-privilege-45i4</guid>
      <description>&lt;h2&gt;
  
  
  CDK Deploy
&lt;/h2&gt;

&lt;p&gt;AWS CDK creates multiple roles at bootstrap time which allows CDK to deploy infrastructure on your behalf using a &lt;strong&gt;CloudFormation&lt;/strong&gt; deployment. This can be kicked off by a developer or by an automated system like a pipeline. &lt;/p&gt;

&lt;p&gt;In order to deploy, the actor needs the permissions to assume the created CDK roles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": [
                "arn:aws:iam::*:role/cdk-*"
            ]
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this permission you have everything you need to deploy your stacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bootstrap
&lt;/h2&gt;

&lt;p&gt;This post is to address the permissions required for &lt;code&gt;cdk deploy&lt;/code&gt;, however, I think it is worth looking at the permissions required to bootstrap an environment. Of course this step is only required once per account for each region so once you have completed this, you won't need these permissions again.&lt;/p&gt;

&lt;p&gt;The permissions are as follows:&lt;/p&gt;

&lt;p&gt;Permissions to deploy the CDK bootstrap CloudFormation stack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Effect": "Allow",
    "Action": [
        "cloudformation:CreateChangeSet",
        "cloudformation:DeleteChangeSet",
        "cloudformation:DeleteStack",
        "cloudformation:DescribeChangeSet",
        "cloudformation:DescribeStacks",
        "cloudformation:DescribeStackEvents",
        "cloudformation:ExecuteChangeSet",
        "cloudformation:GetTemplate"
    ],
    "Resource": [
        "arn:aws:cloudformation:eu-west-1:112233445566:stack/CDKToolkit/*"
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Permissions to create the CDK bootstrap roles we mentioned above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Effect": "Allow",
    "Action": [
        "iam:AttachRolePolicy",
        "iam:CreateRole",
        "iam:DeleteRole",
        "iam:DeleteRolePolicy",
        "iam:DetachRolePolicy",
        "iam:GetRole",
        "iam:GetRolePolicy",
        "iam:PutRolePolicy",
        "iam:TagRole"
    ],
    "Resource": [
        "arn:aws:iam::112233445566:role/cdk-hnb659fds-cfn-exec-role-*",
        "arn:aws:iam::112233445566:role/cdk-hnb659fds-file-publishing-role-*",
        "arn:aws:iam::112233445566:role/cdk-hnb659fds-image-publishing-role-*",
        "arn:aws:iam::112233445566:role/cdk-hnb659fds-lookup-role-*",
        "arn:aws:iam::112233445566:role/cdk-hnb659fds-deploy-role-*"
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Permissions to create the CDK bootstrap bucket. CDK uses this bucket to stage S3 assets in your application, such as Lambda function bundles and static assets in your frontend applications.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Effect": "Allow",
    "Action": [
        "s3:CreateBucket",
        "s3:DeleteBucketPolicy",
        "s3:GetEncryptionConfiguration",
        "s3:GetBucketPolicy",
        "s3:PutBucketPolicy",
        "s3:PutBucketVersioning",
        "s3:PutEncryptionConfiguration",
        "s3:PutLifecycleConfiguration",
        "s3:PutBucketPublicAccessBlock"
    ],
    "Resource": [
        "arn:aws:s3:::cdk-hnb659fds-assets-*"
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Permissions to create CDK bootstrap ECR repository. CDK uses this repository to stage Docker images in your application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Effect": "Allow",
    "Action": [
        "ecr:CreateRepository",
        "ecr:DeleteRepository",
        "ecr:DescribeRepositories",
        "ecr:PutLifecyclePolicy",
        "ecr:SetRepositoryPolicy"
    ],
    "Resource": [
        "arn:aws:ecr:eu-west-1:112233445566:repository/cdk-hnb659fds-container-assets-*"
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Permissions to create CDK bootstrap version SSM parameter. The parameter stores the version of the deployed CDK bootstrap stack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Effect": "Allow",
    "Action": [
        "ssm:DeleteParameter",
        "ssm:GetParameters",
        "ssm:PutParameter"
    ],
    "Resource": [
        "arn:aws:ssm:eu-west-1:112233445566:parameter/cdk-bootstrap/hnb659fds/version"
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Execution Role Issues
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;exec&lt;/code&gt; role that CDK creates has caused some problems for users as this role has a default of &lt;code&gt;AdministratorAccess&lt;/code&gt;. For some users this is simply not possible for security reasons, understandably. &lt;/p&gt;

&lt;p&gt;There is an option at bootstrap time to instead pass the ARNs of &lt;strong&gt;managed&lt;/strong&gt; policies:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;--cloudformation-execution-policies specifies the ARNs of managed policies that should be attached to the deployment role assumed by AWS CloudFormation during deployment of your stacks. By default, stacks are deployed with full administrator permissions using the AdministratorAccess policy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--cloudformation-execution-policies "arn:aws:iam::aws:policy/AWSLambda_FullAccess,arn:aws:iam::aws:policy/AWSCodeDeployFullAccess".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;If you use your pipeline to also run some post deployment tests or to maybe inject some config into your deployed infrastucture you will need to carefully consider all the permissions you need for those steps. &lt;/p&gt;

&lt;h3&gt;
  
  
  Developer Experience
&lt;/h3&gt;

&lt;p&gt;As a developer, you do not want to be continually denied to perform the things you need to do for your job. If your permisions are locked down too tightly then it can hinder your productivity. You may just need to be patient until you find the right balance between least privlege and productivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Least Privilege Principle
&lt;/h2&gt;

&lt;p&gt;It can take time and patience to follow this principle but for security reasons it is always worth sticking with it until you find that balance. If you use AWS Organisations within your company you can save some time by finding the balance once for your developers and then applying it across the board.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>cloud</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>EC2 Charges — They are NAT a joke</title>
      <dc:creator>Emma Moinat</dc:creator>
      <pubDate>Wed, 29 Mar 2023 11:31:52 +0000</pubDate>
      <link>https://dev.to/aws-builders/ec2-charges-they-are-nat-a-joke-7g7</link>
      <guid>https://dev.to/aws-builders/ec2-charges-they-are-nat-a-joke-7g7</guid>
      <description>&lt;p&gt;How often do you check your cloud provider bill and costs? Do you know what your biggest costs in the cloud are? Have you checked if all the costs are justified?&lt;/p&gt;

&lt;p&gt;This story begins with me doing just that...&lt;/p&gt;

&lt;p&gt;I was investigating a project that is running in AWS. One of the biggest costs is &lt;strong&gt;Elastic Compute Cloud&lt;/strong&gt; (EC2), which accounts for nearly 70% of the total bill:&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%2Fjhfiwvt3k0bkj723kfci.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%2Fjhfiwvt3k0bkj723kfci.png" alt="AWS bill" width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;However, when I was having a look at these &lt;code&gt;EC2&lt;/code&gt; costs in more detail, there was an extra &lt;code&gt;EC2-Other&lt;/code&gt; service which accounted for a hefty amount of the total &lt;code&gt;EC2&lt;/code&gt; costs:&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%2F8jzjeea68h6jhhd8pe4w.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%2F8jzjeea68h6jhhd8pe4w.png" alt="EC2 bill" width="800" height="316"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  EC2-Other — sorry, what? 😐
&lt;/h2&gt;

&lt;p&gt;After some research into this &lt;code&gt;EC2-Other&lt;/code&gt; service, it sounds almost like it is an expected cost, and it was just part and parcel of using &lt;code&gt;EC2&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The EC2-Other category includes multiple service-related usage types, tracking costs associated [with] Amazon EBS volumes and snapshots, elastic IP addresses, NAT gateways, data transfer, and more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;– &lt;a href="https://aws.amazon.com/blogs/aws-cloud-financial-management/tips-and-tricks-for-exploring-your-data-in-aws-cost-explorer-part-2/" rel="noopener noreferrer"&gt;EC2 Cost Explorer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, for a moment I stopped looking into these costs and just assumed they were normal.&lt;/p&gt;

&lt;h2&gt;
  
  
  EC2-Other — sorry, no. ✋
&lt;/h2&gt;

&lt;p&gt;A few days had passed, and I was still investigating that same project. This time I was looking specifically at a monthly bill breakdown. This was when I noticed something odd:&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%2Fz5msetq3m9hyhx9fujf9.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%2Fz5msetq3m9hyhx9fujf9.png" alt="NAT Gateway charges" width="800" height="203"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Did you spot it? &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;52,664 GB&lt;/strong&gt; (~53 TB) of data being processed through our &lt;strong&gt;NAT gateway&lt;/strong&gt; — that is suspicious. We would expect to see some data being transferred through the NAT gateway but that amount is absurd.&lt;/p&gt;

&lt;p&gt;My investigations began, and well it did not take long to find an answer.&lt;/p&gt;

&lt;p&gt;I determined that every time a container in &lt;code&gt;EC2&lt;/code&gt; was pulling an image from &lt;code&gt;ECR&lt;/code&gt; (Elastic Container Registry) it was being transferred through our &lt;strong&gt;NAT gateway&lt;/strong&gt;. Every file that was being transferred from &lt;code&gt;S3&lt;/code&gt; to a container or vice versa, this was going through the gateway. Even our logs being sent from any container to &lt;code&gt;CloudWatch&lt;/code&gt;, through the gateway…&lt;/p&gt;

&lt;p&gt;Now the costs make a little more sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  EC2-Other — sorry, bye 👋
&lt;/h2&gt;

&lt;p&gt;Do not panic — there is a solution — enter &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/vpc-endpoints.html" rel="noopener noreferrer"&gt;VPC Endpoints&lt;/a&gt;, also known as &lt;a href="https://docs.aws.amazon.com/vpc/latest/privatelink/concepts.html" rel="noopener noreferrer"&gt;AWS PrivateLink&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can use AWS PrivateLink to connect the resources in your VPC to services using private IP addresses, as if those services were hosted directly in your VPC.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;– &lt;a href="https://docs.aws.amazon.com/vpc/latest/privatelink/concepts.html" rel="noopener noreferrer"&gt;AWS PrivateLink concepts — Amazon Virtual Private Cloud&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you use your console for changes to infrastructure then follow this tutorial on how to set these endpoints up for S3, ECR and logs: &lt;a href="https://www.easydeploy.io/blog/how-to-create-private-link-for-ecr-to-ecs-containers-to-save-nat-gatewayec2-other-charges/" rel="noopener noreferrer"&gt;Create Private Links via Console&lt;/a&gt; or also this one provided by AWS: &lt;a href="https://aws.amazon.com/blogs/aws/new-vpc-endpoint-for-amazon-s3/" rel="noopener noreferrer"&gt;New VPC Endpoint for S3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, if you use &lt;code&gt;CDK&lt;/code&gt; to deploy your infrastructure, like a hero, then here is how to set up your 3 new VPC endpoints. I am using Kotlin but if you use TypeScript or another language it shouldn’t be too hard to adjust.&lt;/p&gt;

&lt;h2&gt;
  
  
  CDK (In Kotlin)
&lt;/h2&gt;

&lt;p&gt;Here are your imports for this setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import software.amazon.awscdk.services.ec2.GatewayVpcEndpoint
import software.amazon.awscdk.services.ec2.GatewayVpcEndpointProps
import software.amazon.awscdk.services.ec2.GatewayVpcEndpointAwsService
import software.amazon.awscdk.services.ec2.InterfaceVpcEndpoint
import software.amazon.awscdk.services.ec2.InterfaceVpcEndpointProps
import software.amazon.awscdk.services.ec2.InterfaceVpcEndpointService
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll need to have your VPC available in this stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gateway Endpoint
&lt;/h2&gt;

&lt;p&gt;Firstly here is your Gateway Endpoint for S3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private val s3VpcEndpoint = GatewayVpcEndpoint(
  scope,
  "your-s3-endpoint",
  GatewayVpcEndpointProps.builder()
    .vpc(vpc)
    .service(GatewayVpcEndpointAwsService.S3)
    .build()
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the time of writing this, &lt;strong&gt;gateway endpoints&lt;/strong&gt; were only available for S3 and DynamoDB — otherwise you must use an &lt;strong&gt;Interface Endpoint&lt;/strong&gt;. Gateway endpoints for S3 are offered at no cost and the routes are managed through route tables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interface Endpoint
&lt;/h2&gt;

&lt;p&gt;Interface endpoints are priced at $0.01/per AZ/per hour. Cost depends on the Region, check &lt;a href="https://aws.amazon.com/privatelink/pricing/" rel="noopener noreferrer"&gt;current pricing&lt;/a&gt;. Data transferred through the interface endpoint is charged at $0.01/per GB (depending on Region).&lt;/p&gt;

&lt;p&gt;For the Interface Endpoint for &lt;strong&gt;ECR&lt;/strong&gt; you need to have your security groups available:&lt;/p&gt;

&lt;p&gt;It is important to include the correct security groups here or else you could have issues with your containers not being able to pull from ECR.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private val ecrVpcEndpoint = InterfaceVpcEndpoint(
  scope,
  "your-ecr-endpoint",
  InterfaceVpcEndpointProps.builder()
.service(InterfaceVpcEndpointService("com.amazonaws.$yourRegion.ecr.dkr"))
    .vpc(vpc)
    .privateDnsEnabled(true)
    .securityGroups(
      listOf(exampleSecurityGroup)
    )
    .build()
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then for the Interface Endpoint for &lt;strong&gt;Logs&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;private val logsVpcEndpoint = InterfaceVpcEndpoint(
  scope,
  "your-logs-endpoint",
  InterfaceVpcEndpointProps.builder()
    .service(InterfaceVpcEndpointService("com.amazonaws.$yourRegion.logs"))
    .vpc(vpc)
    .privateDnsEnabled(true)
    .securityGroups(
      listOf(exampleSecurityGroup)
    )
    .build()
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sadly with CDK you cannot add a name tag to these endpoints (&lt;a href="https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/196" rel="noopener noreferrer"&gt;see issue here&lt;/a&gt;) so when you deploy you will see something that looks like this:&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%2Fz01mhavkgce5txa0n9m2.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%2Fz01mhavkgce5txa0n9m2.png" alt="Resulting endpoints" width="800" height="164"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;So, I pushed these changes to our production environment halfway through the day 23rd June, but I think you can see that quite clearly here:&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%2F5rp00oapujyulpqmksyu.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%2F5rp00oapujyulpqmksyu.png" alt="Resulting costs" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;So after leaving our system to run for a little while with these endpoints in place we started to see the &lt;code&gt;EC2-Other&lt;/code&gt; costs drop significantly. For us, our costs dropped from over $2000 per month to just over $200!&lt;/p&gt;

&lt;p&gt;I hope that you too can find a use for these VPC Endpoints to help lower your costs!&lt;/p&gt;

&lt;p&gt;There is an increase, of course, in the costs for your &lt;code&gt;VPC&lt;/code&gt; for these endpoints, but I am not too worried about the $1 increase there:&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%2Fv8dssbipkos6kbl6hxni.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%2Fv8dssbipkos6kbl6hxni.png" alt="Increased VPC costs" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Thanks for coming on this journey with me, and I hope this can help you save some precious dolla bills 💲&lt;/p&gt;

&lt;p&gt;Emma.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building an OpenSearch Index from DynamoDB with CDK</title>
      <dc:creator>Emma Moinat</dc:creator>
      <pubDate>Wed, 18 Jan 2023 16:06:19 +0000</pubDate>
      <link>https://dev.to/emmamoinat/building-an-opensearch-index-from-dynamodb-1a3e</link>
      <guid>https://dev.to/emmamoinat/building-an-opensearch-index-from-dynamodb-1a3e</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;We will be looking at how to set up an OpenSearch index from a DynamoDB table. We will assume you have some knowledge of DynamoDB and Lambdas and also are familiar with using CDK for deploying infrastructure into AWS.&lt;/p&gt;

&lt;h2&gt;
  
  
  DynamoDB
&lt;/h2&gt;

&lt;p&gt;Firstly, let’s think about our DynamoDB table and how to set it up in a way that it is ready to be indexed. This is actually very straight forward and utilises a DynamoDB stream.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"A DynamoDB stream is an ordered flow of information about changes to items in a DynamoDB table." - AWS&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Using CDK your table might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const userTable = new dynamodb.Table(this, "UserTable", {
  tableName: "user-table",
  billingMode: BillingMode.PAY_PER_REQUEST,
  partitionKey: {name: "partitionKey", type: AttributeType.STRING},
  sortKey: {name: "sortKey", type: AttributeType.STRING},
  pointInTimeRecovery: true,
  stream: StreamViewType.NEW_IMAGE // This is the important line!
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using the console instead of CDK &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.html#Streams.Enabling" rel="noopener noreferrer"&gt;see this&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are different types of streams:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KEYS_ONLY - Only the key attributes of the modified item are written to the stream.
NEW_IMAGE - The entire item, as it appears after it was modified, is written to the stream.
OLD_IMAGE - The entire item, as it appeared before it was modified, is written to the stream.
NEW_AND_OLD_IMAGES - Both new and old item images of the item are written to the stream.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we have chosen &lt;code&gt;NEW_IMAGE&lt;/code&gt; because we only need to know the new item to index.&lt;/p&gt;

&lt;p&gt;This will create a table with a DynamoDB stream, which means any new, updated or deleted item events will be streamed into a place of your choosing; we have chosen a Lambda.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lambda
&lt;/h2&gt;

&lt;p&gt;So, next up, we must think about the indexing Lambda. There is currently no direct way to index your data from a stream to the OpenSearch domain, so we must add a middle man to do the work. More on this can be found &lt;a href="https://docs.aws.amazon.com/opensearch-service/latest/developerguide/integrations.html#integrations-dynamodb" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The code for this Lambda and a few other helpful Lambdas can be found &lt;a href="https://github.com/instil/building-opensearch-index-from-dynamo-db" rel="noopener noreferrer"&gt;here on Github&lt;/a&gt;. This Lambda lives in the &lt;code&gt;index-stream&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Here is a code snippet of this Lambda’s handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const handler = async (event: DynamoDBStreamEvent): Promise&amp;lt;void&amp;gt; =&amp;gt; {
  console.log("Received event from the user table");

  for (const record of event.Records) {
    if (!record.eventName || !record.dynamodb || !record.dynamodb.Keys) continue;

    const partitionKey = record.dynamodb.Keys.partitionKey.S;
    const sortKey = record.dynamodb.Keys.sortKey.S;
    // Note here that we are using a pk and sk 
    // but maybe you are using only an id, this would look like:
    // const id = record.dynamodb.Keys.id.S;

    try {
      if (record.eventName === "REMOVE") {
        // performing a DELETE request to your index
        return await removeDocumentFromOpenSearch(partitionKey, sortKey);
      } else {
        // There are 2 types of events left to handle, INSERT and MODIFY, 
        // which will both contain a NewImage
        if (!record.dynamodb.NewImage) continue;

        const userDocument = DynamoDB.Converter.unmarshall(record.dynamodb.NewImage) as User;
        // performing a PUT request to your index
        return await indexDocumentInOpenSearch(userDocument, partitionKey, sortKey);
      }
    } catch (error) {
      console.error("Error occurred updating OpenSearch domain", error);
      throw error;
    }
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In CDK you can create your Lambda as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const userTableIndexingFunction = new Function(this, "UserTableIndexingFunction", {
  functionName: "UserTableIndexingFunction",
  code: Code.fromAsset("user-table-indexing-lambda-dist-folder"),
  runtime: Runtime.NODEJS_16_X,
  handler: "index.handler"
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can add the DynamoDB stream as a source event to this Lambda.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;userTableIndexingFunction.addEventSource(new DynamoEventSource(userTable, {
  startingPosition: StartingPosition.TRIM_HORIZON,
  batchSize: 1, // Our lambda could handle this being more than 1 as well but of the for loop
  retryAttempts: 3
}));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are 2 types of starting positions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TRIM_HORIZON - Start reading at the last untrimmed record in the shard in the system, 
               which is the oldest data record in the shard. 
               In other words, the stream will look at all the item events and 
               deal with them in chronological order (oldest event to most recent event)

      LATEST - Start reading just after the most recent record in the shard, 
               so that you always read the most recent data in the shard. 
               In other words, the stream will look at all the item events and 
               deal with the most recent first and work down until the oldest event.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this example, we therefore use &lt;code&gt;TRIM_HORIZON&lt;/code&gt; so that the index will reflect the data in its current state.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenSearch
&lt;/h2&gt;

&lt;p&gt;Now, let’s look at the actual OpenSearch domain setup. Now, AWS suggests some substantial power (and therefore money) for a production ready domain. You can find the &lt;a href="https://docs.aws.amazon.com/opensearch-service/latest/developerguide/bp.html" rel="noopener noreferrer"&gt;best practices here&lt;/a&gt;. For this example we will use a very small setup with no redundancy, however, feel free to scale this up based on your needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const openSearchDomain = new Domain(this, "OpenSearchDomain", {
    version: EngineVersion.OPENSEARCH_1_0,
  capacity: {
    dataNodeInstanceType: "t3.small.search",
    dataNodes: 1,
    masterNodes: 0
  },
  ebs: {
    enabled: true,
    volumeSize: 50,
    volumeType: EbsDeviceVolumeType.GENERAL_PURPOSE_SSD
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will deploy your OpenSearch domain, this can take some time, so be patient.&lt;/p&gt;

&lt;p&gt;One final thing to think about is the granting your Lambda the rights to read and write to your domain. In your stack with your OpenSearch domain, add this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openSearchDomain.grantIndexReadWrite("user-index", userTableIndexingFunction);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will allow your Lambda to do its job.&lt;/p&gt;

&lt;p&gt;That's it, you are all set up and ready to index any new data into your OpenSearch index. &lt;/p&gt;

&lt;p&gt;For indexing existing data, you can find a helpful Lambda under the &lt;code&gt;index-data&lt;/code&gt; directory &lt;a href="https://github.com/instil/building-opensearch-index-from-dynamo-db" rel="noopener noreferrer"&gt;here on Github&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
