-
Notifications
You must be signed in to change notification settings - Fork 263
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for GitHub Deployment Keys through key comments (#59)
- Loading branch information
Showing
4 changed files
with
99 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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: | ||
|
@@ -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: | ||
|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
|
@@ -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] | ||
|
@@ -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. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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'); | ||
|
@@ -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); | ||
} | ||
|
@@ -189,6 +217,13 @@ module.exports = require("child_process"); | |
|
||
/***/ }), | ||
|
||
/***/ 417: | ||
/***/ (function(module) { | ||
|
||
module.exports = require("crypto"); | ||
|
||
/***/ }), | ||
|
||
/***/ 431: | ||
/***/ (function(__unusedmodule, exports, __webpack_require__) { | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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'); | ||
|
@@ -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); | ||
} |