<?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>Forem: Jon Holman</title>
    <description>The latest articles on Forem by Jon Holman (@jonholman_).</description>
    <link>https://forem.com/jonholman_</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%2F561660%2F0ba9e049-6dfd-4686-8676-f3c0a5f5e015.jpg</url>
      <title>Forem: Jon Holman</title>
      <link>https://forem.com/jonholman_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jonholman_"/>
    <language>en</language>
    <item>
      <title>Console-to-Code Preview: Test Drive, Bright Future</title>
      <dc:creator>Jon Holman</dc:creator>
      <pubDate>Wed, 13 Dec 2023 17:03:42 +0000</pubDate>
      <link>https://forem.com/aws-builders/console-to-code-preview-test-drive-bright-future-1j4l</link>
      <guid>https://forem.com/aws-builders/console-to-code-preview-test-drive-bright-future-1j4l</guid>
      <description>&lt;p&gt;Every November brings excitement to the tech world with the Amazon Web Services (AWS) re:Invent conference. This year, the focus was on Generative AI (GenAI). Among the many GenAI releases was a new EC2 console feature called "&lt;a href="https://aws.amazon.com/about-aws/whats-new/2023/11/aws-console-to-code-preview-generate-console-actions/" rel="noopener noreferrer"&gt;Console-to-Code&lt;/a&gt;." This tool transforms console actions into code. I love this idea. Using this tool, newcomers can more easily adopt the best practice of using Infrastructure as Code (IaC). This tool can even help seasoned IaC professionals create new templates while adding rarely used resource types. Let's take this new feature for a test drive.&lt;/p&gt;

&lt;p&gt;First, let's create a spot instance and get a CloudFormation template. Hmm, it did not recognize the console action of creating a new spot instance. Oh well. Let's create an on-demand instance. It did see those actions and generated the following CloudFormation YAML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Resources:
  EC2Instance: 
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0230bd60aa48260c6
      InstanceType: t2.micro
      KeyName: mine
      BlockDeviceMappings: 
        - DeviceName: /dev/xvda
          Ebs:
            Encrypted: true
            DeleteOnTermination: true
            Iops: 3000
            KmsKeyId: arn:aws:kms:us-east-1:XXXXXXXXXXXX:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
            SnapshotId: snap-xxxxxxxxxxxxxxxxx
            VolumeSize: 8
            VolumeType: gp3
            Throughput: 125
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: 0
          GroupSet: 
            - sg-xxxxxxxxxxxxxxxxx
      TagSpecifications:
        - ResourceType: instance
          Tags:
            - Key: Name
              Value: My Test
      MetadataOptions:
        HttpTokens: required
        HttpEndpoint: enabled
        HttpPutResponseHopLimit: 2
      PrivateDnsNameOptions:
        HostnameType: ip-name
        EnableResourceNameDnsARecord: true
        EnableResourceNameDnsAAAARecord: false
      ClientToken: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
      Count: 1

  SecurityGroupIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: sg-xxxxxxxxxxxxxxxxx
      IpProtocol: tcp
      FromPort: 22
      ToPort: 22
      CidrIp: 0.0.0.0/0

  SecurityGroup:
    Type: AWS::EC2::SecurityGroup  
    Properties:
      GroupName: launch-wizard-1
      GroupDescription: launch-wizard-1 created 2023-12-11T17:23:59.534Z
      VpcId: vpc-xxxxxxxxxxxxxxxxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's save that as a YAML file and try to create an identical EC2 instance using the CloudFormation console. That attempt failed with the following:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqca1tiqd6z4j4seoigmn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqca1tiqd6z4j4seoigmn.png" alt=" " width="800" height="49"&gt;&lt;/a&gt;&lt;br&gt;
I see the problem. "TagSpecifications:" and "- ResourceType: instance" are invalid properties. Remove those two lines and two indention levels for the "Tags:" block. Now, let's try again. Now, the CloudFormation creation failed with the following:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi5nej5xaycr251zofwf9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi5nej5xaycr251zofwf9.png" alt=" " width="800" height="54"&gt;&lt;/a&gt;&lt;br&gt;
Let's remove the "MetadataOptions" property and the keys within and try again. Next, the CloudFormation stack creation failed with the following:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftrmk197j00sk55g2vyg1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftrmk197j00sk55g2vyg1.png" alt=" " width="800" height="29"&gt;&lt;/a&gt;&lt;br&gt;
I am less concerned about this because no one should create a security group with a default name like launch-wizard-x. Let's try again. This time, it failed with the following:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl267153y306mhwkhus8x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl267153y306mhwkhus8x.png" alt=" " width="800" height="52"&gt;&lt;/a&gt;&lt;br&gt;
"Count: 1." Where did that come from? Terraform? Remove that line and try again. Next, it failed with the following:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe648pge3jb0eijr4it8y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe648pge3jb0eijr4it8y.png" alt=" " width="800" height="48"&gt;&lt;/a&gt;&lt;br&gt;
Remove the "ClientToken:" line and try again. Now, it failed with the following:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdraa2rlwxl80jogpgsek.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdraa2rlwxl80jogpgsek.png" alt=" " width="800" height="52"&gt;&lt;/a&gt;&lt;br&gt;
Remove the "Throughput:" line and try again. It worked!&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqbrr6ztuqiogmjq1dasn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqbrr6ztuqiogmjq1dasn.png" alt=" " width="158" height="53"&gt;&lt;/a&gt;&lt;br&gt;
I thought creating an EC2 instance with all default settings except my key pair would be a simple test of this new feature. However, after needing to fix the provided template over six iterations to deploy successfully, I conclude that this feature is unfortunately not ready to be used. It is fun to play with, though. I understand this feature is still in preview, but what can this current version successfully do? The current version is limited in scope to the EC2 console. In the EC2 console, what is more fundamental than creating an EC2 instance?&lt;/p&gt;

&lt;p&gt;In conclusion, I love the idea of this new feature, and I greatly look forward to it being improved and expanded to other AWS services. This feature will help more customers achieve the best practice of using IaC. I cannot wait for an announcement of improvements to this service so I can take it for another test drive.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>amazon</category>
      <category>ai</category>
      <category>infrastructureascode</category>
    </item>
    <item>
      <title>My First Impressions of Amazon Q</title>
      <dc:creator>Jon Holman</dc:creator>
      <pubDate>Wed, 29 Nov 2023 18:54:58 +0000</pubDate>
      <link>https://forem.com/aws-builders/my-first-impressions-of-amazon-q-263m</link>
      <guid>https://forem.com/aws-builders/my-first-impressions-of-amazon-q-263m</guid>
      <description>&lt;p&gt;Every year in late November, Amazon Web Services (AWS) hosts its re:Invent conference.  This conference introduces many new services and features.  This year has been no exception.  This year is heavily focused on Generative AI (GenAI).  Yesterday, they announced a new service called Amazon Q, a generative AI assistant.  After this announcement, when you log into the web console of your AWS account, you are greeted with a screen similar to the following:&lt;/p&gt;

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

&lt;p&gt;I'm excited about this idea; I see its potential to help many users, myself included, get stuff done.  Let's try it out.  Hmm, what to try.  How about?&lt;/p&gt;

&lt;p&gt;"Please provide me a list of my S3 buckets."&lt;/p&gt;

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

&lt;p&gt;That's not bad.  It would be cool if it gave me my list of buckets or a link to that part of the console.  It also could have suggested running the provided command in CloudShell.  But still, not bad.&lt;/p&gt;

&lt;p&gt;Next, let's try "Create a website." intentionally keeping this generic to see where it goes.&lt;/p&gt;

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

&lt;p&gt;That's disappointing. It could have done a lot with that.  Let's give it more to work with, "How can I create a website?"&lt;/p&gt;

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

&lt;p&gt;I'm happy with that answer.&lt;/p&gt;

&lt;p&gt;Let's see if it will write CloudFormation for us, "Please write me CloudFormation to create a CloudFront distribution."&lt;/p&gt;

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

&lt;p&gt;That's disappointing.  Well, this new service is in preview.  Hopefully, this functionality will be there eventually.  Last, let's try asking a question unrelated to AWS, "What are the latest headlines in world news?"&lt;/p&gt;

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

&lt;p&gt;That answer makes sense to me.&lt;/p&gt;

&lt;p&gt;I like this new service introduction.  I see a lot of potential for it, and it's in preview, so it is understood that it will not be perfect.  I look forward to Amazon continued development of this service.  Some hopes I have to see in the future:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Help users find what they're looking for, for example links to specific places in the AWS web console.&lt;/li&gt;
&lt;li&gt;Educate users and encourage best practices&lt;/li&gt;
&lt;li&gt;Generate IaC (Infrastructure-as-Code)&lt;/li&gt;
&lt;li&gt;Provide answers related to the current AWS account.  For example, "Please list my S3 buckets." or "Please list all of my running EC2 instances."&lt;/li&gt;
&lt;li&gt;Identify cost optimization opportunties across your AWS account.&lt;/li&gt;
&lt;li&gt;Define settings for the type of solutions you create based on regulations, corporate policies, and/or preferences.  For example, "We are an EKS shop." or "All external traffic must be routed through our corporate data center via our direct connect."  Amazon Q should then make recommendations that adhere to those preferences.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What are your thoughts?  Let me know in the comments below.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>amazon</category>
      <category>ai</category>
      <category>aiops</category>
    </item>
    <item>
      <title>Streamline CloudFormation Custom Resource Development with a Wrapper Lambda Function</title>
      <dc:creator>Jon Holman</dc:creator>
      <pubDate>Fri, 31 Mar 2023 02:31:09 +0000</pubDate>
      <link>https://forem.com/aws-builders/streamline-cloudformation-custom-resource-development-with-a-wrapper-lambda-function-26k9</link>
      <guid>https://forem.com/aws-builders/streamline-cloudformation-custom-resource-development-with-a-wrapper-lambda-function-26k9</guid>
      <description>&lt;p&gt;AWS CloudFormation and AWS Lambda are powerful tools available to all AWS customers. AWS CloudFormation enables you to define and manage your AWS infrastructure using code, while AWS Lambda allows you to run code in response to events without managing backend infrastructure. A powerful feature is CloudFormation Custom Resources, which enables you to implement a Lambda function triggered by a CloudFormation template. A CloudFormation template can achieve more possibilities by utilizing this feature.&lt;/p&gt;

&lt;p&gt;Errors during the development of custom resources can result in unresponsive stacks and delays in the development process. This blog post explores how a wrapper Lambda function can enhance your CloudFormation custom resource development experience, preventing unresponsive stacks and boosting overall productivity. The AWS provided cfn-response module assists in sending responses from your Lambda function, which serves as a CloudFormation Custom Resource, back to CloudFormation. Unresponsive stacks typically arise from coding mistakes, such as typos or incorrect permissions, which hinder CloudFormation from receiving the necessary response from the Lambda function. When a stack becomes unresponsive, it disrupts the development process. A common workaround to an unresponsive stack is selecting a new stack name and continuing development while remembering to clean up the unresponsive stack after its one-hour timeout period expires.&lt;/p&gt;

