<?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: Yan Cui</title>
    <description>The latest articles on DEV Community by Yan Cui (@theburningmonk).</description>
    <link>https://dev.to/theburningmonk</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%2F158434%2F3a66d1ca-ea7f-446f-95d6-6e8f89bedb67.jpg</url>
      <title>DEV Community: Yan Cui</title>
      <link>https://dev.to/theburningmonk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/theburningmonk"/>
    <language>en</language>
    <item>
      <title>Is it safe to use ID tokens with Cognito authorizers?</title>
      <dc:creator>Yan Cui</dc:creator>
      <pubDate>Tue, 03 Sep 2024 01:32:48 +0000</pubDate>
      <link>https://dev.to/theburningmonk/is-it-safe-to-use-id-tokens-with-cognito-authorizers-3e3c</link>
      <guid>https://dev.to/theburningmonk/is-it-safe-to-use-id-tokens-with-cognito-authorizers-3e3c</guid>
      <description>&lt;p&gt;A common narrative is that one should always use access tokens to call your APIs, while ID tokens are strictly for identifying users.&lt;/p&gt;

&lt;p&gt;Some of it has come from this &lt;a href="https://auth0.com/blog/id-token-access-token-what-is-the-difference/" rel="noopener noreferrer"&gt;article by Auth0&lt;/a&gt; [1], which makes a strong statement about using ID tokens:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjj0g0qdmna1wvcim35f7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjj0g0qdmna1wvcim35f7.png" width="800" height="115"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, things are usually more nuanced. In some cases, using ID tokens instead of access tokens is both acceptable and pragmatic. Cognito User Pools might be one of these cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost of using access tokens in Cognito
&lt;/h3&gt;

&lt;p&gt;The common practice amongst Cognito users is to use ID tokens.&lt;/p&gt;

&lt;p&gt;Before January 2024, you couldn’t customize access tokens to include the OAuth scopes. So, using access tokens for authorization just wasn’t an option.&lt;/p&gt;

&lt;p&gt;But now, Cognito lets you customize access tokens through the Pre-Token Generation trigger, &lt;a href="https://aws.amazon.com/blogs/security/how-to-customize-access-tokens-in-amazon-cognito-user-pools/" rel="noopener noreferrer"&gt;like this&lt;/a&gt; [2]. With this, you can implement authorization in API Gateway using access tokens and OAuth scopes.&lt;/p&gt;

&lt;p&gt;However, this requires Cognito’s &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-advanced-security.html" rel="noopener noreferrer"&gt;advanced security features&lt;/a&gt; [3], which are charged much higher – starting at $0.050 per MAU and do not have a free tier. You must also pay the standard MAU cost for Cognito User Pools, which starts at $0.0055 per MAU and has a generous free tier of 50,000 MAU.&lt;/p&gt;

&lt;p&gt;This significantly raises the cost of using Cognito User Pools. Here is how much it’d cost you per month with Advanced Security Features (using access tokens) vs. without (using ID tokens).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs6ib69qfc687z77eczcr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs6ib69qfc687z77eczcr.png" width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This might be fine for B2B use cases where you tend to have few high-value users. But it’s practically a death penalty for many B2C businesses, many of whom have thousands of free users.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Why not use something else instead?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For this many MAUs, you’d pay even more with vendors such as Auth0 and Okta. Most of whom require you to sign an enterprise contract before you can reach this scale.&lt;/p&gt;

&lt;p&gt;Cognito’s greatest strengths are cost efficiency and its integration with other AWS services, such as API Gateway and AppSync. If access tokens are significantly more costly, one must ask,&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Is it worth it?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Are access tokens more secure? If so, are they THAT much more secure and worth the extra cost?&lt;/p&gt;

&lt;h3&gt;
  
  
  ID tokens vs. Access tokens
&lt;/h3&gt;

&lt;p&gt;Whenever I show an example of using Cognito with ID tokens, someone would tell me, “You should use access tokens instead!”. But, I have yet to hear a compelling argument for why ID tokens are less secure.&lt;/p&gt;

&lt;p&gt;Let’s quickly debunk the common arguments.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“ID tokens are not designed for authorization”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Yes, access tokens were intended for APIs and ID tokens for authentication purposes. However, this division is not always necessary or practical.&lt;/p&gt;

&lt;p&gt;After all, there is more than one way to implement authorization.&lt;/p&gt;

&lt;p&gt;You can implement the authorization logic in the identity provider and embed the authorization decision in access tokens (in the form of OAuth scopes).&lt;/p&gt;

&lt;p&gt;However, you can also implement the authorization logic directly in the API. In my &lt;a href="https://dev.to/aws-heroes/fine-grained-access-control-in-api-gateway-with-cognito-groups-lambda-authorizer-2bce"&gt;last post&lt;/a&gt; [4], I demonstrated how you can do this with a Lambda authorizer and Cognito groups. We will also explore other ways to implement authorization with API Gateway in the coming weeks.&lt;/p&gt;

&lt;p&gt;It’s two routes to the same result – being able to control who can do what in your system.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“ID tokens have more information”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is true. ID tokens contain information about the user, such as their name and email.&lt;/p&gt;

&lt;p&gt;But what can attackers do with this information if they managed to steal your ID tokens? Nothing. Most likely.&lt;/p&gt;

&lt;p&gt;As an attacker, I have easier ways to acquire names and emails than to steal ID tokens from a system.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“ID tokens give you access to the API”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So does access tokens.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Access token can only be created by a trusted source”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So are ID tokens.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Access tokens have limited lifetime”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So can ID tokens. You can configure the validity period for both access and ID tokens in Cognito (and with other vendors). It’s a matter of making sensible architectural decisions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vsjyuvoa8ajixnd68nh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vsjyuvoa8ajixnd68nh.png" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“You can bind access tokens to specific senders to avoid abuse”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Yes, the Auth0 article mentioned this.&lt;/p&gt;

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

&lt;p&gt;The &lt;a href="https://auth0.com/blog/identity-unlocked-explained-episode-1/" rel="noopener noreferrer"&gt;linked article&lt;/a&gt; &lt;a href="https://dev.towhich%20is%20a%20good%20read,%20btw!"&gt;5&lt;/a&gt; discusses two techniques:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mutual TLS authentication (MTLS)&lt;/li&gt;
&lt;li&gt;Demonstrating Proof-of-Possession (DPoP)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;for binding a token to a specific sender.&lt;/p&gt;

&lt;p&gt;However, nothing about these techniques is specific to access tokens. They will work equally well for ID tokens.&lt;/p&gt;

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

&lt;p&gt;Until someone can prove otherwise, I believe it’s perfectly safe to use ID tokens with Cognito authorizers.&lt;/p&gt;

&lt;p&gt;ID tokens are not inherently less secure than access tokens. Furthermore, all the techniques that make access tokens more secure also apply to ID tokens.&lt;/p&gt;

&lt;p&gt;So, there are no security downsides to using ID tokens with Cognito.&lt;/p&gt;

&lt;p&gt;On the other hand, there are significant costs to using access tokens.&lt;/p&gt;

&lt;p&gt;I’m not saying that you shouldn’t use access tokens! In fact, I will show you how to use access tokens to implement authorization in API Gateway in the next post.&lt;/p&gt;

&lt;p&gt;But you should know the trade-offs and not blindly pick a more costly approach based on hearsay.&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;p&gt;[1] &lt;a href="https://auth0.com/blog/id-token-access-token-what-is-the-difference/" rel="noopener noreferrer"&gt;“ID Token and Access Token: What’s the Difference?” by Auth0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] &lt;a href="https://aws.amazon.com/blogs/security/how-to-customize-access-tokens-in-amazon-cognito-user-pools/" rel="noopener noreferrer"&gt;How to customize access tokens in Amazon Cognito user pools&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[3] &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-advanced-security.html" rel="noopener noreferrer"&gt;Cognito User Pool advanced security features&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[4] &lt;a href="https://dev.to/aws-heroes/fine-grained-access-control-in-api-gateway-with-cognito-groups-lambda-authorizer-2bce"&gt;Fine-grained access control in API Gateway with Cognito groups &amp;amp; Lambda authorizer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[5] &lt;a href="https://auth0.com/blog/identity-unlocked-explained-episode-1/" rel="noopener noreferrer"&gt;Identity, Unlocked… Explained | Episode 1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://theburningmonk.com/2024/09/is-it-safe-to-use-id-tokens-with-cognito-authorizers/" rel="noopener noreferrer"&gt;Is it safe to use ID tokens with Cognito authorizers?&lt;/a&gt; appeared first on &lt;a href="https://theburningmonk.com" rel="noopener noreferrer"&gt;theburningmonk.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>apigateway</category>
      <category>aws</category>
      <category>cognito</category>
      <category>security</category>
    </item>
    <item>
      <title>Fine-grained access control in API Gateway with Cognito groups &amp; Lambda authorizer</title>
      <dc:creator>Yan Cui</dc:creator>
      <pubDate>Thu, 29 Aug 2024 08:09:48 +0000</pubDate>
      <link>https://dev.to/aws-heroes/fine-grained-access-control-in-api-gateway-with-cognito-groups-lambda-authorizer-2bce</link>
      <guid>https://dev.to/aws-heroes/fine-grained-access-control-in-api-gateway-with-cognito-groups-lambda-authorizer-2bce</guid>
      <description>&lt;p&gt;In security and access control, authentication and authorization mean two distinct but related things.&lt;/p&gt;

&lt;p&gt;Authentication verifies the identity of a user or system.&lt;/p&gt;

&lt;p&gt;Authorization determines what actions an authenticated user is allowed to perform in your system.&lt;/p&gt;

&lt;p&gt;API Gateway has built-in integration with Cognito, but it doesn’t provide any fine-grained authorization out-of-the-box.&lt;/p&gt;

&lt;p&gt;By default, a Cognito authorizer only checks if a user’s bearer token is valid and that the user belongs to the right Cognito User Pool.&lt;/p&gt;

&lt;p&gt;Here are many ways you can implement a fine-grained authorization with API Gateway. Here are three that I have come across over the years:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using Lambda authorizer with Cognito groups;&lt;/li&gt;
&lt;li&gt;Using Cognito access tokens with OAuth scopes;&lt;/li&gt;
&lt;li&gt;Using Lambda authorizer with &lt;a href="https://aws.amazon.com/verified-permissions/" rel="noopener noreferrer"&gt;Amazon Verified Permissions&lt;/a&gt; [1];&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Over the next few weeks, let’s look at these approaches in-depth and then compare them at the end.&lt;/p&gt;

&lt;p&gt;Today, let’s look at Lambda authorizer with Cognito groups.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model roles with Cognito groups
&lt;/h3&gt;

&lt;p&gt;In Cognito, you can use groups to model the different roles in your system, e.g. &lt;code&gt;Admin&lt;/code&gt;, &lt;code&gt;ReadOnly&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F85opndiqv6xmt1t1rmwv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F85opndiqv6xmt1t1rmwv.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Users can belong to more than one group at once, just as they can have multiple roles within a system.&lt;/p&gt;

&lt;p&gt;Cognito encodes the groups a user belongs to in the ID token. If you decode the ID token, you will see something like this:&lt;/p&gt;

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

{
  "sub": "f438b478-6031-70f3-a346-4f8e84e00b62",
  "cognito:groups": [
    "ReadOnly",
    "Admin"
  ],
  "email_verified": true,
  "iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxx",
  "cognito:username": "f438b478-6031-70f3-a346-4f8e84e00b62",
  "origin_jti": "e0d4077e-7092-45dd-ac13-1d60d382629b",
  "aud": "1u7c0elmc6v3qrr68s4vpo63sm",
  "event_id": "fd811c5a-5ac7-4644-92ae-a9738a33bd76",
  "token_use": "id",
  "auth_time": 1724807904,
  "exp": 1724811504,
  "iat": 1724807904,
  "jti": "5fa8be1d-411f-418d-8508-b6b8fe64ff9b",
  "email": "example@example.com"
}


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

&lt;/div&gt;

&lt;p&gt;Here, we can see the user belongs to both the &lt;code&gt;Admin&lt;/code&gt; and &lt;code&gt;ReadOnly&lt;/code&gt; groups.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda authorizer
&lt;/h3&gt;

&lt;p&gt;A Lambda authorizer can use this information to generate its policy document. As a reminder, a Lambda authorizer can return a policy document like this:&lt;/p&gt;

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

{
  "principalId": "username",
  "policyDocument": {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Action": "execute-api:Invoke",
        "Effect": "Allow",
        "Resource": "arn:aws:execute-api:us-east-1:12345:xxx/dev/GET/resource"
      }
    ]
  }
}


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

&lt;/div&gt;

&lt;p&gt;So, we need to take the list of groups a user belongs to and turn them into a set of policy statements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fvkjko2odpfxsxwgje6s1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fvkjko2odpfxsxwgje6s1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Mapping roles to policies
&lt;/h4&gt;

&lt;p&gt;One approach is to keep a mapping in your code like this.&lt;/p&gt;

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

const POLICY_STATEMENTS = {
  "Admin": [{
    "Action": "execute-api:Invoke",
    "Effect": "Allow",
    "Resource": "arn:aws:execute-api:us-east-1:123456789012:xxx/dev/*"
  }],
  "ReadOnly": [{
    "Action": "execute-api:Invoke",
    "Effect": "Allow",
    "Resource": [ 
      "arn:aws:execute-api:us-east-1:123456789012:xxx/dev/GET/token",
      "arn:aws:execute-api:us-east-1:123456789012:xxx/dev/POST/task",
      ...
    ]
  }]
}


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

