Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add git deploy key wrapper functionality #52

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 32 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,27 @@ GitHub Actions only have access to the repository they run for. So, in order to
## Usage

1. Create an SSH key with sufficient access privileges. For security reasons, don't use your personal SSH key but set up a dedicated one for use in GitHub Actions. See below for a few hints if you are unsure about this step.
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>
1. Make sure you don't have a passphrase set on the private key.
1. 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.

```yaml
# .github/workflows/my-workflow.yml
jobs:
my_job:
...
steps:
- actions/checkout@v1
# Make sure the @v0.4.1 matches the current version of the
# action
- uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- ... other steps
```
5. If, for some reason, you need to change the location of the SSH agent socket, you can use the `ssh-auth-sock` input to provide a path.
1. In your workflow definition file, add the following step. Preferably this would be rather on top, near the `actions/checkout@v1` line.

```yaml
# .github/workflows/my-workflow.yml
jobs:
my_job:
...
steps:
- actions/checkout@v1
# Make sure the @v0.4.1 matches the current version of the
# action
- uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- ... other steps
```
1. If you are using GitHub deploy keys, set the `use-git-deploy-key-wrapper` input variable to `true`.
1. If, for some reason, you need to change the location of the SSH agent socket, you can use the `ssh-auth-sock` input to provide a path.

### Using multiple keys

Expand All @@ -57,15 +58,24 @@ The `ssh-agent` will load all of the keys and try each one in order when establi
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.

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

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.
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.
For this scenario, you'll want to set `use-git-deploy-key-wrapper` input variable to `true` and create your key with a comment that has the git SSH url in it. For example:
```
ssh-keygen -t ed25519 -a 100 -C "ssh://[email protected]/ORGANIZATION/REPO.git" -m PEM -N "" -f ~/.ssh/REPO -q
```
**Please note that `[email protected]` is followed by a `/`, not a `:`**

## Exported variables
The action exports the `SSH_AUTH_SOCK` and `SSH_AGENT_PID` environment variables through the Github Actions core module.
The action exports the `SSH_AUTH_SOCK`, `SSH_AGENT_PID` and `GIT_SSH_COMMAND` environment variables through the Github Actions core module.

The `$SSH_AUTH_SOCK` is used by several applications like git or rsync to connect to the SSH authentication agent.

The `$SSH_AGENT_PID` contains the process id of the agent. This is used to kill the agent in post job action.

The `$GIT_SSH_COMMAND` contains the path to the `git-deploy-key-wrapper` shell script. Git will use this value for SSH interactions instead of the SSH executable.

## Known issues and limitations

