GitHub Actions Amazon ECR Integration - part1

How do you publish docker images to an Amazon ECR repository from a GitHub Actions workflow? (without using any long-lived tokens)

GitHub Actions (GHA) is commonly used as a continuous integration tool, and docker image is a common format for distributing and deploying container-based software. AWS is a popular IaaS platform that offers a pay-on-use container registry service.

If you use all three, it's important to know how to set up your AWS environment in a secured way to allow seamless publishing of docker images. You need

  1. an Amazon ECR repository
  2. to set up GitHub OpenID Connect provider in IAM
  3. an assumable IAM role with
  4. a policy that allows login from GHA and docker image push to the ECR repository.

I will be using Terraform, my main IaC tool and Terraform AWS modules to make my code succinct and maintainable.


The ECR repo

Assuming the docker images will be used in AWS Lambda functions, I have defined a local variable ecr-repo-names to list the repo names. I have also defined repository_lambda_read_access_arns which lists the lambdas that need image pull permission (from the created ECR repos).

locals {
  ecr-repo-names = [
    "example",
  ]
}

module "ecr-repos" {
  source  = "terraform-aws-modules/ecr/aws"
  version = "~> 1.6.0"

  for_each = toset(local.ecr-repo-names)

  repository_name = each.value
  repository_lambda_read_access_arns = [
    "arn:aws:lambda:${var.region}:${data.aws_caller_identity.current.account_id}:function:*"
  ]

  repository_lifecycle_policy = jsonencode({
    rules = [{
      rulePriority = 1
      description = "Expire images older than 1 year"
      action = {
        type = "expire"
      }
      selection = {
        tagStatus = "any"
        countType = "sinceImagePushed"
        countUnit = "days"
        countNumber = 365
      }
    }]
  })
}

The repository lifecycle policy is to make sure the repository space usage is within control.

GitHub OIDC provider

This will allow IAM to trust GitHub for authentication, so that long-lived tokens are not required.

locals {
  github-repos = [
    "kakarukeys/example",
  ]
}

module "iam_github_oidc_provider" {
  source    = "terraform-aws-modules/iam/aws//modules/iam-github-oidc-provider"
  version = "~> 5.33.1"
}

GitHub will send AWS certain OIDC subject claims during authentication, and it is important you define proper IAM policies to allow the right github repositories to publish docker images based on the claims!

📎 An OIDC provider authenticates the user's identity and allows him to securely access other applications (when they are pre-configured to trust the provider).
The OIDC provider does three main things:

  1. It performs the actual authentication of the user (e.g. by verifying their username and password)
  2. It issues JSON Web Tokens (JWTs) containing information about the user's identity to other applications so they can recognize who the user is without storing passwords.
  3. It protects the user's sensitive information like passwords and only shares necessary details about the user (like name, email) with other applications according to the user's consent.

Assumable IAM role and policies

The IAM policy is based on GitHub's recommendations, with one exception: I have further allow GHA to perform lambda:GetLayerVersion action so that it can download and install Lambda extensions.

data "aws_iam_policy_document" "ecr-publish-policy-document" {
  statement {
    effect = "Allow"
  
    actions = [
      "ecr:GetAuthorizationToken",
      "lambda:GetLayerVersion",
    ]

    resources = ["*"]
  }

  statement {
    effect = "Allow"

    actions = [
      "ecr:BatchCheckLayerAvailability",
      "ecr:BatchGetImage",
      "ecr:CompleteLayerUpload",
      "ecr:GetDownloadUrlForLayer",
      "ecr:InitiateLayerUpload",
      "ecr:PutImage",
      "ecr:UploadLayerPart",
    ]

    resources = [for r in module.ecr-repos : r.repository_arn]
  }
}

resource "aws_iam_policy" "ecr-publish-policy" {
  name        = "ecr-publish-policy"
  description = "Allow principal to publish images into selected ECR repos"
  policy = data.aws_iam_policy_document.ecr-publish-policy-document.json
}

module "gha-build-role" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-github-oidc-role"
  version = "~> 5.33.1"

  # specify for Github Enterprise
  # audience     = "https://mygithub.com/<GITHUB_ORG>"
  # provider_url = "mygithub.com/_services/token"

  subjects = [
    for repo in local.github-repos : 
    "repo:${repo}:ref:refs/heads/main"
  ]

  name = "GHA-Build-Role"
  description = "IAM role for Github Action to assume, for performing tasks related to project build"
  max_session_duration = 3600   # secs

  policies = {
    EcrPublish = aws_iam_policy.ecr-publish-policy.arn
  }
}

The assumable role is defined with a special AWS Terraform module called iam-github-oidc-role (many codes were abstracted away!). What it does is to allow GHA to make API calls to AWS STS service in order to assume the role, which is allowed by us for publishing images.

Pay attention to subjects which defines the repos and branches for which the role can be used for docker image publishing.


Testing

That's all. Use these scripts in a Terraform project, create a plan and apply it to create the necessary resources. Output the role ARN with

output "gha_build_role_arn" {
  description = "arn of GHA Build Role"
  value       = module.gha-build-role.arn
}

You can then build a docker image with a Dockerfile you have and test the integration with

$ docker build -t <account-id>.dkr.ecr.<region>.amazonaws.com/example:0.1.0 .

$ aws ecr get-login-password --region <region> | docker login \
    --username AWS \
    --password-stdin <account-id>.dkr.ecr.<region>.amazonaws.com

$ docker push <account-id>.dkr.ecr.<region>.amazonaws.com/example:0.1.0

Note that the above commands actually uses your AWS profile user's permissions (likely a root user) to interact with ECR. The next step would be to use the role ARN in an actual GHA workflow.

To be continued in part 2.

Subscribe to Jeff's Chronicles

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe