Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
2a421d8
Debug deployment keys on Windows
mpdude Feb 26, 2021
0562472
Remove "IdentitiesOnly"
mpdude Feb 27, 2021
ab4471f
Remove steps that cause noise during debugging
mpdude Feb 27, 2021
bcd9c12
Print key file name
mpdude Feb 27, 2021
d7353c1
Write private key to file (does this work on Windows?)
mpdude Feb 27, 2021
c56b9c4
Encrypt key on disk
mpdude Feb 27, 2021
5e4ad4b
Explicitly disable IdentitiesOnly
mpdude Feb 27, 2021
231e859
Re-add key to agent after setting passphrase
mpdude Feb 27, 2021
253819f
Start another agent
mpdude Feb 27, 2021
3702096
Try writing keys to disk and encrypting them
mpdude Feb 27, 2021
1676d1f
Use an askpass wrapper
mpdude Feb 28, 2021
4d491fc
Fix ssh-add syntax (?)
mpdude Feb 28, 2021
9b7e80d
Use exec
mpdude Feb 28, 2021
7fc4d80
Debug output
mpdude Feb 28, 2021
ef63fdb
Fix syntax
mpdude Feb 28, 2021
9406a51
Remove stdio: 'inherit'
mpdude Feb 28, 2021
25b1b5d
Compile Hello World on Windows
mpdude Mar 1, 2021
c91aeeb
Fake askpass
mpdude Mar 1, 2021
18f5386
Use absolute path for askpass.exe
mpdude Mar 1, 2021
637f9c7
Fix execFileSync call
mpdude Mar 1, 2021
ccd95b9
Use execSync instead of execFileSync
mpdude Mar 1, 2021
f78cad1
Try to output ssh-add failure
mpdude Mar 1, 2021
1660674
More debugging
mpdude Mar 1, 2021
8addcca
Debug with ngrok/ssh
mpdude Mar 1, 2021
1606d19
Preserve process.env so the PATH (and possibly other) vars are availabe
mpdude Mar 1, 2021
93c9b23
Empty commit
mpdude Mar 1, 2021
2bde568
Trigger Actions
mpdude Mar 1, 2021
7f61bbc
Use different ssh-add command for Windows/!Windows
mpdude Mar 1, 2021
ab7e1e8
Trigger workflows
mpdude Mar 1, 2021
5f971b8
Add askpass.c source code
mpdude Mar 1, 2021
02a6899
Keep output
mpdude Mar 1, 2021
88bcf9a
Use IdentitiesOnly=no
mpdude Mar 1, 2021
7d6e731
Use SSH_AUTH_SOCK in following ssh-add invocations
mpdude Mar 1, 2021
f03f6e3
Debug SSH_AUTH_SOCK
mpdude Mar 1, 2021
2bcaae3
Avoid using a separate shell
mpdude Mar 1, 2021
cbf6c2b
Also try AddKeysToAgent=yes
mpdude Mar 1, 2021
e35dbcb
Work around another bug in OpenSSH on Windows
mpdude Mar 2, 2021
feedd60
Fix Windows file path to use backslashes
mpdude Mar 2, 2021
e0d767f
Make sure file creation works
mpdude Mar 2, 2021
8cdc631
Create /dev/tty on D: also
mpdude Mar 2, 2021
6451014
Print env
mpdude Mar 2, 2021
da67187
use printenv (powershell?)
mpdude Mar 2, 2021
7cabdfc
Print cwd
mpdude Mar 2, 2021
a4b2891
Set DISPLAY to circumvent read_passphrase (?)
mpdude Mar 2, 2021
c77dd5a
...
mpdude Mar 2, 2021
7667967
Show loaded keys
mpdude Mar 2, 2021
d3770df
Use IdentitiesOnly=yes always
mpdude Mar 2, 2021
10fed90
Test whether we're using the wrong ssh client
mpdude Mar 2, 2021
71155be
Poke at things with a stick
mpdude Mar 2, 2021
3715bc5
Use IdentitiesOnly=yes, because on Windows the wrong key was sent fir…
mpdude Mar 2, 2021
3fc2400
Debug
mpdude Mar 2, 2021
873b130
Run tmate directly
mpdude Mar 2, 2021
6458b79
Use another action for debugging
mpdude Mar 2, 2021
5a354bf
Run windows only
mpdude Mar 2, 2021
2d8d48e
Run only SSH
mpdude Mar 3, 2021
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
85 changes: 35 additions & 50 deletions .github/workflows/demo.yml
Original file line number Diff line number Diff line change
@@ -1,60 +1,45 @@
on: [push, pull_request]

jobs:
single_key_demo:
strategy:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Setup key
uses: ./
with:
ssh-private-key: |
${{ secrets.DEMO_KEY }}
${{ secrets.DEMO_KEY_2 }}

