<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Adan Alvarez</title>
    <description>The latest articles on DEV Community by Adan Alvarez (@adanalvarez).</description>
    <link>https://dev.to/adanalvarez</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%2F2968174%2F090f7803-170a-44dc-961e-a9a72b1c6eaf.jpg</url>
      <title>DEV Community: Adan Alvarez</title>
      <link>https://dev.to/adanalvarez</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adanalvarez"/>
    <language>en</language>
    <item>
      <title>Gaining Long-Term AWS Access with CodeBuild and GitHub</title>
      <dc:creator>Adan Alvarez</dc:creator>
      <pubDate>Sun, 13 Apr 2025 11:35:08 +0000</pubDate>
      <link>https://dev.to/aws-builders/gaining-long-term-aws-access-with-codebuild-and-github-1nc6</link>
      <guid>https://dev.to/aws-builders/gaining-long-term-aws-access-with-codebuild-and-github-1nc6</guid>
      <description>&lt;p&gt;As I wrote in other blogs, such as &lt;a href="https://medium.com/@adan.alvarez/gaining-aws-persistence-by-updating-a-saml-identity-provider-ef57ebdc8db5" rel="noopener noreferrer"&gt;Gaining AWS Persistence by Updating a SAML Identity Provider&lt;/a&gt;, when an attacker compromises an AWS account, one of the first tactics they will attempt is to gain &lt;a href="https://attack.mitre.org/tactics/TA0003/" rel="noopener noreferrer"&gt;persistence&lt;/a&gt;. This is because access obtained through temporary credentials may expire quickly, or the attacker may want to ensure continued access even if their initial foothold is discovered and removed.&lt;/p&gt;

&lt;p&gt;That’s why I’m interested in exploring how legitimate AWS services can be abused for persistence. While creating IAM users and assigning them admin privileges is still a common and effective tactic (as we continue to observe in data from &lt;a href="https://traildiscover.cloud/" rel="noopener noreferrer"&gt;TrailDiscover&lt;/a&gt;), it’s noisy and easier to detect.&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%2Fezzfb5pbllrtieb69o8b.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%2Fezzfb5pbllrtieb69o8b.PNG" alt="Figure 1. TrailDiscover most reported event names" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this article, I’ll explore how an attacker could use/abuse &lt;a href="https://docs.aws.amazon.com/codebuild/latest/userguide/welcome.html" rel="noopener noreferrer"&gt;AWS CodeBuild&lt;/a&gt; for persistence. I haven’t seen this particular approach documented in other research, but let me know if it’s been covered elsewhere.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Disclaimer&lt;/strong&gt;: This is &lt;strong&gt;NOT&lt;/strong&gt; an AWS vulnerability. The technique described assumes the attacker has already compromised the account and has sufficient permissions. The goal is to expose how CodeBuild can be used to establish long-term access.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is AWS CodeBuild?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/codebuild/" rel="noopener noreferrer"&gt;CodeBuild&lt;/a&gt; is a fully managed continuous integration service that compiles source code, runs tests, and produces software packages for deployment. It supports custom build environments and integrates with popular tools such as GitHub.&lt;/p&gt;

&lt;p&gt;In mid-2024, AWS introduced support for running &lt;a href="https://aws.amazon.com/blogs/devops/aws-codebuild-managed-self-hosted-github-action-runners/" rel="noopener noreferrer"&gt;self-hosted GitHub Actions&lt;/a&gt; runners using CodeBuild. This functionality allows you to configure a CodeBuild project to act as a runner for GitHub Actions.&lt;/p&gt;

&lt;p&gt;This GitHub Actions integration is the option we are going to explore to see how an attacker can abuse it to gain persistence.&lt;/p&gt;

&lt;h2&gt;
  
  
  How an Attacker Could Abuse CodeBuild
&lt;/h2&gt;

&lt;p&gt;The technique is surprisingly straightforward.&lt;/p&gt;

