Terraform Backend

Using a Terraform Backend

terraform {
  backend "s3" {
    bucket = "bvc-terraform-state"
    key    = "live/main.tfstate"
    region = "ca-central-1"

    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
  required_providers {
    aws = {
      version = ">= 4.0.0"
      source  = "hashicorp/aws"
    }
  }
}

Create IAM Role for GitHub Actions

main.tf

# Create an IAM OIDC identity provider that trusts GitHub
resource "aws_iam_openid_connect_provider" "github_actions" {
  url            = "https://token.actions.githubusercontent.com"
  client_id_list = ["sts.amazonaws.com"]
  thumbprint_list = [
    data.tls_certificate.github.certificates[0].sha1_fingerprint
  ]
}

# Fetch GitHub's OIDC thumbprint
data "tls_certificate" "github" {
  url = "https://token.actions.githubusercontent.com"
}

# Create role for the action
data "aws_iam_policy_document" "assume_role" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]
    effect  = "Allow"

    principals {
      identifiers = [aws_iam_openid_connect_provider.github_actions.arn]
      type        = "Federated"
    }

    condition {
      test     = "StringLike"
      variable = "token.actions.githubusercontent.com:sub"
      # The repos and branches defined in var.allowed_repos_branches
      # will be able to assume this IAM role
      values = [
        "repo:masoudkarimif/*:*"
      ]
    }
  }
}

# Assign policy to the role
resource "aws_iam_role" "github_actions_role" {
  name_prefix        = var.role_name_prefix
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

resource "aws_iam_role_policy" "this" {
  role   = aws_iam_role.github_actions_role.name
  policy = <<POLICY
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:PutObject",
                "s3:Get*"
            ],
            "Resource": "arn:aws:s3:::${var.devops_bucket_name}/*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "lambda:UpdateFunctionCode"
            ],
            "Resource": "*",
            "Effect": "Allow"
        }
    ]
}
  POLICY
}

data "aws_iam_policy" "readonlyaccess" {
  arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

resource "aws_iam_role_policy_attachment" "attachment" {
  role       = aws_iam_role.github_actions_role.name
  policy_arn = data.aws_iam_policy.readonlyaccess.arn
}

variables.tf

variable "role_name_prefix" {
  type = string
}

variable "devops_bucket_name" {
  type = string
}

outputs.tf

output "role_name" {
  value = aws_iam_role.github_actions_role.name
}

output "role_arn" {
  value = aws_iam_role.github_actions_role.arn
}

Using Different Roles for Different Environments

In GitHub workflow:

# other stuff ...
name: deploy
# ...
      - name: Configuration for master branch
        if: ${{ github.ref == 'refs/heads/master' }}
        run: |
          echo "ROLE_ARN=${{ secrets.PROD_DEVOPS_ROLE }}" >> $GITHUB_ENV
          echo "DEVOPS_BUCKET=PROD-BUCKET" >> $GITHUB_ENV
          echo "ENV=PROD" >> $GITHUB_ENV

      - name: Configuration for stage branch
        if: ${{ github.ref == 'refs/heads/stage' }}
        run: |
          echo "ROLE_ARN=${{ secrets.STAGE_DEVOPS_ROLE }}" >> $GITHUB_ENV
          echo "DEVOPS_BUCKET=STAGE-BUCKET" >> $GITHUB_ENV
          echo "ENV=STAGE" >> $GITHUB_ENV

      - name: AWS
        uses: aws-actions/configure-aws-credentials@master
        with:
          role-to-assume: ${{ env.ROLE_ARN }}
          role-session-name: samplerolesession
          aws-region: us-east-1

# other stuff ...