DEV Community

Cover image for Securely Push Image from GitHub to AWS with GitHub Action
Husain
Husain

Posted on • Edited on

Securely Push Image from GitHub to AWS with GitHub Action

🚀 In modern cloud-native development, automating deployments is key to ensuring fast, secure, and reliable application delivery. GitHub Actions, a powerful CI/CD tool, allows you to automate your workflows directly from your GitHub repository. When combined with AWS and OpenID Connect (OIDC), you can securely deploy your code without managing long-lived AWS credentials and maintaining high security standards.

Here are the key reasons why OIDC is beneficial:

  • Eliminates Long-Lived Credentials: OIDC allows GitHub Actions to authenticate with AWS without storing permanent credentials like AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in your GitHub repository. This reduces the risk of credential leakage and simplifies credential management.
  • Improves Security: By using short-lived tokens generated during each workflow run, OIDC minimizes the attack surface compared to static credentials.
  • Simplifies Access Management: OIDC enables federated identity management, allowing you to control access permissions centrally in AWS IAM. You can define trust relationships and conditions under which GitHub tokens are considered valid, streamlining access control.
  • Supports Conditional Access: You can configure policies in AWS IAM to restrict access based on specific conditions, such as the repository or branch from which a workflow is triggered. This granular control helps enforce security best practices
  • Integrates Easily with GitHub Actions: GitHub provides official actions like aws-actions/configure-aws-credentials that simplify the integration process by automatically exchanging OIDC tokens for AWS temporary credentials during workflow execution

In this blog post, we’ll walk through how to set up a seamless deployment from GitHub to AWS using GitHub Actions and OIDC.
I setup an example of use case using Amazon Elastic Container Registry (ECR) as our image registry and IAM Role that will be assumed through OIDC. After the resources in AWS are created, we'll use GitHub Actions to build docker image of Flask (Python) application using Docker and push the Docker image to Amazon ECR and secure the connection with AWS using OpenID Connect (OIDC).

Architecture of the GitHub Integration to AWS

Prerequisites

  • A GitHub repository containing a Flask application. There is a simple flask app as an example.
  • An AWS account with access to ECR.
  • AWS CLI installed and configured in the GitHub runner. (I am using Github-hosted runner that already have AWS CLI installed).

Step 1: Set Up AWS Resources

1. Create ECR Repository:

  • Open the AWS Management Console and navigate to ECR.
  • Click on Create repository.
  • Name your repository (e.g., flask-app) and create it.

ECR Repository creation

2. Add an Identity Provider in AWS
The first step is to create an Identity Provider in AWS. The following steps are based on the GitHub documentation here.

  • Go to IAM in the AWS Management Console.
  • In the left navigation pane, click on Identity providers and choose Add provider.
  • For Provider type, select OpenID Connect.
  • For Provider URL, enter https://token.actions.githubusercontent.com. Provider URL is the Github OpenID Connect URL for authentication requests.
  • For Audience, enter sts.amazonaws.com. Audience is a value that identifies the application that is registered with an OpenID Connect provider.
  • Click Add provider.

Created Provider

3. Create an IAM Role for OIDC

  • Create a new role with the Web identity type.
  • Select GitHub as the provider and specify your repository details.
  • Attach policies that allow write access to ECR (In this case I am using AWS-managed Policy AmazonEC2ContainerRegistryPowerUser).

Created Role

  • Create trust relationships for the role. This allow only specific branch or tags to assume the role.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "<Arn of Identity provider>"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": [
                        "repo:<GitHub organization name>/<GitHub repo name>:ref:refs/heads/main",
                        "repo:<GitHub organization name>/<GitHub repo name>:ref:refs/heads/dev*",
                        "repo:<GitHub organization name>/<GitHub repo name>:ref:refs/tags/v*"
                    ],
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                }
            }
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

You have to replace the placeholders with correct values.

  • Arn of Identity Provider - ARN of the Identity Provider that you created in previous step.
  • GitHub organization name - your GitHub Organization name
  • GitHub repo name - your GitHub Repository name