&lt;p&gt;Because CodeBuild now supports self-hosted GitHub Actions runners, an attacker can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure a &lt;strong&gt;CodeBuild project&lt;/strong&gt; to serve as a GitHub Actions runner.&lt;/li&gt;
&lt;li&gt;Link that project to a &lt;strong&gt;GitHub repository controlled by the attacker&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Modify an &lt;strong&gt;IAM role&lt;/strong&gt; to allow &lt;strong&gt;CodeBuild to assume it&lt;/strong&gt;. Then this role will provide access to the AWS environment.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s see these steps in more detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Backdoor a Role
&lt;/h3&gt;

&lt;p&gt;The attacker first needs to choose an IAM role to abuse. Ideally, this is an existing role with broad/admin privileges. In most cases, backdooring an existing role is often stealthier than creating a new one.&lt;/p&gt;

&lt;p&gt;To “backdoor” the role, the attacker modifies its &lt;strong&gt;trust policy&lt;/strong&gt; to allow the CodeBuild service to assume it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "Effect": "Allow",
  "Principal": {
    "Service": "codebuild.amazonaws.com"
  },
  "Action": "sts:AssumeRole"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F0oqms06t5a8yel86rm4c.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%2F0oqms06t5a8yel86rm4c.PNG" alt="Figure 2. Adding CodeBuild to a trust policy" width="706" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This change allows CodeBuild builds to assume the role and inherit its permissions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Backdooring IAM roles for persistence is a well-known technique. It has been &lt;a href="https://hackingthe.cloud/aws/post_exploitation/iam_persistence/#iam-role-assume-role-policy" rel="noopener noreferrer"&gt;documented in research&lt;/a&gt; and &lt;a href="https://www.invictus-ir.com/news/the-curious-case-of-dangerdev-protonmail-me" rel="noopener noreferrer"&gt;observed in real-world incidents&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 2: Create a CodeBuild Project and Connect it to GitHub
&lt;/h3&gt;

&lt;p&gt;Once the attacker has selected and backdoored an IAM role, the next step is to go into AWS CodeBuild and create a new build project linked to a GitHub repository they control.&lt;/p&gt;

&lt;p&gt;While CodeBuild projects can be created via API or CLI, connecting a GitHub repository often requires going through the AWS Console. Fortunately for the attacker, it’s relatively easy to move from IAM credentials to a web console session, as described here: &lt;a href="https://hackingthe.cloud/aws/post_exploitation/create_a_console_session_from_iam_credentials/" rel="noopener noreferrer"&gt;Create a Console Session from IAM Credentials&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Selecting a Project name and type&lt;/strong&gt;&lt;br&gt;
The first step when creating a project will be to select the name and the type. In this case, the type will be a “Runner project”&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%2F9vq5sqn5eap08xs0saf8.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%2F9vq5sqn5eap08xs0saf8.PNG" alt="Figure 3. Create a CodeBuild project with “Runner project” type" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connecting to GitHub&lt;/strong&gt;&lt;br&gt;
To link a GitHub repository to the build project, AWS provides three options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub App&lt;/li&gt;
&lt;li&gt;OAuth App&lt;/li&gt;
&lt;li&gt;Personal Access Token (PAT)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To minimize noise and complexity, the attacker can choose the simplest route: using a GitHub Personal Access Token (PAT). This PAT would grant access to a repository that the attacker controls (it can even be private).&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%2Fq7sxcknnud9n6hw0v1ws.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%2Fq7sxcknnud9n6hw0v1ws.PNG" alt="Figure 4. CodeBuild runner configuration" width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyz4yup6gazhs7njy52mg.webp" 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%2Fyz4yup6gazhs7njy52mg.webp" alt="Figure 5. Adding credentials to connect with GitHub" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the GitHub connection is established, the attacker can select the Repository&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%2F1has1zmpxfz0nuv212u8.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%2F1has1zmpxfz0nuv212u8.PNG" alt="Figure 6. Selecting a repository after successfully connected to GitHub" width="800" height="869"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using the Backdoored Role&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the project settings, under the “Environment” section, the attacker can choose to use an existing service role; this is where they select the backdoored IAM role from Step 1.&lt;/p&gt;

