Charlie Thomas

Automating your Cloud Infrastructure with Github Actions

A step by step guide on how you can use github actions to create a pipeline for your cloud infrastructure on AWS.


Managing cloud resources for a production application can be a major pain. To help teams manage resources, AWS provides the Infrastructure as Code solution Cloudformation, but sometimes automating workflows with cloudformation can be painful in and of itself. Luckily, Github actions and Amazon's out of the box actions kit can make your life much easier!

In this guide we will walk through a series of steps to get a simple pipeline up and running.

Step 1: Define your Cloudformation Stack!

For this example, we will define a simple Cloudformation Stack containing a single dynamo table.

AWSTemplateFormatVersion: '2010-09-09'
Description: My Cloud Resource

Parameters:
  Environment:
    Type: String
    Default: stage

Resources:
  SimpleTable:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      BillingMode: PAY_PER_REQUEST
      TableName: !Sub ${Environment}-tablename
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S        
      KeySchema:
        - AttributeName: id
          KeyType: HASH          

Notice that we have a parameter in the template defined for environment. You can use this if your pipeline needs to deploy to multiple environments.

Step 2: Add permissions keys to your infrastructure github repo!

Next up, you'll want to create some IAM permissions for Github actions to use. As a secure approach, I recommend you create a dedicated user and role for your pipeline to assume when running. In this guide we will define it all in a reusable template to make this a bit future-proof.

Start by defining a user with correlating access keys that we can reference later.

Resources:
  User:
    Type: AWS::IAM::User
    Properties:
      UserName: github-actions-user
  AccessKey:
    Type: AWS::IAM::AccessKey
    Properties:
      UserName: github-actions-user
      Serial: 1
  Credentials:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: github-actions-user
      SecretString: !Sub |
        {
          "AccessKeyId":"${AccessKey}",
          "SecretAccessKey":"${AccessKey.SecretAccessKey}"
        }

Once you have a the User defined, create a Role that we will allow this User to assume, giving it the permissions it needs to manage the resources in our infrastructure templates.

Resources:
  # ... previous User resources from above
  Role:
    Type: AWS::IAM::Role
    Properties:
      RoleName: github-actions-deployer-role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - cloudformation.amazonaws.com
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
        # Any other resources you need to manage should be listed here

Notice that we are giving exact permissions for the deployer to only manage DynamoDB resources in this case. You may even want to restrict it even tighter with your own more specific policies.

Finally, tie the User and Role together with a UserPolicy that allows the User to manage Cloudformation stacks and pass our defined Role to Cloudformation.

Resources:
  # ... previous User and Role resources from above
  UserPolicy:
    Type: AWS::IAM::Policy
    Properties:
      Users:
        - github-actions-user
      PolicyName: github-cloudformation-deploy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - "cloudformation:*"
            Effect: Allow
            Resource: "*"
          - Action: "iam:PassRole"
            Effect: Allow
            Resource: !GetAtt Role.Arn

Once you have all of your IAM written you can manually deploy this with the AWS CLI and subsequently fetch the access credentials.

aws cloudformation deploy \
  --stack-name github-actions-cloudformation-deployer \
  --template-file path/to/your/yaml \
  --capabilities CAPABILITY_NAMED_IAM \
  --region us-east-1

aws secretsmanager get-secret-value \
  --secret-id github-actions-user \
  --region us-east-1 \
  --query SecretString \
  --output text

Then take the output credentials and set up 2 secrets in your github repository under Setting > Secrets > Actions Secrets.

Step 3: Create the pipeline in Github!

Finally, now that you have all the permissions set up and a Cloudformation template ready to deploy, create a Github actions workflow in the .github/workflows directory of your project.

on:
  push:
    branches:
      - master

name: Deploy Cloudformation

jobs:
  deploy-cfn:
    name: Deploy Cloudformation Template
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: "us-east-1"
    - name: Execute Changeset
      uses: aws-actions/aws-cloudformation-github-deploy@v1
      with:
        name: my-cloudformation-stack
        template: path/to/my/yaml
        role-arn: arn:aws:iam::${{ steps.creds.outputs.aws-account-id }}:role/github-actions-deployer-role
        no-fail-on-empty-changeset: "1"
        parameter-overrides: >-
          Environment=dev

Using the aws-actions actions kit allows us to assume the proper IAM roles and the execute cloudformation changes with ease. Make sure to point to the right yaml file and pass all your parameters that you defined on the stack from Step 1 (in this example we just override the Environment param). Once in place, every commit to the repo will result in your cloudformation template being deployed automatically to the correlating AWS account. From here, you can expand on these workflows to support more advanced features.

Such as:

References