DEV Community

Cover image for Combining GitHub Secrets and Actions
Forest Hoffman
Forest Hoffman

Posted on

Combining GitHub Secrets and Actions

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!

GIF of a cat dressed as a musketeer

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
Enter fullscreen mode Exit fullscreen mode

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. 🤔

A screenshot of the Secret creation form

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!

A screenshot of the workflow log, showing a successful check of the Secret

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
Enter fullscreen mode Exit fullscreen mode

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.

A screenshot of the workflow log, showing an unsuccessful check of the Secret

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
Enter fullscreen mode Exit fullscreen mode

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.

Phew

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

Discussion (0)