<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Agnes Olorundare - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Agnes Olorundare - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 24 May 2026 22:23:54 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/Agnes28/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Run a Docker Container in AWS Lambda ]]>
                </title>
                <description>
                    <![CDATA[ While containers are quite lightweight and provide various benefits, it can be challenging to decide how best to deploy them. There are a number of ways to deploy and run Docker containers. But some are best for orchestrating and managing containers,... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-run-a-docker-container-in-aws-lambda/</link>
                <guid isPermaLink="false">694c7990b7478745bce04604</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ serverless ]]>
                    </category>
                
                    <category>
                        <![CDATA[ lambda ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ecr ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Agnes Olorundare ]]>
                </dc:creator>
                <pubDate>Wed, 24 Dec 2025 23:38:56 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1766599506861/86c07e37-7838-4186-971e-29722ccec785.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>While containers are quite lightweight and provide various benefits, it can be challenging to decide how best to deploy them. There are a number of ways to deploy and run Docker containers. But some are best for orchestrating and managing containers, and may not suit a simple use case of running just one container.</p>
<p>In this article, I’ll teach you how you can deploy a single Docker container using a serverless service on AWS called Lambda.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisite-requirements">Prerequisite/ Requirements</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-serverless-with-aws-lambda">Serverless with AWS Lambda</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-run-and-test-a-container-locally">How to Build, Run, and Test a Container Locally</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-push-your-image-to-amazon-elastic-container-registry-ecr">How to Push Your Image to Amazon Elastic Container Registry (ECR)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-deploy-your-docker-image-to-lambda">How to Deploy Your Docker Image to Lambda</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cleanup">Cleanup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisite-requirements">Prerequisite/ Requirements</h2>
<p>The following tools and skills are necessary for following along with this tutorial:</p>
<ul>
<li><p>Knowledge of Docker, and have Docker installed locally.</p>
</li>
<li><p>An AWS account with credentials with administrative privilege for making API calls via the CLI. Best practice would be to limit the privilege to exactly what needs to be done.</p>
</li>
<li><p>AWS CLI installed locally</p>
</li>
<li><p>Python virtual environment managers <a target="_blank" href="https://github.com/astral-sh/uv">such as uv</a> (optional)</p>
</li>
</ul>
<h2 id="heading-serverless-with-aws-lambda">Serverless with AWS Lambda</h2>
<p>Containers provide a lightweight, consistent, and resource-friendly way of running applications. Serverless takes away the overhead of managing the underlying infrastructures on which the container runs. So as you can probably start to see, combining these tools helps you deploy applications in a way that lets you focus on business logic, performance, and what gives your product a competitive edge/ advantage.</p>
<p>One AWS tool that enables you to go serverless is Lambda. With Lambda, you’re only billed for the number of times the code in the function runs, the memory you selected at the time of provisioning the service, and the duration of each invocation of the function.</p>
<p>In addition to removing operational overhead, Lambda can also help you save money since you won’t have to deal with idle resources. The function only comes alive when triggered by a request sent to it.</p>
<h2 id="heading-how-to-build-run-and-test-a-container-locally">How to Build, Run, and Test a Container Locally</h2>
<p>Docker is a tool that helps you package applications or software into portable, standardized and shareable units that have everything the applications need such as libraries, runtime, system tools, application code, in order to run. These units are called containers.</p>
<p>In this section, I’ll walk you through building the Docker image, running the container, and testing it after it’s running.</p>
<p>You can find the project that you’ll be using here in this <a target="_blank" href="https://github.com/Agnes4Him/freecodecamp-lambda-docker">GitHub repository</a>.</p>
<h3 id="heading-build-the-docker-image">Build the Docker Image</h3>
<p>To run a Docker container, you first need to build an image. The image becomes the template or <code>class</code> from which you create the container or <code>instance of the class</code>.</p>
<p>You can find the code to build an image in <code>lambda_function.py</code>.</p>
<pre><code class="lang-python"><span class="hljs-comment"># lambda_function.py</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lambda_handler</span>(<span class="hljs-params">event, context</span>):</span>
    name = event[<span class="hljs-string">"name"</span>]
    message = <span class="hljs-string">f"Hello, <span class="hljs-subst">{name}</span>!"</span>

    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">return</span> {
            <span class="hljs-string">"statusCode"</span>: <span class="hljs-number">200</span>,
            <span class="hljs-string">"body"</span>: message
        }
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        <span class="hljs-keyword">return</span> {
            <span class="hljs-string">"statusCode"</span>: <span class="hljs-number">400</span>,
            <span class="hljs-string">"body"</span>: {<span class="hljs-string">"error"</span>: str(e)}
        }
</code></pre>
<p>As you can see from the code above, this is a very basic Python application that expects a <code>POST</code> HTTP request, with a JSON payload that contains the key – <code>name</code> – and a corresponding value. The code then returns a greeting containing the name it has received. The application has just a single function, which also serves as the entry point to it.</p>
<p>To build a Docker image, you’ll need a Dockerfile to provide the blueprint for the image. For this specific case, the Dockerfile you’ll use is also very basic. Each line in a Dockerfile is called a <code>Directive</code>, and this provides the instruction Docker should follow when creating an image. So building a Docker image means creating a template for a container by following the instructions or directives in the Dockerfile.</p>
<pre><code class="lang-plaintext"># Dockerfile

FROM public.ecr.aws/lambda/python:3.12

# Copy function code... LAMBDA_TASK_ROOT is /var/task, the working directory set in the base image
COPY lambda_function.py ${LAMBDA_TASK_ROOT}    

# Set the CMD to your handler - lambda_handler
CMD ["lambda_function.lambda_handler"]
</code></pre>
<p>A Dockerfile usually starts with a base image. To deploy an application as a Docker container in AWS Lambda, the base image has to be of a specific kind, depending on the application run-time. For this case, you’ll need the Python run-time, so the base image is <code>public.ecr.aws/lambda/python:3.12</code>. It’s okay to use a different Python version.</p>
<p>The next directive in the Dockerfile is copying the <code>lambda_function.py</code> file to a specific path in the base image. That path is referenced using an environment variable that has already been defined in the base image and points to <code>/var/task</code>. This is the directory your code will be running from.</p>
<p>The last directive is simply a command to start the application when the container runs.</p>
<p>Now, you can run the build command from the project’s root directory:</p>
<pre><code class="lang-bash">docker build -t &lt;IMAGE_NAME&gt;:&lt;iIMAGE_TAG&gt; .
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766415846066/f128b7fc-f3a0-4770-b361-3f27c36a6ec4.png" alt="Running docker build command on the terminal" class="image--center mx-auto" width="3710" height="891" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766415895836/d4653144-51b2-437d-8d73-4aaa42651206.png" alt="Output of docker images command showing a list of all existing images" class="image--center mx-auto" width="3710" height="891" loading="lazy"></p>
<h3 id="heading-run-the-docker-container">Run the Docker Container</h3>
<p>Next, let’s create a running container from this image.</p>
<pre><code class="lang-bash">docker run -it --rm -p 8080:8080  lambda_docker:1.0.0
</code></pre>
<p>The command above will create a container and run it in interactive mode just so you can see the logs generated by the application in the container. Port 8080 is also exposed on the host where the container is running and mapped to the container port, which is also 8080 (defined by AWS). The container gets automatically removed once you kill the running process with CTRL + C.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766416250857/62584a3c-bf5e-4cd9-b8d5-fc6734c50075.png" alt="Showing docker run command in interactive mode" class="image--center mx-auto" width="3710" height="891" loading="lazy"></p>
<h3 id="heading-test-the-running-container">Test the Running Container</h3>
<p>Now confirm that the application running within the container can receive and process requests. To do this, use the code in the <code>test.py</code> file:</p>
<pre><code class="lang-python"><span class="hljs-comment"># test.py</span>

<span class="hljs-keyword">import</span> requests

url = <span class="hljs-string">"http://localhost:8080/2015-03-31/functions/function/invocations"</span>

data = {
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"Janet"</span>
}

response = requests.post(url, json=data)