&lt;/div&gt;

&lt;p&gt;In many systems, there are a small number of roles that supersede each other. That is, they are hierarchical, and a higher role has all the permissions of a lower role plus some.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fcorxc4owa97cfpvcijow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fcorxc4owa97cfpvcijow.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, we need to find the most permissive role that the user has.&lt;/p&gt;

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

// assuming we have only two roles, Admin and ReadOnly
// and Admin supercedes Readonly
const statement = groups.includes("Admin")
  ? POLICY_STATEMENTS["Admin"]
  : POLICY_STATEMENTS["ReadOnly"]

return {
  "principalId": username,
  "policyDocument": {
    "Version": "2012-10-17",
    "Statement": statement
  }
}


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

&lt;/div&gt;

&lt;p&gt;But what if the roles are more lateral? That is, a user’s permissions are derived from all its roles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fv6912bg2g0v1mz5dmwhs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fv6912bg2g0v1mz5dmwhs.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, that’s easy enough to accommodate.&lt;/p&gt;


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

&lt;p&gt;const statement = []&lt;/p&gt;

&lt;p&gt;["Admin", "ReadOnly"].forEach(x =&amp;gt; {&lt;br&gt;
  if (groups.includes(x)) {&lt;br&gt;
    POLICY_STATEMENTS[x].forEach(stm =&amp;gt; statement.push(stm))&lt;br&gt;
  }&lt;br&gt;
})&lt;/p&gt;

&lt;p&gt;return {&lt;br&gt;
  "principalId": username,&lt;br&gt;
  "policyDocument": {&lt;br&gt;
    "Version": "2012-10-17",&lt;br&gt;
    "Statement": statement&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

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

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Conclusion&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;This is my preferred approach for simple use cases.&lt;/p&gt;

&lt;p&gt;It’s easy to follow and test and makes no API calls (i.e. no extra latency overhead).&lt;/p&gt;

&lt;p&gt;Furthermore, it does not require Cognito’s &lt;em&gt;Advanced Security Features&lt;/em&gt;, which are charged at a &lt;a href="https://aws.amazon.com/cognito/pricing/" rel="noopener noreferrer"&gt;much higher rate&lt;/a&gt; [2]. This makes it a very cost-efficient approach.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fo6d1cyswlf0ywjfcb531.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fo6d1cyswlf0ywjfcb531.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, using a Lambda authorizer means you need to think about cold starts and their impact on user experience.&lt;/p&gt;

&lt;p&gt;Also, the roles and policies are static. Whilst it’s good enough for simple use cases, it cannot (easily) support more advanced use cases. For example, if you need to allow users to create custom roles while maintaining the tenant boundary.&lt;/p&gt;

&lt;p&gt;Amazon Verified Permissions is a better fit for more advanced use cases. More on it later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;p&gt;[1] &lt;a href="https://aws.amazon.com/verified-permissions/" rel="noopener noreferrer"&gt;Amazon Verified Permissions service&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] &lt;a href="https://aws.amazon.com/cognito/pricing/" rel="noopener noreferrer"&gt;Cognito’s pricing page&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://theburningmonk.com/2024/08/fine-grained-access-control-in-api-gateway-with-cognito-groups-lambda-authorizer/" rel="noopener noreferrer"&gt;Fine-grained access control in API Gateway with Cognito groups &amp;amp; Lambda authorizer&lt;/a&gt; appeared first on &lt;a href="https://theburningmonk.com" rel="noopener noreferrer"&gt;theburningmonk.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>apigateway</category>
      <category>aws</category>
      <category>cognito</category>
    </item>
    <item>
      <title>What’s the best way to do fan-out/fan-in serverlessly in 2024?</title>
      <dc:creator>Yan Cui</dc:creator>
      <pubDate>Sun, 04 Aug 2024 18:48:10 +0000</pubDate>
      <link>https://dev.to/aws-heroes/whats-the-best-way-to-do-fan-outfan-in-serverlessly-in-2024-2eh6</link>
      <guid>https://dev.to/aws-heroes/whats-the-best-way-to-do-fan-outfan-in-serverlessly-in-2024-2eh6</guid>
      <description>&lt;p&gt;Back in 2018, I &lt;a href="https://theburningmonk.com/2018/04/how-to-do-fan-out-and-fan-in-with-aws-lambda/" rel="noopener noreferrer"&gt;shared&lt;/a&gt; [1] several ways to implement fan-out/fan-in with Lambda. A lot has changed since, so let’s explore the solution space in 2024.&lt;/p&gt;

&lt;p&gt;Remember, what’s “best” depends on your context. I will do my best to outline the trade-offs you should consider.&lt;/p&gt;

&lt;p&gt;Also, I will only consider AWS services in this post. But there is a wealth of OpenSource/3rd-party services that you can use too, such as &lt;a href="https://restate.dev/" rel="noopener noreferrer"&gt;Restate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you’re not sure whether you need fan-out/fan-in or map-reduce, then you should my &lt;a href="https://dev.to/theburningmonk/do-you-know-your-fan-outfan-in-from-map-reduce-98j-temp-slug-1158882"&gt;previous post&lt;/a&gt; first [2]. I explained the difference between the two and when to use which.&lt;/p&gt;

&lt;p&gt;Ok, let’s go!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step Functions
&lt;/h2&gt;

&lt;p&gt;Fan-out has always been easy on AWS. You can use SQS to distribute tasks across many workers for parallel processing.&lt;/p&gt;

&lt;p&gt;But fan-in makes you work! You need to keep track of the individual results so you can act on them when all the results are in.&lt;/p&gt;

&lt;p&gt;Luckily for us, Step Function has productized the fan-out/fan-in pattern with its &lt;code&gt;Map&lt;/code&gt; state.&lt;/p&gt;

&lt;p&gt;You provide an input array and specify how to process each item in the array. The &lt;code&gt;Map&lt;/code&gt; state will process the items with as much parallelism as possible. It handles collating the results into the correct order and returns them as an array.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F08%2Fimg_66afcc9c071f0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F08%2Fimg_66afcc9c071f0.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can take this output array as input to another &lt;code&gt;Task&lt;/code&gt; state to act on the results.&lt;/p&gt;

&lt;p&gt;Importantly, the &lt;code&gt;Map&lt;/code&gt; state has two modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Inline mode&lt;/code&gt; takes the input array from the state input and allows up to 40 concurrent iterations. This is good enough for most use cases. But it falls short when you have a large input array or require higher concurrency.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Distributed mode&lt;/code&gt; can use a JSON/CSV file in S3 as input and allows up to 10,000 concurrent iterations! This is referred to as the “distributed Map” state and is priced differently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these two modes, you can use the &lt;code&gt;Map&lt;/code&gt; state for fan-out/fan-in at practically any scale.&lt;/p&gt;

&lt;p&gt;The main consideration here is the ease of configuration and cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ease of configuration
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Inline mode&lt;/code&gt; is easier to configure than &lt;code&gt;Distributed mode&lt;/code&gt;. Both modes need an &lt;code&gt;ItemProcessor&lt;/code&gt; to tell the state machine how to process the array items.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Distributed mode&lt;/code&gt; also needs an &lt;code&gt;ItemReader&lt;/code&gt; and optionally (but likely) a &lt;code&gt;ResultWriter&lt;/code&gt;. This is so the state machine knows how to read the input from S3 and potentially write the results to S3 too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F08%2Fimg_66afccbd3b87d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F08%2Fimg_66afccbd3b87d.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition, you have to decide what &lt;code&gt;ExecutionType&lt;/code&gt; to use for the distributed map.&lt;/p&gt;

&lt;p&gt;This is an important decision.&lt;/p&gt;

&lt;p&gt;Each iteration of the map state is executed as a child workflow. The &lt;code&gt;ExecutionType&lt;/code&gt; setting determines if the child workflow is executed as a standard workflow or an express workflow.&lt;/p&gt;

&lt;p&gt;Standard workflows can run for up to a year. But an express workflow can only run for five minutes. See &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/choosing-workflow-type.html" rel="noopener noreferrer"&gt;this article&lt;/a&gt; [3] for a more detailed comparison of these two workflow types.&lt;/p&gt;

&lt;p&gt;Perhaps most importantly, this decision affects how the iterations are charged.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost
&lt;/h3&gt;

&lt;p&gt;In Inline mode, the states in the ItemProcessor are executed as part of the state machine. For a Standard Workflow, they are charged at $25 / million state transitions. For an Express Workflow, they are charged based on memory used and duration.&lt;/p&gt;

&lt;p&gt;The Distributed mode executes each iteration as a child workflow. The ExecutionType setting dictates whether the iterations are executed as Standard Workflows or Express Workflows.&lt;/p&gt;

&lt;p&gt;Express Workflows are priced exactly as before.&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;Standard Workflows are priced as one state transition per iteration&lt;/strong&gt;. That is, even if an iteration executes 10 state transitions, they are priced as one state transition.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F08%2Fimg_66b0205f8d982-1536x599.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F08%2Fimg_66b0205f8d982-1536x599.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this in mind, the Step Functions cost for processing large input arrays will not be astronomical.&lt;/p&gt;

&lt;p&gt;However, you also need to take into account other associated costs, such as the cost of Lambda invocations. If you need to process a large array of inputs, consider processing them in batches.&lt;/p&gt;

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

&lt;p&gt;Step Function’s Map state is the simplest solution for implementing fan-in/fan-out serverlessly.&lt;/p&gt;

&lt;p&gt;It can handle workloads at any scale.&lt;/p&gt;

&lt;p&gt;Even if you need to fan out to millions of tasks, the distributed map state can offer a cost-effective solution.&lt;/p&gt;

&lt;p&gt;If you want a balanced solution that offers good developer productivity and cost efficiency, you should choose Step Functions.&lt;/p&gt;

&lt;p&gt;But if cost is your primary concern, maybe because you need to produce millions of tasks frequently, then you should implement a custom solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom build solutions
&lt;/h2&gt;

&lt;p&gt;Here’s a common pattern for building a custom fan-out/fan-in solution:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F08%2Fimg_66b02078413d9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F08%2Fimg_66b02078413d9.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;When an input file is dropped in S3, it triggers a Lambda function to process the input file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Lambda function parses the input file and fans out the items to a SQS queue. It also saves the no. of tasks in DynamoDB.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A Lambda function processes the tasks from SQS in batches. To handle partial failures, it should &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/services-sqs-errorhandling.html#services-sqs-batchfailurereporting" rel="noopener noreferrer"&gt;implement partial batch responses&lt;/a&gt; [4].&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;As the SQS function processes each task, it writes the intermediate result to DynamoDB.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The data change events from step 4. are captured in a DynamoDB stream and used to trigger another Lambda function.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This function counts the no. of newly completed tasks and updates the total no. of completed tasks in DynamoDB. When it sees all the tasks are completed, it will iterate over all the results and produce a final output.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is likely a more cost-efficient solution than Step Functions.&lt;/p&gt;

&lt;p&gt;There are built-in batching for the SQS and DynamoDB stream functions, courtesy of Lambda’s EventSourceMapping. So the Lambda-related processing costs will be lower.&lt;/p&gt;

&lt;p&gt;Furthermore, here is a rough estimate for other costs (assuming one million items):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 million SQS SendMessage calls (for fan-out): $0.40&lt;/li&gt;
&lt;li&gt;1 million DynamoDB PutItem calls (assuming each intermediate result is smaller than 1kb): $1.25&lt;/li&gt;
&lt;li&gt;Assuming the Kinesis function uses a batch size of 100, we need to make 10,000 DynamoDB GetItem &amp;amp; PutItem calls to update the count (not using atomic increments because they are not reliable). $0.015&lt;/li&gt;
&lt;li&gt;Once all the results are in, we need to query the table to fetch all results. Assuming each result is 1kb in size, we can fetch 4 results per Read Request Unit (RRU). It will take 250,000 RRUs in total, or $0.0625.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, these estimated costs are much lower than those for Step Functions. However, this solution has many moving parts, and you have to own the uptime.&lt;/p&gt;

&lt;p&gt;If you frequently process millions of items then a custom solution like this can make sense.&lt;/p&gt;

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

&lt;p&gt;When choosing a solution, it’s not just about finding the cheapest option. It’s about getting the most value for your investment.&lt;/p&gt;

&lt;p&gt;Remember, you get what you paid for.&lt;/p&gt;

&lt;p&gt;Think of it like buying tools for a job. You don’t always buy the cheapest tools because they might not last. But you also don’t need the most expensive ones if they offer more than you need. You aim for the right balance – reliable enough to get the job done efficiently without overspending.&lt;/p&gt;

&lt;p&gt;In the same way, when building your serverless architecture, choose the solution that offers the best trade-off between cost, complexity, and capability for your specific needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;[1] &lt;a href="https://theburningmonk.com/2018/04/how-to-do-fan-out-and-fan-in-with-aws-lambda/" rel="noopener noreferrer"&gt;How to do fan-out and fan-in with AWS Lambda&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] &lt;a href="https://dev.to/theburningmonk/do-you-know-your-fan-outfan-in-from-map-reduce-98j-temp-slug-1158882"&gt;Do you know your Fan-Out/Fan-In from Map-Reduce?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[3] &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/choosing-workflow-type.html" rel="noopener noreferrer"&gt;Choosing workflow type in Step Functions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[4] &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/services-sqs-errorhandling.html#services-sqs-batchfailurereporting" rel="noopener noreferrer"&gt;Implementing partial batch responses&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://theburningmonk.com/2024/08/whats-the-best-way-to-do-fan-out-fan-in-serverlessly-in-2024/" rel="noopener noreferrer"&gt;What’s the best way to do fan-out/fan-in serverlessly in 2024?&lt;/a&gt; appeared first on &lt;a href="https://theburningmonk.com" rel="noopener noreferrer"&gt;theburningmonk.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>stepfunctions</category>
    </item>
    <item>
      <title>How to handle execution timeouts in AWS Step Functions</title>
      <dc:creator>Yan Cui</dc:creator>
      <pubDate>Sun, 21 Apr 2024 00:09:42 +0000</pubDate>
      <link>https://dev.to/aws-heroes/how-to-handle-execution-timeouts-in-aws-step-functions-216p</link>
      <guid>https://dev.to/aws-heroes/how-to-handle-execution-timeouts-in-aws-step-functions-216p</guid>
      <description>&lt;p&gt;Step Functions lets you set a timeout on &lt;code&gt;Task&lt;/code&gt; states and the whole execution.&lt;/p&gt;

