Another GitHub Actions Update - Using Deploy Keys Instead Of Personal Access Tokens

Overview

Background

When I restarted my blogging journey in 2020, I switched from Jekyll to Hugo hosted in GitHub pages. It's been a relatively painless journey, and kudos to GitHub as a rock-solid hosting provider. I've covered it before (Initial Setup and First Update), but I've been incredibly happy with private-to-public publishing workflow that allows me to keep drafts and work-in-progress hidden. That said, a recent comment gave me reason to make another update to the Workflow. Read on for more detail...

Updating Security for Hugo Private-to-Public Publishing Workflow

I initially set up my publishing workflow to use a Personal Access Token to deploy the Hugo site to the public GitHub pages repository. To do this, I configured the Personal Access Token and then saved it as a Secret. You can see this being leveraged in the Action's main.yml file in the token field (${{ secrets.ACCESS_TOKEN }}):

1    # Deploy the Static Site to Public Repo (GitHub Pages)
2    - name: Deploy 🚀
3      uses: JamesIves/[email protected]
4      with:
5        token:  ${{ secrets.ACCESS_TOKEN }}
6        repository-name: rterakedis/rterakedis.github.io
7        branch: master # The branch the action should deploy to.
8        folder: docs # The folder the action should deploy.

A recent comment (Thanks Stefan!) on my (Initial Setup brought to my attention a better way to handle security in the publishing workflow. The commenter made the suggestion to leverage Deploy Keys, rather than Personal Access Tokens. By making this change, the Deploy Key access rights are limited to the repository where I need that key for publishing. Contrast this to the access rights granted by a personal access token, which basically gives access to any of the repos within my GitHub account. The deploy key makes far more sense in limiting scope of access in the event the secret is leaked or discovered.

Adding a Deploy Key to the Public Repository

A Deploy Key is an SSH key set you configure in your repository to grant client read-only (or read-write) access. Before making any changes to GitHub Actions, I had to configure the new Deploy Key for the repository containing the GitHub Pages site (i.e. the Public-facing Repository). Here's how to do it:

  1. On your computer, generate a passwordless SSH key (example: ssh-keygen -f id_gha -t rsa -m pem -b 4096 -C "[email protected]" -N "")
    1. NOTE The command generates both your private and public keys (id_gha and id_gha.pub). Be sure to generate them in a directory where you want them saved/secured. You do not want your working directory to be the local working directory for your GitHub repo, as you could inadvertently publish them to GitHub!
  2. Browse to the Public Repository where GitHub Pages is hosted.
  3. Click on the Settings tab for the repository
    1. NOTE If your browser is windowed, it may be hidden in the elipsis ... to the right)
  4. Click on Deploy Keys
  5. Click on Add Deploy Key
  6. Enter a title for the key (i.e. GH-Actions-Workflow), paste the contents of the public key file (starts with ssh-rsa), and click the checkbox for Allow Write Access
  7. Click the Add Key button to save the public key to your repository

Changing the GitHub Actions Workflow to Use the Deploy Key

With the Deploy Key added to the Public Repo hosting my GitHub pages website, I now need to change the GitHub Actions workflow for my private repository. Since I was previously using a Personal Access Token, I need to make changes to both the Secrets AND the workflow to start using the Deploy Key. The detail on how to use the Deploy Key for the Deploy step in my workflow came from JamesIves' documentation. Here's what I did:

Changes to the Private Repository

First, I needed to make some changes within the settings for the public repo where I host my blog.

  1. Browse to the Private Repository where your Hugo site's source is committed.
  2. Click on the Settings tab for the repository
    1. NOTE If your browser is windowed, it may be hidden in the elipsis ... to the right)
  3. Click on Secrets
  4. Click on New Repository Secret
  5. Enter a name (i.e. DEPLOY_KEY) and paste the contents of the private key file (starts with -----BEGIN)
    1. NOTE Be sure to paste the entire contents of the file, including the -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY----- lines.
  6. Click on Add Secret

Changes in Code

Once the changes to the private repository settings were complete, I had to then make some changes to the YAML file that details my GitHub Actions workflow. Here's how:

  1. Open the main.yml file in the /.github/workflows folder in my private repository code.
  2. Find the step starting with name: Deploy 🚀
  3. Changed the line from token: ${{ secrets.ACCESS_TOKEN }} to ssh-key: ${{ secrets.DEPLOY_KEY }}
  4. Save the file and commit the YAML file to GitHub

NOTE: I also took the opportunity to change the revision of the Deploy Step's uses: line to take advantage of the latest code. See below...

Show me the Code!

Here is my entire newly updated main.yml file!

 1# This is a basic workflow to help you get started with Actions
 2
 3name: Hugo Build & Deploy - Private to Public
 4
 5# Controls when the action will run. Triggers the workflow on push or pull request
 6# events but only for the master branch
 7on:
 8  push:
 9    branches: [ main ]
10
11# A workflow run is made up of one or more jobs that can run sequentially or in parallel
12jobs:
13  # This workflow contains a single job called "build"
14  build:
15    # The type of runner that the job will run on
16    runs-on: ubuntu-latest
17
18    # Steps represent a sequence of tasks that will be executed as part of the job
19    steps:
20    
21    # Check Out Repository:   https://github.com/marketplace/actions/checkout
22    - name: Checkout 🛎️
23      uses: actions/[email protected] 
24      with:
25        persist-credentials: false
26        submodules: true  # I recently changed themes and started including the theme as a submodule
27
28
29    # Setup Hugo
30    - name: Hugo setup
31      uses: peaceiris/[email protected]
32      with:
33        # The Hugo version to download (if necessary) and use. Example: 0.58.2
34        hugo-version: 'latest'
35        # Download (if necessary) and use Hugo extended version. Example: true
36        extended: true  # I recently changed themes and the new theme needs the SCSS support in hugo extended version
37
38    # Runs Hugo to build the Static Site
39    - name: Run Hugo
40      run: |
41        hugo --minify --verbose        
42    # Deploy the Static Site to Public Repo (GitHub Pages):  https://github.com/marketplace/actions/deploy-to-github-pages
43    - name: Deploy 🚀
44      uses: JamesIves/[email protected]
45      with:
46        ssh-key:  ${{ secrets.DEPLOY_KEY }}
47        repository-name: rterakedis/rterakedis.github.io
48        branch: master # The branch the action should deploy to.
49        folder: docs # The folder the action should deploy.

Clean Up the Personal Access Token

Since I wasn't using the Personal Access Token for anything else in GitHub, I decided to do some cleanup. I highly recommend this as the token can grant more access than you'd want if the token/secret was ever leaked or stolen. Here's how I went about cleaning up:

Remove the Secret

  1. Browse to the Public Repository where GitHub Pages is hosted.
  2. Click on the Settings tab for the repository
    1. NOTE If your browser is windowed, it may be hidden in the elipsis ... to the right)
  3. Click on Secrets
  4. Click REMOVE next to the Personal Access Token Secret (i.e. ACCESS_TOKEN), and then click Yes, Remove this Token

Remove the Token

  1. Click on your account in the right corner and click on Settings to go to your Account Settings
    1. Do not confuse Account Settings with Repo Settings. The Account Settings screen actually says "Account Settings" in the top of the left-side menu
  2. Click on Developer Settings
  3. Click on Personal Access Tokens
  4. Click Delete next to your GH-Hugo-Workflow token
  5. Click I Understand, delete this token

All cleaned up!

Thoughts?

Let me know if you've set up something similar for blogging. Are you using GitHub Actions to automate your publishing workflow? What has been your experience?