C#, .Net and Azure

Migrating to Github Actions and using them securely

2020/04/18

I recently decided to switch over all my CI/CD deployments from Azure DevOps to Github Actions (both private and opensource). I already had all my code on Github and was using Azure DevOps for deployments because Github Actions wasn’t “quite there yet” during the beta.

Turns out Github Actions is now more then ready (I moved all my pipelines successfully) and after the acquisition of Github by Microsoft, the fast announcement of a new Github Action beta with CI/CD support and the eerie similarities between Azure Pipelines and Github Actions it comes as no surprise that Github Actions is basically a fork of Azure Pipelines.

As such the overall migration effort was minimal. In fact when comparing the Azure Pipelines and Github Actions yml files the major differences seem to renames of various elements (uses instead of task, env instead of variables, the direct reference of Github repositories instead of tasks-by-id, ..).

Overall, the effort to port the yml files is minimal if you use actions that exist in both (Github Actions is currently trailing behind Azure DevOps in term of overall available tasks but they are catching up quickly).

yml comparison

Azure DevOps pipeline (left) to Github Action (right) migration

What I really like is that overall Github Action yml files are much shorter than Azure Pipelines yml files. In my use cases (build and deploy .Net Core code to an Azure function) the Github Action yml is 33-50% shorter than the equivalent Azure Pipelines yml file!

Someone also built an automatic migration tool that maps Azure DevOps pipelines to Github Actions. I can imagine that it comes in quite handy for companies who need to migrate many Azure Yaml Pipelines to Github.

Security

Since Github Actions are built around the community you can pull in actions from any random third party repositories to extend your pipelines.

Security is of course a big concern - afterall these actions have access to your repository, and many require secrets to perform their task.

Unfortunately most of the documentation glances over this point. Many samples even reference actions by branch name (or use no reference at all). The general guidance now is to use git tags and lots of documentation has already been updated to reflect that.

However, the only secure action reference is its commit hash - tags and branches can be updated at any time to point and include malicious code.

Branch references are unstable

With the branch the obvious problem is that at any moment you are pulling in the latest code from the action repository. This assumes that the developer never introduces breaking changes on said branch. I don’t really see any reason to use branch referencing (except when you are building your own action to use it for rapid feedback).

Luckily Github has caught on to this and is now discouraging branch references and instead asking users to use tags.

Use of tag references fixes versioning

Tags offer stability when the action author follows proper semantic versioning: Each release can be tagged with the appropriate version (v1.1, v1.2.1, ..) and the major tag (v1) can always be updated to point to the same commit as the latest compatible version (v1.2.1 in the case above).

However, this ability to freely reapply tags also means the version you reference today might not be the one that is executed tomorrow.

At least some people at Github are aware of the issue with git tags not being secure however no solution has been offered so far.

Trade-off between security and convenience

Using tag references @v1 offers convenience in the form of automatic bug fixes and security updates but it also carries the risk of malicious code being introduced into your pipeline.

If any one action contributor (from any action you are using) has his account hacked, turns evil/had malicious intend from the start or hands over ownership of the repository to someone with ill-intent your actions CI/CD pipeline becomes compromised.

All it takes is to push malicious code and point all tags to the malicious commit and everyone not using commit hash references will automatically run the malicious action as part of their pipelines allowing them to add malware into the output or even uploading the pipeline secrets to their server.

I’m not the only one concerned about security but I also understand that manually referencing commit hashes is cumbersome so I built a tool to solve the problem:

Github action pinner is a .Net Core console application that can replace your action branch/tag references with their respective SHAs automatically while retaining the information which version was used in a comment.

Midway through development (maybe I should have searched a bit more first) I also found an existing implementation and a similar blogpost by Michael Heap: pin-github-action.

I’ve since changed my implementation to use the same format so that both tools are compatible.

- uses: actions/checkout@v2

becomes

- uses: actions/checkout@01aecccf739ca6ff86c0539fbc67a7a5007bbc81 # pin@v2

In my tool I also included an audit log feature so you can mark specific commits of actions as trusted and a trusted repo/org feature which will skip validation for actions by orgs you trust.

If you’d like to improve the security of your pipeline, consider using my github action pinner or if you prefer a npm version check out the one from Michael Heap.

This is how a pipeline will look like once it has been updated with the tool.

Update the actions in the future is as simple as rerunning the tool.

Hopefully Github will provide some built-in security in the future to improve the situation but I have my doubts since the git tagging system is already considered “good enough” and most developers seem to accept the trade-off between security and convenience.

In the meantime feel free to use my action pinner.