&lt;p&gt;There is also a checkbox: “&lt;em&gt;Allow AWS CodeBuild to modify this service role so it can be used with this build project&lt;/em&gt;.”&lt;/p&gt;

&lt;p&gt;If this box is checked, CodeBuild will automatically add extra permissions to the role (for things like writing logs to CloudWatch or interacting with CodeBuild resources). If the role already has admin-level permissions, the attacker won’t need to enable this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8w0pyx3snh86necjei3x.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%2F8w0pyx3snh86necjei3x.PNG" alt="Figure 7. Configuring the backdoored role as the service role" width="800" height="1190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The attacker will likely disable CloudWatch Logs to reduce visibility of their build runs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Step 3: Set Up the GitHub Action
&lt;/h3&gt;

&lt;p&gt;At this point, the attacker has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created a CodeBuild project&lt;/li&gt;
&lt;li&gt;Linked it to a GitHub repository they control&lt;/li&gt;
&lt;li&gt;Configured it to assume a backdoored IAM role&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final step is to create a GitHub Action workflow that executes commands in the AWS environment using the assumed role.&lt;/p&gt;

&lt;p&gt;The workflow can be extremely simple. Here’s an example that will run on every push and call the &lt;code&gt;sts:GetCallerIdentity&lt;/code&gt; API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Backdoor AWS Access
on: [push]
jobs:
  persistence-access:
    runs-on:
      - codebuild-MyGitHubRunner-${{ github.run_id }}-${{ github.run_attempt }}
    steps:
      - name: Verify AWS Access
        run: |
          aws sts get-caller-identity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s an example of the execution of this action:&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%2Fwqotei64jcqwwhp8gbmy.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%2Fwqotei64jcqwwhp8gbmy.PNG" alt="Figure 8. Logs from the GitHub action that has access to the AWS environment" width="800" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This confirms that the workflow is successfully running in the AWS environment with permission from the backdoored role. From here, the attacker can execute any AWS commands their role allows.&lt;/p&gt;

&lt;p&gt;With these three simple steps, the attacker now has persistent access to the AWS environment. Even if their initial credentials are revoked or the original attack path is closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defending Against CodeBuild-Based Persistence
&lt;/h2&gt;

&lt;p&gt;Let’s walk through what defenders can observe (and what they can’t):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudTrail Logs&lt;/strong&gt;&lt;br&gt;
During the setup phase, several key events will appear in CloudTrail, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/IAM/latest/APIReference/API_UpdateAssumeRolePolicy.html" rel="noopener noreferrer"&gt;&lt;strong&gt;UpdateAssumeRolePolicy&lt;/strong&gt;&lt;/a&gt;: This event happens when the role is backdoored. We’ll see in the parameters the policyDocument containing codebuild.amazonaws.com&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/codebuild/latest/APIReference/API_ImportSourceCredentials.html" rel="noopener noreferrer"&gt;&lt;strong&gt;ImportSourceCredentials&lt;/strong&gt;&lt;/a&gt;: This event happens when the connection with GitHub is established, but it will depend on the configuration. In the case of a PAT in codebuild we’ll see a requesParameters that will look like:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F02o5sc4hfq5yzoutm46f.webp" 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%2F02o5sc4hfq5yzoutm46f.webp" alt="Figure 9. requestParameters from the ImportSourceCredentials event&amp;lt;br&amp;gt;
" width="464" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/codebuild/latest/APIReference/API_CreateProject.html" rel="noopener noreferrer"&gt;&lt;strong&gt;CreateProject&lt;/strong&gt;&lt;/a&gt;: This event happens when the project is created.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/codebuild/latest/APIReference/API_CreateWebhook.html" rel="noopener noreferrer"&gt;&lt;strong&gt;CreateWebhook&lt;/strong&gt;&lt;/a&gt;: This event happens when the project is created and is meant to create the webhook that will trigger the build from GitHub.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ProcessWebhook&lt;/strong&gt;: This event happens when CodeBuild processes an event from GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;IAM Role Abuse Visibility&lt;/strong&gt;&lt;br&gt;
Once the GitHub Action runs, all actions will be performed under the backdoored IAM role.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AssumeRole&lt;/strong&gt; will happen every time the CodeBuild assumes the backdoored role.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source IP&lt;/strong&gt; will appear as AWS infrastructure, or even an IP from your own VPC if the attacker configures CodeBuild to run in VPC mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PrincipalID&lt;/strong&gt; and arn will contain &lt;em&gt;AWSCodeBuild&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Access Analyzer Blind Spot
&lt;/h3&gt;

