Secure cloud provisioning pipeline with GitHub automation
As a member of the Platforms engineering team, we understand that security is a shared responsibility throughout the DevSecOps lifecycle for provisioning infrastructure. As a result, we set about championing best practices across the organization, with a focus on:
Configuring short-lived credentials
Automating cloud-provisioning pipelines
Comparing infrastructure-as-code tooling
Securing deployments from code-to-delivery
Figure: How to provision infrastructure-as-code.
Short-lived credentials
GitHub Actions form the basis of our continuous integration/continuous deployment (CI/CD) pipeline as it integrates seamlessly with GitOps: the framework by which we ship peer-reviewed code early and often. It enables us to extend our workflow with Actions from verified creators, such as aws-actions (other cloud computing platforms are available).
This streamlines the process of configuring AWS credentials via OpenID Connect (OIDC): our favorite “keyless” authentication method. OIDC only permits access from federated providers using short-lived credentials, which pairs perfectly with GitHub Actions’ ephemeral runners. What’s more, we can grant least-privilege provisioning permissions to the assumed-role (and include both thumbprints). Let’s add it to our workflow.
Tip While the following pseudo-code is for legibility, we will build up to the complete workflow by the end of this post.
jobs:
provision:
runs-on: ubuntu
steps:
- name: Authenticate AWS
uses: aws-actions/configure-aws-credentials
Figure: Security hardening GitHub Action workflow with OpenID Connect (source).
Automated provisioning pipeline
With authentication sorted and access to infrastructure secured, we turned to Test Double for inspiration on a “roll-your-own” deployment workflow.
It goes on to link DevSecTop/TF-via-PR: another open-source project which combines the outlined concepts into a reusable Action. In support of trunk-based Pull Request (PR) automation, this workflow:
Runs
init > workspace > plan
with optional arguments when a PR is opened, returning the plan output in a PR comment and storing the encrypted plan file as an artifact.Reruns
plan
when the PR is updated with new commits, returning the latest output and replacing the old PR comment to reduce noise.Runs
init > workspace > apply
with the previous plan file artifact, along with optional arguments when the PR is approved and merged.
Tip Passing in the previously-generated plan file when applying prevents any configuration drift from stale plans.
By default, each PR and associated workflow runs hold a complete log of infrastructure change/proposals for ease of collaboration and audit compliance within a set retention period. The use of GitHub Actions also removes the overhead of maintaining dedicated containers or self-hosted compute instances: lending itself to empower development teams to self-service scalably. Time to add another step to our workflow.
on:
pull_request:
jobs:
provision:
runs-on: ubuntu
steps:
- name: Authenticate AWS
uses: aws-actions/configure-aws-credentials
- name: Checkout repository
uses: actions/checkout
- name: Provision infrastructure
uses: devsectop/tf-via-pr
Figure: Flow chart of provisioning pipeline steps.
Infrastructure-as-code tooling
Although plan file encryption is one of the options provided by the Action, it’s also a feature native to OpenTofu: a project backed by The Linux Foundation as an open-source alternative to Terraform.
More recently, OpenTofu released support for early static evaluation of variables/locals, enabling the likes of module versions and backend inputs to be loosely coupled with don’t-repeat-yourself (DRY) configuration. While a number of teams have successfully experimented by replacing terraform with tofu, many others are holding out for dynamically configured provider support as well as Dependabot automation before making the switch.
Tip For anyone else experimenting between the two, whole-heartedly recommend tofuutils/tenv as the go-to package manager of choice with broad compatibility.
Secured deployments workflow
With the GitHub Action dependencies accounted for, all that’s left is to bring them together in a workflow that is reactive to the provisioning result. In other words, if apply fails for any reason, then reject the PR from merge and return the error output in a PR comment for follow-up. This happens to be the ideal use-case for a merge queue of size and concurrency one.
Now that we have all the pieces to form the complete workflow below, can you spot what else has been done to shore up security? Answers below!
on:
pull_request:
branches: [main]
merge_group:
types: [checks_requested]
jobs:
provision:
runs-on: ubuntu-24.04
permissions:
actions: read # Required to download artifact.
checks: write # Required to add status summary.
contents: read # Required to checkout repository.
id-token: write # Required to authenticate via OIDC.
pull-requests: write # Required to add PR comment and label.
env:
AWS_ACCOUNT: platforms-dev
AWS_REGION: us-east-1
AWS_ROLE: arn:aws:iam::123456789012:role/provision-cicd
environment: ${{ github.event_name == 'merge_group' && env.AWS_ACCOUNT || '' }}
steps:
- name: Authenticate AWS
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
with:
aws-region: ${{ env.AWS_REGION }}
role-to-assume: ${{ env.AWS_ROLE }}
- name: Checkout repository
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
with:
persist-credentials: false
- name: Setup TF
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
with:
terraform_version: "1.8.5"
- name: Provision infrastructure
uses: devsectop/tf-via-pr@f1acaae1d94826457fa57bc65f1df318fd81b3bc # v12.0.0
with:
command: ${{ github.event_name == 'merge_group' && 'apply' || 'plan' }}
arg-lock: ${{ github.event_name == 'merge_group' }}
working-directory: path/to/${{ env.AWS_ACCOUNT }}
plan-encrypt: ${{ secrets.TF_ENCRYPTION }}
Make Actions immutable by pinning to a commit SHA to reduce third-party supply-chain risks (and automate updates with Dependabot).
Use
environment:
to enforce deployment protection rules when attempting to apply changes to infrastructure.Use
persist-credentials: false
with checkout to prevent leaking the GitHub token to subsequent steps of the workflow.
Next steps
There’s plenty of potential with this workflow: from interpolating workspaces with dynamic backends to bulk-provisioning multiple accounts in matrix strategy concurrently. Is there any particular setup or tooling you’d like us to explore next?
Subscribe to my newsletter
Read articles from Rishav Dhar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Rishav Dhar
Rishav Dhar
Based in Edinburgh UK, where I’m engaged in a remote role as a Senior DevOps Platform Engineer at Wavelo: https://linkedin.com/in/RishavDhar