&lt;p&gt;Before we get further into our solution, let's demonstrate how easy it is to create an unresponsive stack accidentally. In this demo, we will create an AWS Lambda function that will try to list all DynamoDB tables in the account without being given permission. Another common cause is a simple typo in the code. You may want to avoid following along in your AWS account because this will create an unresponsive stack, so only perform this yourself if you're ready to deal with the unresponsive stack afterward.&lt;/p&gt;

&lt;p&gt;Example in JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: "2010-09-09"
Resources:
  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          const { DynamoDBClient, ListTablesCommand } = require("@aws-sdk/client-dynamodb");
          const cfnresponse = require("cfn-response");

          const asyncCfnResponseSend = (event, context, responseStatus, responseData, physicalResourceId, noEcho) =&amp;gt; {
            return new Promise((resolve, reject) =&amp;gt; {
              cfnresponse.send(event, context, responseStatus, responseData, physicalResourceId, noEcho, (err, res) =&amp;gt; {
                if (err) {
                  reject(err);
                } else {
                  resolve(res);
                }
              });
            });
          };

          exports.handler = async (event, context) =&amp;gt; {
            const client = new DynamoDBClient();
            const response = await client.send(new ListTablesCommand({}));
            await asyncCfnResponseSend(event, context, cfnresponse.SUCCESS, response);
          };

      Handler: index.handler
      Role: !GetAtt LambdaFunctionExecutionRole.Arn
      Runtime: nodejs18.x
      Timeout: 10

  LambdaFunctionExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

  TestInvocationOfCustomResource:
    Type: Custom::TestInvocation
    Properties:
      ServiceToken: !GetAtt LambdaFunction.Arn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: "2010-09-09"
Resources:
  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import boto3
          import cfnresponse

          def handler(event, context):
              client = boto3.client("dynamodb")
              response = client.list_tables()
              cfnresponse.send(event, context, cfnresponse.SUCCESS, response)

      Handler: index.handler
      Role: !GetAtt LambdaFunctionExecutionRole.Arn
      Runtime: python3.9
      Timeout: 10

  LambdaFunctionExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

  TestInvocationOfCustomResource:
    Type: Custom::TestInvocation
    Properties:
      ServiceToken: !GetAtt LambdaFunction.Arn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;As you see, simply trying to perform an SDK action without proper permissions can create an unresponsive stack.  Of course that above can be avoided by wrapping the SDK action in a try/catch, sometimes when we're quickly developing we may forget that or we may make a typo.  It is detrimental to productivity when your process is interrupted by a unresponsive stack.  To avoid this problem, we can use a wrapper Lambda function to handle exceptions and ensure proper communication back to the CloudFormation service. Our wrapper Lambda function is a reliable approach to avoid creating an unresponsive CloudFormation stack due to quick mistakes during the development of custom resources. This wrapper function acts as a handler that invokes the Lambda being developed and communicates a response to CloudFormation, ensuring that errors are handled gracefully and presenting information of items that require attention for the developers to see. This approach streamlines the development process, making it more efficient and dependable. As a result, custom resources can be developed with confidence, knowing that communication with CloudFormation is reliable and error-free.&lt;/p&gt;

&lt;p&gt;To protect ourselves from our development process being interrupted by an unresponsive stack. Let's make some updates to our CloudFormation template. First, we will add a Lambda Function to the Resources block of your CloudFormation template.&lt;/p&gt;

&lt;p&gt;In JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  WrappingLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          const { LambdaClient, InvokeCommand } = require("@aws-sdk/client-lambda");
          const cfnresponse = require("cfn-response");
          const https = require("https");
          const url = require("url");

          cfnresponse.send = async (event, context, responseStatus, responseData, physicalResourceId, noEcho, reason) =&amp;gt; {
            return new Promise((resolve, reject) =&amp;gt; {
              try {
                const responseBody = JSON.stringify({
                  Status: responseStatus,
                  Reason: reason || `See the details in CloudWatch Log Stream: ${context.logStreamName}`,
                  PhysicalResourceId: physicalResourceId || context.logStreamName,
                  StackId: event.StackId,
                  RequestId: event.RequestId,
                  LogicalResourceId: event.LogicalResourceId,
                  NoEcho: noEcho || false,
                  Data: responseData,
                });

                console.log("Response body:\n", responseBody);

                const parsedUrl = url.parse(event.ResponseURL);
                const options = {
                  hostname: parsedUrl.hostname,
                  port: 443,
                  path: parsedUrl.path,
                  method: "PUT",
                  headers: {
                    "content-type": "",
                    "content-length": responseBody.length,
                  },
                };

                const request = https.request(options, (response) =&amp;gt; {
                  console.log("Status code: " + response.statusCode);
                  console.log("Status message: " + response.statusMessage);
                  resolve();
                });

                request.on("error", (error) =&amp;gt; {
                  console.log("send(..) failed executing https.request(..): " + error);
                  reject(error);
                });

                request.write(responseBody);
                request.end();
              } catch (error) {
                console.log("send(..) failed: " + error);
                reject(error);
              }
            });
          };

          async function invokeLambda(functionName, event) {
            try {
              const client = new LambdaClient();
              const response = await client.send(
                new InvokeCommand({
                  FunctionName: functionName,
                  Payload: JSON.stringify(event),
                })
              );
              const decoder = new TextDecoder("utf-8");
              const payloadString = decoder.decode(response.Payload);
              const payload = JSON.parse(payloadString);

              return payload;
            } catch (error) {
              throw new Error(`Lambda invocation failed: ${error.message}`);
            }
          }

          exports.handler = async (event, context) =&amp;gt; {
            console.log("event:", event);

            const resourceProperties = event.ResourceProperties;
            const functionName = resourceProperties.LambdaFunctionName;

            if (!functionName) {
              const errorMessage = "Lambda invocation failed: LambdaFunctionName must be provided in event.ResourceProperties.";
              console.error(errorMessage);
              await cfnresponse.send(event, context, cfnresponse.FAILED, {}, errorMessage, false, errorMessage);
            } else {
              try {
                const payload = await invokeLambda(functionName, event);

                console.log("lambda response:", payload);

                if (payload.hasOwnProperty("errorMessage")) {
                  const errorMessage = `Lambda invocation failed: ${payload.errorMessage}`;
                  await cfnresponse.send(event, context, cfnresponse.FAILED, {}, errorMessage, false, errorMessage);
                } else {
                  await cfnresponse.send(event, context, cfnresponse.SUCCESS, { Response: JSON.stringify(payload) }, "Lambda invocation succeeded", false, "Lambda invocation succeeded");
                }
              } catch (error) {
                const errorMessage = `Lambda invocation failed: ${error.message}`;
                console.error(errorMessage);
                await cfnresponse.send(event, context, cfnresponse.FAILED, {}, errorMessage, false, errorMessage);
              }
            }
          };

      Handler: index.handler
      Role: !GetAtt WrappingLambdaFunctionExecutionRole.Arn
      Runtime: nodejs18.x
      Timeout: 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  WrappingLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import json
          import logging
          import boto3
          import cfnresponse

          logger = logging.getLogger(__name__)
          logger.setLevel(logging.INFO)
          client = boto3.client("lambda")

          def invoke_lambda(function_name, event):
              try:
                  response = client.invoke(
                      FunctionName=function_name,
                      Payload=json.dumps(event),
                  )
                  payload_stream = response["Payload"]
                  payload = json.loads(payload_stream.read())
                  payload_stream.close()

                  return payload
              except Exception as error:
                  raise Exception(f"Lambda invocation failed: {error}")

          def handler(event, context):
              logger.info(f"event: {event}")

              resource_properties = event.get("ResourceProperties", {})
              function_name = resource_properties.get("LambdaFunctionName")

              if not function_name:
                  error_message = "Lambda invocation failed: LambdaFunctionName must be provided in event.ResourceProperties."
                  logger.error(error_message)
                  cfnresponse.send(event, context, cfnresponse.FAILED, {}, error_message, False, error_message)
              else:
                  try:
                      payload = invoke_lambda(function_name, event)

                      logger.info(f"lambda response: {payload}")

                      if "errorMessage" in payload:
                          error_message = f"Lambda invocation failed: {payload['errorMessage']}"
                          cfnresponse.send(event, context, cfnresponse.FAILED, {}, error_message, False, error_message)
                      else:
                          cfnresponse.send(event, context, cfnresponse.SUCCESS, { "Response": json.dumps(payload) }, "Lambda invocation succeeded", False, "Lambda invocation succeeded")

                  except Exception as error:
                      error_message = f"Lambda invocation failed: {error}"
                      logger.error(error_message)
                      cfnresponse.send(event, context, cfnresponse.FAILED, {}, error_message, False, error_message)

      Handler: index.handler
      Role: !GetAtt WrappingLambdaFunctionExecutionRole.Arn
      Runtime: python3.9
      Timeout: 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have added that additional lambda function, let's make some final edits to our CloudFormation template to utilize that wrapper lambda function.  This will be the same for Python and JavaScript.&lt;br&gt;
Add the role for that function we just added:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  WrappingLambdaFunctionExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: invoke-lambda-function
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: lambda:InvokeFunction
                Resource: !GetAtt LambdaFunction.Arn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now from the original lambda function we created:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the cfnresponse import can be removed.  In the JavaScript version the asyncCfnResponseSend function can be removed as well.&lt;/li&gt;
&lt;li&gt;the cfnresponse.send line can be replaced with &lt;code&gt;return response&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In the TestInvocationOfCustomResource resource in the CloudFormation template the &lt;code&gt;ServiceToken: !GetAtt LambdaFunction.Arn&lt;/code&gt; line should be renamed to &lt;code&gt;LambdaFunctionName: !Ref LambdaFunction&lt;/code&gt;, then add a new line &lt;code&gt;ServiceToken: !GetAtt WrappingLambdaFunction.Arn&lt;/code&gt; that will update the CloudFormation template to call the wrapping function which will then call the function in development.&lt;/li&gt;
&lt;li&gt;You can also add an output, so we can demonstrate how return values can flow from our development lambda function back to CloudFormation, at the end of the template, add:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Outputs:
  ResponseFromLambdaFunction:
    Value: !GetAtt TestInvocationOfCustomResource.Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have added our wrapping function and updated our template, lets deploy and take a look.  My results can be seen in this screenshot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9h5jwuwt2lordtetkxtx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9h5jwuwt2lordtetkxtx.png" alt=" " width="800" height="529"&gt;&lt;/a&gt;&lt;br&gt;
As we expected this function did not succeed, as it did not list all DynamoDB tables, because it does not have permissions.  However the stack is still responsive. Note the message in the Status reason column, we are also returning that same value to the Physical ID column on the Resources tab.  We are returning that value and trying to make it visible to the developer without causing the stack to wait for an hour prior to any further actions being performed.&lt;/p&gt;

