# The DevSec Blueprint's AWS DevSecOps Pipeline

## Introduction

While exploring the world of DevSecOps, I came across [Damien Burks](https://youtube.com/@damienjburks)’ [The DevSec Blueprint](https://www.devsecblueprint.com/) - a comprehensive, free, and open-source learning guide that provides the foundational skills and knowledge needed to break into the field. After grasping the basics, I was eager to get hands-on and build a pipeline in AWS. I decided to dive in by following [a guide](https://www.devsecblueprint.com/projects/devsecops-pipeline-aws/) straight from the DevSec Blueprint website. In this post, I’ll share my experience setting everything up, along with some lessons learned that might be helpful for anyone following a similar path.

## Setting up Dependencies

First things first, I began by setting up the dependencies listed below.

* Setup GitHub Repos
    
* Setup Snyk Account
    
* Setup Terraform Cloud
    
* Configure Deployment Role in AWS
    
* Configure Secrets and Environment Variables
    

### Setup GitHub Repos

I forked both the repos [AWS DevSecOps Pipeline](https://github.com/devsecblueprint/aws-devsecops-pipeline) and [Awesome FastAPI](https://github.com/devsecblueprint/awsome-fastapi) to my personal github account and then cloned both the forked repos to my local machine. I used GitHub CLI to interact with my GitHub account.

`gh repo clone mdcn47/aws-devsecops-pipeline`

`gh repo clone mdcn47/awsome-fastapi`

### Setup Snyk Account

I signed up for [Snyk](https://app.snyk.io/login) using my GitHub account. Once logged-in, I clicked on my name in the bottom-left corner of the page and selected **Account Settings**. In the **General** section, I located the **Auth Token** field, generated a token, and noted it down for later integration. After that, I navigated to the Organization **Settings** page to obtain the **Organization ID** as well.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756499806470/4e99c69c-0ed3-43c0-b424-8302b95f88b3.gif align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756499638418/6e96b51f-3359-4d02-bbc8-ecac5e9ca4f8.png align="center")

### Setup Terraform Cloud

I signed up for [Terraform Cloud](https://app.terraform.io/public/signup/account), also using my GitHub account. Once I logged-in, I created an organization named `MDCN` which was pretty straightforward.

### Configure Deployment Role in AWS

In this step, I’m setting up an AWS IAM role with OpenID Connect (OIDC), so Terraform Cloud can assume the role and deploy infrastructure changes on my behalf.

*Adding Terraform Cloud as an OIDC provider*

![Adding Terraform Cloud as an OIDC provider](https://cdn.hashnode.com/res/hashnode/image/upload/v1750964121520/36ac5890-b183-47bb-90c1-b5996732c987.png align="center")

*Creating an IAM Role for Terraform*

I’ve begun by filling out the trust relationship fields, as shown below.

![Creating an IAM Role for Terraform](https://cdn.hashnode.com/res/hashnode/image/upload/v1750964931407/222e5974-5541-46f1-b440-1afdd73bd887.png align="center")

Following that, I created the role named `terraform-cloud-deployer-oidc`, attached the **AdministratorAccess** policy to it, and recorded its **ARN** for use in later steps.

### Configure Secrets and Environment Variables

I started by defining the Organizational Variable Set in Terraform Cloud, which included creating the variables `TFC_AWS_PROVIDER_AUTH` and `TFC_AWS_RUN_ROLE_ARN`.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751049126599/1630f69e-2338-4bb9-ab0e-3b9ff9f2b1e8.png align="center")

The following step was not documented in the guide; I identified it while working through the implementation.

Since we'll be using GitHub Actions to automate deployments when changes are pushed to the `main` branch, we need to generate a Terraform API token and add it as a repository secret in GitHub.

So I generated a Terraform API token using the steps shown below. Be sure to store the token securely, as it will only be displayed once.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751136150942/d2feadad-19be-44a1-87ce-d91d4dcb65bb.gif align="center")

Then I added the Terraform API token as a secret to the forked `aws-devsecops-pipeline` repository.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751049793532/679c0500-84f9-4873-a441-eca045a3c3b2.png align="center")

## Deploying and Testing

In this final stage, I deploy and configure the infrastructure, run the pipeline, and analyze the output.

### Deploying and Configuring the Pipeline

I logged into Terraform Cloud and created a project called `devsecops-aws` under my organization, `MDCN`. Within this project, I set up two workspaces: `aws-devsecops-eks-cluster` and `aws-devsecops-pipelines`.

***Note***\*: In the guide, the workspace names included the organization name as a prefix. However, this will lead to errors later when running the workflow, since the workspace names in the Terraform configuration files (`provider.tf`) and the GitHub Actions workflow files (`terraform-apply-eks.yml` *and* `terraform-apply-pipelines.yml`) do not match.\*

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751134766417/ca316b81-eadf-472d-99bb-317823a7ce88.png align="center")

However, there's an issue: Terraform Cloud is unable to validate the configurations because the organization name in my account differs from the one specified in the Terraform code of the forked repository.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751123007485/f5549263-6ec3-4e06-a74b-3e57f3cc0207.gif align="center")

So, I used `grep` to search the local repository for the string `dsb` - the organization name used in the guide - to identify all the files that needed to be updated.

`grep -rniw . -e "dsb"`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751123362483/f92b12be-0dc6-459d-9850-8b6dd174f1aa.png align="center")

I replaced all instances of `dsb` with `mdcn` in the relevant files and pushed the changes to the repository.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751128130053/359d022f-8636-4456-b5cb-92c78306a6e4.png align="center")

