Recently, I was interviewed for a DevOps role, and I was asked, "Do you use Terraform in CI/CD?" I said yes. How do you use Terraform in CI/CD? Put the AWS credentials in the GitHub secret and use it with the aws-cli tool to provision the infrastructure. Sadly, I didn't get the job, but I was compelled to explore a new way, so here I am sharing what I found.
Prerequisite: AWS & GitHub Account, Terraform
Let me tell you more about the question, he wanted to know how I handle the permission as this is the most important thing in DevOps, the answer I gave was alright, but it turns out we have more security in doing the same task, by letting the GitHub action or any other CI/CD tool to assume the role from AWS directly without us storing the aws secret or access key in the vault of our CI/CD tools. Instead, we can provision the dynamic credentials by using the AWS sts assume role & OIDC.
STEP I
To tackle this challenge securely and efficiently, the first step is to establish trust between AWS and GitHub Actions by setting up an OIDC provider. Let’s dive into how to do that. If you want to read more about OIDC click here.
Click on the Add Provider
Enter these details
OIDC provider: https://token.actions.githubusercontent.com
Audience: sts.amazonaws.com
Now that we have established the OIDC trust between AWS and GitHub, the next step is to create an IAM role with a custom trust policy. This role will allow GitHub Actions to assume the required permissions dynamically.
STEP II
Let's Create the IAM role using a custom trust policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<AWS_ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:your-username/your-repo:ref:refs/heads/branch-name"
}
}
}
]
}
NOTE: Kindly Replace these placeholders
<your-username> with your GitHub username.
<your-repo> with your repository's name.
<branch-name> with the branch name (e.g., main or master)
After Replacing those placeholders, let's add the permission for the services this role will allow the GitHub action to provision, for simplicity I'm giving it PowerUser permission but again change them according to needs.
STEP III
With the IAM role in place, it’s time to configure our GitHub Actions workflow. Here, we’ll set up a YAML file to interact with AWS services using the dynamic credentials generated by the assumed role
name: Deploy to AWS
on:
push:
branches:
- master
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# Step 1: Checkout code
- name: Checkout repository
uses: actions/checkout@v3
# Step 2: Configure AWS credentials using OIDC
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ secrets.AWS_REGION }}
# Step 3: Example AWS command (S3 upload in this case)
- name: Upload files to S3
run: |
aws s3 cp ./12.png s3://${{ secrets.S3_BUCKET_NAME }}/
# Step 4: (Optional) Additional AWS CLI commands or deployment steps
- name: Example additional AWS command
run: |
aws ec2 describe-instances
There are a couple of things which you need to understand
permission block contains 2 fields id-token and content. By id-token we are setting OIDC to be used, by using content: read we are allowing the workflow to read the repo files. Rest is simple
After the setup is completed here are my workflow screenshots
The bucket containing the uploaded file from the repo
At this point, your GitHub Actions pipeline is securely configured to interact with AWS services. Next, we’ll integrate Terraform into the pipeline to demonstrate how to provision infrastructure dynamically.
STEP IV
Now the last leg Running the CI/CD with Terraform For that we need to write the Terraform script and modify the action yml file.
workflow.yml file
name: Terraform EC2 Provisioning
on:
push:
branches:
- master
permissions:
id-token: write
contents: read
jobs:
terraform:
runs-on: ubuntu-latest
steps:
# Step 1: Checkout the repository
- name: Checkout repository
uses: actions/checkout@v3
# Step 2: Configure AWS credentials using OIDC
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ secrets.AWS_REGION }}
# Step 3: Set up Terraform CLI
- name: Set up Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.0
# Step 4: Initialize Terraform
- name: Terraform Init
run: terraform init
# Step 5: Plan Terraform changes
- name: Terraform Plan
run: terraform plan -out=tfplan
# Step 6: Apply Terraform changes
- name: Terraform Apply
run: terraform apply -auto-approve tfplan
Now some Terraform scripts
main.tf
provider "aws" {
region = var.region
}
resource "aws_instance" "example" {
ami = var.ami_id
instance_type = var.instance_type
tags = {
Name = "ExampleInstance"
}
}
output "instance_id" {
value = aws_instance.example.id
}
output "public_ip" {
value = aws_instance.example.public_ip
}
terraform.tfvars
region = "eu-west-2"
ami_id = "ami-0b2ed2e3df8cf9080" # Replace with your preferred AMI ID
instance_type = "t2.micro"
variables.tf
variable "region" {
description = "AWS region"
type = string
default = "eu-west-2"
}
variable "ami_id" {
description = "AMI ID for the EC2 instance"
type = string
}
variable "instance_type" {
description = "Instance type for the EC2 instance"
type = string
default = "t2.micro"
}
With these Terraform scripts in place, the pipeline can now provision an EC2 instance as part of the CI/CD process. Let’s run the workflow and see the results in action.
Checking the Github Logs
And my friend, now you know how to configure the pipeline without using the static credentials stored in GitHub Secrets.
Now that everything is set up, we can observe the results of our secure and dynamic CI/CD pipeline in GitHub Actions logs and AWS resources. This demonstrates the power of OIDC integration in real-world DevOps workflows.
Conclusion
By integrating OIDC (OpenID Connect) with AWS and GitHub Actions, we enhance security and simplify the CI/CD process. Instead of relying on static credentials stored in GitHub secrets, we use dynamically generated, short-lived credentials through AWS STS.
This approach offers several key benefits:
- Improved Security: No sensitive credentials are stored in CI/CD tools, reducing the risk of accidental exposure or misuse.
- Granular Access Control: Using IAM trust policies, access can be tightly scoped to specific repositories, branches, and workflows.
- Automation-Friendly: Dynamic credentials streamline workflows, making them more robust and easier to maintain.
- Reduced Attack Surface: Temporary credentials expire quickly, minimizing the impact of potential leaks.
Thank you for Reading if you face any issues feel free to ask in the comments