&lt;p&gt;One crucial point: AWS Access Analyzer does &lt;strong&gt;&lt;em&gt;not&lt;/em&gt;&lt;/strong&gt; detect the GitHub connection as external access.&lt;/p&gt;

&lt;p&gt;This is a major blind spot. Many security teams rely on Access Analyzer to detect unexpected trust relationships, but in this case, no alerts are triggered, even though the GitHub repo is controlling a role externally.&lt;/p&gt;

&lt;p&gt;I confirmed this with the AWS security team, and “&lt;em&gt;it is expected behavior&lt;/em&gt;”. But it certainly complicates detection.&lt;/p&gt;

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

&lt;p&gt;This is just another example of how attackers can abuse legitimate AWS services for persistence. With over 200 AWS services available, attackers with highly privileged access have plenty of options to carry out their attacks. As defenders, we should be prepared for the day an attacker gains access to our environment, because a compromised account &lt;a href="https://medium.com/@adan.alvarez/breached-not-game-over-learn-how-to-turn-the-tables-on-aws-attackers-1b8ec00fa83c" rel="noopener noreferrer"&gt;shouldn’t be game over&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For this reason, it is essential to understand diverse abuse paths, monitor CloudTrail logs, audit IAM trust relationships, and stay aware of service limitations (like the one described here with Access Analyzer). These are key actions that will allow us to detect and stop an attack before it’s too late.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>DIY — Building a Cost-Effective Questionnaire Automation with Bedrock</title>
      <dc:creator>Adan Alvarez</dc:creator>
      <pubDate>Sun, 23 Mar 2025 12:56:34 +0000</pubDate>
      <link>https://dev.to/aws-builders/diy-building-a-cost-effective-questionnaire-automation-with-bedrock-2fb9</link>
      <guid>https://dev.to/aws-builders/diy-building-a-cost-effective-questionnaire-automation-with-bedrock-2fb9</guid>
      <description>&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%2Fjrym4qy61k4iedauqc6d.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%2Fjrym4qy61k4iedauqc6d.png" alt="Questionnaire Automation with Bedrock" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Security questionnaires are very common today. When customers consider your product, especially if you’re a startup, they often ask for proof that using your service is safe. This can become a huge time-sink: you may spend hours answering questions that don’t always apply to you.&lt;/p&gt;

&lt;p&gt;I won’t cover other strategies, like having a dedicated security page, getting a SOC 2 report, or using a standard security questionnaire. For that, I recommend reading &lt;a href="https://infosecfromhome.medium.com/should-you-accept-the-costs-of-high-assurance-iscompliance-based-security-worth-the-tradeoff-for-36958df66ff" rel="noopener noreferrer"&gt;Rami McCarthy’s post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Instead, this post focuses on how to quickly answer security questionnaires using &lt;strong&gt;Amazon Bedrock&lt;/strong&gt;, &lt;strong&gt;Aurora PostgreSQL with pgvector&lt;/strong&gt;, &lt;strong&gt;AWS Lambda&lt;/strong&gt;, and &lt;strong&gt;Amazon S3&lt;/strong&gt;. With these services, you can create a serverless knowledge base that stores your security documentation and old questionnaire answers. You can query it to generate relevant answers with minimal cost (you only pay when you actually use it).&lt;/p&gt;