Next, I created two variables, `SNYK_ORG_ID` and `SNYK_TOKEN`, in the `aws-devsecops-pipelines` workspace and populated them with their respective values.

I then tried to deploy the changes using GitHub Actions.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751129732290/f8ba3fb2-cb1e-49b3-a898-a4c583c1d09d.gif align="center")

However, the deployment failed at the *Create Apply Run* step. From the error message, it seems the connection to the AWS account failed.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751821156209/3eaddf7d-15a2-4e99-ab62-1894976c81f6.png align="center")

While troubleshooting this issue, I noticed another required change in the `main.tf` file - replacing the AWS username from `damien` to `mdcn47` (my AWS IAM user). I made the change right away.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756315095613/048e757a-4e0a-4739-a75d-f79e43623106.png align="center")

After digging through the Terraform code, IAM role settings, and Terraform Cloud configuration, I finally tracked down the issue. I had originally created my Terraform Cloud project as `aws-devsecops`, but when setting up the IAM role I followed the guide and used `AWS` as the project name. This mismatch caused the trust policy to reference the wrong project. To fix it, I simply renamed my Terraform Cloud project to `AWS`.

I tested the connection by running `terraform plan` locally.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756315041386/eed75835-d0ad-4dac-865e-7c0c3c8be0b1.png align="center")

With that fixed, it’s a good time to kick off another deployment using GitHub Actions. This time the failure happened during the *Apply* step. Turns out the AMI type specified in the configuration is incompatible with the current Kubernetes version.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756480073521/535c46b3-5d32-4504-9fd2-13ada64854b9.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756480150435/31db31e5-cbc3-48b4-bfa8-b97f24151caa.png align="center")

So I updated the code, swapping out `ami_type = AL2_x86_64` for `ami_type = AL2023_x86_64_STANDARD` in the `aws_eks_node_group` resource block (line number 98).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756482089717/03da0539-7f9e-47d3-ae94-5bf790a8209c.png align="center")

The git push automatically triggered the workflow again and this time both the *eks-cluster* and *pipelines* terraform ran successfully.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756483144808/687523cf-172f-4860-9c2c-68d7ca9b551a.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756483131763/10b7d5df-8452-40b9-9978-1f6d4cd75596.png align="center")

I logged into my AWS account, checked the EKS console, and confirmed that the cluster, nodes, and pods were all successfully created.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756493590020/113e889b-97e7-4065-9674-bdb7e99716a9.gif align="center")

However, when I checked AWS CodePipeline, I noticed that the `awsome-fastapi` application failed to deploy because of a GitHub connection error.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756494060844/60c32272-87e1-44e1-acd3-a815524966fd.gif align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756494157047/8730d3a4-5d87-4875-b5e3-1d6b5664a2f3.png align="center")

Navigating to *Settings &gt; Connections* clearly indicates that the connection is in a pending status.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756495402133/a8db7932-4838-4cc2-aa2b-65b0685de190.png align="center")

I followed the steps in the guide and was able to set up the connection without any issues.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756495433671/45ae8998-1e6a-472a-95a1-b64e09bd280f.png align="center")

The pipeline is now operational and configured to automatically detect and deploy changes from the GitHub repository.

### Running the Pipeline and Analyzing Outputs

