While searching for a CI/CD solution for a side project of mine, I discovered the GitHub Secrets feature. Using GitHub Actions, you can setup automated builds triggered in various customizable ways. If part of your automation pipeline includes deployment or delivery to a third-party, you’re probably going to be using some kind of authorization token. Common knowledge dictates that storing super secret authorization files in plain text is a big no-no, so in comes GitHub Secrets to save the day!
Table of Contents
Actions
GitHub Actions can be as simple or as complicated as you need them to be, but here’s an Action that runs a Bash script from the target repository whenever a commit is directly made to the main
branch:
name: Deploy Instance
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Deploy
run: ./script/deploy
In this case, the Action runs a git-checkout on the repository and then executes the deploy script.
Secrets
In order to define a GitHub Secret, you must navigate to the Settings > Secrets > Actions Page for either a specific repository, or if you’re logged in as an organization, all repositories under that organization. The naming convention for this page is a bit odd. GitHub calls it, “Actions secrets”, but the URL points to <GitHub repository url>/settings/secrets/actions
. 🤔
The documentation is quite clear regarding the rules regarding Secrets, such as:
- Names may only contain alphanumeric characters and underscores (e.g.
A-Z
,a-z
,0-9
or_
) - Names may not start with
GITHUB_
, as this prefix is reserved - Secret values may not exceed 64KB There are a few other rules, but they’re not relevant to this post.
In order to use a Secret in an Action workflow (pipeline), the format is: ${{ secrets.NAME }}
, where the NAME
is whatever you called the Secret.
For example, if you create a repository secret named, MY_SECRET
, and give it a value of, helloworld
, the value output by ${{ secrets.MY_SECRET }}
in any Action would be helloworld
.
Since Secrets are censored in log output on GitHub, you can’t explicitly see that value being interpreted when the workflow is run. However, by executing an if
statement in a Bash script run by the workflow, the actual value of the Secret can be confirmed. 😎
See, check.sh
below:
#!/usr/bin/env bash
if [ "$MY_SECRET" == "helloworld" ]; then
echo "that's the one!"
else
echo "secret does not match"
fi
By committing the above Bash script, the Action workflow from previously can be updated to use the new Secret and check that its value is as expected. See check.yml
:
name: Check
on:
push:
branches: [ main ]
jobs:
check:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: chmod
run: chmod +x ./scripts/*
- name: Check
run: MY_SECRET="${{ secrets.MY_SECRET }}" ./scripts/check.sh
After committing the above workflow to the main
branch, the Action should be triggered, and it will pass the current Secret value to the check.sh
script for validation. Since the Secret value is simple, the check should be a success!
Now, let’s spice it up a bit. Say you’re trying to pass along some authentication key, e.g. an SSH key, a Azure DevOps/Google Cloud Authentication token, a password, etc. Does the check succeed the same way? Let’s see…
If the value of MY_SECRET
is reassigned to something more complex like, apple_microsoft_$123
, the check.sh
script should be updated accordingly:
#!/usr/bin/env bash
if [ "$MY_SECRET" == 'apple_microsoft_$123' ]; then
echo "that's the one!"
else
echo "secret does not match"
fi
Remember that the quotes for the new value in check.sh
should be single quotes (’
) so that the script won’t interpret $123
as a variable instead of part of the full value.
Oops! Here we see that the check failed. 😕 The Secret is not equal to the value that it was reassigned to.
So, what happened? Well, it turns out that Secrets embedded in workflows are actually interpreted separate from the job commands. So, when ${{ secrets.MY_SECRET }}
is run GitHub has already interpreted the value of the Secret and inserted it into the job command. The job ends up running, MY_SECRET=”apple_microsoft$123” ./scripts/check.sh
instead of MY_SECRET=apple_microsoft$123 ./scripts/check.sh
. Since the Secrets are interpreted first, the same change that was made to check.sh
needs to be made to the workflow. Use single-quotes instead of double-quotes!
name: Check
on:
push:
branches: [ main ]
jobs:
check:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: chmod
run: chmod +x ./scripts/*
- name: Check
run: MY_SECRET='${{ secrets.MY_SECRET }}' ./scripts/check.sh
Now, the check should succeed and the special characters in the Secret won’t break the intended behavior!
It’s the tiniest change, but wow…I spent many hours figuring that out. 😅 Learn from my mistakes y’all.
If you want to learn more about GitHub Secrets and the limits they have, check out the bottom of the documentation page, which explains how to encrypt larger secrets with GPG. Until next time, thanks for reading!
Credits
Cover image by Stefan Steinbauer on Unsplash! :D
Top comments (0)