### Currently OS X and Linux only
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ inputs:
required: true
ssh-auth-sock:
description: 'Where to place the SSH Agent auth socket'
use-git-deploy-key-wrapper:
description: 'Enable usage of git deploy key wrapper for multiple repositories with individual deploy keys'
default: false
runs:
using: 'node12'
main: 'dist/index.js'
Expand Down
8 changes: 8 additions & 0 deletions cleanup.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ try {
// Kill the started SSH agent
console.log('Stopping SSH agent')
execSync('kill ${SSH_AGENT_PID}', { stdio: 'inherit' })

const home = process.env['HOME'];
const homeSsh = `${home}/.ssh`;
const gitSSHWrapperPath = path.join(homeSsh, 'git-deploy-key-wrapper.sh');
if (fs.existsSync(gitSSHWrapperPath)) {
console.log('Removing ssh git SSH wrapper');
fs.unlinkSync(gitSSHWrapperPath);
}
} catch (error) {
console.log(error.message);
console.log('Error stopping the SSH agent, proceeding anyway');
Expand Down
8 changes: 8 additions & 0 deletions dist/cleanup.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ try {
// Kill the started SSH agent
console.log('Stopping SSH agent')
execSync('kill ${SSH_AGENT_PID}', { stdio: 'inherit' })

const home = process.env['HOME'];
const homeSsh = `${home}/.ssh`;
const gitSSHWrapperPath = path.join(homeSsh, 'git-deploy-key-wrapper.sh');
if (fs.existsSync(gitSSHWrapperPath)) {
console.log('Removing ssh git SSH wrapper');
fs.unlinkSync(gitSSHWrapperPath);
}
} catch (error) {
console.log(error.message);
console.log('Error stopping the SSH agent, proceeding anyway');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# The last argument is the command to be executed on the remote end, which is something
# like "git-upload-pack 'webfactory/ssh-agent.git'". We need the repo path only, so we
# Terraform ends up bing "git-upload-pack '/webfactory/ssh-agent.git'"
# loop over this last argument to get the last part of if.
for last in ${!#}; do :; done

Expand All @@ -12,6 +13,9 @@ trap "rm -f $key_file" EXIT
eval last=$last

# Try to pick the right key
ssh-add -L | grep --word-regexp --max-count=1 $last > $key_file
# No "--word-regexp" because Terraforms usage of git ends up as
# "git-upload-pack 'webfactory/ssh-agent.git'". "--word-regexp" will not match it.
# Other integrations still work without "--word-regexp"
ssh-add -L | grep --max-count=1 $last > $key_file

ssh -i $key_file "$@"
ssh -i $key_file "$@"
9 changes: 9 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,15 @@ try {
console.log("Keys added:");
child_process.execSync('ssh-add -l', { stdio: 'inherit' });

const useGitSSHWrapper = core.getInput('use-git-deploy-key-wrapper');
if(useGitSSHWrapper) {
const gitSSHWrapperFileName = 'git-deploy-key-wrapper.sh';
const gitSSHWrapperPath = path.join(homeSsh, gitSSHWrapperFileName);
fs.copyFileSync(path.join(process.cwd(), gitSSHWrapperFileName), gitSSHWrapperPath);
fs.chmodSync(gitSSHWrapperPath, "755");

core.exportVariable('GIT_SSH_COMMAND', gitSSHWrapperPath);
}
} catch (error) {
core.setFailed(error.message);
}
Expand Down
9 changes: 9 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ try {
console.log("Keys added:");
child_process.execSync('ssh-add -l', { stdio: 'inherit' });

const useGitSSHWrapper = core.getInput('use-git-deploy-key-wrapper');
if(useGitSSHWrapper) {
const gitSSHWrapperFileName = 'git-deploy-key-wrapper.sh';
const gitSSHWrapperPath = path.join(homeSsh, gitSSHWrapperFileName);
fs.copyFileSync(path.join(process.cwd(), gitSSHWrapperFileName), gitSSHWrapperPath);
fs.chmodSync(gitSSHWrapperPath, "755");

core.exportVariable('GIT_SSH_COMMAND', gitSSHWrapperPath);
}
} catch (error) {
core.setFailed(error.message);
}
18 changes: 15 additions & 3 deletions scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ const fs = require('fs')
const buildDir = path.join(process.cwd(), 'build')
const distDir = path.join(process.cwd(), 'dist')

const buildIndexJs = path.join(buildDir, 'index.js')
const distIndexJs = path.join(distDir, 'index.js')
const distCleanupJs = path.join(distDir, 'cleanup.js')
const gitSSHWrapperFileName = 'git-deploy-key-wrapper.sh';
const gitSSHWrapper = path.join(process.cwd(), 'wrapper', gitSSHWrapperFileName);

const buildIndexJs = path.join(buildDir, 'index.js');
const buildGitSSHWrapper = path.join(buildDir, gitSSHWrapperFileName);
const distIndexJs = path.join(distDir, 'index.js');
const distGitSSHWrapper = path.join(distDir, gitSSHWrapperFileName);
const distCleanupJs = path.join(distDir, 'cleanup.js');

if (!fs.existsSync(buildDir)) {
fs.mkdirSync(buildDir)
Expand All @@ -29,6 +34,13 @@ if (fs.existsSync(distCleanupJs)) {
}
fs.renameSync(buildIndexJs, distCleanupJs)

console.log(`Copying "${gitSSHWrapperFileName}"`);
fs.copyFileSync(gitSSHWrapper, buildGitSSHWrapper);
if (fs.existsSync(distGitSSHWrapper)) {
fs.unlinkSync(distGitSSHWrapper);
}
fs.renameSync(buildGitSSHWrapper, distGitSSHWrapper);

console.log('Cleaning up...')
fs.rmdirSync(buildDir)

Expand Down
21 changes: 21 additions & 0 deletions wrapper/git-deploy-key-wrapper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/bash

# The last argument is the command to be executed on the remote end, which is something
# like "git-upload-pack 'webfactory/ssh-agent.git'". We need the repo path only, so we
# Terraform ends up bing "git-upload-pack '/webfactory/ssh-agent.git'"
# loop over this last argument to get the last part of if.
for last in ${!#}; do :; done

# Don't use "exec" to run "ssh" below; then the trap won't work.
key_file=$(mktemp -u)
trap "rm -f $key_file" EXIT

eval last=$last

# Try to pick the right key
# No "--word-regexp" because Terraforms usage of git ends up as
# "git-upload-pack '/webfactory/ssh-agent.git'". "--word-regexp" will not match it.
# Other integrations still work without "--word-regexp"
ssh-add -L | grep --max-count=1 $last > $key_file

ssh -i $key_file "$@"