print(<span class="hljs-string">"Status Code:"</span>, response.status_code)
print(<span class="hljs-string">"Response Body:"</span>, response.json())
</code></pre>
<p>You can use the Python <code>requests</code> library to make this call. Install the library by using a virtual environment to isolate the application from your overall system. This helps prevent issues with conflicts in the versions of libraries you install for an application to use.</p>
<p>If you’re using uv to manage your virtual environment, simply run the command:</p>
<pre><code class="lang-python">uv add requests
</code></pre>
<p>Then run the code in <code>test.py</code> from within the virtual environment:</p>
<pre><code class="lang-python">uv run python3 test.py
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766419713310/1ebc3435-3826-46fb-93f3-4218c367e280.png" alt="Testing that the running docker container is working by running test.py file" class="image--center mx-auto" width="3710" height="891" loading="lazy"></p>
<p>You should see the desired response on the terminal.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766419866358/8f0c2867-64c6-4b16-a5a7-5a0eedf9470f.png" alt="Docker container logs in real time" class="image--center mx-auto" width="3710" height="891" loading="lazy"></p>
<h2 id="heading-how-to-push-your-image-to-amazon-elastic-container-registry-ecr">How to Push Your Image to Amazon Elastic Container Registry (ECR)</h2>
<p>Now that you have a working Docker image to deploy to Lambda, the next step is to push the image to a Docker registry. For this use case, your image has to be pushed to Amazon ECR, a container registry for storing Docker images.</p>
<p>To push your Docker image, you first need to tag the image, which simply means naming the image in a specific way.</p>
<p>Currently, this image tag is <code>lambda-docker:1.0.0</code>. To tag it the AWS way, first create an ECR repository. Let’s use the AWS CLI for this (this requires you to configure the AWS credentials locally by running the <code>aws configure</code> command and providing your credentials).</p>
<h3 id="heading-setup-environment-variables">Setup Environment Variables</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Set AWS profile</span>
<span class="hljs-built_in">export</span> AWS_PROFILE=&lt;PROFILE_NAME&gt;
</code></pre>
<pre><code class="lang-bash"><span class="hljs-comment"># Set other variables</span>

AWS_REGION=&lt;AWS_REGION&gt;
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REPO_NAME=lambda-docker
TAG=1.0.0
</code></pre>
<p>The above commands set the <code>AWS_PROFILE</code> for the CLI to target the right AWS account for API calls. The other variables specify the region, account ID, and the ECR repository name and tag.</p>
<h3 id="heading-create-ecr-repository-and-authenticate">Create ECR Repository and Authenticate</h3>
<p>Now, create the ECR repository:</p>
<pre><code class="lang-bash">aws ecr create-repository \
  --repository-name <span class="hljs-string">"<span class="hljs-variable">$REPO_NAME</span>"</span> \
  --region <span class="hljs-string">"<span class="hljs-variable">$AWS_REGION</span>"</span>
</code></pre>
<p>Authenticate to Amazon ECR:</p>
<pre><code class="lang-bash">aws ecr get-login-password --region <span class="hljs-string">"<span class="hljs-variable">$AWS_REGION</span>"</span> \
  | docker login \
  --username AWS \
  --password-stdin <span class="hljs-string">"<span class="hljs-variable">$ACCOUNT_ID</span>.dkr.ecr.<span class="hljs-variable">$AWS_REGION</span>.amazonaws.com"</span>
</code></pre>
<h3 id="heading-tag-and-push-the-docker-image">Tag and Push the Docker Image</h3>
<p>Now, tag the Docker image:</p>
<pre><code class="lang-bash">docker tag <span class="hljs-variable">$REPO_NAME</span>:<span class="hljs-variable">$TAG</span> \
  <span class="hljs-variable">$ACCOUNT_ID</span>.dkr.ecr.<span class="hljs-variable">$AWS_REGION</span>.amazonaws.com/<span class="hljs-variable">$REPO_NAME</span>:<span class="hljs-variable">$TAG</span>
</code></pre>
<p>Push the image to the ECR repository you created:</p>
<pre><code class="lang-bash">docker push <span class="hljs-variable">$ACCOUNT_ID</span>.dkr.ecr.<span class="hljs-variable">$AWS_REGION</span>.amazonaws.com/<span class="hljs-variable">$REPO_NAME</span>:<span class="hljs-variable">$TAG</span>
</code></pre>
<p>And that’s it! Your image is now in ECR.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766420761622/5a18e41b-be41-4660-8d6c-59b12aebb4de.jpeg" alt="Image of Amazon ECR showing the repository created earlier" class="image--center mx-auto" width="1920" height="1037" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766420810814/9f65af4b-a509-45e3-be8f-0bed08cfe6b2.png" alt="Image of the docker image pushed to the existing ECR repository" class="image--center mx-auto" width="3710" height="1996" loading="lazy"></p>
<h2 id="heading-how-to-deploy-your-docker-image-to-lambda">How to Deploy Your Docker Image to Lambda</h2>
<p>With your image now in ECR, you can create a Lambda function. Navigate to the Lambda console, and click <code>Create a Function</code>.</p>
<h3 id="heading-create-lambda-function">Create Lambda Function</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766421062231/19bae74d-a6d5-4e73-8cca-102be40be214.png" alt="AWS Lambda Console" class="image--center mx-auto" width="3710" height="1996" loading="lazy"></p>
<p>Select <code>Container Image</code> and go ahead to search for the ECR repository you created.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766421207358/25ae6eb2-1b1b-43c7-86dc-6dcd512ddc81.jpeg" alt="Select ECR repository to create a Lambda function" class="image--center mx-auto" width="3710" height="1996" loading="lazy"></p>
<p>Next, select the image:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766421335963/ab7d9103-0ea6-4e25-be8c-139344acb5c5.png" alt="Select the existing Docker image from ECR" class="image--center mx-auto" width="3710" height="1996" loading="lazy"></p>
<p>Leave other configurations as default and click create.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766421506518/2f6e631a-a0c7-4f20-966f-2ef87f91bfb7.jpeg" alt="Hit the Create button to create a Lambda function" class="image--center mx-auto" width="1920" height="1033" loading="lazy"></p>
<p>Navigate to the function after creating.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766421673261/71c60ac4-35e7-4458-b4a7-1be2440b9e16.jpeg" alt="The newly created Lambda function dashboard/ overview" class="image--center mx-auto" width="3710" height="1996" loading="lazy"></p>
<h3 id="heading-test-deployment">Test Deployment</h3>
<p>Now, let’s test the deployment. For this, simply use the existing Lambda <code>Test</code> tab. Provide all the details needed, including the payload for your <code>POST</code> request.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766421769909/008473e4-bb28-4fdd-8c5b-7e1f3489a3a0.png" alt="Create a new test instance to test the Lambda function" class="image--center mx-auto" width="3710" height="1996" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766421889043/86f6dbe6-be94-4dca-973e-9e7b68064ff3.png" alt="The output of testing Lambda function" class="image--center mx-auto" width="3710" height="1996" loading="lazy"></p>
<p>And that’s it. You’ve successfully deployed a Docker container on AWS by leveraging ECR and Lambda. You can go a step forward by integrating API Gateway and making the function accessible from the internet.</p>
<h2 id="heading-cleanup">Cleanup</h2>
<p>Remember to delete the services you’ve created on your AWS ECR repository and Lambda to avoid extra charges.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Deploying your Docker container on AWS Lambda is an efficient way to get your application running quickly without being bothered by managing servers or platforms.</p>
<p>Thanks for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Securely Deploy APIs to Amazon Lambda – A Practical Guide ]]>
                </title>
                <description>
                    <![CDATA[ Cyber attacks against APIs (Application Programming Interfaces) are on the increase. These attacks arise from issues with proper authentication, authorization, unnecessary data exposure, lack of request limits, resource consumption, and use of vulner... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-securely-deploy-apis-to-amazon-lambda-a-practical-guide/</link>
                <guid isPermaLink="false">68e8418df4be6f5ede699317</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ aws lambda ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ APIs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ API Gateway ]]>
                    </category>
                
                    <category>
                        <![CDATA[ serverless ]]>
                    </category>
                
                    <category>
                        <![CDATA[ secrets management ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Agnes Olorundare ]]>
                </dc:creator>
                <pubDate>Thu, 09 Oct 2025 23:13:17 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760051580641/75d09121-6167-4e06-94d7-53cf23a6f6a1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Cyber attacks against APIs (Application Programming Interfaces) are on the increase. These attacks arise from issues with proper authentication, authorization, unnecessary data exposure, lack of request limits, resource consumption, and use of vulnerable third-party APIs.</p>