I triggered the pipeline by navigating to the CodePipeline dashboard, selecting the `awsome-fastapi` pipeline, and clicking **Release change**.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756495446335/a161538a-3e21-48ad-8fcc-04eb9ca3f866.png align="center")

Clicking that button kicks off the pipeline - it’s expected to pull the latest code from GitHub, build the project, run tests and security scans, and finally deploy the application into the EKS cluster. However, the pipeline failed at the *SnykSecurityScan* step within the *Test* phase.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756535214571/69a83142-80d2-4011-bc49-15bc0b3542b8.png align="center")

Digging into the error, I found that it failed while executing the command `snyk auth ${SNYK_TOKEN}`. A bit of research suggested that the curly braces `{}` might not be necessary, and removing them could fix the issue.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756535203646/ea9e30d9-aa62-4929-8c5f-68059a9025e5.png align="center")

I went ahead and updated the `sastscanning.yml` file inside *codepipeline/buildspecs* and pushed the changes to the repository.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756540107624/1567e2f7-a8af-4474-9dba-9d91850af728.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756540429037/2c301a96-157f-4458-af93-491a6faa33d8.png align="center")

The new commit triggered another pipeline run. For the application deployment, I manually triggered the pipeline again by clicking *Release change* in CodePipeline. This time, Snyk authenticated successfully, but the pipeline failed as expected because it detected a **critical** severity vulnerability, exceeding the **high** threshold we had set.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756542614457/800b230a-ea19-4a1a-b58a-d36d73d30e07.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756542638429/f59139fa-aa61-4799-bacf-78381c57ad1d.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756543127837/a1fece86-34d2-4844-889a-ba819c6e750d.png align="center")

The Snyk CLI exits with status code 1 whenever it finds vulnerabilities that meet or exceed the severity threshold we’ve set (high in our case). However, for testing the application deployment, I want the pipeline to continue while still reporting any issues. To achieve this, I appended `|| true` to the command, making it: `snyk test --file=requirements.txt --severity-threshold=high || true`.

I updated the `sastscanning.yml` file once more and pushed the changes to the repository.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756544407375/6e802988-7e3d-4a22-ae78-5fcdcbf252f3.png align="center")

With this run, every test passed and the app was deployed without any issues.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756548673570/c52ced3e-59f8-4fda-9a3b-0e0c717bdd24.png align="center")

While going through the execution logs, I noticed that the Snyk Code scan hadn’t run because it wasn’t enabled in my Snyk account. I quickly turned it on and reran the deployment.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756550298815/085ca04b-a721-469a-a3a4-28cbc573bde6.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756550601490/88199431-d884-44c8-8d90-34f46950fd2a.gif align="center")

*Synk Scan Results*

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756553444495/110e0312-92cf-4f3a-b61e-4c7434df6f12.gif align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756554636271/6ba4036e-dd58-44a4-bae6-f6dfcc7f7bc3.png align="center")

*Trivy Scan Results (Container Security Scan)*

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756554051728/83141b4a-c25e-4be2-9593-59e388492e54.gif align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756554649715/3086fbc9-dc87-4e3b-8e8c-8a98234576d4.png align="center")

With the application successfully deployed, I wanted to verify that it was running smoothly without any issues. I opened my EKS cluster (`mdcn-devsecops-cluster`), navigated to *Resources &gt; Services &gt; awsome-fastapi*, grabbed the load balancer URL, and accessed it in the browser. Since the app was running on port 80, I used HTTP. I was greeted with a *Hello World* message, confirming that the app was up and running without any issues.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756556579447/9cc7f4d5-5d08-4c4d-a6c9-ff6b16d991b2.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756556963155/3b2bfecd-0a80-4b3f-9452-2ce792046cd6.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756557288686/f16d676e-649f-424f-ad12-03e84a8f8c7d.gif align="center")

It’s been a great learning journey, but I can’t stop here - I need to tear down the AWS infrastructure from this exercise before it surprises me with a painful credit card bill.

I navigated to the `pipeline` and `eks-cluster` Terraform directories and ran `terraform destroy --auto-approve`.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756561892579/da5076b0-dea6-44af-b695-6602a1077e2b.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756561907598/45babcae-495e-474c-b593-5feb616ec45d.png align="center")

And that’s a wrap! The pipeline ran, the app deployed successfully, I learned a ton, and all AWS resources are now safely cleaned up - no surprises for my credit card.