&lt;p&gt;By default, a &lt;code&gt;Task&lt;/code&gt; state times out after 60 seconds. But an execution can run for a year if no &lt;code&gt;TimeoutSeconds&lt;/code&gt; is configured. To a user, the execution would appear as “stuck”.&lt;/p&gt;

&lt;p&gt;AWS best practices recommend &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/sfn-stuck-execution.html" rel="noopener noreferrer"&gt;using timeouts to avoid such scenarios&lt;/a&gt; [1]. So it’s important to consider what happens when you experience a timeout&lt;/p&gt;

&lt;p&gt;You can use the &lt;code&gt;Catch&lt;/code&gt; clause to handle the &lt;code&gt;States.Timeout&lt;/code&gt; error when a &lt;code&gt;Task&lt;/code&gt; state times out. You can then perform automated remediation steps.&lt;/p&gt;

&lt;p&gt;But what happens when the whole execution times out? How can we catch and handle execution timeouts like we do with &lt;code&gt;Task&lt;/code&gt; states?&lt;/p&gt;

&lt;p&gt;Here are 3 ways to do it.&lt;/p&gt;

&lt;h3&gt;
  
  
  EventBridge
&lt;/h3&gt;

&lt;p&gt;Standard Workflows publish &lt;code&gt;TIMED_OUT&lt;/code&gt; events to the default EventBridge bus. We can create an EventBridge rule to match against these events. That way, we can trigger a Lambda function to handle the error.&lt;/p&gt;

&lt;p&gt;The event contains the state machine ARN, execution name, input and output. We can even use the execution ARN to fetch the full audit history of the execution.&lt;/p&gt;

&lt;p&gt;That should give us everything we need to figure out what happened.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F04%2Fsfn-timeout-01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F04%2Fsfn-timeout-01.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, this approach only works for Standard Workflows. Express Workflows do not emit events to EventBridge.&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudWatch Logs
&lt;/h3&gt;

&lt;p&gt;Both Standard and Express Workflows can write logs to CloudWatch. When an execution times out, it writes a log event like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F04%2Fsfn-timeout-02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F04%2Fsfn-timeout-02.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can use CloudWatch log subscription to send these events to a Lambda function to handle the timeout.&lt;/p&gt;

&lt;p&gt;However, these log events are not as easy to use as the EventBridge events.&lt;/p&gt;

&lt;p&gt;We can extract the state machine name and execution name from the execution ARN. But not the input and output.&lt;/p&gt;

&lt;p&gt;For Standard Workflows, we can use the &lt;a href="https://docs.aws.amazon.com/step-functions/latest/apireference/API_GetExecutionHistory.html" rel="noopener noreferrer"&gt;GetExecutionHistory&lt;/a&gt; [2] API to fetch the execution history. But this does not support Express Workflows. Instead, we must rely on the audit history logged to CloudWatch.&lt;/p&gt;

&lt;p&gt;These are not always available. Because we will likely set the log level to &lt;code&gt;ERROR&lt;/code&gt; to minimize the cost of CloudWatch Logs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F04%2Fsfn-timeout-03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F04%2Fsfn-timeout-03.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This approach can work for both Standard and Express Workflows. However, it might not be practical because the log event provides limited information about the execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nested workflows
&lt;/h3&gt;

&lt;p&gt;We can solve the abovementioned problems by nesting our state machine inside a parent Standard Workflow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Works for both Standard and Express Workflows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We have the input and output for the execution.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F04%2Fsfn-timeout-04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F04%2Fsfn-timeout-04.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a simple and elegant solution. It’s definitely my favourite approach for handling execution timeouts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Honourable mentions
&lt;/h3&gt;

&lt;p&gt;There are other variants besides the approaches we discussed here. You can even turn this problem into an ad-hoc scheduling problem.&lt;/p&gt;

&lt;p&gt;For example, you can send a message to SQS with a delivery delay matching the state machine timeout. Or create a schedule in EventBridge Scheduler to be executed when the state machine would have timed out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F04%2Fsfn-timeout-05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F04%2Fsfn-timeout-05.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In both cases, you run into the limitation that Step Functions’ &lt;code&gt;DescribeExecution&lt;/code&gt; and &lt;code&gt;ListExecutions&lt;/code&gt; APIs don’t support Express Workflows.&lt;/p&gt;

&lt;p&gt;This makes it difficult to find out if an execution timed out in the end. It’s only possible to do this by querying CloudWatch Logs. I don’t think the extra complexity and cost are worth it. So, I’d recommend using one of the three proposed solutions here instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;p&gt;[1] &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/sfn-stuck-execution.html" rel="noopener noreferrer"&gt;Use timeouts to avoid stuck executions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] &lt;a href="https://docs.aws.amazon.com/step-functions/latest/apireference/API_GetExecutionHistory.html" rel="noopener noreferrer"&gt;Step Function’s GetExecutionHistory API&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://theburningmonk.com/2024/04/how-to-handle-execution-timeouts-in-aws-step-functions/" rel="noopener noreferrer"&gt;How to handle execution timeouts in AWS Step Functions&lt;/a&gt; appeared first on &lt;a href="https://theburningmonk.com" rel="noopener noreferrer"&gt;theburningmonk.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>stepfunctions</category>
    </item>
    <item>
      <title>How to apply the TDD mindset to serverless</title>
      <dc:creator>Yan Cui</dc:creator>
      <pubDate>Tue, 09 Apr 2024 08:28:29 +0000</pubDate>
      <link>https://dev.to/theburningmonk/how-to-apply-the-tdd-mindset-to-serverless-3fhh</link>
      <guid>https://dev.to/theburningmonk/how-to-apply-the-tdd-mindset-to-serverless-3fhh</guid>
      <description>&lt;p&gt;Testing is an integral part of software development. Your tests are a living documentation of your system. They inform others how to use your system, but they are so much more than that.&lt;/p&gt;

&lt;p&gt;One of the most misunderstood parts of Test-Driven Development (TDD) is the “Driven” part of the name.&lt;/p&gt;

&lt;p&gt;It’s not just about “writing tests before you write the code”. If your tests do not inform and drive your API design, then you’re not really doing TDD.&lt;/p&gt;

&lt;p&gt;When I say “API”, I mean the general meaning of the term, not the API spec for HTTP APIs, although they are part of it. In the context of a serverless application, you have to cover multiple APIs with your tests.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For an HTTP API, there are the API contracts you agree with the caller.&lt;/li&gt;
&lt;li&gt;For an event-driven architecture, there are event contracts that everyone agrees on.&lt;/li&gt;
&lt;li&gt;For the Lambda function, there is the handler function’s signature (its input and output types). In most cases, we don’t have control over these, as the function’s event source dictates them.&lt;/li&gt;
&lt;li&gt;The code modules that are executed when a Lambda function is invoked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RTHMiMKX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/tdd-serverless-01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RTHMiMKX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/tdd-serverless-01.png" alt="" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=p3M1Y2Oi_Yk"&gt;Different types of tests&lt;/a&gt; [1] can drive the design of the different APIs above.&lt;/p&gt;

&lt;p&gt;For example, end-to-end tests exercise the system from the outside. An e2e test will exercise an HTTP API by calling its HTTP endpoints as a client would. They should tell you if your API is difficult to use and &lt;em&gt;drive&lt;/em&gt; you towards a better API design.&lt;/p&gt;

&lt;p&gt;Similarly, unit tests exercise the business logic encapsulated in the code modules. They should tell you if you have the right abstraction and modularity in place and &lt;em&gt;drive&lt;/em&gt; you towards a better code organization.&lt;/p&gt;

&lt;p&gt;To apply the TDD mindset to serverless development:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write e2e tests for your API before you even think about the Lambda functions behind those API endpoints.&lt;/li&gt;
&lt;li&gt;Use the tests to identify problems with the API design. Is data missing from the API responses? Are we asking the caller to make multiple calls when we can do everything they want in one?&lt;/li&gt;
&lt;li&gt;Iterate and improve.&lt;/li&gt;
&lt;li&gt;Once the API spec is set, you can map API endpoints to Lambda function(s).&lt;/li&gt;
&lt;li&gt;Now, it’s time to implement the Lambda function handlers.&lt;/li&gt;
&lt;li&gt;Use unit tests to test your domain logic and use them to drive your API design.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Does this look similar to what you’d do if you were building serverful applications running on containers or EC2?&lt;/p&gt;

&lt;p&gt;It should! The environment our code runs in should not influence how we can use tests to drive our design.&lt;/p&gt;

&lt;p&gt;But with serverless, we want to leverage the cloud to its full potential and delegate the heavy lifting to the cloud provider. It changes &lt;em&gt;what&lt;/em&gt; and &lt;em&gt;how much&lt;/em&gt; code we have to write and maintain. So, it changes &lt;em&gt;how&lt;/em&gt; and &lt;em&gt;what&lt;/em&gt; we need to test.&lt;/p&gt;

&lt;p&gt;For example, I’d delegate authentication and authorization to API Gateway. So, there is no authentication-related code in my Lambda function, and there’s no need for me to write unit tests for it. Instead, authentication is checked as part of my e2e tests. I might even have an e2e test to make sure that unauthorized requests are rejected by API Gateway.&lt;/p&gt;

&lt;p&gt;Similarly, most of my Lambda functions are simple and do not have complex business logic. So, there is a low return on investment (ROI) from unit tests. Instead, I focus on testing the integration with the external dependencies (such as DynamoDB tables) with “remocal tests”.&lt;/p&gt;

&lt;p&gt;I wrote about &lt;a href="https://theburningmonk.com/2022/05/my-testing-strategy-for-serverless-applications"&gt;my testing strategy for serverless applications&lt;/a&gt; [2] previously. If you want to learn more about testing serverless applications, please read that.&lt;/p&gt;

&lt;p&gt;But remember, tests are not just for catching bugs and preventing regressions. They are also living documentation for your system and a way to drive its design.&lt;/p&gt;

&lt;h1&gt;
  
  
  Links
&lt;/h1&gt;

&lt;p&gt;[1] &lt;a href="https://www.youtube.com/watch?v=p3M1Y2Oi_Yk"&gt;What is the Test Honeycomb? (and why you should care)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] &lt;a href="https://theburningmonk.com/2022/05/my-testing-strategy-for-serverless-applications"&gt;My testing strategy for serverless applications&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://theburningmonk.com/2024/04/how-to-apply-the-tdd-mindset-to-serverless/"&gt;How to apply the TDD mindset to serverless&lt;/a&gt; appeared first on &lt;a href="https://theburningmonk.com"&gt;theburningmonk.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>testing</category>
      <category>lambda</category>
    </item>
    <item>
      <title>Here are four ways you can implement WebSockets using serverless</title>
      <dc:creator>Yan Cui</dc:creator>
      <pubDate>Wed, 03 Apr 2024 00:02:18 +0000</pubDate>
      <link>https://dev.to/aws-heroes/here-are-four-ways-you-can-implement-websockets-using-serverless-50df</link>
      <guid>https://dev.to/aws-heroes/here-are-four-ways-you-can-implement-websockets-using-serverless-50df</guid>
      <description>&lt;p&gt;The myth that “you can’t do WebSockets with serverless” still persists today, even though we have some very good ways to implement WebSockets without needing to manage any servers.&lt;/p&gt;

&lt;p&gt;Part of the problem is that many still falsely equate “serverless” with Lambda. But serverless is much more than that. To me, it describes any technology that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No need to manage servers.&lt;/li&gt;
&lt;li&gt;Scale to zero.&lt;/li&gt;
&lt;li&gt;Usage-based pricing with no minimum charge.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With this in mind, API Gateway, AppSync, and IoT Core are all serverless technologies. All three let you implement WebSockets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gomomento.com/platform/topics"&gt;Momento Topics&lt;/a&gt; [1] is another good option if you are open to exploring non-AWS options.&lt;/p&gt;

&lt;h3&gt;
  
  
  API Gateway
&lt;/h3&gt;

&lt;p&gt;API Gateway WebSockets are very low-level and require a lot of work.&lt;/p&gt;

&lt;p&gt;As the application developer, you must maintain the mapping from WebSocket connection IDs to users. You do this by implementing Lambda functions that handle API Gateway’s &lt;code&gt;onConnect&lt;/code&gt; and &lt;code&gt;onDisconnect&lt;/code&gt; events.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ld2zz_Ut--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ld2zz_Ut--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-01.png" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When sending messages to a user, you must find the user’s connection ID (in your own database) and call the API Gateway Management API to send the message.&lt;/p&gt;

&lt;p&gt;Because you can only send messages &lt;strong&gt;one at a time&lt;/strong&gt; , it can be very inefficient and costly to implement group chats or broadcasts.&lt;/p&gt;