<p>Gaps in APIs can occur before requests reach the APIs, within the code housing the APIs, and even along the path of the APIs’ communication with downstream services, dependencies, or other microservices.</p>
<p>Attackers leverage flaws in APIs to gain access to confidential data, harvest or manipulate data, or even make your service unavailable through distributed denial of service attacks.</p>
<p>In this article, you’ll learn to deploy your APIs in Lambda and apply some security measures pre-function, within the function, and post-function.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-an-api">What is an API?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-requirementsprerequisites">Requirements/Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-goal">Project Goal</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-overall-architecture">Project Overall Architecture</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-aws-set-up">AWS Set Up</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-clone-project">Clone Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-set-up-simple-notification-service">Set Up Simple Notification Service</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-set-up-secrets-manager">Set Up Secrets Manager</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-set-up-internal-lambda">Set Up Internal Lambda</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-set-up-external-lambda">Set Up External Lambda</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-configure-web-application-firewall">Configure Web Application Firewall</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-configure-cognito-user-pools">Configure Cognito User Pools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-configure-api-gateway">Configure API Gateway</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-test-setup-end-to-end">Test Setup End-to-End</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-clean-up">Clean Up</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-improvements">Improvements</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-an-api">What is an API?</h2>
<p>The focus of this article is the security of Application Programming Interfaces (APIs). An API is an interface that connects two programms or applications, allowing them to exchange data and communicate.</p>
<p>An API can be internal to an organization or it can belong to a third-party that allows other users to consume their data through the API.</p>
<h2 id="heading-requirementsprerequisites">Requirements/Prerequisites</h2>
<p>While this tutorial is beginner-friendly, you’ll need the following prerequisites to follow along seamlessly:</p>
<ul>
<li><p>A basic knowledge of the AWS Cloud.</p>
</li>
<li><p>An AWS account with administrator access.</p>
</li>
<li><p>AWS CLI. You can find the installation guide <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html">here</a>. Follow the instructions for your operating system.</p>
</li>
<li><p>Python. You can visit Python’s official documentation <a target="_blank" href="https://www.python.org/downloads/">site</a> for a guide on how to download and install Python for your specific operating system.</p>
</li>
<li><p>Pipenv or any Python virtual environment creation tool. You can find the Pipenv installation guide <a target="_blank" href="https://pypi.org/project/pipenv/">here</a><em>.</em></p>
</li>
<li><p>A basic knowledge of Git.</p>
</li>
<li><p>An API client, like Postman or Thunderclient.</p>
</li>
</ul>
<h2 id="heading-project-goal">Project Goal</h2>
<p>By the end of this project, you should be able to deploy APIs in Lambda securely, leveraging AWS cloud-native security services.</p>
<h2 id="heading-project-overall-architecture">Project Overall Architecture</h2>
<p>Below is the architecture of the project workflow:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758829544078/b76347ee-bbd3-41f4-88c8-2b3a89ad9087.png" alt="Project Architectural Diagram" class="image--center mx-auto" width="1159" height="464" loading="lazy"></p>
<p>As shown in the architectural diagram, when a user sends a request (a JSON object consisting of the user’s name) to an API hosted in Lambda, the user first gets authenticated by an authentication service called Amazon Cognito.</p>
<p>The request passes through a Web Application Firewall, then an API Gateway. API Gateway will perform a check to see if the user is authorized to access the API using the token that the user sends with the request after authentication. API Gateway then allows the traffic to pass through to the API if the user is authorized.</p>
<p>The user’s request will first get to an External Lambda function, which will then save the user’s name as a message to a Simple Notification Service (SNS) topic. This will then invoke an Internal Lambda to run and log the output in Amazon CloudWatch logs. The SNS topic will be accessed by External Lambda using the SNS’s unique identifier stored in Amazon Secrets Manager.</p>
<h3 id="heading-aws-set-up">AWS Set Up</h3>
<p>You’ll need to set up an AWS environment to get started. This requires creating an account if you don’t already have one.</p>
<p>Following account creation, a root user is automatically created, with all privileges attached to the user. Security best practice is to create another user with administrator privileges and use this user for subsequent tasks.</p>
<p>Then, create an access key for this user, which usually consists of two parts (Access Key ID and Secret Access Key) by navigating to the following:</p>
<p>IAM —→ Users —→ Create Access key</p>
<p>Follow the prompts and choose the <code>Command Line Interface</code> option. Check the <code>Confirmation</code> box, and go on to create the key. Download the CSV file provided, or manually copy the <code>Access Key ID</code> and <code>Secret Access Key</code>. Save them securely.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758890397608/a88ec1c6-511c-4a66-aa7a-d0dd3f41f665.png" alt="IAM Dashboard" class="image--center mx-auto" width="951" height="513" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758890497481/faab2cb7-b7ba-4e5c-b67e-a00d8fb27a10.png" alt="IAM User Page" class="image--center mx-auto" width="1366" height="542" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758890928429/1a4b3163-6340-47d2-be3e-0e61c275ba8f.png" alt="Create Access Key Page" class="image--center mx-auto" width="1348" height="551" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758890991928/5bb150b6-b014-4398-b839-ee5d6e49c425.png" alt="Access Key Use Option Page" class="image--center mx-auto" width="1366" height="494" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758891021874/a2e4eb61-eaca-4732-9377-b499fa7eab5d.png" alt="Set Access Key Tag Page" class="image--center mx-auto" width="1366" height="348" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758891049362/372f3a63-8e64-478d-9b06-61f7aa88f73a.png" alt="Download Access Key" class="image--center mx-auto" width="1366" height="517" loading="lazy"></p>
<p>Open up your terminal and run the following commands using the AWS CLI:</p>
<pre><code class="lang-bash">aws configure
</code></pre>
<p>The above command will give some prompts to provide the components of the <code>Access Key</code> created earlier and your default region (the AWS region hosting the service you intend to interact with).</p>
<h3 id="heading-clone-project">Clone Project</h3>
<p>In the next step, you’ll clone the GitHub repository containing the assets and resources used in the project implementation.</p>
<p>Visit the project <a target="_blank" href="https://github.com/Agnes4Him/secure-lambda">URL</a> and clone the repository locally.</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> &lt;repository_clone_url&gt;
</code></pre>
<h3 id="heading-set-up-simple-notification-service">Set Up Simple Notification Service</h3>
<p>Amazon Simple Notification Service (SNS) connects system components, enabling asynchronous communication and messaging among them.</p>
<p>Find <code>SNS</code> on the console, click on it, and create a topic that your APIs will send messages to. After successfully creating a topic, navigate to the topic, and in the topic details, you’ll find the topic’s <code>ARN</code>. An ARN is an Amazon Resource Name, and it’s a unique string attached to a resource you’ve created on AWS to help identify the resource. Copy the <code>ARN</code> of the topic.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758983690093/a2820581-46fb-41d1-aed9-9471a0c2db02.png" alt="SNS Dashboard" class="image--center mx-auto" width="1349" height="517" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758982964553/3eb717c7-8ce3-497b-96fb-c16483cff43e.png" alt="Create SNS Topic" class="image--center mx-auto" width="1366" height="371" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758983004729/854335ec-3d53-42e4-8bef-0e8a7d3fb2e6.png" alt="Topic Details" class="image--center mx-auto" width="1348" height="541" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758983094356/320ebaf9-9f2d-4241-b747-fd3fd0f0b62b.png" alt="SNS Topic Access Policy" class="image--center mx-auto" width="1348" height="535" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758983451385/7886c2c4-a52f-4538-8e9d-0d33738f7632.png" alt="Topic Created" class="image--center mx-auto" width="1351" height="546" loading="lazy"></p>
<h3 id="heading-set-up-secrets-manager">Set Up Secrets Manager</h3>
<p>Amazon Secrets Manager is used to store, manage, and retrieve sensitive information such as keys, credentials, tokens, and so on. You’ll store the <code>Topic ARN</code> created earlier. With this approach, you’ll demonstrate how your API can securely access the data and information it needs for its performance.</p>
<p>Go to <code>Secrets Manager</code> on the AWS console and create a secret. Provide the secret’s details, and add a new secret named <code>TOPIC_ARN</code> as the key and the actual SNS Topic ARN as the value.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758984342157/38c7855c-2221-4406-9078-496cbb480e47.png" alt="Secrets Manager Console" class="image--center mx-auto" width="1351" height="546" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758984379959/3c4ebdf7-26c8-4b74-a175-0ea2eefd258d.png" alt="Create Secret" class="image--center mx-auto" width="1351" height="542" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758984459477/cfdd8fce-4a33-45c3-8f12-d6a4a04bf799.png" alt="Choose Other Types of Secret" class="image--center mx-auto" width="1351" height="546" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758984512368/7a76618e-18f6-4b3d-bc03-5941c89909ef.png" alt="Secret Details" class="image--center mx-auto" width="1348" height="546" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758984575543/91ca9360-6320-442a-a121-37f9f35b175b.png" alt="Final Secret Store" class="image--center mx-auto" width="1356" height="314" loading="lazy"></p>
<p>Next, you’ll create some Lambda functions to serve your APIs and consume the output of the APIs. There’re three Lambda functions to set up. Two of the functions will host APIs, each of which can only be accessed by specific users. These will be referred to as <code>ExternalLambda</code>. The third Lambda will consume the output of the External Lambda functions through SNS.</p>
<h3 id="heading-set-up-internal-lambda">Set Up Internal Lambda</h3>
<p>AWS Lambda is a serverless service on AWS that users can leverage to run application functions or code when needed. You’re billed for your Lambda function based on the number of invocations of the function, the duration each invocation lasted, and the amount of memory allocated to the function. Lambda can be provisioned to use any runtime, such as Python or NodeJS. In this demonstration, you’ll focus on the NodeJS runtime.</p>
<p>Now that you know what Lambda is and does, you can create one. Let’s call the first Lambda function InternalLambda. On the AWS console, search for <code>Lambda</code>, and on the <code>Lambda</code> dashboard, click <code>Create a function</code> and provide the details. We’ll be using <code>Node.js</code> – JavaScript at the backend as the runtime of choice.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759140048151/60d5c813-a190-456e-9bad-50b429bdc6f7.png" alt="AWS Lambda" class="image--center mx-auto" width="1351" height="542" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759140165012/d519f8e9-cb98-4f75-b94e-d4d343e3003c.png" alt="Lambda Details" class="image--center mx-auto" width="1348" height="549" loading="lazy"></p>
<p>For the <code>Permissions</code> details, let Lambda create a default <code>IAM Role</code>. This default role is named according to your function, and the permissions attached to the role allow your Lambda function to send logs to CloudWatch, another AWS service used for monitoring and observability.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759140368576/3f922a46-7b3c-4034-b20b-c2b9ab5dde94.png" alt="Lambda Permissions" class="image--center mx-auto" width="1348" height="528" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759146593382/39d50020-d0bf-4cfe-950f-eec8a2ff8989.png" alt="39d50020-d0bf-4cfe-950f-eec8a2ff8989" class="image--center mx-auto" width="1345" height="546" loading="lazy"></p>
<p>As you can see in the last image above, the Lambda function you’ve created needs a <code>trigger</code> and sometimes, a <code>destination</code>. For your <code>InternalLambda</code>, the trigger is the SNS topic we configured earlier. This Lambda will read the messages that’ve been published to it, and then you can access the message from your client or even CloudWatch logs.</p>
<p>To achieve this, click the <code>Add trigger</code> button and provide the details.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759140925997/2534535f-5ad3-4e13-99f1-d8bf48c9cec1.png" alt="Add SNS to Lambda" class="image--center mx-auto" width="1358" height="508" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759146639798/1f3d75c9-ef5d-4538-9f3a-aaf2e8c0ddbb.png" alt="SNS ARN" class="image--center mx-auto" width="1366" height="533" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759146670136/367f7ca9-0b41-41ed-8749-ff70e1770ebb.png" alt="InternalLambda Overview" class="image--center mx-auto" width="1339" height="535" loading="lazy"></p>
<p>Next, you’ll provide the <code>code</code> you want to invoke through Lambda. Find the code in the GitHub repository that you cloned earlier. Paste the code in the Lambda function code space and click on <code>Deploy</code> to deploy the function.</p>
<p><code>secure-lambda/InternalLambda/index.js</code></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> handler = <span class="hljs-keyword">async</span> (event) =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Request successfully received from SNS'</span>);                            

        <span class="hljs-keyword">let</span> name = event[<span class="hljs-string">'Records'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'Sns'</span>][<span class="hljs-string">'Message'</span>];
        <span class="hljs-keyword">let</span> response = {
            <span class="hljs-attr">statusCode</span>: <span class="hljs-number">200</span>,
            <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-string">`Hello <span class="hljs-subst">${name}</span>. Greetings from InternalLambda!`</span>),
        };       
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Response: '</span>, response);                                                
        <span class="hljs-keyword">return</span> response;
    } <span class="hljs-keyword">catch</span> (err) {                            
        <span class="hljs-keyword">let</span> response = {
            <span class="hljs-attr">statusCode</span>: <span class="hljs-number">500</span>,
            <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-string">'An error occurred while processing your request.'</span>),
        };

        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error processing event'</span>, err);
        <span class="hljs-keyword">return</span> response;
    }   
};
</code></pre>
<p>The function defined in the index.js file above is simply taking the <code>event</code> object sent to it from SNS and extracting the <code>Message</code> attribute within it. We’re using <code>console.log</code> here to view outputs from the function and ensure it behaves as expected. Just don’t use this in a production-ready application.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759142277747/f4437ff2-4495-485d-b891-d9dda3fc939c.png" alt="InternalLambda Code" class="image--center mx-auto" width="1342" height="550" loading="lazy"></p>
<h3 id="heading-set-up-external-lambda">Set Up External Lambda</h3>
<p>You’ll be creating two external Lambda functions: 1 and 2. These two functions will receive user requests, process them, and publish messages to your SNS topic.</p>
<p>On the Lambda console, create another function and name it <code>ExternalLambda1</code>. Allow Lambda to create a default IAM Role, as previously.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759144966306/ee8a2ed1-5a2e-48df-8556-5dedd7ecdde1.png" alt="Create ExternalLambda1" class="image--center mx-auto" width="1351" height="539" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759146732803/82a46fd1-e3e5-4d72-a9fe-b41496ba076b.png" alt="ExternalLambda1 Overview" class="image--center mx-auto" width="1345" height="546" loading="lazy"></p>
<p>Paste the code snippet below in the <code>ExternalLambda1</code> code space:</p>
<p><code>secure-lambda/ExternalLambda1/insex.js</code></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> {
  GetSecretValueCommand,
  SecretsManagerClient,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@aws-sdk/client-secrets-manager"</span>;

<span class="hljs-keyword">import</span> { SNSClient, 
    PublishCommand 
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@aws-sdk/client-sns"</span>;

<span class="hljs-keyword">const</span> secretsManagerClient = <span class="hljs-keyword">new</span> SecretsManagerClient();

<span class="hljs-keyword">const</span> snsClient = <span class="hljs-keyword">new</span> SNSClient({});

<span class="hljs-comment">// Fetch topicArn from AWS Secrets Manager</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSecretValue</span>(<span class="hljs-params">secretName</span>) </span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> secretsManagerClient.send(
                            <span class="hljs-keyword">new</span> GetSecretValueCommand({
                            <span class="hljs-attr">SecretId</span>: secretName,
                            }),
                        );
        <span class="hljs-keyword">if</span> (data.SecretString) {
            <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.parse(data.SecretString);
        }   <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">let</span> buff = Buffer.from(data.SecretBinary, <span class="hljs-string">'base64'</span>);
            <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.parse(buff.toString(<span class="hljs-string">"utf-8"</span>));
        }
    } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error retrieving secret'</span>, err);                             <span class="hljs-comment">// added for debugging</span>
        <span class="hljs-keyword">throw</span> err;
    }
}                                        

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> handler = <span class="hljs-keyword">async</span> (event) =&gt; {

    <span class="hljs-keyword">let</span> name = event[<span class="hljs-string">'name'</span>];
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Request successfully received from <span class="hljs-subst">${name}</span>`</span>);    

    <span class="hljs-comment">// Retrieve SNS Topic ARN from Secrets Manager</span>
    <span class="hljs-keyword">let</span> topicArn;
    <span class="hljs-keyword">let</span> response;
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> secret = <span class="hljs-keyword">await</span> getSecretValue(<span class="hljs-string">'LambdaSNSTopicARN'</span>);
        topicArn = secret.TOPIC_ARN;
    } <span class="hljs-keyword">catch</span> (err) {
        response = {
            <span class="hljs-attr">statusCode</span>: <span class="hljs-number">500</span>,
            <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-string">'An error occured, try again later.'</span>),
        };
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Failed to load SNS Topic ARN from Secrets Manager'</span>, err);
        <span class="hljs-keyword">return</span> response;        
    }

    <span class="hljs-comment">// Publish to SNS topic</span>
   <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> snsResponse = <span class="hljs-keyword">await</span> snsClient.send(
        <span class="hljs-keyword">new</span> PublishCommand({
            <span class="hljs-attr">Message</span>: name,
            <span class="hljs-attr">TopicArn</span>: topicArn,
        })
        );
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Message published successfully:"</span>, snsResponse.MessageId);
        response = {
            <span class="hljs-attr">statusCode</span>: <span class="hljs-number">200</span>,
            <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-string">`Hello <span class="hljs-subst">${name}</span>. Greetings from ExternalLambda1! Message forwarded to InternalLambda.`</span>),
        };
        <span class="hljs-keyword">return</span> response;
  } <span class="hljs-keyword">catch</span> (err) {
        response = {
            <span class="hljs-attr">statusCode</span>: <span class="hljs-number">500</span>,
            <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-string">`Sorry <span class="hljs-subst">${name}</span>.An error occurred while processing your request.`</span>),
        };
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Failed to publish message:"</span>, err);
        <span class="hljs-keyword">return</span> response;
  }  
};
</code></pre>
<p>The code above leverages the AWS SDK to fetch the ARN of the SNS topic created earlier from Secrets Manager. It then publishes a message to the topic.</p>
<p>The SDK already comes installed in the Lambda function. Outside of Lambda, the SDK has to be explicitly installed. The function receives its <code>event</code> from the client via API Gateway, which we’ll configure later.</p>
<p>The SNS topic you created earlier will be the destination for this function. For Lambda to publish a topic to SNS, it needs the necessary permission attached to its IAM Role. AWS can automatically take care of that during your configuration, as shown below.</p>
<p>For the trigger, you’ll use another service known as <code>API Gateway</code>. More on that later.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759146816330/3c542eff-984e-4d02-85b3-c1da142f94d7.png" alt="ExternalLambda1 Add Destination" class="image--center mx-auto" width="1345" height="535" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759146778567/9b3650f1-90fd-47ce-99c3-627654f2d41f.png" alt="ExternalLambda1 Destination Permissions" class="image--center mx-auto" width="1348" height="539" loading="lazy"></p>
<p>Follow the same steps to provision another Lambda known as <code>ExternalLambda2</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759145915181/631aa639-493a-4f12-af1e-45f425ca2c16.png" alt="ExternalLambda2" class="image--center mx-auto" width="1351" height="539" loading="lazy"></p>
<p>The outcome of the External Lambda setup is as shown below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759146919917/93aaf281-387f-44c3-bf6d-995b076150e9.png" alt="ExternalLambda2 Overview" class="image--center mx-auto" width="1346" height="539" loading="lazy"></p>
<p>Paste the code below into <code>ExternalLambda2</code>. It performs the same function as <code>ExternalLambda1</code>, but their output differ. Each of the two Lambda functions will be receiving traffic to a specific user that’s authorized to access the function.</p>
<p><code>secure-lambda/ExternalLambda2/index.js</code></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> {
  GetSecretValueCommand,
  SecretsManagerClient,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@aws-sdk/client-secrets-manager"</span>;

<span class="hljs-keyword">import</span> { SNSClient, 
    PublishCommand 
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@aws-sdk/client-sns"</span>;

<span class="hljs-keyword">const</span> secretsManagerClient = <span class="hljs-keyword">new</span> SecretsManagerClient();

<span class="hljs-keyword">const</span> snsClient = <span class="hljs-keyword">new</span> SNSClient({});

<span class="hljs-comment">// Fetch topicArn from AWS Secrets Manager</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSecretValue</span>(<span class="hljs-params">secretName</span>) </span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> secretsManagerClient.send(
                            <span class="hljs-keyword">new</span> GetSecretValueCommand({
                            <span class="hljs-attr">SecretId</span>: secretName,
                            }),
                        );
        <span class="hljs-keyword">if</span> (data.SecretString) {
            <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.parse(data.SecretString);
        }   <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">let</span> buff = Buffer.from(data.SecretBinary, <span class="hljs-string">'base64'</span>);
            <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.parse(buff.toString(<span class="hljs-string">"utf-8"</span>));
        }
    } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error retrieving secret'</span>, err);  
        <span class="hljs-keyword">throw</span> err;
    }
}                                        

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> handler = <span class="hljs-keyword">async</span> (event) =&gt; {

    <span class="hljs-keyword">let</span> name = event[<span class="hljs-string">'name'</span>];
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Request successfully received from <span class="hljs-subst">${name}</span>`</span>);    

    <span class="hljs-comment">// Retrieve SNS Topic ARN from Secrets Manager</span>
    <span class="hljs-keyword">let</span> topicArn;
    <span class="hljs-keyword">let</span> response;
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> secret = <span class="hljs-keyword">await</span> getSecretValue(<span class="hljs-string">'LambdaSNSTopicARN'</span>);
        topicArn = secret.TOPIC_ARN;
    } <span class="hljs-keyword">catch</span> (err) {
        response = {
            <span class="hljs-attr">statusCode</span>: <span class="hljs-number">500</span>,
            <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-string">'An error occured, try again later.'</span>),
        };
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Failed to load SNS Topic ARN from Secrets Manager'</span>, err);
        <span class="hljs-keyword">return</span> response;        
    }

    <span class="hljs-comment">// Publish to SNS topic</span>
   <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> snsResponse = <span class="hljs-keyword">await</span> snsClient.send(
        <span class="hljs-keyword">new</span> PublishCommand({
            <span class="hljs-attr">Message</span>: name,
            <span class="hljs-attr">TopicArn</span>: topicArn,
        })
        );
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Message published successfully:"</span>, snsResponse.MessageId);
        response = {
            <span class="hljs-attr">statusCode</span>: <span class="hljs-number">200</span>,
            <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-string">`Hello <span class="hljs-subst">${name}</span>. Greetings from ExternalLambda2! Message forwarded to InternalLambda.`</span>),
        };
        <span class="hljs-keyword">return</span> response;
  } <span class="hljs-keyword">catch</span> (err) {
        response = {
            <span class="hljs-attr">statusCode</span>: <span class="hljs-number">500</span>,
            <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-string">`Sorry <span class="hljs-subst">${name}</span>.An error occurred while processing your request.`</span>),
        };
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Failed to publish message:"</span>, err);
        <span class="hljs-keyword">return</span> response;
  }              
};
</code></pre>
<p>Before moving on, you need to modify the External Lambda’s IAM Roles. Currently, IAM Roles only have permissions to write to CloudWatch and SNS (automatically added). External Lambda also needs permission to fetch the ARN of the SNS topic that was created earlier.</p>
<p>The point here is to show how to leverage a secrets manager, such as AWS Secrets Manager, to store sensitive information or data, and still access these securely. This approach is more secure than storing the ARN as an environment variable within Lambda.</p>
<p>Navigate to IAM, and click on <code>Policies</code> tab on the left. This brings you to a list of policies. Next, click on <code>Create policy</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759320098124/71bde9ad-c6d9-4c0d-8472-d56107708be2.png" alt="IAM Policies" class="image--center mx-auto" width="1348" height="542" loading="lazy"></p>
<p>Search for <code>secrets manager</code> in the Policy editor.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759320163875/a040af9a-1e92-4aea-8c2e-5c029f60f54e.png" alt="Policy Editor" class="image--center mx-auto" width="1366" height="539" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759320923537/c3e8bb54-e78d-498c-9fec-7c7a5225b116.png" alt="Policy Editor2" class="image--center mx-auto" width="1366" height="546" loading="lazy"></p>
<p>Select the permissions Lambda needs to access Secrets Manager. In this case, that would be <code>Read —&gt; GetSecretValue</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759321060458/963c1876-dbcc-4d7d-a6fe-9f65281c1a26.png" alt="Policy Editor - Specify Permissions" class="image--center mx-auto" width="1348" height="549" loading="lazy"></p>
<p>Select <code>Specific</code> for Resources, and click on <code>Add ARNs</code>. On the next tab, add the details of the Secrets Manager Secret created earlier.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759321219657/f50fc354-a238-499c-9009-958bbc624299.png" alt="Policy Editor - Select Access" class="image--center mx-auto" width="1348" height="532" loading="lazy"></p>
<p>The Secret’s ARN will be populated here.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759321662642/356fb6bd-adc7-4663-a337-3cfaedb74b2d.png" alt="Policy Editor - Add Secrets Manager ARN" class="image--center mx-auto" width="1351" height="550" loading="lazy"></p>
<p>Next, give the policy a name and create it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759321510186/fa5da448-293f-4d95-a3b5-651292a91a7f.png" alt="Policy Editor - Create Policy" class="image--center mx-auto" width="1342" height="542" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759321721882/27807dda-8ea3-4489-bcab-d03efc201655.png" alt="Newly Added Policy" class="image--center mx-auto" width="1354" height="546" loading="lazy"></p>
<p>Next, navigate to <code>Roles</code>, and search for the IAM Roles assigned to the External Lambda functions. These are named according to the Lambda.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759321748368/dfc73acb-622c-44f9-9cf8-be51b31e3fe9.png" alt="IAM Roles" class="image--center mx-auto" width="1351" height="549" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759321856410/f1d4a13c-a568-4c9c-b14f-bb3d24b870f8.png" alt="Lambda IAM Roles" class="image--center mx-auto" width="1366" height="551" loading="lazy"></p>
<p>Click <code>Add permissions</code> to add a new permission to the IAM Role selected.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759322020293/689715ef-7e8c-45cb-9473-010f5aa105fa.png" alt="ExternalLambda1 Role" class="image--center mx-auto" width="1352" height="547" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759322453532/83996cca-7a05-48fb-8e31-d3fc679df7bc.png" alt="ExternalLambda1 Role - Policy Added" class="image--center mx-auto" width="1349" height="547" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759322498243/29a8fff5-af9a-4d4c-b3ff-3790b82b6339.png" alt="ExternalLambda2 Role" class="image--center mx-auto" width="1352" height="549" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759322570298/ab28750d-eb99-40f3-bf48-936b63bba1f0.png" alt="ExternalLambda2 Role - Policy Added" class="image--center mx-auto" width="1350" height="553" loading="lazy"></p>
<h3 id="heading-configure-web-application-firewall">Configure Web Application Firewall</h3>
<p>A firewall is a system placed in front of an application, workload, APIs, and so on to inspect traffic, filter it, and either allow or block the traffic based on some preconfigured rules.</p>
<p>For this project, you’ll use the AWS Web Application Firewall (WAF) service to inspect user requests before routing the traffic to your APIs running in Lambda.</p>
<p>Head over to the AWS console and search for WAF.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759310730367/bbcbdf00-2759-4dd7-9b7b-63ee9c252542.png" alt="AWS Web Application Firewall" class="image--center mx-auto" width="1353" height="546" loading="lazy"></p>
<p>Click on the <code>IP sets</code> tab on the left. This will enable you to create a list of IP addresses that you want to allow (as in this case) or deny.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759311298551/4537577d-a574-417e-8352-3f72b3732926.png" alt="IP Sets Page" class="image--center mx-auto" width="1366" height="547" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759311354043/edd29c9c-63e7-4bf6-a503-23ef0af5ac20.png" alt="IP Set Configuration" class="image--center mx-auto" width="1351" height="549" loading="lazy"></p>
<p>The IP addresses should include a CIDR block. For instance, if adding a single IP address, it should be <code>X.X.X.X/32</code>. The same applies to IP address ranges such as <code>X.X.X.X/24</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759311560565/0ad16e51-b70b-4a80-98f4-821659fa61b8.png" alt="IP Set Overview" class="image--center mx-auto" width="1366" height="551" loading="lazy"></p>
<p>Next, click on the <code>Web ACLs</code> tab, then <code>Create web ACL</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759311623780/9742ab87-3303-4046-84df-f9f770ed7c41.png" alt="Web ACL Page" class="image--center mx-auto" width="1366" height="549" loading="lazy"></p>
<p>Choose <code>Regional resources</code> as the Resource type, and enter your region. It’s best to keep all resources you’re creating in this project within the same region. Give your Web ACL a name, then click next.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759311736091/b4201885-2dc0-4ed8-aa38-5e25824c363b.png" alt="Web ACL Description" class="image--center mx-auto" width="1351" height="544" loading="lazy"></p>
<p>Add rules to the Web ACL.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759311892739/5efad662-6c20-4678-b490-54fa33bc3a7b.png" alt="WAF Rule" class="image--center mx-auto" width="1352" height="546" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759311985197/9e7157c8-bfb4-47a9-a850-67ce8bb302b2.png" alt="Add Rule" class="image--center mx-auto" width="1350" height="551" loading="lazy"></p>
<p>Choose a rule type. In this case, you’ll use <code>IP set</code>, and give the rule a name. Choose the IP set created earlier.</p>
<p>Select <code>Source IP address</code>, and <code>Count</code> as the Action. For this project, you’ll focus on counting the requests sent to your APIs. But as shown in the image below, you can perform other actions, such as allow, block, and so on.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759312925911/ea491527-970c-4b5f-b658-4345ce3d08e4.png" alt="WAF Rule Configuration" class="image--center mx-auto" width="1366" height="548" loading="lazy"></p>
<p>Your final rule configuration will appear this way.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759313133810/8e1be6d3-f6bf-42d9-881d-87216862b3bd.png" alt="WAF Rule Overview" class="image--center mx-auto" width="1350" height="553" loading="lazy"></p>
<p>Scroll down, then click on <code>Create web ACL</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759313210947/d625c4f3-a5a3-47c9-961f-ca67f652c992.png" alt="Create Rule" class="image--center mx-auto" width="1348" height="546" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759313261493/3bd16ec5-3376-4607-86cc-fd1716ad68aa.png" alt="Web ACL Dashboard" class="image--center mx-auto" width="1366" height="551" loading="lazy"></p>
<h3 id="heading-configure-cognito-user-pools">Configure Cognito User Pools</h3>
<p>Amazon Cognito is an identity management service used for creating and managing users. You can leverage it to authenticate and authorize users to applications, APIs, or other workloads.</p>
<p>You’ll create <code>User Pools</code> within Cognito and add a user to each pool. You’ll configure how these users can be authenticated and authorized to access the External Lambda functions already created.</p>
<p>Search for <code>Cognito</code> on AWS.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759315681568/c2e7df4e-0e51-4c03-bf59-41ca895df74d.png" alt="Amazon Cognito" class="image--center mx-auto" width="1356" height="542" loading="lazy"></p>
<p>Click on <code>Get started for free</code>, then <code>Create user pool</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759315735324/1c09e934-186f-49db-811f-dd84d7400285.png" alt="Create User Pool" class="image--center mx-auto" width="1366" height="554" loading="lazy"></p>
<p>Select Single-page application (SPA), give the User pool the name <code>MyUserPool1</code>, and select <code>Email</code> as an option for sign-in. This means the main attribute users will provide at signup and sign-in will be their email address. Leave everything else as the default. We’ll keep things as simple as possible.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759315828576/73cb66a3-cbde-4443-8dfd-34338091aabc.png" alt="Use Pool Configuration" class="image--center mx-auto" width="1348" height="549" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759315901551/9fdc173d-f92b-4080-98d4-513b404a9aeb.png" alt="User Pool Configuration2" class="image--center mx-auto" width="1351" height="546" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759315994247/3fb17ade-90aa-4d47-b2f9-258f6b547a1f.png" alt="User Pool Configuration3" class="image--center mx-auto" width="1351" height="546" loading="lazy"></p>
<p>After creating the User pool, you’ll find the page shown below. You can view the login and signup page for the pool you’ve just created by clicking on the <code>View login page</code> button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759316208497/4db5f370-deb2-449e-8017-505dc1e13079.png" alt="Cognito App Client Login URL" class="image--center mx-auto" width="1352" height="551" loading="lazy"></p>
<p>You can add <code>App clients</code> to your User Pool. By default, a client named <code>MyUserPool1</code> will be added to the pool. Navigate to your User pool, and click on <code>App clients</code> to see details of this client. Note the <code>Client ID</code>. You’ll also make some edits to the App client by clicking on the <code>Edit</code> button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759316443170/58081c40-cdcb-4af9-a60b-79156f6d2d68.png" alt="User Pool App Client Overview" class="image--center mx-auto" width="1348" height="551" loading="lazy"></p>
<p>You’ll edit the <code>Authentication flows</code> field by ticking the <code>Sign in with username and password…</code> and <code>Sign in with server-side administrative credentials…</code> boxes. These changes will enable you to authenticate the user who will be added to this client programmatically, rather than through a UI. With this approach, we can fetch the token assigned to the user by Cognito and use this token to authorize access to Lambda.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759317429341/f8db3816-c603-49fe-b661-696bfff98639.png" alt="Edit App Client" class="image--center mx-auto" width="1349" height="549" loading="lazy"></p>
<p>Now, add a user to this pool. The user needs a valid email address. You’ll need the login page URL to create the user.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759318555729/d9a8ac0c-72d4-4fca-8a94-ff71a5a20caf.png" alt="Cognito Create New User" class="image--center mx-auto" width="1347" height="636" loading="lazy"></p>
<p>You need access to the email used to create the user. Fetch the code sent to the email address and submit to confirm the account.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759318672240/42d4418d-a4e1-4af9-b8b5-deaf5fb63118.png" alt="Cognito Confirm Email" class="image--center mx-auto" width="1345" height="603" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759318710760/4505260f-13c9-4b3b-af99-1cb9e7436147.png" alt="Cognito Successful Sign up" class="image--center mx-auto" width="1366" height="636" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759318734000/1b3789d9-917a-4341-84ef-b8e498628557.png" alt="User Pool Users" class="image--center mx-auto" width="1366" height="547" loading="lazy"></p>
<p>Follow the same steps and create another User pool named <code>MyUserPool2</code>. Add a user with a different email to this pool.</p>
<h3 id="heading-configure-api-gateway">Configure API Gateway</h3>
<p>API Gateway is a service used to manage access and route traffic to API backend services such as APIs. It serves as a reverse proxy and provides an extra layer of security for backend services.</p>
<p>You’ll configure API Gateway to direct traffic to your Lambda functions.</p>
<p>Navigate to <code>API Gateway</code> and click on <code>Create an API</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759336384538/0e2e4120-ade3-43c3-8a3e-9ec4d0e2b343.png" alt="API Gateway" class="image--center mx-auto" width="1351" height="546" loading="lazy"></p>
<p>Select the <code>REST API</code> option —→ <code>Build</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759336533762/56d96fcb-ff27-4f96-b739-fc9658dca50e.png" alt="Select API Type" class="image--center mx-auto" width="1352" height="551" loading="lazy"></p>
<p>Select <code>New API</code>, provide a name, and choose <code>Regional</code> as the API endpoint type. IP address type can be IPv4 or Dualstack. We’ll select IPv4 here. Then create.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759336585590/ff2b11c2-cb32-4657-913e-6dfc9922531f.png" alt="API Gateway Configuration" class="image--center mx-auto" width="1349" height="549" loading="lazy"></p>
<p>An important part of API Gateway configuration for this project is the Authorizer. API Gateway uses Authorizer to allow traffic from clients to backend services.</p>
<p>You’ll create two Authorizers. Each will be connected to one of the User pools you configured earlier. On the left-hand side of the API Gateway you configured, click on <code>Authorizers</code> —→ <code>Create authorizer</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759336744757/bb00a260-3897-4867-96d3-5370e40eae59.png" alt="API Gateway Authorizer" class="image--center mx-auto" width="1366" height="547" loading="lazy"></p>
<p>Provide the name <code>AGAuthorizer1</code>, and select <code>Cognito</code> as the Authorizer type. Add the User pool for MyUserPool1 created earlier. For the Token source, use <code>Authorization</code>. When you send a request from your API client, a token will be added to the request header for authorization. The token’s key will be named <code>Authorization</code>, while the value will be the token itself.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759337066816/1ef4b11c-3508-49a5-ae7b-561b4c7f4259.png" alt="Authorizer1 Configuration" class="image--center mx-auto" width="1366" height="548" loading="lazy"></p>
<p>Create another Authorization for MyUserPool2 the same way.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759337472200/aacad54f-8927-4c3e-9a60-f207bbf45577.png" alt="Authorizer2 Configuration" class="image--center mx-auto" width="1366" height="544" loading="lazy"></p>
<p>Both Authorizers will appear this way.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759337540381/56362d9c-7f84-4021-b077-59992abd979b.png" alt="Authorizers Overview" class="image--center mx-auto" width="1366" height="547" loading="lazy"></p>
<p>Next, you’ll create resources and endpoints within the API Gateway that you’ve defined.</p>
<p>A <code>resource</code> in API Gateway is used to group certain endpoints within a specific path. You’ll define two resources within the API Gateway you’ve created. This will create two different paths, &lt;BASE_URL&gt;/&lt;RESOURCE1&gt; and &lt;BASE_URL/RESOURCE2&gt;.</p>
<p>On the API Gateway dashboard, navigate to your Gateway, click on <code>Create resource</code>, define your root path (‘/’ in your case), and provide the resource name (<code>lambda1</code>).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759420480646/c6e7c1c9-9eee-4dbf-af5b-8c335e14927c.png" alt="API Gateway Lambda1 Resource" class="image--center mx-auto" width="1366" height="528" loading="lazy"></p>
<p>Create another resource named <code>lambda2</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759420822042/74d1790e-c752-490c-883f-b42bd00d91eb.png" alt="API Gateway Resources Overview" class="image--center mx-auto" width="1366" height="532" loading="lazy"></p>
<p>Now, click on <code>/lambda1</code>, then <code>Create method</code> to define an endpoint within this resource. You’ll use the <code>POST</code> method to send requests to the backend service via this endpoint.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759420769955/bd861a6a-884d-4873-aaeb-dc958a0915b1.png" alt="API Gateway Method Configuration" class="image--center mx-auto" width="1349" height="541" loading="lazy"></p>
<p>For the backend service or Integration type, select Lambda function, and provide the ARN of ExternalLambda1.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759421208532/8d84c272-58ad-4ca4-ae56-682151495b76.png" alt="API Gateway Method Configuration2" class="image--center mx-auto" width="1352" height="538" loading="lazy"></p>
<p>For Authorization, select <code>AWS IAM —→ Cognito user pool authorizers —→ AGAuthorizer1</code>. Leave other configurations, then create the endpoint.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759421234684/1e76c46e-ba07-4178-94b3-e579ed752278.png" alt="API Gateway Method Configuration3" class="image--center mx-auto" width="1352" height="542" loading="lazy"></p>
<p>Repeat the same step to create a <code>POST</code> method for <code>/lambda2</code> resource. The <code>method</code> should be attached to <code>ExternalLambda2</code>, and <code>AGAuthorizer2</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759421691530/563c85e5-52ff-43fa-8216-41fc269989e0.png" alt="API Gateway Deployment" class="image--center mx-auto" width="1349" height="539" loading="lazy"></p>
<p>The API Gateway you’ve created needs to be deployed to become accessible. Deployment is usually done to a Stage.</p>
<p>Click on <code>Deploy API</code>, select New stage and name the stage development. Then, deploy.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759421954217/f0ca31c8-78d9-4b1b-a47c-424f6ef32093.png" alt="API Gateway Stage" class="image--center mx-auto" width="1366" height="541" loading="lazy"></p>
<p>After deployment to a stage, an invoke URL will be provided. This will serve as the base URL for the endpoints you’ve defined.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759422031311/c7cac4e7-52e9-43dd-a565-46aa062aa364.png" alt="API Gateway Stage Overview" class="image--center mx-auto" width="1345" height="539" loading="lazy"></p>
<p>The stage you’ve created needs some modifications for enhanced security. Firstly, you need to attach the <code>WAF</code> that you created earlier. Secondly, the default rate limit for the API deployed to this stage is 10000. Rate limit restricts excessive resource consumption and protects your API from abuse. For this project, you can reduce the limit to 50.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759422132101/b0f67d30-30ec-4d1f-9eee-735dc3b26500.png" alt="Edit API Gateway Stage" class="image--center mx-auto" width="1352" height="542" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759423441047/95dc8f74-c71d-449a-b15a-46caa53c7595.png" alt="API Gateway Stage - Add Rate Limit and WAF" class="image--center mx-auto" width="1352" height="536" loading="lazy"></p>
<p>To test the API Gateway set up, click on the endpoint you want to test, then the <code>Test</code> button. This initial test doesn’t need any authorization, since the test is done directly within the Gateway.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759435394293/8933349c-3e2a-4795-adb4-bb9eeb990e81.png" alt="API Gateway Endpoint Testing" class="image--center mx-auto" width="1346" height="549" loading="lazy"></p>
<p>Add JSON data as the Request body. The key will be <code>name</code>, and the value will be any string.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759435424778/a613816e-8b89-4978-9b3e-6734e48119eb.png" alt="API Gateway Testing2" class="image--center mx-auto" width="1351" height="541" loading="lazy"></p>
<p>The response sent back from ExternalLambda1 shows a status code of 200, and a response body containing exactly the message expected from the Lambda function.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759435744694/516bab58-99de-4b41-8926-e9928b8c42e4.png" alt="API Gateway Test Response" class="image--center mx-auto" width="1349" height="540" loading="lazy"></p>
<p>If you head over to CloudWatch Log groups, you should also find the Log groups that were automatically created for the Lambda functions. Click on the Log group for ExternalLambda1 and navigate to the latest Log stream. You should find the logs for the request you’ve just made from API Gateway.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759435884121/05741f0c-82dd-43f1-855c-157df5c112fc.png" alt="CloudWatch Logs for Testing" class="image--center mx-auto" width="1366" height="546" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759436073425/d4a0a2c7-8d86-44ce-adb7-4c67c3cdf40b.png" alt="CloudWatch Logs for Testing2" class="image--center mx-auto" width="1366" height="551" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759436150374/5fae1a11-c80d-41df-8ea9-39303287144b.png" alt="CloudWatch Logs - Output from InternalLambda" class="image--center mx-auto" width="1366" height="551" loading="lazy"></p>
<h3 id="heading-test-setup-end-to-end">Test Setup End-to-End</h3>
<p>To test our setup properly, and from the internet, send the same request from your API client with no additional information in the request header. This should return a <code>401</code> error – Unauthorized. This is expected.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759436452961/342e7659-5a58-45a2-af26-a657b622a83a.png" alt="Request without Token" class="image--center mx-auto" width="1321" height="448" loading="lazy"></p>
<p>API Gateway expects an authorization token from each request it receives before routing traffic to the appropriate backend service. It validates this token through Cognito.</p>
<p>You’ll mimic a user login for each user added to Coginito User pools to get a token for the user. This token will then be sent alongside any request. To achieve this, you’ll use the two Python scripts I’ve provided below:</p>
<p><code>secure-lambda/auth-scripts/user1.py</code></p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> boto3