multiple_keys_demo:
deployment_keys_demo:
env:
GIT_SSH_COMMAND: ssh -v
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest]
os: [windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Setup key
uses: ./
# - name: Setup key
# uses: ./
# with:
# ssh-private-key: |
# ${{ secrets.MPDUDE_TEST_1_DEPLOY_KEY }}
# ${{ secrets.MPDUDE_TEST_2_DEPLOY_KEY }}
# - run: |
# cat ~/.ssh/config
# ssh-add -l
# C:/Windows/System32/OpenSSH/ssh.exe -v git@key-2 'echo octocat'
- name: Start SSH session
uses: luchihoratiu/debug-via-ssh@main
with:
ssh-private-key: ${{ secrets.DEMO_KEY }}
NGROK_AUTH_TOKEN: ${{ secrets.NGROK_AUTH_TOKEN }}
SSH_PASS: ${{ secrets.SSH_PASS }}

# git clone [email protected]:mpdude/test-2.git test-2-git
# ls -alh ~/.ssh
# 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

docker_demo:
runs-on: ubuntu-latest
container:
image: ubuntu:latest
steps:
- 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
# git clone ssh://[email protected]/mpdude/test-2.git test-2-git-ssh

# cat > ~/.ssh/5965bf89ab6e2900262e3f6802dfb4d65cb0de539d0fbb97d381e7130a4ba7e9 <<< "${{ secrets.MPDUDE_TEST_2_DEPLOY_KEY }}"
# ssh-keygen -p -f ~/.ssh/5965bf89ab6e2900262e3f6802dfb4d65cb0de539d0fbb97d381e7130a4ba7e9 -N secret-passphrase
# eval `ssh-agent`
# echo "secret-passphrase" | ssh-add ~/.ssh/5965bf89ab6e2900262e3f6802dfb4d65cb0de539d0fbb97d381e7130a4ba7e9
# ssh-add -L
# git clone [email protected]:mpdude/test-2.git test-2-git
# shell: bash
24 changes: 24 additions & 0 deletions askpass.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
ssh-add on Windows (probably part of the source at https://github.com/PowerShell/openssh-portable)
does not/can not read the passphrase from stdin.

However, when the DISPLAY env var is set and ssh-add is not run from a terminal (however it tests
that), it will run the executable pointed to by SSH_ASKPASS in a subprocess and read the passphrase
from that subprocess' stdout.

This program can be used as the SSH_ASKPASS implementation. It will return the passphrase set
in the SSH_PASS env variable.

To cross-compile from Ubuntu, I installed the `mingw-w64` package and ran
$ x86_64-w64-mingw32-gcc askpass.c -static -o askpass.exe
*/

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv)
{
printf("%s\n", getenv("SSH_PASS"));

return 0;
}
Binary file added askpass.exe
Binary file not shown.
82 changes: 58 additions & 24 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ 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);
const token = __webpack_require__(417).randomBytes(64).toString('hex');
const isWindows = (process.env['OS'] == 'Windows_NT');