&lt;p&gt;Imagine building a sports streaming app and wanting to notify everyone watching Barcelona vs Real Madrid that a goal has gone in. If you have a million live viewers, then that translates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a million reads from your DynamoDB table&lt;/li&gt;
&lt;li&gt;a million API requests to the API Gateway Management API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of all the options here, this is my least favourite.&lt;/p&gt;

&lt;h3&gt;
  
  
  AppSync
&lt;/h3&gt;

&lt;p&gt;By comparison, AppSync subscriptions are a breeze to work with.&lt;/p&gt;

&lt;p&gt;In the GraphQL schema, you connect a &lt;code&gt;subscription&lt;/code&gt; operation (i.e. what a client can subscribe to) to a &lt;code&gt;mutation&lt;/code&gt; operation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nXlUh4Bg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nXlUh4Bg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-02.png" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it! AppSync takes care of the rest.&lt;/p&gt;

&lt;p&gt;Multiple subscribers can subscribe to the same update. When someone publishes a post, the update is automatically broadcast to all the subscribers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--a4LWocsJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a4LWocsJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-03.png" width="800" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Subscribers can filter what updates they want. To support this, you only have to add arguments to the &lt;code&gt;subscription&lt;/code&gt; operation. Here, we allow the caller to filter on &lt;code&gt;author&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;publishYear&lt;/code&gt; and &lt;code&gt;publishMonth&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6JBjYo-H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6JBjYo-H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-04.png" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you only want to receive new posts from “Cixin Liu” then you simply call&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;addedPost(author: "Cixin Liu") {
  id 
  author
  title
  content
  url
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to learn more about AppSync subscriptions, then check out &lt;a href="https://youtu.be/0G5UsNqh4ak"&gt;this explainer video&lt;/a&gt; [2].&lt;/p&gt;

&lt;h3&gt;
  
  
  IoT Core
&lt;/h3&gt;

&lt;p&gt;Despite its name, the AWS IoT Core service is not just for IoT. It’s actually just a serverless messaging service that speaks &lt;a href="https://mqtt.org/"&gt;MQTT&lt;/a&gt; [3].&lt;/p&gt;

&lt;p&gt;You can subscribe and publish messages to topics. Pretty simple.&lt;/p&gt;

&lt;p&gt;But it has some nice quality-of-life features because it’s designed with IoT in mind. For example, it can store messages if a device is offline and deliver them when it next comes online.&lt;/p&gt;

&lt;p&gt;The quickest way to understand how it works is to try it out yourself. Head over to the &lt;a href="https://eu-west-1.console.aws.amazon.com/iot/home?region=eu-west-1#/test"&gt;IoT Core console&lt;/a&gt; and click “MQTT test client”. Here, you can subscribe to a topic (e.g. “test”) and then publish a message to the topic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5x3Rx_my--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5x3Rx_my--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-05.png" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Momento Topics
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.gomomento.com/platform/topics"&gt;Momento Topics&lt;/a&gt; [1] are conceptually very simple. You can subscribe to a topic and receive and publish messages through a WebSocket connection.&lt;/p&gt;

&lt;p&gt;Allen Helton wrote a &lt;a href="https://medium.com/momento-serverless/how-we-built-momento-topics-a-serverless-websocket-service-8d79f3b9a9e3"&gt;nice article&lt;/a&gt; [4] on the architecture behind Momento Topics. I highly recommend giving it a read.&lt;/p&gt;

&lt;p&gt;The innovation that Momento Topics brings to the table is its pricing structure.&lt;/p&gt;

&lt;p&gt;AppSync, API Gateway and IoT Core all charge based on a combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Number of messages sent and received through WebSockets.&lt;/li&gt;
&lt;li&gt;Connection time.&lt;/li&gt;
&lt;li&gt;Standard EC2 data transfer cost. Which is code for &lt;a href="https://www.lastweekinaws.com/blog/understanding-data-transfer-in-aws/"&gt;“it’s complicated”&lt;/a&gt; [5].&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Momento, on the other hand, offers a &lt;strong&gt;pay-per-use&lt;/strong&gt; pricing model. Even if you have millions of connected users, you don’t pay for the connections if they are idle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SdLuXY82--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SdLuXY82--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-06.png" width="800" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This offers a different cost dimension that we can use to align with our business model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thefrugalarchitect.com/"&gt;The Frugal Architect&lt;/a&gt; [6] Law 2 says that our architecture’s cost should grow along the same dimension as how the business would make money.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aRHJbAfn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aRHJbAfn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-07.png" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In other words, if your business revenue grows with user engagement, then so should your architecture’s cost. If millions of connected but inactive users don’t generate revenue for your business, then connection time shouldn’t be the driver of your cost either.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gBeKURBd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gBeKURBd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-08.png" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparison
&lt;/h3&gt;

&lt;p&gt;Of the four options we discussed here, API Gateway WebSockets requires the most effort to implement. Again, it’s a low-level construct, and you must work with connections and map them to users.&lt;/p&gt;

&lt;p&gt;API Gateway also doesn’t support broadcasts. You can only send one message at a time.&lt;/p&gt;

&lt;p&gt;However, it’s a two-way (i.e. duplex) connection. The client can send and receive messages through the WebSocket connection. It’s also a vanilla WebSockets connection, and you can develop your own application protocol on top of it.&lt;/p&gt;

&lt;p&gt;With AppSync and IoT Core, your application must speak GraphQL and MQTT, respectively. This can be a barrier to entry because it requires additional application dependencies, and you need to understand how these protocols work.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Security&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Regarding security, all four options provide some form of authentication and authorization mechanism.&lt;/p&gt;

&lt;p&gt;With API Gateway, you can use AWS IAM or a Lambda authorizer to protect the WebSocket API. Once connected, you can decide what data is sent to the user and validate any messages received from the user.&lt;/p&gt;

&lt;p&gt;You have a lot of control here but also a lot of responsibilities!&lt;/p&gt;

&lt;p&gt;With AppSync, you can use all the &lt;a href="https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html"&gt;available authentication and authorization mechanisms&lt;/a&gt; [7] to protect the &lt;code&gt;Subscription&lt;/code&gt; endpoint. This controls who can subscribe for updates.&lt;/p&gt;

&lt;p&gt;Additionally, you can use a resolver template to &lt;a href="https://medium.com/@kemalahmed/authorized-appsync-subscriptions-91df3a7a2f5"&gt;control who can subscribe to which updates&lt;/a&gt; [8]. For example, to stop a user from subscribing to notifications intended for another user.&lt;/p&gt;

&lt;p&gt;With MQTT and Momento Topics, you can control who can access which topic. To ensure a user can’t subscribe to other users’ updates, you should scope a user’s permission to just his/her topic. Luckily, in both cases, topics are a virtual construct and do not need to be explicitly created before use. So there’s no limit on how many topics you can have.&lt;/p&gt;

&lt;p&gt;With all this in mind, here’s a summary of how these services stack up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sKVPTzxb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sKVPTzxb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/04/serverless-websockets-09.png" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key takeaway is that you have several options for implementing WebSockets serverlessly. Each offers a slightly different set of trade-offs. And that’s great place to be.&lt;/p&gt;

&lt;p&gt;If you want to learn more about building serverless applications, then check out my &lt;a href="https://productionreadyserverless.com/?utm_campaign=four-ways-serverless-websocket&amp;amp;utm_source=devto&amp;amp;utm_medium=text"&gt;upcoming workshop&lt;/a&gt; [9] and get &lt;strong&gt;30% OFF&lt;/strong&gt; with our early bird tickets (available until April 26th 2024).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://productionreadyserverless.com/?utm_campaign=four-ways-serverless-websocket&amp;amp;utm_source=devto&amp;amp;utm_medium=image"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PC5irUPm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2023/12/prsls-in-4-weeks.jpeg" width="512" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;p&gt;[1] &lt;a href="https://www.gomomento.com/platform/topics"&gt;Momento Topics&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] &lt;a href="https://www.youtube.com/watch?v=0G5UsNqh4ak"&gt;What are AppSync Subscriptions?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[3] &lt;a href="https://mqtt.org/"&gt;The MQTT specification&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[4] &lt;a href="https://medium.com/momento-serverless/how-we-built-momento-topics-a-serverless-websocket-service-8d79f3b9a9e3"&gt;How we built Momento Topics, a serverless WebSocket service&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[5] &lt;a href="https://www.lastweekinaws.com/blog/understanding-data-transfer-in-aws/"&gt;Understanding data transfer cost in AWS&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[6] &lt;a href="https://thefrugalarchitect.com/"&gt;The Frugal Architect&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[7] &lt;a href="https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html"&gt;AppSync authentication and authorization&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[8] &lt;a href="https://medium.com/@kemalahmed/authorized-appsync-subscriptions-91df3a7a2f5"&gt;Authorized AppSync Subscriptions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[9] &lt;a href="https://productionreadyserverless.com/?utm_campaign=four-ways-serverless-websocket&amp;amp;utm_source=devto&amp;amp;utm_medium=links"&gt;Production-Ready Serverless workshop&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://theburningmonk.com/2024/04/here-are-four-ways-you-can-implement-websockets-using-serverless/"&gt;Here are four ways you can implement WebSockets using serverless&lt;/a&gt; appeared first on &lt;a href="https://theburningmonk.com"&gt;theburningmonk.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>apigateway</category>
      <category>appsync</category>
      <category>aws</category>
      <category>iotcore</category>
    </item>
    <item>
      <title>DynamoDB now supports resource-based policies. But is that a good idea?</title>
      <dc:creator>Yan Cui</dc:creator>
      <pubDate>Sat, 23 Mar 2024 13:04:26 +0000</pubDate>
      <link>https://dev.to/aws-heroes/dynamodb-now-supports-resource-based-policies-but-is-that-a-good-idea-14en</link>
      <guid>https://dev.to/aws-heroes/dynamodb-now-supports-resource-based-policies-but-is-that-a-good-idea-14en</guid>
      <description>&lt;p&gt;DynamoDB &lt;a href="https://aws.amazon.com/about-aws/whats-new/2024/03/amazon-dynamodb-resource-based-policies/"&gt;announced support for resource-based policies&lt;/a&gt; [1] a few days ago. It makes cross-account access to DynamoDB tables easier. You no longer need to assume an IAM role in the target table’s account.&lt;/p&gt;

&lt;p&gt;I was confused by this update and &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7176350550681137156/"&gt;wondered if it was even a good idea&lt;/a&gt;. If you need cross-account access to DynamoDB, then it’s surely a sign you’re breaking service boundaries, right?&lt;/p&gt;

&lt;p&gt;As I &lt;a href="https://www.linkedin.com/posts/theburningmonk_aws-serverless-microservices-activity-7167428003239583745-_p3h?utm_source=share&amp;amp;utm_medium=member_desktop"&gt;said before&lt;/a&gt; [2], a microservice should own its data and shouldn’t share a database with another microservice.&lt;/p&gt;

&lt;p&gt;In many organizations, microservices run in their own accounts. This provides another layer of insulation between services. It can also compartmentalize security breaches and problems related to AWS limits.&lt;/p&gt;

&lt;p&gt;When a service needs to access another service’s data, it should go through its API. It shouldn’t reach in and help itself to their data. It’s insecure and creates a hidden coupling between the two services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KGz3V511--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/img_65fed2b4b9520-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KGz3V511--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/img_65fed2b4b9520-1.png" alt="" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is not how you should use the new resource-based policies!&lt;/p&gt;

&lt;p&gt;However, there are legitimate use cases where cross-account access can be helpful. I want to thank everyone who shared their thoughts and experiences with me.&lt;/p&gt;

&lt;h3&gt;
  
  
  ETL / data pipelines
&lt;/h3&gt;

&lt;p&gt;ETL jobs and data pipelines often need to read data from multiple sources or subscribe to DynamoDB streams to capture live data change events.&lt;/p&gt;

&lt;p&gt;These require cross-account access to DynamoDB tables, and the new resource-based policies make them easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration to new service/account
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://www.youtube.com/watch?v=-HQw-UK2bHc"&gt;this video&lt;/a&gt; [3], I discussed using the Strangler Pattern to migrate an existing monolith to serverless. The new service must still access the old database as part of the transitional phase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q3I3ZjEW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/ddb-resources-02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q3I3ZjEW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/ddb-resources-02.png" alt="" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is when you will need cross-account access to the original DynamoDB table. Once your new service owns all the data access patterns, you can safely migrate existing data to a new table in the new account. See &lt;a href="https://dev.to/aws-heroes/how-to-perform-database-migration-for-a-live-service-with-no-downtime-5972"&gt;this post&lt;/a&gt; [4] to see how you can do this without downtime.&lt;/p&gt;

&lt;p&gt;Once the migration is complete, you can drop the cross-account access.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WHMXSSrw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/ddb-resources-03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WHMXSSrw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/ddb-resources-03.png" alt="" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are other cases when this type of transitional need arises.&lt;/p&gt;

&lt;p&gt;For example, when moving from a single-account strategy to a multi-account strategy.&lt;/p&gt;

&lt;p&gt;In that scenario, you’d create a new instance of the service in a new account. During the transition period, the new account must access data in the old account until the data is migrated to the new account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Cz68DPUh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/ddb-resources-04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Cz68DPUh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/ddb-resources-04.png" alt="" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Another layer of access control
&lt;/h3&gt;

&lt;p&gt;Resource-based policies give InfoSec teams more control.&lt;/p&gt;

&lt;p&gt;Application teams can use identity-based policies to control data access, and InfoSec teams can use resource-based policies to add another layer of control and governance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kVsyUCoc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/ddb-resources-05.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kVsyUCoc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/ddb-resources-05.jpg" alt="" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is important in regulated environments that handle sensitive user information. For example, developers need read-only permissions to debug problems in production. However, InfoSec can use resource-based policies to deny access to DynamoDB tables by IAM users.&lt;/p&gt;

&lt;p&gt;This can be applied selectively to only the tables holding sensitive user information. You can also carve out exceptions, such as allowing an “emergency breakglass” role to modify table settings (e.g. provisioned throughput settings) without granting data access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sharing data with external parties
&lt;/h3&gt;

&lt;p&gt;In rare situations, you might need to share data with external vendors or partners. Resource-based policies make it easier to control who can access what in your account.&lt;/p&gt;

&lt;p&gt;Instead of keeping track of the different IAM roles the partners have to assume, you can see all the policies in one place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-account global table
&lt;/h3&gt;

&lt;p&gt;Several Amazon employees mentioned a common architectural pattern within Amazon. A global service would deploy to different accounts – one region per account.&lt;/p&gt;

&lt;p&gt;DynamoDB global tables do not support cross-account replication, so the application must replicate data changes across regions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ehg6wYqJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/ddb-resources-06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ehg6wYqJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/ddb-resources-06.png" alt="" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Understandably, this is a rather painful exercise. Resource-based policies significantly simplify this process.&lt;/p&gt;

&lt;p&gt;This is an interesting architectural pattern. It’s not something that I have seen myself. All the global services I have seen or worked on would deploy to multiple regions in the same account.&lt;/p&gt;

&lt;p&gt;From a security point of view, I can understand the rationale. If an account is compromised, an attacker will likely gain access to other regions. An account boundary offers stronger isolation.&lt;/p&gt;

&lt;p&gt;Internal tooling likely played a role as well. This pattern is probably ingrained in how internal automation platforms work within Amazon.&lt;/p&gt;

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

&lt;p&gt;So, DynamoDB finally supports resource-based policies.&lt;/p&gt;

&lt;p&gt;While it simplifies cross-account access to DynamoDB tables, eliminating the need to assume IAM roles. It still begs the question: “Is cross-account data access even a good idea?”&lt;/p&gt;

&lt;p&gt;Suggestions of a “central data account” are misguided. Placing an account boundary between a service and its data is a bad idea. It will introduce unnecessary complexity to IaC and CI/CD and increase the likelihood of hitting service limits. It also erodes service boundaries and the autonomy of a team to manage and own its services.&lt;/p&gt;

&lt;p&gt;It’s also a single point of failure from a security perspective. A compromised account would give the attacker access to all of your data!&lt;/p&gt;

&lt;p&gt;Despite these concerns, several valid use cases will benefit from this, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ETL jobs or data pipelines.&lt;/li&gt;
&lt;li&gt;The transitional phase of an account migration process.&lt;/li&gt;
&lt;li&gt;InfoSec teams looking for strong data governance.&lt;/li&gt;
&lt;li&gt;When you need to share data with external parties.&lt;/li&gt;
&lt;li&gt;When you require multi-account global tables.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will leave you with this: cross-account access to DynamoDB tables is &lt;em&gt;almost&lt;/em&gt; always a smell. But as with everything, there are exceptions and edge cases. You should think carefully before you use resource-based policies to enable cross-account access to your DynamoDB tables.&lt;/p&gt;

&lt;p&gt;Because exceptions are just that, &lt;em&gt;exceptions&lt;/em&gt;. Most of you (myself included) are the rule, not the exception.&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;p&gt;[1] &lt;a href="https://aws.amazon.com/about-aws/whats-new/2024/03/amazon-dynamodb-resource-based-policies/"&gt;DynamoDB now supports resource-based policies&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] &lt;a href="https://www.linkedin.com/posts/theburningmonk_aws-serverless-microservices-activity-7167428003239583745-_p3h/?utm_source=share&amp;amp;utm_medium=member_desktop"&gt;My thoughts on the microservices vs. serverless false dichotomy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[3] &lt;a href="https://www.youtube.com/watch?v=-HQw-UK2bHc"&gt;Migrating existing monolith to serverless in 8 steps&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[4] &lt;a href="https://dev.to/aws-heroes/how-to-perform-database-migration-for-a-live-service-with-no-downtime-5972"&gt;How to perform database migration for a live service with no downtime&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://theburningmonk.com/2024/03/dynamodb-now-supports-resource-based-policies-but-is-that-a-good-idea/"&gt;DynamoDB now supports resource-based policies. But is that a good idea?&lt;/a&gt; appeared first on &lt;a href="https://theburningmonk.com"&gt;theburningmonk.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>dynamodb</category>
      <category>security</category>
      <category>architecture</category>
    </item>
    <item>
      <title>When to use Step Functions vs. doing it all in a Lambda function</title>
      <dc:creator>Yan Cui</dc:creator>
      <pubDate>Sun, 10 Mar 2024 21:12:42 +0000</pubDate>
      <link>https://dev.to/aws-heroes/when-to-use-step-functions-vs-doing-it-all-in-a-lambda-function-10fm</link>
      <guid>https://dev.to/aws-heroes/when-to-use-step-functions-vs-doing-it-all-in-a-lambda-function-10fm</guid>
      <description>&lt;p&gt;I’m a big fan of AWS Step Functions. I use it to orchestrate all sorts of workflows, from payment processing to map-reduce jobs.&lt;/p&gt;