&lt;p&gt;Side note, while writing this post, I discovered that I prefer cfn-response module's Python implementation over its JavaScript counterpart. Since python typically executes code in a single thread, we don't need to worry about cfn-response not being asynchronous.  The JavaScript version of cfn-response also lacks support for the reason parameter. To address these items in our examples above you will see the JavaScript examples required more code to remedy the lack of support for the reason property and cfn-response's send function not being asynchronous.&lt;/p&gt;

&lt;p&gt;If you would like to take this demo a step further, we can remediate the fictitious issue we created for demonstration by adding the appropriate permission to the inner (wrapped) Lambda function to list DynamoDB tables and deploy the template.  If you add the appropriate permission to the inner (wrapped) lambda function to list DynamoDB tables and re-create the template you will see how the data from that call is returned back to the wrapping function, which then in turn makes it available to the CloudFormation stack (as an example it is on the outputs tab of the CloudFormation console.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      Policies:
        - PolicyName: temp
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: dynamodb:ListTables
                Resource: "*"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In conclusion, a wrapper Lambda function can be a valuable tool for streamlining your CloudFormation custom resource development process and preventing unresponsive stacks. By incorporating them into your workflow, you can achieve a more efficient and reliable development experience.  Thanks for reading.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Securing a new AWS account - Starting with Trusted Advisor</title>
      <dc:creator>Jon Holman</dc:creator>
      <pubDate>Sat, 26 Nov 2022 02:38:31 +0000</pubDate>
      <link>https://forem.com/aws-builders/securing-a-new-aws-account-starting-with-trusted-advisor-2dhb</link>
      <guid>https://forem.com/aws-builders/securing-a-new-aws-account-starting-with-trusted-advisor-2dhb</guid>
      <description>&lt;p&gt;This blog post is the start of a new series of posts. In this series, I will create a brand new AWS account and then use several common tools to identify areas of vulnerability which we will then remediate. We will secure this account prior to the account being used to host any workloads. Everything we create in this account will be to follow recommendations for securing the account. After this series, the next step could be to spin up some sample workloads and see new guidance from the tools we use based on the resources those workloads create.&lt;/p&gt;

&lt;p&gt;Although it is best practice to always have a multi-account organization in AWS [1], this series will focus on a single AWS account for simplicity. I previously wrote about &lt;a href="https://www.softrams.com/post/aws-control-tower" rel="noopener noreferrer"&gt;my recommendation not to use AWS Control Tower (in its current form)&lt;/a&gt; for setting up and managing multiple accounts in an AWS organization. This post comes from my exploration of alternatives to Control Tower. When you have an AWS organization with numerous accounts to manage, you have some additional tools at your disposal that greatly assist in achieving your goals. However, everyone starts their AWS journey with a single account. We need to know how to secure that single account before scaling it to many accounts. For additional tools available in an organization, I am referring to tools like an Organizational CloudTrail, Service Control Policies (SCPs), and IAM Identity Center (the AWS service formerly known as Single-Sign-On or SSO).&lt;/p&gt;

&lt;p&gt;The first tool we will start with is AWS’s Trusted Advisor. Let’s start with a brand-new account. I  created this account through the standard process any new customer will use and not through an account vending machine [2] process. To create the account, I went to &lt;a href="https://aws.amazon.com/" rel="noopener noreferrer"&gt;https://aws.amazon.com/&lt;/a&gt; and clicked “Create an AWS Account.” During sign-up, I chose the basic (free) support plan.&lt;/p&gt;

&lt;p&gt;After signing up for the new account, we have the root user credentials, which is the email/password provided during setup. While the best practice to never use the root account is commonly known, let’s log in and open AWS Trusted Advisor to see what recommendations it gives us on a brand new account.&lt;/p&gt;

&lt;p&gt;When you first open Trusted Advisor in a new account, it is not immediately obvious that many checks have yet to run. Trusted Advisor presents us with its dashboard with zero recommendations across all severity levels. Click on one of the categories to confirm that the checks have yet to run. Security is a good category to pick because some of that category’s checks are available with a basic support plan. After running a check, you will see something like this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvuplv03smy8d2lhu05k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvuplv03smy8d2lhu05k.png" alt=" " width="244" height="46"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the timestamp of when it was the last run. If you instead see this, click the refresh icon:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fovhpz1ntg1n6hxqt8h6x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fovhpz1ntg1n6hxqt8h6x.png" alt=" " width="258" height="42"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or even better, at the top of the page, click: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fat0x8jwl8duskckgcozc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fat0x8jwl8duskckgcozc.png" alt=" " width="196" height="42"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It shouldn’t take long for the checks available for free to complete. As of this writing, with the basic (free) support plan in Trusted Advisor, there are six security checks and 51 service limit checks.&lt;/p&gt;

&lt;p&gt;Now that all of the checks are updated, click back to the Trusted Advisor dashboard, and we will see the accurate results:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw0ye3twk6cysj904r90m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw0ye3twk6cysj904r90m.png" alt=" " width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We now have two items to fix, MFA on Root Account and IAM Use. We will do that now. We will configure MFA on our Root Account using a Virtual MFA application. Then we will create an IAM user with the AdministratorAccess policy attached, log out of the root user and log back in as that user.&lt;/p&gt;

&lt;p&gt;Now those two items are solved:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctg4rl9y94z97fnsg7i2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctg4rl9y94z97fnsg7i2.png" alt=" " width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the bottom of that screenshot, it explains that we need to upgrade our support plan to get the rest of the Trusted Advisor checks. Let’s do that. I have now upgraded the support plan to the business level ($100 a month), as that’s the lowest cost plan to get the “Full set of checks” [3] in Trusted Advisor. Then I refreshed all of the checks again.&lt;/p&gt;

&lt;p&gt;Now we have a lot more checks, especially in the security area. In total, we now have the following number of checks in each category:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cost Optimization: 22&lt;/li&gt;
&lt;li&gt;Security: 155&lt;/li&gt;
&lt;li&gt;Fault Tolerance: 25&lt;/li&gt;
&lt;li&gt;Performance: 13&lt;/li&gt;
&lt;li&gt;Service Limits: 51 (unchanged)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As of this writing, we have a total of 155 checks in the security category of Trusted Advisor, which is impressive. However, digging a little deeper, I was disappointed to see 134 of those checks have no data because they rely on AWS Security Hub. We still need to set up AWS Security Hub in this account. I have confirmed that if you set up Security Hub but are not paying for at least the business-level support plan, you will not be able to see those 134 Security Hub recommendations in Trusted Advisor. In my opinion, Trusted Advisor should show those 134 checks if you have already configured and are paying for Security Hub (and Config) since Trusted Advisor is just giving you another view of the checks you already have.&lt;/p&gt;

&lt;p&gt;Now back to discussing the 15 new security checks available in Trusted Advisor after we upgraded to the business support plan. We now have recommendations on two in our brand-new empty account. They are:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq155gcsxn90ludf9ybst.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq155gcsxn90ludf9ybst.png" alt=" " width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s easy enough to solve. Now we will set a password policy for our account in IAM (why this isn’t there by default is beyond me.) and set up a multi-region CloudTrail. &lt;/p&gt;

&lt;p&gt;Now let’s refresh the checks in Trusted Advisor, then wait a few minutes. Hmm, now we have two new recommendations:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhixcv1nbjvdakhd7by9f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhixcv1nbjvdakhd7by9f.png" alt=" " width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are now in the fault tolerance section and say “Investigation” is recommended rather than “Action” is recommended, so a lower severity level. The fault tolerance section now looks like this: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp59bgl4ucsamj1e42a6s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp59bgl4ucsamj1e42a6s.png" alt=" " width="800" height="807"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In resolving the previous recommendations, we set up a CloudTrail, which requires an S3 bucket (and CloudTrail created it for us). We can easily enable versioning on that bucket for the second finding here. However, the first finding, “Amazon S3 Bucket Logging”, will be more challenging to solve, which I will explain next.&lt;/p&gt;

&lt;p&gt;To resolve this “Amazon S3 Bucket Logging” recommendation for a bucket, you must configure the bucket’s “server access logging” settings to point to another S3 bucket in the same AWS account [4]. This finding gets flagged on every S3 bucket that does not have “server access logging” configured. I cannot imagine it will go well if you enable a bucket to log to itself. This same account requirement means you will still have this finding on your AWS account unless you create an infinite loop of logging within your account (please don’t).&lt;/p&gt;

&lt;p&gt;A side note, despite AWS’s documentation recommending against using bucket ACLs to grant the S3 log delivery group access to your logging bucket [4]. AWS Support has confirmed bucket ACLs are the only way to satisfy this check in Trusted Advisor. Since I have confirmed, there is no way we can resolve this recommendation for the entire account. I recommend suppressing this one. However, I will leave it here for the rest of this post. The last thing I would like to say is, in addition to the bucket ACL piece. I also find it ridiculous that setting up a CloudTrail to log S3 Data Events does not satisfy this check in Trusted Advisor.&lt;/p&gt;

&lt;p&gt;That brings us to the end of the recommendations Trusted Advisor will provide us on our new AWS account that is not yet running any workloads. Next, let’s move to Security Hub, which requires AWS Config. &lt;/p&gt;

&lt;p&gt;I set up AWS Config and enabled Security Hub with all the security standards. As of the time of this writing, that is the following standards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Foundational Security Best Practices v1.0.0&lt;/li&gt;
&lt;li&gt;CIS AWS Foundations Benchmark v1.2.0&lt;/li&gt;
&lt;li&gt;CIS AWS Foundations Benchmark v1.4.0&lt;/li&gt;
&lt;li&gt;PCI DSS v3.2.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, we give Security Hub a reasonable amount of time to update. I waited until the following day to resume, but Security Hub’s console says it can take up to two hours. Upon updating, Trusted Advisor now has the following recommendations:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbbdmyf5v5cscvbyd00d9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbbdmyf5v5cscvbyd00d9.png" alt=" " width="800" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And Security Hub reports the following:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqd28a1dpt4t4gwh93o9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqd28a1dpt4t4gwh93o9s.png" alt=" " width="788" height="629"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How did AWS decide which checks to bring over to Trusted Advisor? Let’s get started by addressing the recommendations in Trusted Advisor and then we can come back to Security Hub and see what’s left.&lt;/p&gt;

&lt;p&gt;In Trusted Advisor, we have three items in Trusted Advisor’s higher priority category of “action recommended,” all three in security. Those are:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qyzzdhq7u5ha69yo9ho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qyzzdhq7u5ha69yo9ho.png" alt=" " width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are straightforward. Let’s fix these.&lt;br&gt;&lt;br&gt;
We have now enabled GuardDuty.&lt;/p&gt;

&lt;p&gt;Next, the default VPC seems to have a default security group that allows all network traffic from members of that security group to communicate with other members. Usually, I would delete the default VPC, but since Trusted Advisor recommends changes to the default VPC, let’s do that and see where this leads. I’ll remove those inbound and outbound rules from that security group.&lt;/p&gt;

&lt;p&gt;Now to resolve the Hardware MFA recommendation. Previously Trusted Advisor told us to enable MFA for the root user, so we set up virtual MFA as most users do. However, now that we have set up Security Hub, we now see a recommendation that more than virtual MFA is needed. Trusted Advisor is now recommending that we configure a Hardware MFA for the root user. Yubikey is the most common for these hardware MFA keys. The great news is that in July 2022, AWS began providing hardware MFA keys for “U.S.-based AWS account root users who have spent more than $100 each month over the past three months” [5]. If that is you, go to &lt;a href="https://console.aws.amazon.com/securityhub/home/#/free-mfa-security-key" rel="noopener noreferrer"&gt;https://console.aws.amazon.com/securityhub/home/#/free-mfa-security-key&lt;/a&gt; to order your key.  &lt;/p&gt;

&lt;p&gt;In November 2022, AWS made it so that you can assign multiple MFA devices to your user, including the root user. However, I have confirmed that this check will flag non-compliant if you have any Virtual MFA devices assigned to your root user. So if you previously added a virtual MFA to your root user, you will need to remove it while adding the hardware MFA device. Now let’s give Trusted Advisor some time to refresh the checks.&lt;/p&gt;

&lt;p&gt;As we work on remediating these checks that Trusted Advisor is displaying from Security Hub, it is essential to know that the “last updated” timestamp in Trusted Advisor does not necessarily mean when the check was the last run. That timestamp is when Trusted Advisor was last updated, but it’s updating from AWS Config, which may not have checked the resource for several hours. The rule in AWS Config will say that Security Hub created it. To update these in Trusted Advisor, go to AWS Config, find the associated config rule, and then you can force AWS Config to re-evaluate the rule. Once the compliance status in AWS Config is updated, return to Trusted Advisor to refresh the check.&lt;/p&gt;

&lt;p&gt;Now our Trusted Advisor Recommendations look like this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwladtekiy5xdlzc9igsk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwladtekiy5xdlzc9igsk.png" alt=" " width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s take a look at those 2 in the fault-tolerance category:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ib2ipzu1vgjhbmhbmxu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ib2ipzu1vgjhbmhbmxu.png" alt=" " width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first is the bucket logging issue we discussed in depth above. The second is that we do not have versioning enabled on the AWS config bucket. I will enable versioning now on that bucket.&lt;/p&gt;

&lt;p&gt;Then the 15 in the Security category:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsf50xygyuz4obu09ytjb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsf50xygyuz4obu09ytjb.png" alt=" " width="800" height="944"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s start working our way through these.&lt;br&gt;
&lt;strong&gt;1. Amazon EC2 should be configured to use VPC endpoints that are created for the Amazon EC2 service&lt;/strong&gt;&lt;br&gt;
Trusted Advisor wants us to add an Amazon EC2 VPC endpoint to the default VPC. Ok, so this is going to start incurring costs. A basic VPC (without a NAT Gateway, endpoints, flow logs, etc.), like the default VPC, does not incur any charges. But VPC endpoints are not free. Oh well, I’ll create it and see where this leads. We now have a VPC endpoint for the EC2 service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. AWS Config should be enabled&lt;/strong&gt;&lt;br&gt;
This check, “PCI.Config.1”, requires AWS Config to record global resources to pass. [6]  So in AWS Config, I updated our settings to include global resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. CloudTrail log file validation should be enabled&lt;/strong&gt;&lt;br&gt;
We have enabled log file validation on our CloudTrail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. CloudTrail should have encryption at-rest enabled&lt;/strong&gt;&lt;br&gt;
We have enabled Log file SSE-KMS encryption on our CloudTrail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. CloudTrail trails should be integrated with Amazon CloudWatch Logs&lt;/strong&gt;&lt;br&gt;
We have configured CloudWatch logs integration from within our CloudTrail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. EBS default encryption should be enabled&lt;/strong&gt;&lt;br&gt;
Configured new EBS volumes to default to being encrypted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. EC2 subnets should not automatically assign public IP addresses&lt;/strong&gt;&lt;br&gt;
In our default VPC, we turned off “Auto-assign public IPv4 address” on all subnets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Network ACLs should not allow ingress from 0.0.0.0/0 to port 22 or port 3389&lt;/strong&gt;&lt;br&gt;
On our default VPC’s NACL, we have added deny rules for TCP ports 22 and 3389.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. S3 Block Public Access setting should be enabled&lt;/strong&gt;&lt;br&gt;
The two S3 buckets we have at this point already have this setting, but we want to enable “Block Public Access settings for this account” in S3 for the entire account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. S3 bucket server access logging should be enabled&lt;/strong&gt;&lt;br&gt;
We already discussed this earlier in this post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;11. S3 buckets should have event notifications enabled&lt;/strong&gt;&lt;br&gt;
I do not understand why Trusted Advisor runs this check on all buckets. In my experience, it is rare to need event notifications on buckets. When you need them, you set them up. I recommend suppressing this one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12. S3 buckets should have server-side encryption enabled&lt;/strong&gt;&lt;br&gt;
We have now enabled default encryption on the S3 buckets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;13. S3 buckets should require requests to use Secure Socket Layer&lt;/strong&gt;&lt;br&gt;
We have updated the bucket policy on our two buckets to require SSL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;14. S3 buckets with versioning enabled should have lifecycle policies configured&lt;/strong&gt;&lt;br&gt;
We have created a lifecycle rule on each of our buckets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;15. VPC flow logging should be enabled in all VPCs&lt;/strong&gt;&lt;br&gt;
We have created a VPC flow log.&lt;/p&gt;

&lt;p&gt;While I was waiting for all of the checks to refresh, two new recommendations came up in Trusted Advisor:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gjgcn44ci69h69tb2t5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gjgcn44ci69h69tb2t5.png" alt=" " width="800" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To fix those, we will create a group, attach the AdministratorAccess policy to that group, make our IAM user that we’ve been using a member of that group, and disconnect the policy from being directly attached to our user. A new user is automatically attached to the policy IAMUserChangePassword. We will disconnect that as well. Then we will add MFA to our IAM user.&lt;/p&gt;

&lt;p&gt;Now our Trusted Advisor has two recommendations, both at the investigation level.  One in the fault tolerance and the other in the security category. However, both are the server access logging item we have already discussed in depth.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpu1uue6cc1h44q4wfjz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpu1uue6cc1h44q4wfjz.png" alt=" " width="800" height="250"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7sq32n1ommwov3d51l33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7sq32n1ommwov3d51l33.png" alt=" " width="800" height="577"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhm35c4jm1fyfdtrhyzmb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhm35c4jm1fyfdtrhyzmb.png" alt=" " width="800" height="786"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have reached the end of Trusted Advisor giving us recommendations regarding our brand-new AWS account. Trusted Advisor will generate new recommendations if you keep the business or higher support plan as you add workloads to this account. To wrap up this post, let’s look at the remaining items in Security Hub to see what else we may want to do prior to adding workloads.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjy6szfkqaaqerhvmq1zb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjy6szfkqaaqerhvmq1zb.png" alt=" " width="509" height="682"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, this gives us a good amount of additional items to discuss.&lt;/p&gt;

&lt;p&gt;AWS Foundational Security Best Practices v1.0.0:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyf7a2nj3tivaqpwjierh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyf7a2nj3tivaqpwjierh.png" alt=" " width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CIS AWS Foundations Benchmark v1.2.0:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdwvbxx0i1yl9a09k7ni.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdwvbxx0i1yl9a09k7ni.jpg" alt=" " width="800" height="1238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CIS AWS Foundations Benchmark v1.4.0&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F38xvkxc52slh2vxkniag.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F38xvkxc52slh2vxkniag.jpg" alt=" " width="800" height="1095"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;In the next post, we will start resolving the findings that Security Hub is giving us. We can also run Cloud Mapper. What else should we use to scan our account for possible results? Let me know in the comments or via &lt;a href="https://twitter.com/JonHolman_" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Please let me know if you would like to see me run any other tools against this account to report on Security Issues.&lt;/p&gt;

&lt;p&gt;References:&lt;br&gt;
[1] &lt;a href="https://aws.amazon.com/organizations/getting-started/best-practices/" rel="noopener noreferrer"&gt;https://aws.amazon.com/organizations/getting-started/best-practices/&lt;/a&gt;&lt;br&gt;
[2] &lt;a href="https://aws.amazon.com/blogs/mt/automate-account-creation-and-resource-provisioning-for-aws-govcloudus-using-aws-service-catalog-aws-organizations-and-aws-lambda/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/mt/automate-account-creation-and-resource-provisioning-for-aws-govcloudus-using-aws-service-catalog-aws-organizations-and-aws-lambda/&lt;/a&gt;&lt;br&gt;
[3] &lt;a href="https://aws.amazon.com/premiumsupport/plans/" rel="noopener noreferrer"&gt;https://aws.amazon.com/premiumsupport/plans/&lt;/a&gt;&lt;br&gt;
[4] &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerLogs.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerLogs.html&lt;/a&gt;&lt;br&gt;
[5] &lt;a href="https://aws.amazon.com/blogs/security/eligible-customers-can-now-order-a-free-mfa-security-key/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/security/eligible-customers-can-now-order-a-free-mfa-security-key/&lt;/a&gt;&lt;br&gt;
[6] &lt;a href="https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-pcidss-to-disable.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-pcidss-to-disable.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>emptystring</category>
    </item>
    <item>
      <title>Go Global with AWS - Use AWS CloudFormation StackSets to go global with ease</title>
      <dc:creator>Jon Holman</dc:creator>
      <pubDate>Thu, 07 Apr 2022 02:35:59 +0000</pubDate>
      <link>https://forem.com/jonholman_/go-global-with-aws-use-aws-cloudformation-stacksets-to-go-global-with-ease-45ld</link>
      <guid>https://forem.com/jonholman_/go-global-with-aws-use-aws-cloudformation-stacksets-to-go-global-with-ease-45ld</guid>
      <description>&lt;p&gt;I have wanted to create a multi-region, global application with AWS to see how much effort it would be. I finally took some time to create a simple proof of concept, and I am amazed by how easy it was, thanks to CloudFormation StackSets. CloudFormation StackSets makes deploying the same stack to multiple regions and accounts easy. AWS CDK, AWS SAM, AWS CloudFormation, and I'm sure many other IaC tools can create CloudFormation StackSets. To keep this proof of concept small and straightforward, I used AWS SAM to simplify the creation of the CloudFormation resources for AWS Lambda with Amazon API Gateway in the regional deployments. These concepts can easily be applied to a more robust application to deploy across multiple accounts and regions.&lt;/p&gt;

&lt;p&gt;To begin, I created a simple single-region template using AWS SAM and its CLI while I worked on that. With the lambda function's code inline within the template. Once I had the per region template the way I wanted, I moved that template to be nested within a CloudFormation StackSet. I would have preferred to be able to keep the nested template as YAML. Supplying YAML for the StackSet's TemplateBody attributed resulted in the error message 'expected type: String, found: JSONObject.' It wanted a string, so I put a | after TemplateBody, which converted the YAML indented below into a multiline string. This sample leverages 16 AWS regions. The last time I ran it, it completed deployment in 18 minutes.&lt;/p&gt;

&lt;p&gt;This simple proof of concept only has one file, template.yaml, available in the GitHub gist below with instructions to deploy and remove beneath it.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;To deploy this sample application, you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  An AWS Account&lt;/li&gt;
&lt;li&gt;  A Route53 hosted zone in the AWS account you are using&lt;/li&gt;
&lt;li&gt;  Then to deploy, you can use any of these:

&lt;ul&gt;
&lt;li&gt;  The AWS CLI installed on your workstation &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;Install the AWS CLI&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  The AWS SAM CLI installed on your workstation &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html" rel="noopener noreferrer"&gt;Install the SAM CLI&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  The AWS CloudFormation Console&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  To deploy your application for the first time.
&lt;/h3&gt;

&lt;p&gt;Run the following in your shell (replace &amp;lt;domain_name&amp;gt; and &amp;lt;route_53_zone_id&amp;gt; for your environment):&lt;/p&gt;

&lt;p&gt;For the AWS CLI:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aws cloudformation create-stack --region us-east-1 --stack-name multi-region-sam-poc --template-body file://template.yaml --capabilities CAPABILITY_NAMED_IAM --parameters ParameterKey=DomainName,ParameterValue=&amp;lt;domain_name&amp;gt; ParameterKey=Route53ZoneId,ParameterValue=&amp;lt;route_53_zone_id&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For the AWS SAM CLI:&lt;br&gt;&lt;br&gt;
&lt;code&gt;sam deploy --guided&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  To delete the sample application that you created.
&lt;/h3&gt;

&lt;p&gt;For the AWS CLI:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aws cloudformation delete-stack --region us-east-1 --stack-name multi-region-sam-poc&lt;/code&gt;  &lt;/p&gt;

&lt;p&gt;For the AWS SAM CLI:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sam delete&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading. If you have any questions or feedback, please leave a comment or message me on Twitter, link to the right.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>tooling</category>
      <category>serverless</category>
    </item>
    <item>
      <title>My Favorite Infrastructure as Code (IAC) Tool</title>
      <dc:creator>Jon Holman</dc:creator>
      <pubDate>Tue, 22 Mar 2022 14:14:35 +0000</pubDate>
      <link>https://forem.com/aws-builders/my-favorite-infrastructure-as-code-iac-tool-1m1k</link>
      <guid>https://forem.com/aws-builders/my-favorite-infrastructure-as-code-iac-tool-1m1k</guid>
      <description>&lt;p&gt;If you've read my previous blog posts, you know I love serverless technologies. My love of serverless technologies comes from my passion for creating performant, efficient solutions. My favorite aspect of serverless technologies is its ability to 'scale to zero,' meaning that when there are no requests, there are no consumed resources (other than persistent storage) and therefore no costs. Of course, we can turn off every type of web service. However, the difference between traditional systems and good serverless solutions is that our web service is still online and available to accept requests. Until there's a request to act on, nothing is running. While there are no running resources, there are no costs.&lt;/p&gt;

&lt;p&gt;Other points about serverless technologies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Of course, there are still servers.

&lt;ul&gt;
&lt;li&gt;  The servers are abstracted away by the Cloud Provider, and the application team does not manage or need to worry about them.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  The service's pricing follows a pay-per-use model.

&lt;ul&gt;
&lt;li&gt;  Pay for actual usage, not per hour of your web service being available.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  No need for capacity planning and buying for your expected peak usage

&lt;ul&gt;
&lt;li&gt;  The managed services scale to meet demand.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  Highly available / fault-tolerant

&lt;ul&gt;
&lt;li&gt;  The services are multi-AZ.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The serverless technology that I use the most is AWS Lambda. AWS Lambda is AWS's Functions-as-a-Service offering. When I first started using AWS Lambda, it was through the console. Managing lambdas, or any resource, through the console is not recommended because it is slow, not scalable, challenging to improve iteratively, and risks inconsistency from one environment to the next. The best practice is to have everything defined as code. In addition to our application code, we should also define our infrastructure as code. Our Infrastructure-as-Code (IaC) makes it easy to improve iteratively, easily repeatable (i.e., create another environment), and ready to be moved into an automated pipeline.&lt;/p&gt;

&lt;p&gt;I originally started using the serverless framework to move to an IaC solution for my lambda projects. As I started using the serverless framework, I quickly found that while it is best practice to manage your serverless projects as IaC. It is easier as well. With everything for the project in code, the infrastructure, and the actual application, you can commit it to version control (Git) and then iteratively improve on it from there. With git diff, you can always compare what you currently have in your working copy to what was last committed. I find that setup very productive while working on the solution I am creating. Then using a framework, like the serverless framework, makes it a lot easier to manage configuration items. The first thing I remember finding with the serverless framework was how much easier it was to manage cross-origin resource sharing (CORS) settings with its IaC rather than directly in the console. &lt;/p&gt;

&lt;p&gt;Later I moved to a company where CloudFormation was the de facto IaC. I had previously used Terraform for all of my non-serverless IaC project needs. Soon after, I learned about AWS Serverless Application Model (SAM). AWS SAM is an extension of AWS CloudFormation that makes the building of serverless projects even more efficient. AWS SAM includes a CloudFormation macro, referenced in the template under the Transform key. That macro adds to the library of CloudFormation resources a group of elements prefaced with AWS::Serverless. When you include that transform in your CloudFormation template, you can use those additional resources in your template. When the CloudFormation service receives your template, the macro will run and expand those elements into the actual CloudFormation required had you not used the macro. This process often will create many more CloudFormation resources to accomplish what the SAM macro had simplified for you. After processing that transform, CloudFormation creates the resources like it usually would.&lt;/p&gt;

&lt;p&gt;AWS SAM also includes a useful CLI that quickly enables us to get things done from the command line. So in my IaC journey, I then moved to use AWS SAM. I was pleased with AWS SAM until I started trying out the AWS Cloud Development Kit (CDK). The AWS CDK won me over because it uses the programming languages we already use for our projects. Using the programming languages, we are already familiar with obviously brings that language's looping and conditional logic, which can be a massive plus to writing and maintaining IaC. Another big plus is using the programming language to provide familiar ways to abstract reusable code into modules (which CDK calls Level 3 Constructs).&lt;/p&gt;

&lt;p&gt;I was happy with AWS CDK and could not imagine a way for it to get better. But it did. The team at &lt;a href="https://serverless-stack.com/" rel="noopener noreferrer"&gt;serverless-stack.com&lt;/a&gt; created SST. As the project's GitHub page states, 'Serverless Stack (SST) is a framework that makes it easy to build serverless apps. It's an extension of &lt;a href="https://aws.amazon.com/cdk/" rel="noopener noreferrer"&gt;AWS CDK&lt;/a&gt; and its features'. My favorite features of SST are its CLI and the Static Site construct it provides. The CLI and Static Site construct enables you to create a serverless application's backend and frontend (full-stack) in one project and deploy it with a single command. Your frontend application will require a reference to the backend for the application to work. The provided CLI and construct does that for you. &lt;/p&gt;

&lt;p&gt;Not only does SST extend AWS CDK, to make it even better. The Serverless-Stack team provides full-stack demo applications, including a step-by-step guide to build a full-stack notes application. Having a working full-stack application to play around with eased the task for me to get comfortable working on frontend applications using both React and Angular.&lt;/p&gt;

&lt;p&gt;SST also features a live lambda development environment. To me, this is magical. When you are in an SST project at your terminal, you run npx sst start. At that point, it deploys resources to your AWS account and runs locally in this live development mode. You can then edit your lambda functions locally, and they immediately take effect. I do not need to understand how this works fully. I know it works, and it's fantastic. I have navigated around in the console from the projects I have used SST. From that observation, I understand that SST creates a particular lambda function for local development stacks and an associated API Gateway configured for WebSockets. I gather that the particular lambda function and WebSockets API Gateway relay the requests back to your running SST CLI where the dev environment invocations occur. If you would like to learn more about how that works, I see they documented it at &lt;a href="https://docs.serverless-stack.com/live-lambda-development#sst-start" rel="noopener noreferrer"&gt;https://docs.serverless-stack.com/live-lambda-development#sst-start&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even then, I thought it could not get better than SST. The team at serverless-stack then added a console on their website that you can launch and easily manage many resources within your stack that is currently running using your local CLI.&lt;/p&gt;

&lt;p&gt;I encourage you to give SST a try.&lt;/p&gt;

&lt;p&gt;References:&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://serverless-stack.com/" rel="noopener noreferrer"&gt;https://serverless-stack.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.serverless-stack.com/" rel="noopener noreferrer"&gt;https://docs.serverless-stack.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.serverless-stack.com/live-lambda-development#sst-start" rel="noopener noreferrer"&gt;https://docs.serverless-stack.com/live-lambda-development#sst-start&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-resources-and-properties.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-resources-and-properties.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>tooling</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Part 5: Adding CloudFront</title>
      <dc:creator>Jon Holman</dc:creator>
      <pubDate>Sun, 17 Jan 2021 23:19:11 +0000</pubDate>
      <link>https://forem.com/aws-builders/part-5-adding-cloudfront-557h</link>
      <guid>https://forem.com/aws-builders/part-5-adding-cloudfront-557h</guid>
      <description>&lt;p&gt;In this post we will add a CloudFront distribution to our project.  Amazon CloudFront is AWS's Content Delivery Network (CDN).  Other popular CDNs that you may have heard of are Cloudflare, Fastly, and Akamai.  A CDN has servers distributed around the world for the purpose of caching content closer to its end users.  By caching content closer to the end users, the resources load faster and less traffic has to be served by your origin (the term used to refer to the source of the content).&lt;/p&gt;
&lt;p&gt;This post picks up where we left off in &lt;a href="https://www.jonholman.com/ComputelessBlog/part-4-adding-category-landing-and-home-page" rel="noopener noreferrer"&gt;Part 4: Adding the category and home landing pages&lt;/a&gt;.  If you did not go through that post, you can get the project files we will be using as our starting point &lt;a href="https://github.com/JonHolman/ComputelessBlog/tree/part-4-adding-category-landing-and-home-page" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The first thing we will do is add an additional item to the resources section of our template.yaml file.  This new item in the template.yaml file will create our CloudFront Distribution when we apply our updated CloudFormation template (via sam deploy).&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;p&gt;Then we will add an additional item to the output section of our template.yaml file.  This new output will provide us with the URL to our CloudFront distribution.&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
As a part of the CloudFront distribution that we added, we specified that the OriginPath is /Prod.  That tells CloudFront to use that base path for all items being requested from origin.  So, now with that added, we should remove /Prod from the hyperlinks in our page.yaml file.  We can just do a global replace to remove /Prod from our page.yaml file, it should be there 5 times.&lt;p&gt;Now let's run sam deploy so that these CloudFormation updates will be deployed.&lt;/p&gt;
&lt;p&gt;When sam deploy completes, you will see our new output being returned for the CloudFrontDistribution's URL which will be something like &lt;a href="https://d2mubg31z2zmzn.cloudfront.net/" rel="noopener noreferrer"&gt;https://d2mubg31z2zmzn.cloudfront.net/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I can imagine a possibility that someone would like their blog to have a more memorable URL than &lt;a href="https://d2mubg31z2zmzn.cloudfront.net/." rel="noopener noreferrer"&gt;https://d2mubg31z2zmzn.cloudfront.net/.&lt;/a&gt;  So, in this next section we will add a custom domain for our CloudFront distribution.&lt;/p&gt;
&lt;p&gt;First, you will need a domain name and the ability to add a DNS record to that domain.  For this the AWS answer is Route 53, but like I have said in previous posts, I am a very cost conscious person.  With that in mind, I cannot get myself to pay 50 cents a month for a hosted zone when every other registrar I have used provides that service for free.  Yes, even though AWS took liberties with the DNS standard and added their own record type (ALIAS).  I still don't think it is worth 50 cents a month.&lt;/p&gt;
&lt;p&gt;To add a custom domain to our blog, via CloudFront, we will first add parameters and conditions to our CloudFormation template.  There will be two parameters, one for the DNS alias we want to use and the second for the Amazon Resource Names (ARN) for your certificate stored in AWS Certificate Manager.  We are adding these as parameters, so we do not have to hard code these values in the CloudFormation template.  The conditions are being added, so that if anyone wants to follow along and they do not have a domain name they want to use (or maybe they are happy with a URL like &lt;a href="https://d2mubg31z2zmzn.cloudfront.net/)," rel="noopener noreferrer"&gt;https://d2mubg31z2zmzn.cloudfront.net/),&lt;/a&gt; they can just leave these new parameters blank and CloudFormation will apply just like it did before we added them.  To add those parameters and conditions add the following to your template.yaml file.  I like to add these right above the Mappings sections.&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;p&gt;Next we will add logic to our CloudFront resource in template.yaml to add the alias and certificate properties when the condition we defined above is met.  To do that add these lines to the bottom of the &lt;code&gt;AWS::CloudFront::Distribution&lt;/code&gt; resource.&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;p&gt;And last, we will add another output to our CloudFormation template.  This output will give us the information we need to create our CNAME DNS record to point it to our CloudFront Distribution.  To add that just add the following to the outputs section of your template.yaml file.&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;p&gt;That is everything we need to add to our project files.  If you will be using a custom domain, we need a certificate in AWS Certificate Manager (ACM) before we are ready to apply our updated CloudFormation.  We will be using a public certificate, so it is free.  Since generating a certificate is not usually something you want to do often, and I commonly create/destroy CloudFormation stacks while keeping the certificate, I do it outside of the CloudFormation.  This is one of the rare things that I feel is ok to do through the AWS console.  To create a certificate in AWS Certificate Manger follow these steps.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open your web browser and log into the AWS console.&lt;/li&gt;
&lt;li&gt;Search for Certificate Manager and open it.&lt;/li&gt;
&lt;li&gt;Click request a certificate.&lt;/li&gt;
&lt;li&gt;Keep request a public certificate selected and press request a certificate.&lt;/li&gt;
&lt;li&gt;For domain name enter the domain name that you will be using for your blog (I commonly create a wildcard certificate by entering *.domainname.tld, like *.jonholman.com) and press next.  Note that it would be helpful to have the wildcard (*) certificate as we will be using the same certificate later when we create our admin page.&lt;/li&gt;
&lt;li&gt;Now for validation method you can pick whichever option you prefer, either way you need to validate 'that you own or control the domains for which you are requesting the certificate' and press next.&lt;/li&gt;
&lt;li&gt;On the next page you can add any AWS tags to your certificate, if you would like, and press review.&lt;/li&gt;
&lt;li&gt;Then review your selections and press confirm and request.&lt;/li&gt;
&lt;li&gt;The next screen will give you information about how your validation is being processed, press continue.&lt;/li&gt;
&lt;li&gt;Now follow the instructed steps to complete validation of your request.&lt;/li&gt;
&lt;li&gt;Soon after you complete validation, you can refresh the AWS Certificate Manager console and see that your certificate has updated from pending validation to issued.&lt;/li&gt;
&lt;li&gt;Expand your new certificate and copy the ARN value, so that you can provide it to our next step as the value to that parameter.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now that we have added those items and generated our certificate (if necessary), let's deploy these changes by running sam deploy --guided.  Note that we need to run sam deploy with the guided option this time to allow us to add values for the two new parameters that we added.  If you will not be adding a custom domain name just press enter for each of those values when prompted.&lt;/p&gt;
&lt;p&gt;Our project directory at the end of this post is available &lt;a href="https://github.com/JonHolman/ComputelessBlog/tree/part-5-adding-cloudfront" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks for reading.  If you have any feedback or questions, please leave a comment or message me on Twitter/LinkedIn (links on the right side bar).&lt;/p&gt;
&lt;p&gt;In the next post, we will move our static assets to an S3 Bucket so that our web pages do not rely on resources from other web sites.  We will then add that s3 bucket as another origin on this same CloudFront distribution.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>serverless</category>
      <category>database</category>
    </item>
    <item>
      <title>Part 4: Adding the category and home landing pages</title>
      <dc:creator>Jon Holman</dc:creator>
      <pubDate>Sun, 17 Jan 2021 23:15:44 +0000</pubDate>
      <link>https://forem.com/aws-builders/part-4-adding-the-category-and-home-landing-pages-3feo</link>
      <guid>https://forem.com/aws-builders/part-4-adding-the-category-and-home-landing-pages-3feo</guid>
      <description>&lt;p&gt;In this post we will add category and home landing pages to our blog.  We will add HTTP 404 responses for invalid URLs as well. &lt;/p&gt;
&lt;p&gt;This post picks up where we left off in &lt;a href="https://www.jonholman.com/ComputelessBlog/part-3-adding-api-gateway-and-article-page" rel="noopener noreferrer"&gt;Part 3: Adding API Gateway and the Article Page&lt;/a&gt;.  If you did not go through that post, you can get the project files we will be using as our starting point &lt;a href="https://github.com/JonHolman/ComputelessBlog/tree/part-3-adding-api-gateway-and-article-page" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let's start by adding some logic to handle requests for items that do not exist in our DynamoDB table.  At the same time we will also create a variable ($type) that will help us later as we do things that are specific to the article page or the landing pages.  Add the following to your page.yaml file after &lt;code&gt;#set($items = $input.path('$.Items'))&lt;/code&gt; so this new content should start on line 3.&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;p&gt;In this new VTL code, the first thing we do is set a variable $count to the size of the array that is returned.  We then have an if..elseif..else based on that $count variable.  If $count is 0 we know our table did not have any data to return for that query, so we should respond with a 404 and a clear message to inform the user.  Next we handle the condition where $count is 1 and the current path contains the entire sk of that 1 item.  I added the and condition so that when we are just starting and only have one post so far, the home page does not end up using the article page template.  Then we get to the else condition, all other combinations of data (count &amp;gt; 1 or count is 1 but that 1 item's sk is not completely in the path) will have the $type variable set to category.  We will use that $type variable throughout the rest of this post to handle items that are specific to one page type and not the other.&lt;/p&gt;
&lt;p&gt;Next, let's set the page title based on certain conditions.  I do not want the page title to always be the same thing.  I like for the home page to have the base page title, then the category pages to have a page title to let you know you're on a category page, and then the article page titles can include the title of the article.  To do that, replace the line &lt;code&gt;&amp;lt;title&amp;gt;W3.CSS Template - $items[0].heading.S&amp;lt;/title&amp;gt;&lt;/code&gt; in our page.yaml file with the block below.&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;p&gt;You will notice in the code block above that we are not using the $type = category condition for the category page.  We were unable to use that condition in this case because that same condition applies to the home page (as there are only two distinct page types article and category), all future uses of category will be identical.  However in this case we are creating a distinction between the home page and a category page.  So we are identifying the category page as not an article page and has more than two slashes in the context path.&lt;/p&gt;
&lt;p&gt;Now we will introduce the changes to the main content section of the page, which will be different for article pages vs category landing pages (including the home page).  The easiest way to make this update is to just replace that section of the page.yaml file and then we'll talk through what the new section is doing.  The block below will replace from the line &lt;code&gt;&amp;lt;!-- Blog entries --&amp;gt;&lt;/code&gt; to the line &lt;code&gt;&amp;lt;!-- Introduction menu --&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;p&gt;Now let's go through what we just changed.  The main change here is creating a main content section for when $type is article and when it is not (landing pages).  Another update is within the article page header we are now looping through the sk value to create links to any categories the article may be nested underneath.  This will allow you to have many levels of categories, if you would like.  We have also wrapped the article description and date in a condition to verify description is not empty, this is to cleanup for HTTP 404 response as I did not have any content I wanted to put in the description field for a 404 response.&lt;/p&gt;
&lt;p&gt;The aspect that is very different from the landing pages to the article page is since the landing page will potentially have many articles to list, it is truncating each article to its first paragraph.  That was the idea I came up with on how to easily give a preview of each article, open to other ideas.&lt;/p&gt;
&lt;p&gt;Now we will update our openapi.yaml file.  Our category landing page will be served by the route that we already defined in our openapi.yaml file.  The route we defined in the previous post is using a greedy path that accepts everything after the slash for the API Gateway's root.  So the category requests will go there as well.  That is not a problem, the minor modification we need to make for the query to support the category requests will still allow the article requests to function properly.  In our query to support that greedy path request, we will change it from looking for an exact match on pk and sk, to looking for a match on pk with an sk that begins with the provided path parameter.  This will match the single article, when an article is requested, while still matching the entire category when a category is requested.&lt;/p&gt;
&lt;p&gt;To make that change all you have to do in the openapi.yaml file is replace the line &lt;code&gt;'KeyConditionExpression': 'pk = :pk and sk = :sk',&lt;/code&gt; with &lt;code&gt;'KeyConditionExpression': 'pk = :pk and begins_with(sk, :sk)',&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Now for our final change for this post, to create a root route for the home page.  As we discussed in &lt;a href="https://www.jonholman.com/ComputelessBlog/part-2-planning-the-data-model" rel="noopener noreferrer"&gt;Part 2: Planning the Data Model&lt;/a&gt; our home page will use the access pattern 'Get all articles sorted by date' that is not limited to a single category.  This will be achieved by leveraging the Local Secondary Index (LSI) we created on our DynamoDB table.  An LSI uses the same pk, but a different sk.  The sk for our LSI is date, so we can get all articles sorted by date.  Add the following to the paths section of your openapi.yaml file, I like to put the root route as the first route in the paths section.&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;p&gt;You will notice that this route's configuration is almost identical to the greedy route that we are using for everything else.  The only differences are this is using another index, this is querying by only pk (to get all articles), and I specified False for ScanIndexForward so that the newest articles will be a the top of the page.&lt;/p&gt;
&lt;p&gt;Now run sam deploy and wait for AWS SAM to deploy all of our updates.  When it is complete you can browse directly to the root of our API Gateway endpoint (as it is output by the sam deployment).  You will see that the home page is now working.  You can also append a non-existing page to the URL to see the HTTP 404 response.&lt;/p&gt;
&lt;p&gt;Our project directory at the end of this post is available &lt;a href="https://github.com/JonHolman/ComputelessBlog/tree/part-4-adding-category-landing-and-home-page" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks for reading.  If you have any feedback or questions, please leave a comment or message me on Twitter/LinkedIn (links on the right side bar).&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>serverless</category>
      <category>database</category>
    </item>
    <item>
      <title>Part 3: Adding API Gateway and the Article Page</title>
      <dc:creator>Jon Holman</dc:creator>
      <pubDate>Sun, 17 Jan 2021 18:53:35 +0000</pubDate>
      <link>https://forem.com/aws-builders/part-3-adding-api-gateway-and-the-article-page-1ai4</link>
      <guid>https://forem.com/aws-builders/part-3-adding-api-gateway-and-the-article-page-1ai4</guid>
      <description>&lt;p&gt;In this post we will add API Gateway to our CloudFormation template and add the article page endpoint to API Gateway using OpenAPI.  We will demo that new endpoint being used to display the sample article we added directly to the DynamoDB table in the previous post.&lt;/p&gt;
&lt;p&gt;This post picks up where we left off in &lt;a href="https://www.jonholman.com/ComputelessBlog/part-2-planning-the-data-model" rel="noopener noreferrer"&gt;Part 2: Planning the Data Model&lt;/a&gt;.  If you did not go through that post, you can get the project files we will be using as our starting point &lt;a href="https://github.com/JonHolman/ComputelessBlog/tree/part-2-planning-the-data-model" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First we need to create an IAM role for our API Gateway to use.  This role will give API Gateway the permissions it needs to read the data from our DynamoDB Table.  We will then create our API Gateway and add an output to our CloudFormation template.  That output we are adding will return the URL to our new endpoint.  To add these CloudFormation resources, add the following to your template.yaml file.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;p&gt;In the API Gateway definition above, you will notice that it is very brief and it references another file openapi.yaml.  That is the OpenAPI definition file and almost all of the API definition occurs in that file.  Create a new file in your project named openapi.yaml with the content below:&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;p&gt;Let's walk through what is going on in this openapi.yaml file.  The important part of this file is the paths section.  Right now we are defining only one path, but it is a very powerful path definition, using what is called a greedy path parameter.  The path specification of &lt;code&gt;/{path+}&lt;/code&gt; defines that any request that is more than just the root of the API Gateway endpoint will be accepted by this route.  Also everything in the URL after the API Gateway's root will be available to us as a parameter named path.  On the next line we specify the only HTTP verb that we are configuring for that path at this time, the GET HTTP verb.&lt;/p&gt;
&lt;p&gt;Now for the section &lt;code&gt;x-amazon-apigateway-integration&lt;/code&gt; this is where all of the magic happens with API Gateway and it proxying DynamoDB for us.  The highlights are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;credentials - this references the IAM role that we created in template.yaml which gives our API Gateway permissions to access our DynamoDB table.&lt;/li&gt;
&lt;li&gt;uri - this is where we specify what action we want this endpoint to execute on our DynamoDB table, we want to Query a DynamoDB table in us-east-1 so our uri is 'arn:aws:apigateway:us-east-1:dynamodb:action/Query'&lt;/li&gt;
&lt;li&gt;responseTemplates - this is where we define exactly how we want the response to be presented to the requestor.  We have Velocity Template Language (VTL) available to use to transform the response however we need.&lt;/li&gt;
&lt;li&gt;requestTemplates - this is where we define exactly how we want our request to be executed against DynamoDB.  We also have VTL available to us here, so that we can transform the request from how it comes into API Gateway to exactly how it needs to be for DynamoDB.  In this example I am using the intrinsic function Fn::Join to substitute in our DynamoDB table name (named the same as our stack) so that our table name is not hardcoded.&lt;/li&gt;
&lt;li&gt;httpMethod - For DynamoDB actions the httpMethod from API Gateway to DynamoDB should always be POST.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One last edit we should make to our template.yaml file, is we will add a Globals: section and in there define OpenApiVersion: '3.0.1'.  This setting is necessary to resolve a &lt;a href="https://github.com/aws/serverless-application-model/issues/191" rel="noopener noreferrer"&gt;bug with AWS SAM&lt;/a&gt; and without this setting we end up with two Stages being created in API Gateway - the stage Prod that we are specifying and an additional stage named Stage.  I usually create the Globals section in template.yaml after Description (at line 3), the additional block will look like:&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;p&gt;Now we can run sam deploy (if this is your first time, run sam deploy --guided and I suggest allowing it to save your configuration to a file) to update our CloudFormation stack.  When AWS SAM completes updating our CloudFormation stack it will return the URL to our API Gateway as WebSiteOrigin.  For me the URL was &lt;a href="https://40hfrzhqoj.execute-api.us-east-1.amazonaws.com/Prod." rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://40hfrzhqoj.execute-api.us-east-1.amazonaws.com/Prod" rel="noopener noreferrer"&gt;https://40hfrzhqoj.execute-api.us-east-1.amazonaws.com/Prod&lt;/a&gt;.  We can browse to that URL with our sample record's sk value (/sample/post-1) appended to the end, and you will see a display like below:&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.jonholman.com%2Fpart-3-adding-api-gateway-and-article-page-1.png" width="800" height="400"&gt;&lt;p&gt;This is displaying the values we set in that DynamoDB sample record to our browser, via API Gateway.  Now we probably want this information to be displayed in a nicer way than this, so we need to add some HTML and accompanying CSS.  For that I will turn to w3schools.com which has many prebuilt HTML templates to choose from. I will use &lt;a href="https://www.w3schools.com/w3css/tryit.asp?filename=tryw3css_templates_blog&amp;amp;stacked=h" rel="noopener noreferrer"&gt;their blog template&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I made some minor changes to that template, removing items that we will not cover adding functionality for at this point (most popular posts, tagging posts, comments) and removing placeholder images. We can add that functionality later in this series if it is desired.&lt;/p&gt;
&lt;p&gt;The page template is somewhat large and is quite distracting when defined within the CloudFormation or OpenAPI template files, so I opted to move it to another file and import it from there into a CloudFormation mapping (another file cannot be imported into the openapi.yaml file).  In the future we will also need to associate this same page template with our home page, to avoid a lot of duplication, so having it defined elsewhere and able to be reference it where needed will make that cleaner as well.&lt;/p&gt;
&lt;p&gt;To do that, let's first add the mapping section to our template.yaml file, I like to place the mappings between global and resources.&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;p&gt;Then create the page.yaml file.  This is the template from w3schools, minus the sections I removed, with our VTL variables being placed within to put our articles stored in DynamoDB in to the template.&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;p&gt;and finally let's replace the responseTemplates section in the openapi.yaml file with this below to use that new CloudFormation mapping for its response template.&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;p&gt;Now, let's run sam deploy again and then browse to the page&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.jonholman.com%2Fpart-3-adding-api-gateway-and-article-page-2.png" width="800" height="400"&gt;&lt;p&gt;That's what I was looking for.  I am not a front-end developer.  If you are, I'm sure you can make a page template that fits your style much better, but for me, this works.&lt;/p&gt;
&lt;p&gt;Please note that, I have noticed for the API Gateway definition to update and deploy the changes, the openapi.yaml file needs to be modified.  Just modifying the page.yaml file that is referenced within it via a mapping is not enough, so I usually just make minor edits to the description field at the top of the OpenAPI file, like add a 1.&lt;/p&gt;
&lt;p&gt;Our project directory at the end of this post is available &lt;a href="https://github.com/JonHolman/ComputelessBlog/tree/part-3-adding-api-gateway-and-article-page" rel="noopener noreferrer"&gt;&lt;u&gt;here&lt;/u&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks for reading.  If you have any feedback or questions, please leave a comment or message me on Twitter/LinkedIn (links on the right side bar).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.jonholman.com/ComputelessBlog/part-4-adding-category-landing-and-home-page" rel="noopener noreferrer"&gt;Continue with the next article in this series.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>serverless</category>
      <category>database</category>
    </item>
    <item>
      <title>Part 2: Planning the Data Model</title>
      <dc:creator>Jon Holman</dc:creator>
      <pubDate>Sun, 17 Jan 2021 18:48:21 +0000</pubDate>
      <link>https://forem.com/aws-builders/preparing-to-create-our-computeless-blog-by-preparing-our-data-model-g85</link>
      <guid>https://forem.com/aws-builders/preparing-to-create-our-computeless-blog-by-preparing-our-data-model-g85</guid>
      <description>&lt;p&gt;As the first step to creating our blog, we need to create our DynamoDB table that will store all of our content. To prepare to create our DynamoDB table, we will list out all of our data access patterns, so we can select our partition key(s) and sort key(s), and determine if we need to create any additional indexes.&lt;/p&gt;
&lt;p&gt;This is Part 2 of our series on creating a Computeless Blog.  If you did not read the introduction, it can be found &lt;a href="https://www.jonholman.com/ComputelessBlog/part-1-introduction" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To do this, I brainstormed the access patterns I anticipate and came up with this list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get all articles sorted by date&lt;/li&gt;
&lt;li&gt;Get all articles for a category sorted by date&lt;/li&gt;
&lt;li&gt;Get one specific article&lt;/li&gt;
&lt;li&gt;Get all comments for an article&lt;/li&gt;
&lt;li&gt;Get the number of comments for each article&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We will then put these in a table with pk (partition key) and sk (sort key) to identify what keys we will need to query that data.&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Access Pattern&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PK&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;SK&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Get all articles sorted by date&lt;/td&gt;
&lt;td&gt;article&lt;/td&gt;
&lt;td&gt;date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Get all articles for a category sorted by date&lt;/td&gt;
&lt;td&gt;article&lt;/td&gt;
&lt;td&gt;article path begins with category name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Get one specific article&lt;/td&gt;
&lt;td&gt;article&lt;/td&gt;
&lt;td&gt;article path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Get all comments for an article&lt;/td&gt;
&lt;td&gt;comment&lt;/td&gt;
&lt;td&gt;begins with article path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Get the number of comments for each article&lt;/td&gt;
&lt;td&gt;comment&lt;/td&gt;
&lt;td&gt; &lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;To satisfy these access patterns, we will create a single DynamoDB table with a partition key and sort key.  To avoid locking our partition key and sort key into a specific use case, we will name them generically as pk and sk.  That still leaves us without a way to solve the first access pattern 'Get all articles sorted by date' as the articles will have its PK as article and its SK as the article path.  We will then add a Local Secondary Index (LSI) to satisfy that access pattern.&lt;/p&gt;
&lt;p&gt;To learn more about data modeling for DynamoDB I recommend:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://youtu.be/DIQVJqiSUkE" rel="noopener noreferrer"&gt;Watching Alex DeBrie's talk from AWS re:Invent 2019: Data modeling with Amazon DynamoDB (CMY304)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.alexdebrie.com/posts/dynamodb-single-table/" rel="noopener noreferrer"&gt;Reading Alex DeBrie's post The What, Why, and When of Single-Table Design with DynamoDB&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now let's discuss ReadCapacityUnits (RCU) and WriteCapacityUnits (WCU) on the DynamoDB table.  We could set the &lt;code&gt;BillingMode: PAY_PER_REQUEST&lt;/code&gt; on our DynamoDB table, to have it scale based on demand, but I do not like not being able to set an upper limit of how high it may go.  Like I said in the introduction, I am a very cost conscious person, so I like to have control of these settings.  I also really like how DynamoDB's burst capacity works, it's like rollover minutes on a cell phone plan, but instead of last month we're looking at the last 5 minutes.  Accumulating the last 5 minutes of unused RCU and WCU we can get some great burst capacity, with a very small RCU and WCU set on the table.  With that said, I am going to set each of these to 1 to start and then I'll keep an eye on it.&lt;/p&gt;
&lt;p&gt;Now that we have ironed out the configuration for our DynamoDB table we can move on to defining our DynamoDB table in CloudFormation.  Since this will be the first resource we are defining, we will start by creating a folder for our project with a new file named template.yaml.  This will be the main file in our AWS SAM project, which will be read first every time we run &lt;code&gt;sam deploy&lt;/code&gt;. When reviewing this CloudFormation definition of the DynamoDB table, note that partition key is also known as hash key and sort key is also known as range key.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Now that the template.yaml file is created, open a terminal and browse to the folder you created for this project.  Then run &lt;code&gt;sam deploy --guided&lt;/code&gt; and the guided deploy wizard will walk you through all of the settings needed for your deployment.  I recommend to answer yes to the prompt 'Save arguments to configuration file', when that configuration file is saved future stack updates can be done by just running &lt;code&gt;sam deploy&lt;/code&gt;, the guided wizard will only need to be run again if we add a new parameter or need to change a setting in the saved configuration.&lt;/p&gt;
&lt;p&gt;Now let the SAM deployment run and when it's complete, it will return a message like 'Successfully created/updated stack - [Stack Name] in [AWS Region]'.  Our table is now created, to prepare for the next part of this series where we will create an API Gateway endpoint to display a blog post, lets create a sample blog post directly in the DynamoDB table we just created.  Open your web browser and log into the AWS console, then browse to DynamoDB (ensure you have the region selected that you specified in &lt;code&gt;sam deploy&lt;/code&gt;), select tables on the left sidebar, and then select your new table that will be named the same as the stack name you provided to &lt;code&gt;sam deploy&lt;/code&gt;. Now that the new table is selected, on the right side (main content) area select the items tab, and then click the button to Create Item.  In the modal window that opens up, in the upper left select the drop down that currently has Tree selected and change the selection to Text, then copy and paste in the sample below and click Save.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We now have an AWS SAM project that defines a DynamoDB table, we have deployed one stack of this template and created a sample blog post in that table.  That's all for this post, thank you for taking the time to read it.  Next we will create the article page that will display the sample blog post in to a web browser.  If you have any questions or have any suggestions to make this better please leave a comment or reach out via Twitter or LinkedIn and let me know.&lt;/p&gt;
&lt;p&gt;Our project directory at the end of this post is available &lt;a href="https://github.com/JonHolman/ComputelessBlog/tree/part-2-planning-the-data-model" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.jonholman.com/ComputelessBlog/part-3-adding-api-gateway-and-article-page" rel="noopener noreferrer"&gt;Continue with the next article in this series.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>serverless</category>
      <category>database</category>
    </item>
    <item>
      <title>Part 1: Introduction to creating a Computeless Blog on AWS using API Gateway and DynamoDB</title>
      <dc:creator>Jon Holman</dc:creator>
      <pubDate>Sun, 17 Jan 2021 18:39:01 +0000</pubDate>
      <link>https://forem.com/aws-builders/introduction-to-creating-a-computeless-blog-on-aws-using-api-gateway-and-dynamodb-mcb</link>
      <guid>https://forem.com/aws-builders/introduction-to-creating-a-computeless-blog-on-aws-using-api-gateway-and-dynamodb-mcb</guid>
      <description>&lt;p&gt;I thought a good way to start my blog would be to go over the steps I took to create it.  In choosing the technology stack, the first thing to know about me is I am a very cost conscious person, especially when it comes to recurring costs. I am also a huge fan of the latest technologies and finding if I can use them to create more efficient / performant solutions. As a result of that, for the past several years, I have been a huge advocate of Serverless technologies, AKA Functions as a Service (FaaS), with most of that experience focusing on Amazon Web Services (AWS) offering of Lambda.&lt;/p&gt;
&lt;p&gt;For this blog I definitely knew that I wanted to go the serverless route; there is no way I would be OK with the blog requiring a VM or a container running for the site to be available.  Strictly hosting static content was not an option because I wanted to have some interaction (like comments).  In general, when creating solutions, I try to use AWS Lambda until the problem being solved proves in some way that Lambda is not the right answer, which is rare. &lt;/p&gt;
&lt;p&gt;In addition to serverless no longer requiring a VM or a container running for the site to be available, it introduces some other great benefits as well.  For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Availability - with VMs and containers, if you want your solution to be able to sustain the loss of an availability zone (data center), you need to create a fully functioning application stack in multiple availability zones. With FaaS or Lambda, your function is not locked into an availability zone; it spins up where there is capacity when it is needed and is seamless for you.&lt;/li&gt;
&lt;li&gt;Scalability - with VMs and containers, if you want your application to support a peak of 10,000 concurrent users, you need to configure auto-scaling groups with scaling rules to start enough servers/containers to handle that many concurrent users (or just keep that many servers running 24/7... I've seen it done. ☹). With Lambda, your function is invoked in response to events and more events mean more invocations. There is nothing you need to do to prepare your lambda functions for this increase in demand.&lt;/li&gt;
&lt;li&gt;Infrastructure management - Sure with Lambda you may still need to manage its association with a VPC's subnets, if you need to connect to resources within your VPC, but you no longer need to manage servers, containers, AMIs, docker images.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I actually originally started creating this blog using the same approach that I have used for the last few years for most serverless projects, creating a Lambda function written in Python and then adding API Gateway to make the lambda available as an HTTP event.  Then in my research, I was reading more about API Gateway being used as a direct proxy to other AWS services (which I had not leveraged before), with articles like these:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/compute/using-amazon-api-gateway-as-a-proxy-for-dynamodb/" rel="noopener noreferrer"&gt;AWS: Using Amazon API Gateway as a proxy for DynamoDB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.alexdebrie.com/posts/aws-api-gateway-service-proxy/" rel="noopener noreferrer"&gt;AlexDeBrie.com: Connect AWS API Gateway directly to SNS using a service integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=WBg9Ds7j44k" rel="noopener noreferrer"&gt;ServerlessLand (Video): Building Happy Little APIs: Look Ma, No Compute! (S1E6)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As that first article had me thinking about this, I was realizing the most responsible way to create my blog would be simply using API Gateway as a service proxy to DynamoDB; no need to add another piece in creating a lambda that is simply retrieving the record from DynamoDB and putting it into the HTML format that we want the browser to receive.  That solution would still have all of the benefits of serverless that I stated above, but would remove another piece, theoretically making it less complex.  Let's ignore the complexity of Velocity Template Language (VTL) for now ☺.  The high level architecture would look like:&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.jonholman.com%2FComputelessBlog.png" width="800" height="400"&gt;ComputelessBlog Architecture&lt;p&gt;Next, I would like to thank &lt;a href="https://twitter.com/andmoredev" rel="noopener noreferrer"&gt;Andres Moreno&lt;/a&gt; for reviewing my initial draft that turned into this blog series and pointing me to his blog post, &lt;a href="https://medium.com/@amorenom/how-to-build-a-serverless-api-in-aws-without-using-a-single-lambda-522ce43a6fb6" rel="noopener noreferrer"&gt;How to build a Serverless API in AWS without using a single lambda&lt;/a&gt;.  Based on his feedback I cleaned up several things in this series, and moved to creating the APIs using the OpenAPI definition files rather than many CloudFormation resources as I was initially.&lt;/p&gt;
&lt;p&gt;In this series we will be doing everything via CloudFormation, more specifically CloudFormation with the AWS Serverless Application Model (SAM) extensions.  The serverless framework and terraform are great options as well, but AWS SAM is my current preference.  Most importantly though, on all projects do everything as code, Infrastructure as Code (IaC).  Doing things manually by hand is not recommended due to it being slow, error-prone, inconsistent, not scalable and not repeatable. So, it is important that everything in our project is defined as code, that way it is self-documenting of what is deployed, easily repeatable, ready to be moved into an automated pipeline and easy to iteratively improve.&lt;/p&gt;
&lt;p&gt;If you want to run through this series of posts on your own, you will need:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;An AWS account&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/cli/" rel="noopener noreferrer"&gt;AWS CLI&lt;/a&gt; installed on your workstation and configured with access keys to an IAM user that has sufficient permissions to that AWS account&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html" rel="noopener noreferrer"&gt;AWS SAM CLI&lt;/a&gt; installed on your workstation&lt;/li&gt;
&lt;li&gt;Then later for the Admin Page you will need NodeJS &amp;gt; 0.12.0&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The rough plan I have in mind for this series is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Introduction (this)&lt;/li&gt;
&lt;li&gt;Planning the Data Model&lt;/li&gt;
&lt;li&gt;Creating the article page&lt;/li&gt;
&lt;li&gt;Adding the category and home landing pages&lt;/li&gt;
&lt;li&gt;Adding CloudFront&lt;/li&gt;
&lt;li&gt;Moving Static Assets to an S3 Bucket&lt;/li&gt;
&lt;li&gt;Adding Commenting Functionality&lt;/li&gt;
&lt;li&gt;Creating an Admin Page (with a WYSIWYG Editor)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If there is something else you would like to see, please leave a comment or message me on Twitter/LinkedIn (links on the right side bar).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.jonholman.com/ComputelessBlog/part-2-planning-the-data-model" rel="noopener noreferrer"&gt;Continue with the next article in this series.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>database</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