&lt;h2&gt;
  
  
  Searching for a solution
&lt;/h2&gt;

&lt;p&gt;When I started looking for ways to build a security knowledge base in AWS, somewhere I could keep all my security documentation in one place and let others query it easily, I noticed most blogs recommended using Bedrock with Amazon OpenSearch Service’s vector database. However, many of them ended with a warning: “After testing, you should delete your OpenSearch domain, or else you’ll end up with a big bill.” Indeed, OpenSearch can become quite expensive.&lt;/p&gt;

&lt;p&gt;I discovered Aurora PostgreSQL as another option for storing documents. After testing, Aurora’s cost was lower than OpenSearch, but it was still around $1 per day, so over $30/month. For something used only occasionally (like security questionnaires), I wanted an almost cost-free solution when not used.&lt;/p&gt;

&lt;p&gt;Then I found that if I upgraded Aurora PostgreSQL to version 8.5, I could configure the minimum capacity to 0. This makes the cost $0 when not in use. If you try to use the recommended &lt;em&gt;quick create&lt;/em&gt; way, you’ll end up with a PostgreSQL that does not scale to 0, so I built Terraform code to create the infrastructure for me.&lt;/p&gt;

&lt;p&gt;Additionally, because my main goal was to handle security questionnaires, I wrote two Lambda functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sync Function&lt;/strong&gt;: Whenever I upload new security documents to an S3 bucket, it ingests them into my Aurora-based knowledge base.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Q&amp;amp;A Function&lt;/strong&gt;: I can upload a text file of questions to another S3 bucket; the Lambda queries the knowledge base via Bedrock and then saves the answers as a CSV in that same bucket.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my setup, I’ve been using Anthropic Claude to generate the responses. However, the Terraform variables in my repository allow you to customize which model ARN you want to use. Of course, these answers need a review, but having a first pass speeds things up. The more questionnaires you fill out and upload to your knowledge base, the better your system becomes over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Infrastructure Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Aurora PostgreSQL v8.5
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Stores your documentation and security policies in a table that supports vector similarity search (pgvector).&lt;/li&gt;
&lt;li&gt;Uses a min capacity of 0, so you pay nothing when it’s idle.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Amazon Bedrock
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Handles both retrieving the right documents and generating text (a Retrieval-Augmented Generation approach).&lt;/li&gt;
&lt;li&gt;Links to your Aurora database via a “knowledge base” configuration.&lt;/li&gt;
&lt;li&gt;In the Terraform code, you can specify which LLM model ARN to use (Anthropic Claude, Amazon Titan, etc.).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AWS Lambda and S3
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data Ingestion&lt;/strong&gt;: One Lambda function watches for new files in an S3 bucket and loads them into Aurora.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Q&amp;amp;A&lt;/strong&gt;: Another Lambda function reads a file of questions from a different S3 bucket, calls Bedrock for answers, and saves the results as a CSV.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;The complete Terraform code is here: &lt;a href="https://github.com/adanalvarez/bedrock-secure-questionnaire-automation" rel="noopener noreferrer"&gt;adanalvarez/bedrock-secure-questionnaire-automation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In that repo, you’ll find all the terraform modules to set up Aurora Serverless, S3 buckets, Lambda functions, and the Bedrock knowledge base.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-Up
&lt;/h2&gt;

&lt;p&gt;When security questionnaires can’t be avoided, automation with an LLM can save time. By storing key documents and previous answers in a secure, queryable knowledge base, you can quickly generate responses and cut down on repetitive work. This approach helps you keep costs near zero when you’re not actively using it, which is ideal for a task that hopefully doesn’t happen every day!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>rag</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