&lt;p&gt;Why it’s yet another AWS service you need to learn and pay for. And it introduces additional complexities, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://youtu.be/x7RZl5Zms8A"&gt;It’s hard to test&lt;/a&gt; [1].&lt;/li&gt;
&lt;li&gt;Your business logic is split between configuration and code.&lt;/li&gt;
&lt;li&gt;New decision points. Such as &lt;a href="https://youtu.be/BTLQjUb2EPk"&gt;whether to use Express Workflows or Standard Workflows&lt;/a&gt; [2].&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So it’s fair to ask “&lt;em&gt;Why should we even bother with Step Functions?&lt;/em&gt;” when you can do all the orchestration in code, inside a Lambda function.&lt;/p&gt;

&lt;p&gt;Let’s break it down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda pros
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Doing all the orchestration in code is simpler. It’s more familiar.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything you can do in Step Functions, you can do with just a few lines of code. Case in point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports.handler = async (event) =&amp;gt; {
    // error handling
    try {
        await doX()
    } catch (err) {
        // handle errors
    }

    // branching logic
    if (condition === true) {
        await doSomething()
    } else {
        await doSomethingElse()
    }

    // parallelism
    const promises = event.input.map(x =&amp;gt; doY())
    const results = await Promise.all(promises)

    return results
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;It’s &lt;em&gt;likely&lt;/em&gt; cheaper.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A Step Functions state machine would likely use Lambda for its &lt;code&gt;Task&lt;/code&gt; states.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---dz6DgcV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/img_65ee20b722289.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---dz6DgcV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/img_65ee20b722289.png" width="800" height="957"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In which case, you’d end up paying for both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Lambda invocations. There are multiple invocations instead of just one. However, the total billable execution time should be similar.&lt;/li&gt;
&lt;li&gt;Step Functions state transitions. At $25 per million state transitions, it’s one of the more expensive services in AWS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Paying for two services is likely more expensive than paying for just one.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It’s &lt;em&gt;likely&lt;/em&gt; more scalable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When you use both Step Functions and Lambda functions (for the &lt;code&gt;Task&lt;/code&gt; states) you are constrained by the throughput limits of both services.&lt;/p&gt;

&lt;p&gt;Step Functions Standard Workflows have modest limits on the no. of state transitions and the no. of executions you can start per second.&lt;/p&gt;

&lt;p&gt;Both of these limits can be raised. So with proper planning, they wouldn’t be an issue.&lt;/p&gt;

&lt;p&gt;Without Step Functions, you are limited only by the concurrent executions limit on Lambda. Similarly, Lambda has default throughput limits on the no. of concurrent executions.&lt;/p&gt;

&lt;p&gt;Again, with proper planning, and given the &lt;a href="https://aws.amazon.com/about-aws/whats-new/2023/12/aws-lambda-functions-scale-up/"&gt;recent scaling changes for Lambda&lt;/a&gt; [3], you will be OK.&lt;/p&gt;

&lt;p&gt;Both the cost and scalability arguments are situational and depend on several architectural choices.&lt;/p&gt;

&lt;p&gt;E.g. do you use Standard or Express workflows?&lt;/p&gt;

&lt;p&gt;Do you use Lambda functions for &lt;code&gt;Task&lt;/code&gt; states or direct service integrations?&lt;/p&gt;

&lt;p&gt;Have you estimated your throughput needs and raised the soft limits accordingly?&lt;/p&gt;

&lt;p&gt;Because of these factors, they are only “likely” to be true based on what I think the average AWS customer is capable of.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Cold starts.&lt;/li&gt;
&lt;li&gt;15 mins max duration.&lt;/li&gt;
&lt;li&gt;Not good for waiting. Because you pay for execution time by the milliseconds. It’s a poor solution when it comes to waiting for something to happen because all that execution time (and money) is wasted.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step Functions pros
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The visual workflows make it very easy to debug problems. This is true even for non-technical team members, such as product owners and customer support teams.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BZU9OWKh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/img_65ee21453758c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BZU9OWKh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/img_65ee21453758c.png" width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Step Functions has a built-in audit history of everything that happened, including:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;When did a state start?&lt;/li&gt;
&lt;li&gt;What were the input and output?&lt;/li&gt;
&lt;li&gt;Error message and stack trace.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Step Functions have direct service integration with almost every AWS service. So it’s possible to implement an entire workflow without needing any Lambda functions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No Lambda, no cold starts. No cold starts = more predictable performance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Long execution time. A Standard Workflow can run for up to a year.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Callback patterns are a great way to support human decisions (e.g. approve a deployment request) in a workflow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Standard Workflows are arguably the most cost-efficient way to wait. Because you don’t pay for the duration, only the state transition.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can implement more robust error handling. This is important for business-critical workflows.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To make a workflow more robust, you need to have both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retries. Preferably with exponential backoff and random jitters.&lt;/li&gt;
&lt;li&gt;A fallback or error handling logic. In case an operation fails after a reasonable number of retries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Lambda, this puts you between two opposing forces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Security best practices recommend short timeouts. This mitigates the impact of denial-of-wallet attacks.&lt;/li&gt;
&lt;li&gt;We need a long timeout to cater for the worst-case scenario. We need to allow enough time for the retries and the fallback. This means the timeout needs to be many times the expected execution time on a happy path. Oh, and you might also need retries in your fallback!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D3Hc3cXx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/img_65ee219eb63b2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D3Hc3cXx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/img_65ee219eb63b2.png" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s difficult to guess the right timeout in these situations. Especially when your workflow might have different branches. And if you get it wrong, then your workflow would be killed off halfway and there’s no easy way to restart from the point of failure.&lt;/p&gt;

&lt;p&gt;By lifting the error handling and retries out of your code and into the state machine itself, you alleviate this tension.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Step Functions lets you &lt;a href="https://aws.amazon.com/blogs/compute/introducing-aws-step-functions-redrive-a-new-way-to-restart-workflows/"&gt;resume an execution from that point of failure&lt;/a&gt; [4].&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step Functions cons
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Cost.&lt;/li&gt;
&lt;li&gt;Learning curve.&lt;/li&gt;
&lt;li&gt;More complexity. There are a lot more things to configure and manage.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your business logic is split between the State Machine definition and your code (if using Lambda for &lt;code&gt;Task&lt;/code&gt; states).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hard to test. However, this is &lt;a href="https://lumigo.io/blog/does-step-functions-new-teststate-api-make-end-to-end-tests-obsolete/"&gt;getting easier with the new TestState API&lt;/a&gt; [5].&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;In conclusion, Step Functions offer a plethora of capabilities. But they come with their own set of complexities and costs.&lt;/p&gt;

&lt;p&gt;Whether it’s right for you depends on the demands of your use case.&lt;/p&gt;

&lt;p&gt;Generally, I advocate for the path of least resistance: simple workflows call for simple solutions. And Lambda functions excel in these scenarios.&lt;/p&gt;

&lt;p&gt;However, for more workloads, Step Functions can simplify the implementation with its built-in functionalities that would otherwise require custom solutions.&lt;/p&gt;

&lt;p&gt;For example, when you need to incorporate human decisions into a workflow. You should use Step Functions and leverage its callback patterns instead of creating a bespoke solution.&lt;/p&gt;

&lt;p&gt;Similarly, for workflows where resuming from a point of failure is important, you should go with Step Functions.&lt;/p&gt;

&lt;p&gt;Personally, I heavily lean towards Step Functions for business-critical workflows. The advantages of visualization, audit trails, and robust error handling align with the high stakes involved. These workflows, like payment processing, warrant the extra investment in Step Functions due to their critical nature.&lt;/p&gt;

&lt;p&gt;I will leave you with this diagram with the pros &amp;amp; cons captured in one place. To learn more about testing Step Function, check out my course, &lt;a href="https://testserverlessapps.com/?utm_campaign=lambda-vs-step-functions&amp;amp;utm_source=blog"&gt;&lt;strong&gt;Testing Serverless Architectures&lt;/strong&gt;&lt;/a&gt; [6]. I have a whole chapter dedicated to Step Functions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b_asTJsG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/img_65ee220ef1642.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b_asTJsG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/img_65ee220ef1642.png" width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;[1] &lt;a href="https://youtu.be/x7RZl5Zms8A"&gt;Why is Step Functions so hard to test?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] &lt;a href="https://youtu.be/BTLQjUb2EPk"&gt;What is AWS Step Functions? An in-depth overview.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[3] &lt;a href="https://aws.amazon.com/about-aws/whats-new/2023/12/aws-lambda-functions-scale-up/"&gt;AWS Lambda functions now scale up to 12X faster&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[4] &lt;a href="https://aws.amazon.com/blogs/compute/introducing-aws-step-functions-redrive-a-new-way-to-restart-workflows/"&gt;Introducing AWS Step Functions redrive to recover from failures more easily&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[5] &lt;a href="https://lumigo.io/blog/does-step-functions-new-teststate-api-make-end-to-end-tests-obsolete?utm_source=yan"&gt;Does Step Function’s new TestState API make end-to-end tests obsolete?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[6] &lt;a href="https://testserverlessapps.com/?utm_campaign=lambda-vs-step-functions&amp;amp;utm_source=blog"&gt;Testing Serverless Architectures course&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://theburningmonk.com/2024/03/when-to-use-step-functions-vs-doing-it-all-in-a-lambda-function/"&gt;When to use Step Functions vs. doing it all in a Lambda function&lt;/a&gt; appeared first on &lt;a href="https://theburningmonk.com"&gt;theburningmonk.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>serverless</category>
      <category>stepfunctions</category>
    </item>
    <item>
      <title>When to use API Gateway vs. Lambda Function URLs</title>
      <dc:creator>Yan Cui</dc:creator>
      <pubDate>Sun, 03 Mar 2024 23:06:11 +0000</pubDate>
      <link>https://dev.to/theburningmonk/when-to-use-api-gateway-vs-lambda-function-urls-3c82</link>
      <guid>https://dev.to/theburningmonk/when-to-use-api-gateway-vs-lambda-function-urls-3c82</guid>
      <description>&lt;p&gt;“Lambdalith” is a monolithic approach to building serverless applications where a single Lambda function serves an entire API, instead of one function per endpoint.&lt;/p&gt;

&lt;p&gt;It’s an increasingly popular approach.&lt;/p&gt;

&lt;p&gt;It provides portability between Lambda functions and container applications. You can lift and shift an existing application into Lambda without rewriting it. You can use web frameworks you are already familiar with, and lean on the existing ecosystems of tools, ORMs and middleware. It also makes testing easier, because you can apply familiar testing methodologies.&lt;/p&gt;

&lt;p&gt;Tools like the &lt;a href="https://github.com/awslabs/aws-lambda-web-adapter"&gt;AWS Lambda Web Adapter&lt;/a&gt; [1] have made this approach more accessible. In addition, &lt;a href="https://lumigo.io/blog/aws-lambda-function-url-is-live/"&gt;Lambda Function URLs&lt;/a&gt; [2] also work well with this pattern.&lt;/p&gt;

&lt;p&gt;Which brings up a good question.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“In 2024, if you want to build a REST API using serverless technologies. Should you use Lambda Function URLs or API Gateway?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here are some trade-offs you should consider when making this decision.&lt;/p&gt;

&lt;h3&gt;
  
  
  Function URL pros
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;It works naturally with Lambdaliths.&lt;/li&gt;
&lt;li&gt;No latency overhead from API Gateway.&lt;/li&gt;
&lt;li&gt;No API Gateway-related costs.&lt;/li&gt;
&lt;li&gt;It supports &lt;a href="https://aws.amazon.com/blogs/compute/introducing-aws-lambda-response-streaming/"&gt;response streaming&lt;/a&gt; [3]. This is useful for returning large payloads in the HTTP response.&lt;/li&gt;
&lt;li&gt;There are fewer things to configure.&lt;/li&gt;
&lt;li&gt;Your API can run for up to 15 mins.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Function URL cons
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;You have to use Lambdaliths and deal with all the shortcomings that come with that.&lt;/li&gt;
&lt;li&gt;No per-endpoint metrics. It’s possible to make up for this by implementing custom metrics with &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html"&gt;embedded metric format&lt;/a&gt; (EMF) [4].&lt;/li&gt;
&lt;li&gt;No direct integration with WAF. Although this is possible through CloudFront, the original function URL would still be unprotected.&lt;/li&gt;
&lt;li&gt;It only supports AWS_IAM auth.&lt;/li&gt;
&lt;li&gt;You cannot configure different auth methods per endpoint.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Where Function URL makes sense
&lt;/h3&gt;

&lt;p&gt;If you want to (i.e. not forced into it by the choice to use Function URL) build a Lambdalith and you don’t need any of the additional features that API Gateway offers. Then Function URLs make sense?—?it’s cheaper, faster and has fewer moving parts.&lt;/p&gt;

&lt;p&gt;Similarly, if you need to return a large payload (&amp;gt; 10MB) or to run for more than 29s, then Function URL can also make sense. That is if you can’t refactor the client-server interaction.&lt;/p&gt;

&lt;p&gt;Given the limited support for authentication &amp;amp; authorization, it’s not suitable for user-facing APIs. These APIs often require a Cognito authorizer or a custom Lambda authorizer.&lt;/p&gt;

&lt;p&gt;This leaves function URLs as best suited for public APIs or internal APIs inside a microservices architecture.&lt;/p&gt;

&lt;p&gt;By “internal API”, I refer to APIs that are used by other services but not by the frontend application directly. These APIs usually require AWS_IAM auth because the caller is another AWS resource?—?a Lambda function, an EC2 instance, an ECS task, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  API Gateway pros
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;It’s more flexible. It works with both Lambdaliths and the one function per endpoint approach.&lt;/li&gt;
&lt;li&gt;It has direct integration with most AWS services. So in many cases, you don’t even need a Lambda function. If you’re curious about when and why you should consider this, check out &lt;a href="https://www.youtube.com/watch?v=_NnO2JiPTEw"&gt;this video&lt;/a&gt; [5] where I explain the rationale behind service proxies.&lt;/li&gt;
&lt;li&gt;It can proxy requests to any HTTP APIs. This is very useful for integrating with 3rd party APIs.&lt;/li&gt;
&lt;li&gt;It offers lots more features, including (but limited to) the following:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;

&lt;ul&gt;
&lt;li&gt;cognito authorizer&lt;/li&gt;
&lt;li&gt;usage plans (great for SAAS applications that offer tiered pricing)&lt;/li&gt;
&lt;li&gt;built-in request validation with request models&lt;/li&gt;
&lt;li&gt;detailed per-endpoint metrics&lt;/li&gt;
&lt;li&gt;mock endpoints (useful for endpoints that return static data)&lt;/li&gt;
&lt;li&gt;request and response transformation (also useful for integrating with 3rd party APIs)&lt;/li&gt;
&lt;li&gt;WebSockets support&lt;/li&gt;
&lt;li&gt;and lots more…&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  API Gateway cons
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Additional latency overhead.&lt;/li&gt;
&lt;li&gt;Additional cost.&lt;/li&gt;
&lt;li&gt;No response streaming.&lt;/li&gt;
&lt;li&gt;29s integration limit.&lt;/li&gt;
&lt;li&gt;10MB response limit.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  When API Gateway makes sense
&lt;/h3&gt;

&lt;p&gt;Given the vast array of features that API Gateway offers, it makes sense in most cases if you’re OK with the additional cost that comes with the convenience.&lt;/p&gt;

&lt;p&gt;The 29s and 10MB response limits can be problematic in some cases.&lt;/p&gt;

&lt;p&gt;But they can be mitigated with patterns such as &lt;a href="https://theburningmonk.medium.com/applying-the-decoupled-invocation-pattern-with-aws-lambda-2f5f7e78d18"&gt;Decoupled Invocations&lt;/a&gt; [6] and &lt;a href="https://dev.to/theburningmonk/hit-the-6mb-lambda-payload-limit-here-s-what-you-can-do-2ea2"&gt;S3 presigned URLs&lt;/a&gt; [7]. However, these workarounds require you to refactor the client-server interaction, so they are not always viable.&lt;/p&gt;

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

&lt;p&gt;Because of its flexibility, I prefer API Gateway over Function URLs or ALBs. But Function URL is a useful tool, especially when cost and performance are your primary concerns.&lt;/p&gt;

&lt;p&gt;It’s also an important lift-and-shift option for people migrating from an existing EC2 or container-based application. It lets them enjoy the benefits of Lambda without rewriting their application.&lt;/p&gt;

&lt;p&gt;Finally, as Ben eloquently &lt;a href="https://twitter.com/ben11kehoe/status/1764252904519553352"&gt;puts it&lt;/a&gt;, this tradeoff represents a deeper choice we often face.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--29_Jxs6R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/img_65e5022de8084.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--29_Jxs6R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://theburningmonk.com/wp-content/uploads/2024/03/img_65e5022de8084.png" alt="" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What’s easier at author time might mean more ownership at runtime. For example, we don’t get per-endpoint metrics from function URLs so we have to bring that capability to the table ourselves and be responsible for them.&lt;/p&gt;

&lt;p&gt;Something for you to think about ;-)&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;p&gt;[1] &lt;a href="https://github.com/awslabs/aws-lambda-web-adapter"&gt;AWS Lambda Web Adapter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] &lt;a href="https://lumigo.io/blog/aws-lambda-function-url-is-live/"&gt;AWS Lambda: function URL is live!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[3] &lt;a href="https://aws.amazon.com/blogs/compute/introducing-aws-lambda-response-streaming/"&gt;Introducing AWS Lambda response streaming&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[4] &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html"&gt;Embedded metric format specification&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[5] &lt;a href="https://www.youtube.com/watch?v=_NnO2JiPTEw"&gt;API Gateway: Why you should use Service Proxies&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[6] &lt;a href="https://theburningmonk.medium.com/applying-the-decoupled-invocation-pattern-with-aws-lambda-2f5f7e78d18"&gt;How to use the Decoupled Invocation pattern with AWS Lambda and serverless&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[7] &lt;a href="https://dev.to/theburningmonk/hit-the-6mb-lambda-payload-limit-here-s-what-you-can-do-2ea2"&gt;Hit the 6MB Lambda payload limit? Here’s what you can do&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://theburningmonk.com/2024/03/when-to-use-api-gateway-vs-lambda-function-urls/"&gt;When to use API Gateway vs. Lambda Function URLs&lt;/a&gt; appeared first on &lt;a href="https://theburningmonk.com"&gt;theburningmonk.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>apigateway</category>
      <category>aws</category>
      <category>lambda</category>
      <category>serverless</category>
    </item>
    <item>
      <title>First impressions of the fastest JavaScript runtime for Lambda</title>
      <dc:creator>Yan Cui</dc:creator>
      <pubDate>Tue, 27 Feb 2024 12:27:10 +0000</pubDate>
      <link>https://dev.to/aws-heroes/first-impressions-of-the-fastest-javascript-runtime-for-lambda-3km5</link>
      <guid>https://dev.to/aws-heroes/first-impressions-of-the-fastest-javascript-runtime-for-lambda-3km5</guid>
      <description>&lt;p&gt;I thought Lambda needed a specialised runtime. One that works well with its resource-constraint execution environment. I even &lt;a href="https://twitter.com/theburningmonk/status/1202290912853757952" rel="noopener noreferrer"&gt;floated a few ideas&lt;/a&gt; in the past but sadly I don’t have the chops to make them happen myself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/theburningmonk/status/1202290912853757952" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fimg_65ddce8289e7b.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I was pleasantly surprised when AWS open-sourced the &lt;a href="https://github.com/awslabs/llrt" rel="noopener noreferrer"&gt;LLRT runtime for JavaScript&lt;/a&gt; [1]!&lt;/p&gt;

