Securely Deploy to AWS

Varish AnsariVarish Ansari
4 min read

With OIDC and IAM Roles and Github Action

https://token.actions.githubusercontent.com

sts.amazonaws.com

Create a bucket

Create an S3 bucket to store our Terraform states.

Create your AWS IAM Role and policies

For permission set create poliicy

1. Create Policy for access bucket for state file

Copy{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::YOUR_BUCKET/*",
                "arn:aws:s3:::YOUR_BUCKET"
            ]
        }
    ]
}
  1. Create on more policy with requied permission to iam role to create infra on aws

Add this both policy to role

Step 2: Select trusted entity

Copy{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::YOUR_ACCOUNT_NUMBER:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:YOUR_GITHUB_USERNAME/YOUR_REPO_NAME:*"
                }
            }
        }
    ]
}

You will need to modify:

  1. YOUR_ACCOUNT_NUMBER (AWS)

  2. YOUR_GITHUB_USERNAME

  3. YOUR_REPO_NAME

    Go to ssh to get “YOUR_GITHUB_USERNAME/YOUR_REPO_NAME”

Terraform code

create your terraform code and also provider block

Provider Block

terraform {
  required_version = ">= 1.5.3, < 1.10.0"  # Supports Terraform v1.5.3 and higher, but under 1.10.0
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      #version = ">= 5.73.0"  # Use AWS provider version 5.73.0 or newer
      version = ">= 5.0"
    }
  }


  # Adding Backend as S3 for Remote State Storage
  backend "s3" {
    bucket = "BUCKETNAME"
    key    = "terraform/BUCKET-FOLDER/terraform.tfstate"
    region = "ap-south-1"

    # For State Locking
    dynamodb_table = "TABLE-NAME"
  }    
}

# Provider Block
provider "aws" {
  region  = var.aws_region
  #profile = "default"
}

GitHub Secrets

Before creating the GitHub Actions, let’s add some Secrets inside our repository.

Left side » Secrets » Actions

Click on: New repository secret

secreat for aws role

  • AWS_ROLE : ARN OF YOUR ROLE

GitHub Action

Let’s create our GitHub Action, for this, we need to create a file in our GitHub repository :

.github/workflows/deploy.yml

Copyname: "Terraform Action" # Name of the workflow 

# Trigger the workflow 
on:
  push:
    paths:
      - 'FOLDER/**'  # Trigger only if files in FOLDER folder are modified
    branches:
        [ "BRANCH-NAME" ]
    #   "clem-devops/v1.1"   # Trigger only on pushes to the devops v1.1 branch
  pull_request:
    paths:
      - 'FOLDER/**'  # Trigger only if PR modifies files in FOLDER folder

# Set permissions required for the workflow
permissions:
  id-token: write  # Required for AWS OIDC connection
  contents: read   # Required to checkout the repository
  pull-requests: write # Required to comment on PRs

# Environment variables shared across the workflow
env:
  TF_LOG: INFO         # Enable Terraform logging
  AWS_REGION: ap-south-1 # Specify AWS region

jobs:
  deploy:
    runs-on: ubuntu-latest # Run the job on the latest Ubuntu environment
    defaults:
      run:
        shell: bash
        working-directory: ./FOLDER  # Change working directory for FOLDER module

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3  # Check out the code from the repository

      - name: Configure AWS credentials using OIDC
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_DEV }}  # Role to assume for AWS access
          aws-region: ${{ env.AWS_REGION }}            # AWS region
          role-session-name: GitHub-OIDC-TERRAFORM     # Session name for identification

      - name: Clear Terraform Cache
        run: |

          rm -rf ~/.terraform.d/plugin-cache  # Clear Terraform plugin cache
          rm -rf .terraform
          rm -f .terraform.lock.hcl

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: 1.5.3  # Specify Terraform version

      - name: Terraform Format Check
        id: fmt
        run: terraform fmt -check   # Check if Terraform files are formatted
        continue-on-error: true     # Continue even if formatting errors are found

      - name: Terraform Init
        id: init
        run: terraform init         # Initialize Terraform configuration

      - name: Terraform Validate
        id: validate
        run: terraform validate -no-color  # Validate Terraform files

      - name: Terraform Plan
        id: plan
        run: terraform plan -no-color      # Generate execution plan
        if: github.event_name == 'pull_request'
        continue-on-error: true

      - uses: actions/github-script@v6
        if: github.event_name == 'pull_request'
        env:
          PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
            #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
            #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
            <details><summary>Validation Output</summary>

            \`\`\`\n
            ${{ steps.validate.outputs.stdout }}
            \`\`\`

            </details>

            #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`

            <details><summary>Show Plan</summary>

            \`\`\`\n
            ${process.env.PLAN}
            \`\`\`

            </details>

            *Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })

      - name: Terraform Plan Status
        if: steps.plan.outcome == 'failure'
        run: exit 1   # Fail the workflow if the plan step fails

      - name: Terraform Apply
        if: github.ref == 'refs/heads/BRANCH-NAME' && github.event_name == 'push'
        run: terraform apply -auto-approve -input=false  # Apply changes if on BRANCH-NAME branch
0
Subscribe to my newsletter

Read articles from Varish Ansari directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Varish Ansari
Varish Ansari

As a DevOps and Cloud Engineer, I design and manage scalable AWS cloud infrastructures, implement Infrastructure as Code using Terraform, and automate CI/CD pipelines with GitHub Actions. I also work with Docker and Docker Swarm and ECS for containerized deployments, optimize cloud security using AWS WAF, Security Hub, and GuardDuty, and enhance monitoring and observability with CloudWatch, Prometheus, and Grafana. My focus is on automation, scalability, and security to ensure efficient and resilient cloud environments