How to build Go projects with multiple private dependencies in GitHub Actions
November 30, 2021 in GolangNote: the same approach should work for any other environment that can download dependencies from Git - Ruby’s Bundler, JavaScript’s NPM, and so on.
Building with private dependencies feels like a Mission Impossible. What mechanism to use to give access? What config files to check? How to make sense of cryptic git error messages?
But fear not - there is a path without giving up private repos or giving out access tokens with broad permissions.
1. Allow access to dependency repos
The least compromising method to give access from one repo to another is deploy keys.
A deploy key is simply an SSH key allowing read-only access to a specific repo. (A characteristic feature is that the same deploy key can only be used to access one repo.) With deploy keys, if a repo’s secrets are compromised, the attacker only gains read-only access to its dependencies’ source code.
I suggest you create a new deploy key for every repo that needs access (although it is possible to use the same key from different places.)
For example, for 2 projects that share the same 3 private dependencies, you need 6 keys.
So, let’s create some deploy keys.
ssh-keygen -N '' -C 'git@github.com:myorg/dep.git' -f dep.key
-N ''
means “no passphrase” - as this key will be used for automation-C 'git@github.com:myorg/dep.git'
is the key comment; it MUST be the repo Git URL, for later steps to work.-f dep1.key
saves the key to a file
Then, go to “Settings” -> “Deploy keys” in the dependency repo, and add the public key (the one from the .pub
file). Make sure to note the destination repo’s name in the title of the key, so you can easily revoke access later:
Repeat to generate and add keys for all dependencies. Keep private keys at hand, we will use them shortly.
2. Set up the project repo’s secrets
Now we can move to the project repo, the one that you want to build with those private dependencies.
Firstly, go to “Settings” -> “Secrets” and add every private key you created in step 1 as a separate secret:
Now you have DEP1_DEPLOY_KEY
, DEP2_DEPLOY_KEY
, and so on.
3. Set up the GitHub Actions workflow
To set up your workflow to use the keys to access repos, I suggest using the wonderful ssh-agent action. (The alternative is setting up host aliases in SSH config, and repo URL aliases in gitconfig, but ssh-agent covers everything with minimal configuration).
Among other features, ssh-agent can handle keys to multiple repos with no additional configuration.
Add such a step before any steps that do go get
or go mod download
from the private repos. (You only need it once per workflow job.)
- uses: webfactory/ssh-agent@v0.5.4
with:
ssh-private-key: |
${{ secrets.DEP1_DEPLOY_KEY }}
${{ secrets.DEP2_DEPLOY_KEY }}
Remember in step 1, I asked you to set the key comment to the repo’s URL? This is used by the ssh-agent action to configure all the proper aliases for keys to work. If you check the action’s output, it should say something like:
Added deploy-key mapping: Use identity '/home/runner/.ssh/key-abc' for GitHub repository myorg/dep1
Added deploy-key mapping: Use identity '/home/runner/.ssh/key-cde' for GitHub repository myorg/dep2
4. Mark Go modules as private
And the final essential setup step is letting Go know that the modules are private. For this, set the GOPRIVATE environment variable.
Private, in this context, means that Go will always download the module directly from the repo, and will not attempt to checksum it against public records. (So, only add trusted modules to GOPRIVATE.)
In every job that downloads Go modules, set the GOPRIVATE
environment variable:
env:
GOPRIVATE: github.com/myorg # covers all repos under your org
or, you can enumerate every repo individually:
env:
GOPRIVATE: github.com/myorg/dep1,github.com/myorg/dep2
Compatibility with developer’s machines
In case you haven’t already discovered this, developers also need to set GOPRIVATE
on their machine. Other than that, they don’t need any SSH or Git setup, assuming they already have access to the repos.
Troubleshooting
- Check that deploy key works (from your machine)
- Check that explicit SSH access works from GH action
- Check that an explicit
git clone
works from GH action - Check that
go get
for a specific module works.
Final words
If you follow everything correctly, builds with private dependencies are easy to set up and secure.
To build multiple project this way, just repeat the instructions for each one.
And to revoke access, it’s enough to remove the deploy key from a dependency repo - the private key would then have no access and pose no threat.
Liked the post? Treat me to a coffee