&lt;h3&gt;
  
  
  What is LLRT?
&lt;/h3&gt;

&lt;p&gt;LLRT, or Low Latency Runtime, is a new and experimental JavaScript runtime for Lambda. It promises 10x faster startup time. Which should significantly help with the dreaded Lambda cold starts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fimg_65ddcf24e3085.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fimg_65ddcf24e3085.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Naturally, I had to test it out for myself and see if the hype was real.&lt;/p&gt;

&lt;h3&gt;
  
  
  My first impressions of LLRT
&lt;/h3&gt;

&lt;p&gt;In my limited tests, the &lt;code&gt;init duration&lt;/code&gt; for a simple function went from ~750ms to 55ms. Which was very impressive! The function under test only uses the DynamoDB client from the AWS SDK v3 and makes one request to DynamoDB.&lt;/p&gt;

&lt;p&gt;Along the way, I also discovered several limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It only supports ESM modules.&lt;/li&gt;
&lt;li&gt;It doesn’t work with the popular &lt;a href="https://github.com/middyjs/middy" rel="noopener noreferrer"&gt;middy&lt;/a&gt; [2] middelware engine. Because LLRT hasn’t implemented the &lt;code&gt;node:stream&lt;/code&gt; API. See the &lt;a href="https://github.com/awslabs/llrt/blob/main/API.md" rel="noopener noreferrer"&gt;API compatibility&lt;/a&gt; [3] page for the list of supported APIs.&lt;/li&gt;
&lt;li&gt;It doesn’t work with the &lt;a href="https://docs.powertools.aws.dev/lambda/typescript/latest/" rel="noopener noreferrer"&gt;Lambda Powertools&lt;/a&gt; [4]. Because the LLRT doesn’t allow importing the &lt;code&gt;node:console&lt;/code&gt; method in userland. See Andrea Amorosi’s response &lt;a href="https://twitter.com/dreamorosi/status/1760575292937015431" rel="noopener noreferrer"&gt;here&lt;/a&gt; for more details.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LLRT is not ready for prime time yet. And given that it’s been in the works for almost two years, we should set expectations accordingly.&lt;/p&gt;

