Adding repo-mappings and drop-extra-header options

Updated README.md to include the two new options
Fixed build.js to work on windows
Fixed homedir lookup for windows
Moved param names to const vars at the top and replaced all references
This commit is contained in:
Shaun Cooley 2020-08-17 16:30:59 -07:00
parent 780d0ee9a3
commit d561d1a80b
5 changed files with 309 additions and 25 deletions

108
README.md
View file

@ -50,17 +50,42 @@ You can set up different keys as different secrets and pass them all to the acti
${{ secrets.FIRST_KEY }} ${{ secrets.FIRST_KEY }}
${{ secrets.NEXT_KEY }} ${{ secrets.NEXT_KEY }}
${{ secrets.ANOTHER_KEY }} ${{ secrets.ANOTHER_KEY }}
repo-mappings: |
github.com/OWNERX/REPO1
bitbucket.com/OWNERY/REPO2
github.com/OWNERX/REPO3
``` ```
The `ssh-agent` will load all of the keys and try each one in order when establishing SSH connections. 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 Optionally, `repo-mappings` provides a list of git repos that correlate to the keys provided. If you specify `repo-mappings` you **MUST** specify the same number mappings as you provided `ssh-private-key` entries and they **MUST** be in the same order. Each mapping **MUST** be in the format of `{HOSTNAME}/{OWNER}/{REPO}` without any *https://*, *git@* , or *ssh://* prefix and using **slashes** not the mixed slashes and colons used in the ssh format.
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.
These mappings are used to generate git config `insteadOf` entries to psuedo hostnames, where the pseudo hostnames are each assigned the associated `ssh-private-key`. See the [Repo Mappings](#repo-mappings) section for details on how this works.
There's one **caveat**, though, if you're not using `repo-mappings`: 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. 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.
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. 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.
### Dropping the http.extraHeader added by actions/checkout@v2
If you are using (actions/checkout@v2)[], it adds an `AUTHORIZATION: basic ${GITHUB_TOKEN}` header to all git calls. This header can conflict with the `repo-mappings` in some apps (like `go get`). If you are having issues, try setting this option to `true`.
```yaml
# ... contens as before
- uses: webfactory/ssh-agent@v0.4.0
with:
ssh-private-key: |
${{ secrets.FIRST_KEY }}
${{ secrets.NEXT_KEY }}
${{ secrets.ANOTHER_KEY }}
repo-mappings: |
github.com/OWNERX/REPO1
bitbucket.com/OWNERY/REPO2
github.com/OWNERX/REPO3
drop-extra-header: true
```
## Exported variables ## 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` and `SSH_AGENT_PID` 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_AUTH_SOCK` is used by several applications like git or rsync to connect to the SSH authentication agent.
@ -117,6 +142,66 @@ To actually grant the SSH key access, you can on GitHub use at least two
* A [machine user](https://developer.github.com/v3/guides/managing-deploy-keys/#machine-users) can be used for more fine-grained permissions management and have access to multiple repositories with just one instance of the key being registered. It will, however, count against your number of users on paid GitHub plans. * A [machine user](https://developer.github.com/v3/guides/managing-deploy-keys/#machine-users) can be used for more fine-grained permissions management and have access to multiple repositories with just one instance of the key being registered. It will, however, count against your number of users on paid GitHub plans.
## Repo Mappings
When git connects over SSH, it sends the target path [see git connect.c](https://github.com/git/git/blob/e870325/connect.c#L1254), but GitHub glady accepts any valid ssh key without ensuring access to the specified path, only to then return 404. In order to work around this, we do three things:
1. Parse `repo-mappings`
2. Create git config `insteadOf` url-rewrite rules
2. Configure per-host ssh details
### Parse repo-mappings
Each mapping **MUST** be in the format of `{HOSTNAME}/{OWNER}/{REPO}` without any *https://*, *git@* , or *ssh://* prefix and using **slashes** not the mixed slashes and colons used in the ssh format. For the next two sections, we will use the following as our example mapping:
```
github.com/webfactory/ssh-agent
```
### insteadOf Entries
- A pseudo hostname is established using `{REPO}.{HOSTNAME}` (example: `ssh-agent.github.com`).
- insteadOf entries are created in the **global** .gitconfig file for both https and ssh, forcing them to use the pseudo hostname over ssh:
```
git config url."git@http.{PSEUDOHOST}:{OWNER}/{REPO}".insteadOf "https://{HOSTNAME}/{OWNER}/{REPO}"
git config url."git@ssh.{PSEUDOHOST}:{OWNER}/{REPO}".insteadOf "git@{HOSTNAME}:{OWNER}/{REPO}";
```
- The resulting .gitconfig looks something like (using the example):
```
[url "git@github.com:webfactory/ssh-agent"]
insteadOf = https://ssh-agent.github.com/webfactory/ssh-agent
[url "git@github.com:webfactory/ssh-agent"]
insteadOf = git@github.com:webfactory/ssh-agent
```
### Per-host SSH Entries
For each mapping/key pair, we create custom named entries in `~/.ssh/config`:
```
Host http.{PSEUDOHOST}
HostName {HOSTNAME}
User git
IdentityFile ~/.ssh/{PSEUDOHOST}
IdentitiesOnly yes
Host ssh.{PSEUDOHOST}
HostName {HOSTNAME}
User git
IdentityFile ~/.ssh/{PSEUDOHOST}
IdentitiesOnly yes
```
For the example, that is:
```
Host http.ssh-agent.github.com
HostName github.com
User git
IdentityFile ~/.ssh/ssh-agent.github.com
IdentitiesOnly yes
Host ssh.ssh-agent.github.com
HostName github.com
User git
IdentityFile ~/.ssh/ssh-agent.github.com
IdentitiesOnly yes
```
Also note that we set `IdentitiesOnly`, which prevents ssh from trying every key when connecting to a host. This helps the caveat for (Using multiple keys)[#using-multiple-keys].
## Hacking ## Hacking
As a note to my future self, in order to work on this repo: As a note to my future self, in order to work on this repo:
@ -124,7 +209,24 @@ As a note to my future self, in order to work on this repo:
* Clone it * Clone it
* Run `yarn install` to fetch dependencies * Run `yarn install` to fetch dependencies
* _hack hack hack_ * _hack hack hack_
* `node index.js`. Inputs are passed through `INPUT_` env vars with their names uppercased. Use `env "INPUT_SSH-PRIVATE-KEY=\`cat file\`" node index.js` for this action. * `node index.js`. Inputs are passed through `INPUT_` env vars with their names uppercased.
On *nix use:
```bash
env "INPUT_SSH-PRIVATE-KEY=\`cat file\`" node index.js
```
On Windows (cmd):
```cmd
set /P INPUT_SSH-PRIVATE-KEY=< file
node index.js
```
On Windows (PowerShell):
```ps
${env:INPUT_SSH-PRIVATE-KEY} = (Get-Content .\test-keys -Raw); node index.js
node index.js
```
* Run `npm run build` to update `dist/*`, which holds the files actually run * Run `npm run build` to update `dist/*`, which holds the files actually run
* Read https://help.github.com/en/articles/creating-a-javascript-action if unsure. * Read https://help.github.com/en/articles/creating-a-javascript-action if unsure.
* Maybe update the README example when publishing a new version. * Maybe update the README example when publishing a new version.

View file

@ -4,8 +4,16 @@ inputs:
ssh-private-key: ssh-private-key:
description: 'Private SSH key to register in the SSH agent' description: 'Private SSH key to register in the SSH agent'
required: true required: true
repo-mappings:
description: 'Git Repo Mappings, order and count must match ssh-private-key'
required: false
ssh-auth-sock: ssh-auth-sock:
description: 'Where to place the SSH Agent auth socket' description: 'Where to place the SSH Agent auth socket'
required: false
drop-extra-header:
description: 'Remove the .gitconfig http.extraheader auth token added by actions/checkout@v2'
required: false
runs: runs:
using: 'node12' using: 'node12'
main: 'dist/index.js' main: 'dist/index.js'

104
dist/index.js vendored
View file

@ -118,27 +118,36 @@ exports.issueCommand = issueCommand;
const core = __webpack_require__(470); const core = __webpack_require__(470);
const child_process = __webpack_require__(129); const child_process = __webpack_require__(129);
const fs = __webpack_require__(747); const fs = __webpack_require__(747);
const os = __webpack_require__(87);
// Param names
const privateKeyName = 'ssh-private-key';
const repoMappingsName = 'repo-mappings';
const authSockName = 'ssh-auth-sock';
const dropExtraHeaderName = 'drop-extra-header';
try { try {
const home = process.env['HOME']; const home = os.homedir();
const homeSsh = home + '/.ssh'; const homeSsh = home + '/.ssh';
const sshConfig = homeSsh + '/config';
const sshKnownHosts = homeSsh + '/known_hosts';
const privateKey = core.getInput('ssh-private-key'); const privateKey = core.getInput(privateKeyName);
if (!privateKey) { if (!privateKey) {
core.setFailed("The ssh-private-key argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); core.setFailed(`The ${privateKeyName} argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file.`);
return; return;
} }
console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); console.log(`Adding GitHub.com keys to ${sshKnownHosts}`);
fs.mkdirSync(homeSsh, { recursive: true }); fs.mkdirSync(homeSsh, { recursive: true });
fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); fs.appendFileSync(sshKnownHosts, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n');
fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-dss AAAAB3NzaC1kc3MAAACBANGFW2P9xlGU3zWrymJgI/lKo//ZW2WfVtmbsUZJ5uyKArtlQOT2+WRhcg4979aFxgKdcsqAYW3/LS1T2km3jYW/vr4Uzn+dXWODVk5VlUiZ1HFOHf6s6ITcZvjvdbp6ZbpM+DuJT7Bw+h5Fx8Qt8I16oCZYmAPJRtu46o9C2zk1AAAAFQC4gdFGcSbp5Gr0Wd5Ay/jtcldMewAAAIATTgn4sY4Nem/FQE+XJlyUQptPWMem5fwOcWtSXiTKaaN0lkk2p2snz+EJvAGXGq9dTSWHyLJSM2W6ZdQDqWJ1k+cL8CARAqL+UMwF84CR0m3hj+wtVGD/J4G5kW2DBAf4/bqzP4469lT+dF2FRQ2L9JKXrCWcnhMtJUvua8dvnwAAAIB6C4nQfAA7x8oLta6tT+oCk2WQcydNsyugE8vLrHlogoWEicla6cWPk7oXSspbzUcfkjN3Qa6e74PhRkc7JdSdAlFzU3m7LMkXo1MHgkqNX8glxWNVqBSc0YRdbFdTkL0C6gtpklilhvuHQCdbgB3LBAikcRkDp+FCVkUgPC/7Rw==\n'); fs.appendFileSync(sshKnownHosts, '\ngithub.com ssh-dss AAAAB3NzaC1kc3MAAACBANGFW2P9xlGU3zWrymJgI/lKo//ZW2WfVtmbsUZJ5uyKArtlQOT2+WRhcg4979aFxgKdcsqAYW3/LS1T2km3jYW/vr4Uzn+dXWODVk5VlUiZ1HFOHf6s6ITcZvjvdbp6ZbpM+DuJT7Bw+h5Fx8Qt8I16oCZYmAPJRtu46o9C2zk1AAAAFQC4gdFGcSbp5Gr0Wd5Ay/jtcldMewAAAIATTgn4sY4Nem/FQE+XJlyUQptPWMem5fwOcWtSXiTKaaN0lkk2p2snz+EJvAGXGq9dTSWHyLJSM2W6ZdQDqWJ1k+cL8CARAqL+UMwF84CR0m3hj+wtVGD/J4G5kW2DBAf4/bqzP4469lT+dF2FRQ2L9JKXrCWcnhMtJUvua8dvnwAAAIB6C4nQfAA7x8oLta6tT+oCk2WQcydNsyugE8vLrHlogoWEicla6cWPk7oXSspbzUcfkjN3Qa6e74PhRkc7JdSdAlFzU3m7LMkXo1MHgkqNX8glxWNVqBSc0YRdbFdTkL0C6gtpklilhvuHQCdbgB3LBAikcRkDp+FCVkUgPC/7Rw==\n');
console.log("Starting ssh-agent"); console.log("Starting ssh-agent");
const authSock = core.getInput('ssh-auth-sock'); const authSock = core.getInput(authSockName);
let sshAgentOutput = '' let sshAgentOutput = ''
if (authSock && authSock.length > 0) { if (authSock && authSock.length > 0) {
sshAgentOutput = child_process.execFileSync('ssh-agent', ['-a', authSock]); sshAgentOutput = child_process.execFileSync('ssh-agent', ['-a', authSock]);
@ -155,13 +164,88 @@ try {
} }
} }
// Do we need to drop the http.extraheader added by actions/checkout@v2?
const dropExtraHeader = (core.getInput(dropExtraHeaderName).toLowerCase() === 'true');
if (dropExtraHeader) {
console.log("Dropping any existing http.extraheader git config");
child_process.execSync(`git config --global http.https://github.com/.extraheader ''`);
}
// Grab the repo mappings
console.log("Parsing repo mappings");
const repoMappingsInput = core.getInput(repoMappingsName);
let repoMappings = null;
if (repoMappingsInput) {
repoMappings = new Array();
repoMappingsInput.split(/\r?\n/).forEach(function(key) {
// Get the hostname, org name, and repo name
// format expected: sub.host.com/OWNER/REPO
let parts = key.trim().match(/(.*)\/(.*)\/(.*)/);
if (parts.length != 4) {
throw `Invalid ${repoMappingsName} format at: ${key}`;
}
// Add this to the array of mappings
let mapping = {
host: parts[1],
owner: parts[2],
repo: parts[3],
pseudoHost: `${parts[3]}.${parts[1]}`
};
repoMappings.push(mapping);
// Create rewrites
console.log(`Adding insteadOf entries in git config for ${key}`);
child_process.execSync(`git config --global url."git@http.${mapping.pseudoHost}:${mapping.owner}/${mapping.repo}".insteadOf "https://${mapping.host}/${mapping.owner}/${mapping.repo}"`);
child_process.execSync(`git config --global url."git@ssh.${mapping.pseudoHost}:${mapping.owner}/${mapping.repo}".insteadOf "git@${mapping.host}:${mapping.owner}/${mapping.repo}"`);
});
}
// Add private keys to ssh-agent
console.log("Adding private key to agent"); console.log("Adding private key to agent");
privateKey.split(/(?=-----BEGIN)/).forEach(function(key) { const privateKeys = privateKey.split(/(?=-----BEGIN)/);
child_process.execSync('ssh-add -', { input: key.trim() + "\n" }); if (repoMappings && privateKeys.length != repoMappings.length) {
core.setFailed(`The number of ${privateKeyName} arguments and ${repoMappingsName} must match.`);
return;
}
privateKeys.forEach(function(key, i) {
if (repoMappings) {
let mapping = repoMappings[i];
let keyFile = `${mapping.pseudoHost}.key`;
// Since we can't specify hostname/user/host options in a ssh-add call...
// Write the key to a file
fs.writeFileSync(`${homeSsh}/${keyFile}`, key.replace("\r\n", "\n").trim() + "\n", { mode: '600' });
// Update ssh config
let hostEntry = `\nHost http.${mapping.pseudoHost}\n`
+ ` HostName ${mapping.host}\n`
+ ` User git\n`
+ ` IdentityFile ~/.ssh/${keyFile}\n`
+ ` IdentitiesOnly yes\n`
+ `\nHost ssh.${mapping.pseudoHost}\n`
+ ` HostName ${mapping.host}\n`
+ ` User git\n`
+ ` IdentityFile ~/.ssh/${keyFile}\n`
+ ` IdentitiesOnly yes\n`;
fs.appendFileSync(sshConfig, hostEntry);
} else {
// No mappings, just use ssh-add
child_process.execSync('ssh-add -', { input: key.trim() + "\n" });
}
}); });
console.log("Keys added:"); console.log("Keys added:");
child_process.execSync('ssh-add -l', { stdio: 'inherit' }); if (repoMappings) {
repoMappings.forEach(function(key) {
console.log(`~/.ssh/${key.pseudoHost}.key`);
});
} else {
child_process.execSync('ssh-add -l', { stdio: 'inherit' });
}
} catch (error) { } catch (error) {
core.setFailed(error.message); core.setFailed(error.message);

104
index.js
View file

@ -1,27 +1,36 @@
const core = require('@actions/core'); const core = require('@actions/core');
const child_process = require('child_process'); const child_process = require('child_process');
const fs = require('fs'); const fs = require('fs');
const os = require('os');
// Param names
const privateKeyName = 'ssh-private-key';
const repoMappingsName = 'repo-mappings';
const authSockName = 'ssh-auth-sock';
const dropExtraHeaderName = 'drop-extra-header';
try { try {
const home = process.env['HOME']; const home = os.homedir();
const homeSsh = home + '/.ssh'; const homeSsh = home + '/.ssh';
const sshConfig = homeSsh + '/config';
const sshKnownHosts = homeSsh + '/known_hosts';
const privateKey = core.getInput('ssh-private-key'); const privateKey = core.getInput(privateKeyName);
if (!privateKey) { if (!privateKey) {
core.setFailed("The ssh-private-key argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); core.setFailed(`The ${privateKeyName} argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file.`);
return; return;
} }
console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); console.log(`Adding GitHub.com keys to ${sshKnownHosts}`);
fs.mkdirSync(homeSsh, { recursive: true }); fs.mkdirSync(homeSsh, { recursive: true });
fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); fs.appendFileSync(sshKnownHosts, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n');
fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-dss AAAAB3NzaC1kc3MAAACBANGFW2P9xlGU3zWrymJgI/lKo//ZW2WfVtmbsUZJ5uyKArtlQOT2+WRhcg4979aFxgKdcsqAYW3/LS1T2km3jYW/vr4Uzn+dXWODVk5VlUiZ1HFOHf6s6ITcZvjvdbp6ZbpM+DuJT7Bw+h5Fx8Qt8I16oCZYmAPJRtu46o9C2zk1AAAAFQC4gdFGcSbp5Gr0Wd5Ay/jtcldMewAAAIATTgn4sY4Nem/FQE+XJlyUQptPWMem5fwOcWtSXiTKaaN0lkk2p2snz+EJvAGXGq9dTSWHyLJSM2W6ZdQDqWJ1k+cL8CARAqL+UMwF84CR0m3hj+wtVGD/J4G5kW2DBAf4/bqzP4469lT+dF2FRQ2L9JKXrCWcnhMtJUvua8dvnwAAAIB6C4nQfAA7x8oLta6tT+oCk2WQcydNsyugE8vLrHlogoWEicla6cWPk7oXSspbzUcfkjN3Qa6e74PhRkc7JdSdAlFzU3m7LMkXo1MHgkqNX8glxWNVqBSc0YRdbFdTkL0C6gtpklilhvuHQCdbgB3LBAikcRkDp+FCVkUgPC/7Rw==\n'); fs.appendFileSync(sshKnownHosts, '\ngithub.com ssh-dss AAAAB3NzaC1kc3MAAACBANGFW2P9xlGU3zWrymJgI/lKo//ZW2WfVtmbsUZJ5uyKArtlQOT2+WRhcg4979aFxgKdcsqAYW3/LS1T2km3jYW/vr4Uzn+dXWODVk5VlUiZ1HFOHf6s6ITcZvjvdbp6ZbpM+DuJT7Bw+h5Fx8Qt8I16oCZYmAPJRtu46o9C2zk1AAAAFQC4gdFGcSbp5Gr0Wd5Ay/jtcldMewAAAIATTgn4sY4Nem/FQE+XJlyUQptPWMem5fwOcWtSXiTKaaN0lkk2p2snz+EJvAGXGq9dTSWHyLJSM2W6ZdQDqWJ1k+cL8CARAqL+UMwF84CR0m3hj+wtVGD/J4G5kW2DBAf4/bqzP4469lT+dF2FRQ2L9JKXrCWcnhMtJUvua8dvnwAAAIB6C4nQfAA7x8oLta6tT+oCk2WQcydNsyugE8vLrHlogoWEicla6cWPk7oXSspbzUcfkjN3Qa6e74PhRkc7JdSdAlFzU3m7LMkXo1MHgkqNX8glxWNVqBSc0YRdbFdTkL0C6gtpklilhvuHQCdbgB3LBAikcRkDp+FCVkUgPC/7Rw==\n');
console.log("Starting ssh-agent"); console.log("Starting ssh-agent");
const authSock = core.getInput('ssh-auth-sock'); const authSock = core.getInput(authSockName);
let sshAgentOutput = '' let sshAgentOutput = ''
if (authSock && authSock.length > 0) { if (authSock && authSock.length > 0) {
sshAgentOutput = child_process.execFileSync('ssh-agent', ['-a', authSock]); sshAgentOutput = child_process.execFileSync('ssh-agent', ['-a', authSock]);
@ -38,13 +47,88 @@ try {
} }
} }
// Do we need to drop the http.extraheader added by actions/checkout@v2?
const dropExtraHeader = (core.getInput(dropExtraHeaderName).toLowerCase() === 'true');
if (dropExtraHeader) {
console.log("Dropping any existing http.extraheader git config");
child_process.execSync(`git config --global http.https://github.com/.extraheader ''`);
}
// Grab the repo mappings
console.log("Parsing repo mappings");
const repoMappingsInput = core.getInput(repoMappingsName);
let repoMappings = null;
if (repoMappingsInput) {
repoMappings = new Array();
repoMappingsInput.split(/\r?\n/).forEach(function(key) {
// Get the hostname, org name, and repo name
// format expected: sub.host.com/OWNER/REPO
let parts = key.trim().match(/(.*)\/(.*)\/(.*)/);
if (parts.length != 4) {
throw `Invalid ${repoMappingsName} format at: ${key}`;
}
// Add this to the array of mappings
let mapping = {
host: parts[1],
owner: parts[2],
repo: parts[3],
pseudoHost: `${parts[3]}.${parts[1]}`
};
repoMappings.push(mapping);
// Create rewrites
console.log(`Adding insteadOf entries in git config for ${key}`);
child_process.execSync(`git config --global url."git@http.${mapping.pseudoHost}:${mapping.owner}/${mapping.repo}".insteadOf "https://${mapping.host}/${mapping.owner}/${mapping.repo}"`);
child_process.execSync(`git config --global url."git@ssh.${mapping.pseudoHost}:${mapping.owner}/${mapping.repo}".insteadOf "git@${mapping.host}:${mapping.owner}/${mapping.repo}"`);
});
}
// Add private keys to ssh-agent
console.log("Adding private key to agent"); console.log("Adding private key to agent");
privateKey.split(/(?=-----BEGIN)/).forEach(function(key) { const privateKeys = privateKey.split(/(?=-----BEGIN)/);
child_process.execSync('ssh-add -', { input: key.trim() + "\n" }); if (repoMappings && privateKeys.length != repoMappings.length) {
core.setFailed(`The number of ${privateKeyName} arguments and ${repoMappingsName} must match.`);
return;
}
privateKeys.forEach(function(key, i) {
if (repoMappings) {
let mapping = repoMappings[i];
let keyFile = `${mapping.pseudoHost}.key`;
// Since we can't specify hostname/user/host options in a ssh-add call...
// Write the key to a file
fs.writeFileSync(`${homeSsh}/${keyFile}`, key.replace("\r\n", "\n").trim() + "\n", { mode: '600' });
// Update ssh config
let hostEntry = `\nHost http.${mapping.pseudoHost}\n`
+ ` HostName ${mapping.host}\n`
+ ` User git\n`
+ ` IdentityFile ~/.ssh/${keyFile}\n`
+ ` IdentitiesOnly yes\n`
+ `\nHost ssh.${mapping.pseudoHost}\n`
+ ` HostName ${mapping.host}\n`
+ ` User git\n`
+ ` IdentityFile ~/.ssh/${keyFile}\n`
+ ` IdentitiesOnly yes\n`;
fs.appendFileSync(sshConfig, hostEntry);
} else {
// No mappings, just use ssh-add
child_process.execSync('ssh-add -', { input: key.trim() + "\n" });
}
}); });
console.log("Keys added:"); console.log("Keys added:");
child_process.execSync('ssh-add -l', { stdio: 'inherit' }); if (repoMappings) {
repoMappings.forEach(function(key) {
console.log(`~/.ssh/${key.pseudoHost}.key`);
});
} else {
child_process.execSync('ssh-add -l', { stdio: 'inherit' });
}
} catch (error) { } catch (error) {
core.setFailed(error.message); core.setFailed(error.message);

View file

@ -1,6 +1,7 @@
const { execSync } = require('child_process') const { execSync } = require('child_process')
const path = require('path') const path = require('path')
const fs = require('fs') const fs = require('fs')
const process = require('process')
const buildDir = path.join(process.cwd(), 'build') const buildDir = path.join(process.cwd(), 'build')
const distDir = path.join(process.cwd(), 'dist') const distDir = path.join(process.cwd(), 'dist')
@ -9,13 +10,18 @@ const buildIndexJs = path.join(buildDir, 'index.js')
const distIndexJs = path.join(distDir, 'index.js') const distIndexJs = path.join(distDir, 'index.js')
const distCleanupJs = path.join(distDir, 'cleanup.js') const distCleanupJs = path.join(distDir, 'cleanup.js')
var ncc = `./node_modules/.bin/ncc`;
if (process.platform === "win32") {
ncc = `.\\node_modules\\.bin\\ncc.cmd`;
}
if (!fs.existsSync(buildDir)) { if (!fs.existsSync(buildDir)) {
fs.mkdirSync(buildDir) fs.mkdirSync(buildDir)
} }
// Build the main index.js file // Build the main index.js file
console.log('Building index.js...') console.log('Building index.js...')
execSync(`./node_modules/.bin/ncc build index.js -q -o ${buildDir}`) execSync(`${ncc} build index.js -q -o ${buildDir}`)
if (fs.existsSync(distIndexJs)) { if (fs.existsSync(distIndexJs)) {
fs.unlinkSync(distIndexJs) fs.unlinkSync(distIndexJs)
} }
@ -23,7 +29,7 @@ fs.renameSync(buildIndexJs, distIndexJs)
// Build the cleanup.js file // Build the cleanup.js file
console.log('Building cleanup.js...') console.log('Building cleanup.js...')
execSync(`./node_modules/.bin/ncc build cleanup.js -q -o ${buildDir}`) execSync(`${ncc} build cleanup.js -q -o ${buildDir}`)
if (fs.existsSync(distCleanupJs)) { if (fs.existsSync(distCleanupJs)) {
fs.unlinkSync(distCleanupJs) fs.unlinkSync(distCleanupJs)
} }