You can use the StringLike condition in the IAM policy to allow more flexible pattern matching with wildcards. Instead of using StringEquals, which requires an exact match, StringLike lets you use patterns like * to match multiple variations.
In this case, to allow deployment from branches starting with dev (like dev, develop, etc.) and any version tag that follows the semver rule (e.g., v1.0, v2.1.3), you can set up the condition like this:

  • Branches: Use StringLike with dev* to match any branch that begins with dev.
  • Tags: Use StringLike with v* to match any tag that starts with v, allowing flexibility for any versioning system that follows the semantic versioning convention.

Step 2: Update GitHub Repository Secrets

1. Add Repository Secrets:

  • In your GitHub repository, go to Settings > Secrets and variables > Actions.
  • Create following secrets:

AWS_REGION = Region of AWS where you deploy your AWS resources.

AWS_ROLE = Arn of the IAM role that the GitHub Action has to assume.

GitHub secrets

Step 3: Create GitHub Actions Workflow

1. Create Workflow File:

  • In the GitHub repository, create a .github/workflows/build_image.yml file.
name: Deploy to AWS

on:
  push:
    branches:
      - main

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    permissions:
      id-token: write
      contents: read

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE }}
          role-session-name: ecr-push-registry-${{ github.run_number }}
          aws-region: ${{ secrets.AWS_REGION }}

      - name: Log in to Amazon ECR
        uses: aws-actions/amazon-ecr-login@v2
        id: login-ecr

      - name: Build, tag, and push Docker image to Amazon ECR
        env:
          ECR_URL: ${{ steps.login-ecr.outputs.registry }}
          REPOSITORY: flask-app
          VERSION_TAG: ${{ github.sha }}
        run: bash ./build_image.sh

      - name: Log out of Amazon ECR
        if: always()
        run: docker logout ${{ steps.login-ecr.outputs.registry }}
Enter fullscreen mode Exit fullscreen mode

This is the overview of the workflow code:

  • Name: The workflow is named "Deploy to AWS".
  • Trigger: The workflow is triggered by a push event on the main branch.
  • Jobs: The workflow consists of a single job named build-and-deploy which runs on an ubuntu-latest virtual machine.
  • Permissions
    • id-token: write: This permission allows the workflow to request an OpenID Connect (OIDC) token, which is used to authenticate with AWS securely.
    • contents: read: This permission allows the workflow to read repository contents, which is necessary for checking out the code.
  • Steps

    • Checkout Code: This step checks out the repository code so that subsequent steps have access to it.
    • Configure AWS Credentials: This step configures AWS credentials using OIDC. It assumes a role specified by the secret AWS_ROLE and sets the AWS region using the secret AWS_REGION.
    • Log in to Amazon ECR: This step logs into Amazon Elastic Container Registry (ECR) so that Docker images can be pushed. The step ID is set to login-ecr, which allows referencing its outputs in later steps.
    • Build, Tag, and Push Docker Image to Amazon ECR: This step create environment variables ECR_URL (The URL of the ECR registry obtained from the output of the login step) , REPOSITORY and VERSION_TAG (A unique tag for the Docker image based on the commit SHA (github.sha)). This step executes a shell script (build_image.sh) that builds, tags, and pushes the Docker image to ECR.
    • Log out of Amazon ECR: Logs out from Amazon ECR using Docker to ensure that credentials are not left lingering. This step always runs (if: always()) regardless of whether previous steps succeeded or failed.

Step 4: Test the GitHub Action workflow

Once you have configured GitHub Actions, you can test the integration by pushing a change to your GitHub repository. GitHub Actions should automatically trigger a build and deploy process in AWS, using the OpenID Connect authentication data to authenticate the user.

GitHub Workflow

Conclusion

And there you have it, folks! We've successfully sent our trusty Flask app on a whirlwind adventure to the cloud ☁️, all thanks to the magic of GitHub Actions and AWS. With Docker as its suitcase and OIDC as its security blanket, our app is now living its best life.

Stay tuned for my next blog post, where we'll dive even deeper into the world of cloud automation and deployment magic. See you then! 😎

Top comments (0)