&lt;p&gt;It’s probably not going to be production-ready in the coming months. But the potential is there and I’m really excited about it!&lt;/p&gt;

&lt;p&gt;For one thing, it might finally stop people talking about cold starts. As I &lt;a href="https://www.linkedin.com/posts/theburningmonk_aws-serverless-awslambda-activity-7167986688810541056-nwK7/?utm_source=share&amp;amp;utm_medium=member_desktop" rel="noopener noreferrer"&gt;said on LinkedIn&lt;/a&gt; [5] yesterday, most people are overthinking about Lambda cold starts. If you’re not sure if Lambda cold starts is likely a problem for you, then go read the LinkedIn post and come back here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fimg_65ddd02f5d8b6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fimg_65ddd02f5d8b6.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Back to LLRT.&lt;/p&gt;

&lt;p&gt;I later spoke with &lt;a href="https://www.linkedin.com/in/richard-davison-992462101/" rel="noopener noreferrer"&gt;Richard Davison&lt;/a&gt;, the creator of the LLRT project.&lt;/p&gt;

&lt;p&gt;I wanted to learn more about the project and the design decisions behind it.&lt;/p&gt;

&lt;p&gt;What made it start so much faster?&lt;/p&gt;

&lt;p&gt;What trade-offs did they make?&lt;/p&gt;

&lt;p&gt;LLRT is the answer to the question “What would a purposely built JavaScript runtime look like for Lambda.”&lt;/p&gt;

&lt;p&gt;A lot of people see that “It’s built in Rust” and automatically assume that’s why it’s fast. But it’s more than that.&lt;/p&gt;

&lt;p&gt;When it comes to performance optimizations, it always boils down to what you let go. If you choose to do everything the incumbent does, then you won’t make any significant performance gains.&lt;/p&gt;

&lt;h3&gt;
  
  
  No JIT compilation
&lt;/h3&gt;

&lt;p&gt;With LLRT, they chose to not include a JIT compiler. Because it’s focused on Lambda’s &lt;strong&gt;resource constraint and short-lived&lt;/strong&gt; execution environments.&lt;/p&gt;

&lt;p&gt;As a result, LLRT is likely less performant than the Node.js runtime for CPU-intensive tasks.&lt;/p&gt;

&lt;p&gt;However, most Lambda functions do not perform CPU-intensive tasks. Instead, they tend to be IO-intensive.&lt;/p&gt;

&lt;p&gt;And the Lambda execution environments are short-lived. So a JIT compiler would have been less effective at optimizing hot code paths.&lt;/p&gt;

&lt;p&gt;At the same time, a JIT compiler comes with significant startup costs. It also introduces occasional latency spikes when it needs to evict cached items.&lt;/p&gt;

&lt;p&gt;So it appears a sensible trade-off for LLRT to not include a JIT compiler.&lt;/p&gt;

&lt;h3&gt;
  
  
  QuickJs + Rust
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://bellard.org/quickjs/quickjs.html" rel="noopener noreferrer"&gt;QuickJs engine&lt;/a&gt; [6] plays a crucial role in LLRT and its outstanding performance. Another reason why LLRT is fast is because they wrote all the APIs in Rust.&lt;/p&gt;

&lt;p&gt;As much as possible, the team wants to stay in native code to guarantee a strong performance. &lt;a href="https://bun.sh/" rel="noopener noreferrer"&gt;Bun&lt;/a&gt; [7] took the same approach and implemented all the APIs in a system language called &lt;a href="https://ziglang.org/" rel="noopener noreferrer"&gt;Zig&lt;/a&gt; [8].&lt;/p&gt;

&lt;p&gt;The downside to this approach is that it’s harder for contributors to get in on the action. There are a lot fewer Rust developers than Node.js developers. And even fewer Rust developers who are interested in a JavaScript runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  LLRT vs Bun (and other JS runtimes)
&lt;/h3&gt;

&lt;p&gt;LLRT is different from other JavaScript runtimes. It’s not a general-purpose runtime for JavaScript. It doesn’t have to worry about running in the browser or on mobile phones.&lt;/p&gt;

&lt;p&gt;Instead, it’s solely focused on the Lambda execution environment.&lt;/p&gt;

&lt;p&gt;This allows them to make decisions that just wouldn’t make sense with Bun or Node.js. Decisions such as not including a JIT compiler. Or which of the JavaScript APIs do they implement first, or at all?&lt;/p&gt;

&lt;p&gt;The goal is to eventually become &lt;a href="https://wintercg.org/" rel="noopener noreferrer"&gt;WinterCG&lt;/a&gt; compliant. But we don’t have to wait for that to start using LLRT. For LLRT to be useful (but not perfect!), it just needs to support the AWS SDK and a few popular libraries.&lt;/p&gt;

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

&lt;p&gt;To summarize, LLRT is an exciting new runtime for JavaScript.&lt;/p&gt;

&lt;p&gt;It’s purposely built for Lambda.&lt;/p&gt;

&lt;p&gt;It’s not intended as a general-purpose runtime for JavaScript.&lt;/p&gt;

&lt;p&gt;It makes design trade-offs (such as not having a JIT compiler) to achieve an optimal cold start time.&lt;/p&gt;

&lt;p&gt;It uses the QuickJs engine and implements all the JavaScript APIs in Rust.&lt;/p&gt;

&lt;p&gt;To learn more about LLRT and how to contribute to it, please check out &lt;a href="https://youtu.be/XQism0mZ4pE" rel="noopener noreferrer"&gt;my conversation with Richard on YouTube&lt;/a&gt; [9].&lt;/p&gt;

&lt;p&gt;I will be covering LLRT in my upcoming workshop, including how to use it with the Serverless Framework and CDK. If you wanna take your serverless game to the next level, then you should check it out! More information is available on the &lt;a href="https://productionreadyserverless.com/?utm_campaign=llrt-first-impressions&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;course page&lt;/a&gt; [10].&lt;/p&gt;

&lt;p&gt;&lt;a href="https://productionreadyserverless.com/?utm_campaign=llrt-first-impressions&amp;amp;utm_source=blog&amp;amp;utm_content=image" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2023%2F12%2Fprsls-in-4-weeks.jpeg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;p&gt;[1] &lt;a href="https://github.com/awslabs/llrt" rel="noopener noreferrer"&gt;GitHub repo for LLRT&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] &lt;a href="https://github.com/middyjs/middy" rel="noopener noreferrer"&gt;Middy middleware engine&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[3] &lt;a href="https://github.com/awslabs/llrt/blob/main/API.md" rel="noopener noreferrer"&gt;LLRT’s API compatibility page&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[4] &lt;a href="https://docs.powertools.aws.dev/lambda/typescript/latest/" rel="noopener noreferrer"&gt;AWS Lambda Powertools for TypeScript&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[5] &lt;a href="https://www.linkedin.com/posts/theburningmonk_aws-serverless-awslambda-activity-7167986688810541056-nwK7/?utm_source=share&amp;amp;utm_medium=member_desktop" rel="noopener noreferrer"&gt;Most people are overthinking about Lambda cold starts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[6] &lt;a href="https://bellard.org/quickjs/quickjs.html" rel="noopener noreferrer"&gt;QuickJs engine&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[7] &lt;a href="https://bun.sh/" rel="noopener noreferrer"&gt;The Bun runtime for JavaScript&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[8] &lt;a href="https://ziglang.org/" rel="noopener noreferrer"&gt;The Zip programming language&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[9] &lt;a href="https://www.youtube.com/watch?v=XQism0mZ4pE" rel="noopener noreferrer"&gt;My interview with Richard Davison, the creator of LLRT&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[10] &lt;a href="https://productionreadyserverless.com/?utm_campaign=llrt-first-impressions&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;Production-Ready Serverless workshop&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://theburningmonk.com/2024/02/first-impressions-of-the-fastest-javascript-runtime-for-lambda/" rel="noopener noreferrer"&gt;First impressions of the fastest JavaScript runtime for Lambda&lt;/a&gt; appeared first on &lt;a href="https://theburningmonk.com" rel="noopener noreferrer"&gt;theburningmonk.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>javascript</category>
      <category>lambda</category>
      <category>serverless</category>
    </item>
    <item>
      <title>What’s the best way to migrate Cognito users to a new user pool?</title>
      <dc:creator>Yan Cui</dc:creator>
      <pubDate>Wed, 21 Feb 2024 01:18:10 +0000</pubDate>
      <link>https://dev.to/aws-heroes/whats-the-best-way-to-migrate-cognito-users-to-a-new-user-pool-4jfl</link>
      <guid>https://dev.to/aws-heroes/whats-the-best-way-to-migrate-cognito-users-to-a-new-user-pool-4jfl</guid>
      <description>&lt;p&gt;I &lt;a href="https://www.linkedin.com/posts/theburningmonk_aws-serverless-cognito-activity-7165374559964102656-f8Tr?utm_source=share&amp;amp;utm_medium=member_desktop" rel="noopener noreferrer"&gt;shared on Linkedin&lt;/a&gt; [1] the other day that you should avoid using Cognito subs as the user ID for your system. One of the reasons is that a user’s sub does not carry over when you migrate to a new user pool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/posts/theburningmonk_aws-serverless-cognito-activity-7165374559964102656-f8Tr" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftheburningmonk.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fimg_65d54f5eb3413.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Someone responded by asking “&lt;em&gt;Is this type of migration really that common that it necessitates consideration?&lt;/em&gt;”&lt;/p&gt;

&lt;p&gt;It’s a great question, so let’s dive into it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When should you consider a user pool migration?&lt;/li&gt;
&lt;li&gt;How best to do this migration?&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  When to consider user pool migration
&lt;/h3&gt;

&lt;p&gt;Migrating users from one Cognito User Pool to another can be highly disruptive. But sometimes it’s our last resort.&lt;/p&gt;

&lt;p&gt;Here are some common reasons why you have to consider it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Changing user pool settings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many user pool settings cannot be changed after it’s created. Most notably, immutable attributes cannot be changed to mutable later. For example, if you mark the &lt;code&gt;email&lt;/code&gt; attribute as immutable but change your mind later.&lt;/p&gt;

&lt;p&gt;This is by far the most common reason for migration between Cognito user pools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reorganization of environments/apps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Another reason is when you need to reorganize your application(s) and AWS environments. Maybe you want to consolidate multiple user pools into one. Or maybe you want to move your production users to a new production account.&lt;/p&gt;

&lt;p&gt;This might be to meet regulatory requirements.&lt;/p&gt;

&lt;p&gt;For example, you may need to limit the no. of people who have access to production user data. But if you had one AWS account for all environments then it’s difficult to meet that access control requirement. So to stay compliant, you have to move the production user data into its own account and restrict access to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data sovereignty requirements&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You may need to move some users to a different region to comply with data protection regulations. This means moving some users from one user pool to another.&lt;/p&gt;

&lt;h3&gt;
  
  
  The BEST way to migrate to a new user pool
&lt;/h3&gt;

&lt;p&gt;The challenge with this migration is that it’s not possible to extract the user password from Cognito.&lt;/p&gt;

&lt;p&gt;This is a good thing. It shows that Cognito follows security best practices and does not store user passwords in plain text.&lt;/p&gt;

&lt;p&gt;But it makes our lives more difficult during a migration.&lt;/p&gt;

&lt;p&gt;Broadly speaking, here are three ways to approach this migration:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1.&lt;/strong&gt; Add existing users to the new user pool and set a temporary password. Email the users with their temporary password and ask them to log in and change the password.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2.&lt;/strong&gt; Use the &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-migrate-user.html" rel="noopener noreferrer"&gt;migrate user Lambda trigger&lt;/a&gt; &lt;a href="https://dev.toon%20the%20new%20user%20pool"&gt;2&lt;/a&gt; to migrate users from the old user pool when the user next signs in. This trigger fires whenever a user is not found at the time of sign-in or in the forgot password flow. See the &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-import-using-lambda.html" rel="noopener noreferrer"&gt;official guide&lt;/a&gt; [3] on how to use this trigger to import users into Cognito.&lt;/p&gt;

&lt;p&gt;Both of these options suck…&lt;/p&gt;

&lt;p&gt;Option 1 puts the burden of migration on the users. Some users will churn, and it’s not a rabbit that you can pull out of the hat too many times.&lt;/p&gt;

&lt;p&gt;Option 2 has a loooong tail. You have to keep the Lambda trigger until the last user has been migrated. Chances are some users have churned and will never come back and sign in.&lt;/p&gt;

&lt;p&gt;In practice, most people use a combination of these two options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the migrate user Lambda trigger to migrate active users. Leave the trigger running for, say, 6 months.&lt;/li&gt;
&lt;li&gt;After 6 months, disable the trigger and apply option 1) on all remaining users. Those who have not churned will log back in and set a new password.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that leaves us with my favourite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 3.&lt;/strong&gt; Go passwordless.&lt;/p&gt;

&lt;p&gt;If passwords make migration difficult, then ditch them! Use this opportunity and modernize your app with passwordless authentication.&lt;/p&gt;

&lt;p&gt;Cognito doesn’t support passwordless authentication out-of-the-box. But you can implement these custom flows with Lambda triggers.&lt;/p&gt;