try {
const privateKey = core.getInput('ssh-private-key');
Expand All @@ -132,10 +133,16 @@ try {

var home;

if (process.env['OS'] == 'Windows_NT') {
if (isWindows) {
console.log('Preparing ssh-agent service on Windows');
child_process.execSync('sc config ssh-agent start=demand', { stdio: 'inherit' });

// Work around https://github.com/PowerShell/openssh-portable/pull/447 by creating a \dev\tty file
/*fs.mkdirSync('c:\\dev');
fs.closeSync(fs.openSync('c:\\dev\\tty', 'a'));
fs.mkdirSync('d:\\dev');
fs.closeSync(fs.openSync('d:\\dev\\tty', 'a'));*/

home = os.homedir();
} else {
// Use getent() system call, since this is what ssh does; makes a difference in Docker-based
Expand Down Expand Up @@ -168,41 +175,68 @@ try {
}
}

console.log("Adding private key to agent");
console.log("Adding private keys to agent");
var keyNumber = 0;

privateKey.split(/(?=-----BEGIN)/).forEach(function(key) {
child_process.execSync('ssh-add -', { input: key.trim() + "\n" });
});
++keyNumber;
let keyFile = `${homeSsh}/key_${keyNumber}`;

console.log("Keys added:");
child_process.execSync('ssh-add -l', { stdio: 'inherit' });
// Write private key (unencrypted!) to file
console.log(`Write file ${keyFile}`);
fs.writeFileSync(keyFile, key.replace("\r\n", "\n").trim() + "\n", { mode: '600' });

child_process.execFileSync('ssh-add', ['-L']).toString().split(/\r?\n/).forEach(function(key) {
let parts = key.match(/\bgithub.com[:/](.*)(?:\.git)?\b/);
// Set private key passphrase
let output = '';
try {
console.log(`Set passphrase on ${keyFile}`);
output = child_process.execFileSync('ssh-keygen', ['-p', '-f', keyFile, '-N', token]);
} catch (exception) {
fs.unlinkSync(keyFile);

throw exception;
}

if (parts == null) {
return;
// Load key into agent
if (isWindows) {
child_process.execFileSync('ssh-add', [keyFile], { env: { ...process.env, ...{ 'DISPLAY': 'fake', 'SSH_PASS': token, 'SSH_ASKPASS': 'D:\\a\\ssh-agent\\ssh-agent\\askpass.exe' } } });
} else {
child_process.execFileSync('ssh-add', [keyFile], { env: process.env, input: token });
}

output.toString().split(/\r?\n/).forEach(function(key) {
let parts = key.match(/^Key has comment '.*\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+?)(?=\.git|\s|\')/);

let ownerAndRepo = parts[1];
let sha256 = crypto.createHash('sha256').update(key).digest('hex');
if (parts == null) {
return;
}

fs.writeFileSync(`${homeSsh}/${sha256}`, key + "\n", { mode: '600' });
let ownerAndRepo = parts[1];

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}"`);
child_process.execSync(`git config --global --replace-all url."git@key-${keyNumber}:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`);
child_process.execSync(`git config --global --add url."git@key-${keyNumber}:${ownerAndRepo}".insteadOf "[email protected]:${ownerAndRepo}"`);
child_process.execSync(`git config --global --add url."git@key-${keyNumber}:${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`;
// On Linux and OS X, IdentitiesOnly=no will send all keys from agent before the explicit key, so use "yes".
// On Windows, IdentitiesOnly=yes will ignore keys from the agent, but send explicit keys first; so use "no" (https://github.com/PowerShell/Win32-OpenSSH/issues/1550)
//let identitiesOnly = isWindows ? 'no' : 'yes';

let sshConfig = `\nHost key-${keyNumber}\n`
+ ` HostName github.com\n`
+ ` User git\n`
+ ` IdentitiesOnly yes\n`
+ ` AddKeysToAgent yes\n`
+ ` IdentityFile ${keyFile}\n`;

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

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

console.log("Keys added:");
child_process.execSync('ssh-add -l', { stdio: 'inherit' });

} catch (error) {
core.setFailed(error.message);
}
Expand Down
96 changes: 65 additions & 31 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ const core = require('@actions/core');
const child_process = require('child_process');
const fs = require('fs');
const os = require('os');
const crypto = require('crypto');
const token = require('crypto').randomBytes(64).toString('hex');
const isWindows = (process.env['OS'] == 'Windows_NT');

try {
const privateKey = core.getInput('ssh-private-key');
Expand All @@ -15,10 +16,16 @@ try {

var home;

if (process.env['OS'] == 'Windows_NT') {
if (isWindows) {
console.log('Preparing ssh-agent service on Windows');
child_process.execSync('sc config ssh-agent start=demand', { stdio: 'inherit' });

// Work around https://github.com/PowerShell/openssh-portable/pull/447 by creating a \dev\tty file
/*fs.mkdirSync('c:\\dev');
fs.closeSync(fs.openSync('c:\\dev\\tty', 'a'));
fs.mkdirSync('d:\\dev');
fs.closeSync(fs.openSync('d:\\dev\\tty', 'a'));*/

home = os.homedir();
} else {
// Use getent() system call, since this is what ssh does; makes a difference in Docker-based
Expand Down Expand Up @@ -51,41 +58,68 @@ try {
}
}

console.log("Adding private key to agent");
console.log("Adding private keys to agent");
var keyNumber = 0;

privateKey.split(/(?=-----BEGIN)/).forEach(function(key) {
child_process.execSync('ssh-add -', { input: key.trim() + "\n" });
++keyNumber;
let keyFile = `${homeSsh}/key_${keyNumber}`;

// Write private key (unencrypted!) to file
console.log(`Write file ${keyFile}`);
fs.writeFileSync(keyFile, key.replace("\r\n", "\n").trim() + "\n", { mode: '600' });

// Set private key passphrase
let output = '';
try {
console.log(`Set passphrase on ${keyFile}`);
output = child_process.execFileSync('ssh-keygen', ['-p', '-f', keyFile, '-N', token]);
} catch (exception) {
fs.unlinkSync(keyFile);

throw exception;
}

// Load key into agent
if (isWindows) {
child_process.execFileSync('ssh-add', [keyFile], { env: { ...process.env, ...{ 'DISPLAY': 'fake', 'SSH_PASS': token, 'SSH_ASKPASS': 'D:\\a\\ssh-agent\\ssh-agent\\askpass.exe' } } });
} else {
child_process.execFileSync('ssh-add', [keyFile], { env: process.env, input: token });
}

output.toString().split(/\r?\n/).forEach(function(key) {
let parts = key.match(/^Key has comment '.*\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+?)(?=\.git|\s|\')/);

if (parts == null) {
return;
}

let ownerAndRepo = parts[1];

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

// On Linux and OS X, IdentitiesOnly=no will send all keys from agent before the explicit key, so use "yes".
// On Windows, IdentitiesOnly=yes will ignore keys from the agent, but send explicit keys first; so use "no" (https://github.com/PowerShell/Win32-OpenSSH/issues/1550)
//let identitiesOnly = isWindows ? 'no' : 'yes';

let sshConfig = `\nHost key-${keyNumber}\n`
+ ` HostName github.com\n`
+ ` User git\n`
+ ` IdentitiesOnly yes\n`
+ ` AddKeysToAgent yes\n`
+ ` IdentityFile ${keyFile}\n`;

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

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

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);
}