client = boto3.client(<span class="hljs-string">"cognito-idp"</span>)

response = client.initiate_auth(
    AuthFlow=<span class="hljs-string">"USER_PASSWORD_AUTH"</span>,  <span class="hljs-comment"># or ADMIN_USER_PASSWORD_AUTH if using admin creds</span>
    AuthParameters={
        <span class="hljs-string">"USERNAME"</span>: <span class="hljs-string">""</span>,             <span class="hljs-comment"># user1 email</span>
        <span class="hljs-string">"PASSWORD"</span>: <span class="hljs-string">""</span>              <span class="hljs-comment"># user1 password</span>
    },
    ClientId=<span class="hljs-string">""</span>                     <span class="hljs-comment"># Cognito App Client ID</span>
)

id_token = response[<span class="hljs-string">"AuthenticationResult"</span>][<span class="hljs-string">"IdToken"</span>]
access_token = response[<span class="hljs-string">"AuthenticationResult"</span>][<span class="hljs-string">"AccessToken"</span>]
refresh_token = response[<span class="hljs-string">"AuthenticationResult"</span>][<span class="hljs-string">"RefreshToken"</span>]

print(<span class="hljs-string">"ID Token:"</span>, id_token)
</code></pre>
<p>Using the Python boto3 library, you’ll initiate an authentication request to Cognito. Provide the email address and password of the user in MyUserPool1. Also, add the Client ID of the App client.</p>
<p>To run the script, create an isolated environment using Pipenv, uv, or a similar library. Install the dependency used in the project as defined in the Pipfile, and run the script with the Pipenv shell.</p>
<pre><code class="lang-bash">pipenv install
pipenv shell
Python secure-lambda/auth-scripts/user1.py
</code></pre>
<p>The Python command will return with a token assigned to the user. Next, you use this token to authorize a user to access ExternalLambda1.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759437804885/f03ab150-74f9-4ecf-9c7c-0ee07e8662a2.png" alt="Add Token to Request Header" class="image--center mx-auto" width="711" height="446" loading="lazy"></p>
<p>Ensure that the URL for the POST request is in the format: &lt;BASE_URL/lambda1&gt;. You should receive a response from API Gateway indicating success.</p>
<p>Now try accessing ExternalLambda2 using User1 token. You should get an <code>Unauthorized</code> message. Note that user1 will always receive an unauthorized message when it tries accessing ExternalLambda1 without an Authorization token in the header, a wrong token, or when it tries accessing ExternalLambda2, which it is not authorized to access.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759438020972/39e2650a-6a99-466a-ad40-6bcf72c491c4.png" alt="User1 Access ExternalLambda2" class="image--center mx-auto" width="1323" height="439" loading="lazy"></p>
<p>Repeat the process with User2 using the token generated for the user in MyUserPool2. First, test access to ExternalLambda2 without a token in the request header.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759438058029/37dbffcb-7f58-4ce0-abfa-abf0e6d1d3a4.png" alt="User2 Request without Token" class="image--center mx-auto" width="1366" height="450" loading="lazy"></p>
<p>Then test access with the token.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759438118097/a408bfe0-6e94-46a1-b98e-c959f31673f8.png" alt="User2 Request with Token" class="image--center mx-auto" width="1311" height="460" loading="lazy"></p>
<p>Next, try accessing ExternalLambda1 using User2.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759438144636/06ba905d-ff7b-48d6-8e5b-e35b959221ba.png" alt="User2 Access ExternalLambda1" class="image--center mx-auto" width="1366" height="466" loading="lazy"></p>
<p>You can also view the outcome of some of the requests made by your client on CloudWatch Logs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759438188476/2e5aaa35-b52c-4fd3-88f4-00bc704cd809.png" alt="CloudWatch Logs Output" class="image--center mx-auto" width="1366" height="535" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759438223720/630bd443-41d2-40b7-b28f-ff937fbf13f9.png" alt="CloudWatch Logs Output2" class="image--center mx-auto" width="1366" height="528" loading="lazy"></p>
<p>Also, since WAF has been configured previously to count requests (although, in a real scenario, you want to achieve much more with WAF, such as allow or block certain traffic), you can view activities captured by WAF by navigating to the service on AWS, then searching for the WAF you configured, and navigating to Traffic overview.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759438469300/df718ef7-7eaa-49ad-8780-c43878d2d388.png" alt="WAF - Traffic details" class="image--center mx-auto" width="1366" height="541" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759438498828/b1619abf-db45-4946-85e2-53e5a769cdb8.png" alt="WAF - Traffic Details2" class="image--center mx-auto" width="1351" height="541" loading="lazy"></p>
<p>You can find other details, such as the client device types and where requests originated.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759438552359/477c4f39-04e4-4427-a2d2-74a6066622dd.png" alt="WAF - Traffic Details3" class="image--center mx-auto" width="1348" height="546" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759438590513/31acecd0-7f45-4fb9-b352-5dc5fcf49e75.png" alt="WAF - Traffic Details4" class="image--center mx-auto" width="1348" height="547" loading="lazy"></p>
<h3 id="heading-clean-up">Clean Up</h3>
<p>It’s important to clean up the resources created so far after the hands-on exercise. Due to the dependencies among the resources, trying to delete a resource that another resource depends on may lead to an error. So, you should delete them in this order:</p>
<ul>
<li><p>Secrets Manager</p>
</li>
<li><p>Cognito – Users, App Client, then User Pool</p>
</li>
<li><p>API Gateway – Endpoints/ Methods, Resources, API, Stage</p>
</li>
<li><p>Web Application Firewall – IP Set, Web ACL</p>
</li>
<li><p>All Lambda Functions</p>
</li>
<li><p>Lambda IAM Roles and the policies attached to them</p>
</li>
<li><p>CloudWatch Log Group for all the Lambda functions</p>
</li>
<li><p>SNS Topic</p>
</li>
</ul>
<p>Also, you can deactivate or delete the credentials created for your IAM Admin user if not in use.</p>
<h2 id="heading-improvements">Improvements</h2>
<p>Consider the following areas to improve, apply best practices to, and enhance the security posture of your systems further.</p>
<ol>
<li><p>Use of API keys</p>
</li>
<li><p>Third-party API consumption</p>
</li>
<li><p>API inventory management/ documentation</p>
</li>
<li><p>Resource provisioning using Infrastructure as Code</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Security at every layer of an IT system is not negotiable. In this project, we’ve demonstrated how to leverage cloud-native solutions to secure APIs hosted in a serverless service, allowing only authorized users access to the APIs.</p>
<p>I’m Agnes Olorundare, and you can find out more about me on <a target="_blank" href="https://www.linkedin.com/in/agnes-olorundare-446055b8/"><strong>LinkedIn</strong></a><strong><em>.</em></strong></p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