&lt;p&gt;I shared two ways to implement passwordless authentication with Cognito:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-heroes/passwordless-authentication-made-easy-with-cognito-a-step-by-step-guide-2pcn"&gt;Using one-time passcodes&lt;/a&gt; [4]&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-heroes/implementing-magic-links-with-amazon-cognito-a-step-by-step-guide-nbn"&gt;Using magic email links&lt;/a&gt; [5]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Besides these, you should also consider social sign-in with Google, LinkedIn, X/Twitter and so on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;Migrating users to a new Cognito user pool is perhaps more nuanced than you might think. There are technical limitations to consider. And you have to worry about the impact on your user experience.&lt;/p&gt;

&lt;p&gt;It’s something that I try to avoid if I can. But if I must, I prefer to turn my misfortunes into an opportunity and improve my sign-in experience with passwordless authentication.&lt;/p&gt;

&lt;p&gt;Do you agree?&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;p&gt;[1] &lt;a href="https://www.linkedin.com/posts/theburningmonk_aws-serverless-cognito-activity-7165374559964102656-f8Tr/?utm_source=share&amp;amp;utm_medium=member_desktop" rel="noopener noreferrer"&gt;Why you should stop using Cognito subs as user ID&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-migrate-user.html" rel="noopener noreferrer"&gt;Migrate user Lambda trigger&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[3] &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-import-using-lambda.html" rel="noopener noreferrer"&gt;Importing users into user pool with a user migration Lambda trigger&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[4] &lt;a href="https://dev.to/aws-heroes/passwordless-authentication-made-easy-with-cognito-a-step-by-step-guide-2pcn"&gt;Passwordless authentication with Cognito: one-time passcodes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[5] &lt;a href="https://dev.to/aws-heroes/implementing-magic-links-with-amazon-cognito-a-step-by-step-guide-nbn"&gt;Passwordless authentication with Cognito: magic email links&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &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;What’s the best way to migrate Cognito users to a new user pool?&lt;/a&gt; appeared first on &lt;a href="https://theburningmonk.com" rel="noopener noreferrer"&gt;theburningmonk.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cognito</category>
      <category>lambda</category>
      <category>serverless</category>
    </item>
    <item>
      <title>How to secure CI/CD roles without burning production to the ground</title>
      <dc:creator>Yan Cui</dc:creator>
      <pubDate>Fri, 16 Feb 2024 00:43:03 +0000</pubDate>
      <link>https://dev.to/theburningmonk/how-to-secure-cicd-roles-without-burning-production-to-the-ground-n0e</link>
      <guid>https://dev.to/theburningmonk/how-to-secure-cicd-roles-without-burning-production-to-the-ground-n0e</guid>
      <description>&lt;p&gt;By now, most of us have moved away from using IAM users for CI/CD pipelines. Instead, we’d use dedicated CI/CD roles, one for each pipeline. This forces us to consider &lt;em&gt;who&lt;/em&gt; can assume this role.&lt;/p&gt;

&lt;p&gt;Identity federation is widely supported by 3rd-party providers such as &lt;a href="https://scalesec.com/blog/identity-federation-for-github-actions-on-aws/"&gt;GitHub Actions&lt;/a&gt; [1]. So, no more putting IAM credentials in CI/CD tools and worry that they &lt;a href="https://dev.to/ronpowelljr/circleci-incident-report-for-january-4-2023-security-incident-29gb-temp-slug-4629174"&gt;might be compromised in a security breach&lt;/a&gt; [2].&lt;/p&gt;

&lt;p&gt;However, attackers can still compromise the pipeline through supply chain attacks. For example, by compromising a Docker image we depend on in our CI/CD pipeline. Or by compromising static analysis tools such as &lt;a href="https://cycode.com/blog/eslint-compromising-the-build-using-supply-chain-attack/"&gt;eslint&lt;/a&gt; [3].&lt;/p&gt;

&lt;p&gt;So, the question of “ &lt;strong&gt;How best to limit CI/CD role’s permissions?&lt;/strong&gt; ” has come up many times during my &lt;a href="https://productionreadyserverless.com/?utm_campaign=how-to-secure-ci-role&amp;amp;utm_source=blog"&gt;Production-Ready Serverless&lt;/a&gt; [4] workshop.&lt;/p&gt;

&lt;p&gt;Your instinct might be to lock down the CI/CD role to just what it needs. Because we all want to follow the principle of least privilege.&lt;/p&gt;

&lt;p&gt;There are different ways to achieve this. Here are two common approaches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with a blank slate and add permissions to the role until it’s able to deploy your application.&lt;/li&gt;
&lt;li&gt;Start with the AdministratorAccess policy and allow the role to do everything. Use CloudTrail to track the actions performed by the role over some time. Then use the CloudTrail data to create a tailored set of IAM permissions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first approach is very time-consuming and laborious and is best avoided. But in truth, both approaches have significant drawbacks in the long run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rollback surprises
&lt;/h3&gt;

&lt;p&gt;For most people, CloudFormation rollbacks don’t happen often. Especially for those of us who don’t write CloudFormation templates by hand.&lt;/p&gt;

&lt;p&gt;So when a deployment fails for the first time, you find out that the CI/CD role is missing a whole bunch of permissions! This can leave you in an awkward position where your stack is stuck in the ROLLBACK_FAILED state. And you are stuck until you resolve the missing permissions.&lt;/p&gt;

&lt;p&gt;In essence, crafting a least privileged CI/CD role takes twice as much work as you think. Because you also need to factor in all the permissions for rollbacks.&lt;/p&gt;

&lt;h3&gt;
  
  
  A tax on productivity
&lt;/h3&gt;

&lt;p&gt;Your architecture is not static. It needs to evolve with your application and your business needs. Every time you introduce a new service, you also need to revise the CI/CD role.&lt;/p&gt;

&lt;p&gt;Want to start using OpenSearch? Not before you update the CI/CD role!&lt;/p&gt;

&lt;p&gt;Want to start using Bedrock? Not before you … you get the point.&lt;/p&gt;

&lt;p&gt;In organizations where the application team doesn’t own the CI/CD role, this productivity tax can be steep. It requires many back-and-forth between teams and hits productivity hard.&lt;/p&gt;

&lt;p&gt;And you likely have to do these for every service because every service has its own CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;This friction is a constant tax on your productivity. It hampers innovation and delays the adoption of new services into the tech stack. Because introducing a new service means new CI/CD permissions. Who in their right mind wants to deal with all that bureaucracy?&lt;/p&gt;

&lt;h3&gt;
  
  
  They are not as safe as you think
&lt;/h3&gt;

&lt;p&gt;The reason why we pursue least privileged CI/CD roles is to reduce the blast radius of a security breach. If an attacker compromises our CI/CD pipeline, we want to limit what the attacker can do.&lt;/p&gt;

&lt;p&gt;However, the CI/CD role likely needs to deploy IAM roles and Lambda functions. In the event of a compromise, a least privileged CI/CD role is not enough to stop the attacker.&lt;/p&gt;

&lt;p&gt;Because the attackers can use the CI/CD role to create confused deputies to act on their behalf.&lt;/p&gt;

&lt;p&gt;For example, attackers can use the CI/CD role to create IAM roles for them to assume. And they can also create Lambda functions to carry out malicious activities.&lt;/p&gt;

&lt;p&gt;We can mitigate these risks to some extent with permission boundaries. But that requires lots more work and it’s hard to get right. And again, you have to do it for every CI/CD role.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prefer a permissive role that is hard to abuse
&lt;/h3&gt;

&lt;p&gt;It’s counterproductive to blindly pursue least privileged CI/CD roles. Beyond a certain point, you get diminished return-on-investments.&lt;/p&gt;

&lt;p&gt;That is, you have to work much harder to address security risks that are unlikely to happen.&lt;/p&gt;

&lt;p&gt;With that in mind, here’s my preferred approach to securing a CI/CD pipeline.&lt;/p&gt;

&lt;h5&gt;
  
  
  Use a separate AWS account for each environment
&lt;/h5&gt;

&lt;p&gt;This is something everyone should do. It insulates environments from each other. If one environment is compromised, it’s contained by the account boundary. If an attacker compromises your dev environment, they won’t be able to access your production data.&lt;/p&gt;

&lt;p&gt;For large organizations, you should go a step further. I recommend having at least one account per team per environment. That way, teams are insulated from other teams’ mistakes.&lt;/p&gt;

&lt;p&gt;For business critical systems, they should have their own set of accounts too. That is one account per service per environment. It’s not always necessary. But it’s a good idea to protect these business critical systems from other systems.&lt;/p&gt;

&lt;h5&gt;
  
  
  Service Control Policies (SCPs)
&lt;/h5&gt;

&lt;p&gt;Use SCPs to deny access to unused resources. These broad strokes include access to unused AWS regions and unused services. This way, you can eliminate large parts of AWS as possible attack surfaces.&lt;/p&gt;

&lt;p&gt;For example, if you’re not running any EC2 instances, then deny all EC2 activities. That’s one less target for crypto-jacking attacks. But hey, &lt;a href="https://securitylabs.datadoghq.com/articles/tales-from-the-cloud-trenches-ecs-crypto-mining/"&gt;ECS is the new EC2 for crypto-jacking&lt;/a&gt; [5]. If you can’t disable ECS as a whole, then at least deny access to the regions where you don’t have any containers.&lt;/p&gt;

&lt;h5&gt;
  
  
  Attribute-Based Access Control (ABAC)
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html#all_svcs"&gt;Support for ABAC&lt;/a&gt; [6] is improving all the time, although it’s still lacklustre in its current form. But many services already support the &lt;code&gt;aws:ResourceTag/${TagKey}&lt;/code&gt; and &lt;code&gt;aws:PrincipalTag/${TagKey}&lt;/code&gt; conditions.&lt;/p&gt;

&lt;p&gt;These conditions can be used to stop attackers from accessing and creating resources. They can be added to the IAM permissions of the CI/CD role. Or they can be enforced through a permission boundary.&lt;/p&gt;

&lt;p&gt;To make them more effective, the CI/CD role should be denied the ability to find out about itself. That way, the attacker can’t find out what tags the CI/CD role has and what tags they need to use to create new resources.&lt;/p&gt;

&lt;p&gt;Doing so helps us stop the attacker from accessing our resources through the CI/CD role. But they might target the following services to execute malicious code or escalate their privilege:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda&lt;/li&gt;
&lt;li&gt;EC2&lt;/li&gt;
&lt;li&gt;ECS&lt;/li&gt;
&lt;li&gt;IAM&lt;/li&gt;
&lt;li&gt;CodePipeline&lt;/li&gt;
&lt;li&gt;CloudFormation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Luckily, all these services (and many others) support the &lt;code&gt;aws:ResourceTag/${TagKey}&lt;/code&gt; condition. We can use this condition to stop the attacker from creating new resources.&lt;/p&gt;

&lt;h5&gt;
  
  
  A permissive CI/CD role
&lt;/h5&gt;

&lt;p&gt;After doing the above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We have removed the attack surface associated with used regions and services.&lt;/li&gt;
&lt;li&gt;We have limited the blast radius of a compromise to individual teams and services.&lt;/li&gt;
&lt;li&gt;We have made it difficult for attackers to abuse the CI/CD role by requiring correct tags on resources. Which the attacker is not able to figure out easily because the CI/CD role is not allowed to describe itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we can allow ourselves to have a permissive CI/CD role that is not a pain in the ass to create and maintain! Because the role will be difficult to abuse, and there is a ceiling to what it can actually do.&lt;/p&gt;

&lt;p&gt;By “permissive”, I don’t necessarily mean “AdministratorAccess”. But you can give broad read &amp;amp; write permissions to all the services you use. Instead of picking out just the actions you need with a fine comb!&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;Achieving a balance between security and productivity is critical.&lt;/p&gt;

&lt;p&gt;Adopting more permissive CI/CD roles introduces higher risks. But it’s counterbalanced with the aforementioned compensating controls and security practices.&lt;/p&gt;

&lt;p&gt;You also need continuous monitoring and regular security audits. The SCPs and ABAC settings should be reviewed regularly.&lt;/p&gt;

&lt;p&gt;It’s equally important to cultivate a security-conscious culture within teams. When everyone is aware of and responsible for security, the CI/CD pipeline becomes more resilient.&lt;/p&gt;

&lt;p&gt;Ultimately, the goal is not to eliminate risk but to manage it efficiently. When we combine a permissive role with strong security controls, we can be safe and productive!&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;p&gt;[1] &lt;a href="https://scalesec.com/blog/identity-federation-for-github-actions-on-aws/"&gt;Identity federation for GitHub Actions on AWS&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] &lt;a href="https://dev.to/ronpowelljr/circleci-incident-report-for-january-4-2023-security-incident-29gb-temp-slug-4629174"&gt;CircleCi incident report for Jan 4, 2023 security breach&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[3] &lt;a href="https://cycode.com/blog/eslint-compromising-the-build-using-supply-chain-attack/"&gt;ESLint: Compromising the Build using Supply Chain Attack&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[4] &lt;a href="https://productionreadyserverless.com/?utm_campaign=how-to-secure-ci-role&amp;amp;utm_source=blog"&gt;Production-Ready Serverless workshop&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[5] &lt;a href="https://securitylabs.datadoghq.com/articles/tales-from-the-cloud-trenches-ecs-crypto-mining/"&gt;Tales from the cloud trenches: Amazon ECS is the new EC2 for crypto mining&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[6] &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html#all_svcs"&gt;Which AWS services support ABAC&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://theburningmonk.com/2024/02/how-to-secure-ci-cd-roles-without-burning-production-to-the-ground/"&gt;How to secure CI/CD roles without burning production to the ground&lt;/a&gt; appeared first on &lt;a href="https://theburningmonk.com"&gt;theburningmonk.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
