Picture of the author
Visit my website
Published on
·
Reading time
4 min read

Restrict GitHub Actions Deployments

Protection rules to the rescue

Share this page

Lock and chains
Image source: Unsplash

Introduction

Let's imagine that you've set up your project in GitHub and you've also got your build and deploy pipeline configured and working using GitHub Actions — well done!

You've given your team a quick shareback on how you did it and the team is now upskilled in using this workflow. A couple of weeks go by, and your team is starting to feel comfortable but then someone in the team decides to take a shortcut.

They create a branch called feature-x and update the workflow trigger such that commits from this feature-x branch go all the way to production. Disaster!

on:
  push:
    branches:
      - 'main'
      - 'feature-x' # They added this line. Disaster!

They've managed to push changes from a branch by working around the system. It wouldn't look good on their resume, but they probably don't care.

So, how might we secure the system so that such a blunder doesn't happen? Let's take a look.

Reproducing the problem

Let's scale this down to a simple GitHub Action that can be triggered manually. As you can see, the build and deploy steps just print a console message.

I have a main branch and a test branch in my repository. From the Actions page, I can manually trigger the workflow to run from the test branch, as seen in the screenshot below.

Image courtesy of the author

As we'd expect, the workflow executes all the way through, thus reproducing the problem scenario where code can be deployed from a non-main branch.

Image courtesy of the author

Solution

We'll start by clicking on the GitHub repository Settings and then click on Environments and then add a new environment called production.

Image courtesy of the author

After the environment is created in GitHub, we should be able to click on the environment and edit the environment settings.

In the Deployment branches and tags section, we'll click on Selected branches and tags and then click on the Add deployment branch or tag rule button.

Image courtesy of the author

In the pop-up, specify the branch name you want to whitelist, in other words, the branch which should be allowed to deploy to production from, and then click on the Add rule button. For this article, I've added the branch main to this list. Don't forget to also click the Save protection rules button.

Image courtesy of the author

We'll also add the environment key to the deploy job, as per the code snippet below. The remainder of the workflow file remains as above. Let's set the environment name the same as what we created earlier, i.e. production. If you have multiple deployment jobs per environment, then you could repeat the process for all of them and name them appropriately.

deploy:
  environment: production
  needs: build
  runs-on: ubuntu-latest

Testing the solution

Let's try to manually trigger the workflow from the test branch now.

As you can see from the screenshot below, the job has failed because it didn't meet the environment protection rules. Hooray!

Image courtesy of the author

Somethings to consider

If it doesn't meet the environment protection rules, it fails the job, as seen above. While this is functionally what you want, it could also trigger any SLOs or alerts you might've set up for failures. If you have set something up, ensure that you cater to this scenario.

This solution relies on setting the environment: production tag to the deploy job. This is part of the workflow code and unfortunately, in the test or feature-x branch, someone can either remove this line of code or update this to an environment tag which doesn't have the protection rules.

The environment protection rules page in GitHub is available for all repository owners. So, if everyone in the team has this role, then anyone can come to this page and just remove the protection rules, making all this effort go to waste.

That's it! Thanks for reading.