Skip to content

Commit

Permalink
Add support for GitHub Deployment Keys through key comments (#59)
Browse files Browse the repository at this point in the history
Fixes #30, closes #38.
  • Loading branch information
mpdude authored Feb 19, 2021
1 parent 8535391 commit 4d06ea6
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 10 deletions.
24 changes: 21 additions & 3 deletions .github/workflows/demo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
os: [ubuntu-latest, macOS-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Setup key
uses: ./
with:
Expand All @@ -21,7 +21,7 @@ jobs:
os: [ubuntu-latest, macOS-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Setup key
uses: ./
with:
Expand All @@ -32,11 +32,29 @@ jobs:
container:
image: ubuntu:latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- run: apt update && apt install -y openssh-client
- name: Setup key
uses: ./
with:
ssh-private-key: |
${{ secrets.DEMO_KEY }}
${{ secrets.DEMO_KEY_2 }}
deployment_keys_demo:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup key
uses: ./
with:
ssh-private-key: |
${{ secrets.MPDUDE_TEST_1_DEPLOY_KEY }}
${{ secrets.MPDUDE_TEST_2_DEPLOY_KEY }}
- run: |
git clone https://github.com/mpdude/test-1.git test-1-http
git clone [email protected]:mpdude/test-1.git test-1-git
git clone ssh://[email protected]/mpdude/test-1.git test-1-git-ssh
git clone https://github.com/mpdude/test-2.git test-2-http
git clone [email protected]:mpdude/test-2.git test-2-git
git clone ssh://[email protected]/mpdude/test-2.git test-2-git-ssh
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
This action
* starts the `ssh-agent`,
* exports the `SSH_AUTH_SOCK` environment variable,
* loads a private SSH key into the agent and
* loads one or several private SSH key into the agent and
* configures `known_hosts` for GitHub.com.

It should work in all GitHub Actions virtual environments, including container-based workflows.

Windows and Docker support is, however, somewhat new. Since we have little feedback from the field, things might not run so smooth for you as we'd hope. If Windows and/or Docker-based workflows work well for you, leave a :+1: at https://github.com/webfactory/ssh-agent/pull/17.

Also, using multiple GitHub deployment keys is supported; keys are mapped to repositories by using SSH key comments (see below).

## Why?

When running a GitHub Action workflow to stage your project, run tests or build images, you might need to fetch additional libraries or _vendors_ from private repositories.
Expand All @@ -22,15 +24,15 @@ GitHub Actions only have access to the repository they run for. So, in order to
2. Make sure you don't have a passphrase set on the private key.
3. In your repository, go to the *Settings > Secrets* menu and create a new secret. In this example, we'll call it `SSH_PRIVATE_KEY`. Put the contents of the *private* SSH key file into the contents field. <br>
This key should start with `-----BEGIN ... PRIVATE KEY-----`, consist of many lines and ends with `-----END ... PRIVATE KEY-----`.
4. In your workflow definition file, add the following step. Preferably this would be rather on top, near the `actions/checkout@v1` line.
4. In your workflow definition file, add the following step. Preferably this would be rather on top, near the `actions/checkout@v2` line.

```yaml
# .github/workflows/my-workflow.yml
jobs:
my_job:
...
steps:
- actions/checkout@v1
- actions/checkout@v2
# Make sure the @v0.4.1 matches the current version of the
# action
- uses: webfactory/[email protected]
Expand Down Expand Up @@ -58,12 +60,18 @@ You can set up different keys as different secrets and pass them all to the acti

The `ssh-agent` will load all of the keys and try each one in order when establishing SSH connections.

There's one **caveat**, though: SSH servers may abort the connection attempt after a number of mismatching keys have been presented. So if, for example, you have
six different keys loaded into the `ssh-agent`, but the server aborts after five unknown keys, the last key (which might be the right one) will never even be tried.
There's one **caveat**, though: SSH servers may abort the connection attempt after a number of mismatching keys have been presented. So if, for example, you have six different keys loaded into the `ssh-agent`, but the server aborts after five unknown keys, the last key (which might be the right one) will never even be tried. But when you're using GitHub Deploy Keys, read on!

### Support for GitHub Deploy Keys

When using **Github deploy keys**, GitHub servers will accept the _first_ known key. But since deploy keys are scoped to a single repository, this might not be the key needed to access a particular repository. Thus, you will get the error message `fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.` if the wrong key/repository combination is tried.

Also, when using **Github deploy keys**, GitHub servers will accept the first known key. But since deploy keys are scoped to a single repository, you might get the error message `fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.` if the wrong key/repository combination is tried.
To support picking the right key in this use case, this action scans _key comments_ and will set up extra Git and SSH configuration to make things work.

In both cases, you might want to [try a wrapper script around `ssh`](https://gist.github.com/mpdude/e56fcae5bc541b95187fa764aafb5e6d) that can pick the right key, based on key comments. See [our blog post](https://www.webfactory.de/blog/using-multiple-ssh-deploy-keys-with-github) for the full story.
1. When creating the deploy key for a repository like `[email protected]:owner/repo.git` or `https://github.com/owner/repo`, put that URL into the key comment.
2. After keys have been added to the agent, this action will scan the key comments.
3. For key comments containing such URLs, a Git config setting is written that uses [`url.<base>.insteadof`](https://git-scm.com/docs/git-config#Documentation/git-config.txt-urlltbasegtinsteadOf). It will redirect `git` requests to URLs starting with either `https://github.com/owner/repo` or `[email protected]:owner/repo` to a fake hostname/URL like `[email protected]...:owner/repo`.
4. An SSH configuration section is generated that applies to the fake hostname. It will map the SSH connection back to `github.com`, while at the same time pointing SSH to a file containing the appropriate key's public part. That will make SSH use the right key when connecting to GitHub.com.

## Exported variables
The action exports the `SSH_AUTH_SOCK` and `SSH_AGENT_PID` environment variables through the Github Actions core module.
Expand Down
35 changes: 35 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ const core = __webpack_require__(470);
const child_process = __webpack_require__(129);
const fs = __webpack_require__(747);
const os = __webpack_require__(87);
const crypto = __webpack_require__(417);

try {
const privateKey = core.getInput('ssh-private-key');
Expand Down Expand Up @@ -175,6 +176,33 @@ try {
console.log("Keys added:");
child_process.execSync('ssh-add -l', { stdio: 'inherit' });

child_process.execFileSync('ssh-add', ['-L']).toString().split(/\r?\n/).forEach(function(key) {
let parts = key.match(/\bgithub.com[:/](.*)(?:\.git)?\b/);

if (parts == null) {
return;
}

let ownerAndRepo = parts[1];
let sha256 = crypto.createHash('sha256').update(key).digest('hex');

fs.writeFileSync(`${homeSsh}/${sha256}`, key + "\n", { mode: '600' });

child_process.execSync(`git config --global --replace-all url."git@${sha256}:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`);
child_process.execSync(`git config --global --add url."git@${sha256}:${ownerAndRepo}".insteadOf "[email protected]:${ownerAndRepo}"`);
child_process.execSync(`git config --global --add url."git@${sha256}:${ownerAndRepo}".insteadOf "ssh://[email protected]/${ownerAndRepo}"`);

let sshConfig = `\nHost ${sha256}\n`
+ ` HostName github.com\n`
+ ` User git\n`
+ ` IdentityFile ${homeSsh}/${sha256}\n`
+ ` IdentitiesOnly yes\n`;

fs.appendFileSync(`${homeSsh}/config`, sshConfig);

console.log(`Added deploy-key mapping: Use key "${key}" for GitHub repository ${ownerAndRepo}`);
});

} catch (error) {
core.setFailed(error.message);
}
Expand All @@ -189,6 +217,13 @@ module.exports = require("child_process");

/***/ }),

/***/ 417:
/***/ (function(module) {

module.exports = require("crypto");

/***/ }),

/***/ 431:
/***/ (function(__unusedmodule, exports, __webpack_require__) {

Expand Down
28 changes: 28 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const core = require('@actions/core');
const child_process = require('child_process');
const fs = require('fs');
const os = require('os');
const crypto = require('crypto');

try {
const privateKey = core.getInput('ssh-private-key');
Expand Down Expand Up @@ -58,6 +59,33 @@ try {
console.log("Keys added:");
child_process.execSync('ssh-add -l', { stdio: 'inherit' });

child_process.execFileSync('ssh-add', ['-L']).toString().split(/\r?\n/).forEach(function(key) {
let parts = key.match(/\bgithub.com[:/](.*)(?:\.git)?\b/);

if (parts == null) {
return;
}

let ownerAndRepo = parts[1];
let sha256 = crypto.createHash('sha256').update(key).digest('hex');

fs.writeFileSync(`${homeSsh}/${sha256}`, key + "\n", { mode: '600' });

child_process.execSync(`git config --global --replace-all url."git@${sha256}:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`);
child_process.execSync(`git config --global --add url."git@${sha256}:${ownerAndRepo}".insteadOf "[email protected]:${ownerAndRepo}"`);
child_process.execSync(`git config --global --add url."git@${sha256}:${ownerAndRepo}".insteadOf "ssh://[email protected]/${ownerAndRepo}"`);

let sshConfig = `\nHost ${sha256}\n`
+ ` HostName github.com\n`
+ ` User git\n`
+ ` IdentityFile ${homeSsh}/${sha256}\n`
+ ` IdentitiesOnly yes\n`;

fs.appendFileSync(`${homeSsh}/config`, sshConfig);

console.log(`Added deploy-key mapping: Use key "${key}" for GitHub repository ${ownerAndRepo}`);
});

} catch (error) {
core.setFailed(error.message);
}

0 comments on commit 4d06ea6

Please sign in to comment.