Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
824c6f1364 | ||
|
|
2386f9980f | ||
|
|
ee3e96e70a | ||
|
|
cbd3dbb31d | ||
|
|
a2cfb8a8f3 | ||
|
|
8b38cd56a1 | ||
|
|
6d672bd70f | ||
|
|
c16ca4bad7 | ||
|
|
158101ca4d | ||
|
|
ac5ec3b3d2 | ||
|
|
6bab396b51 | ||
|
|
0165c9bc33 | ||
|
|
c39c55721f |
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
@@ -13,17 +13,14 @@ jobs:
|
||||
name: ${{ matrix.friendlyName }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
# Needed until macos-11.0 hosted runners are available
|
||||
SDKROOT: '/Library/Developer/CommandLineTools/SDKs/MacOSX11.1.sdk'
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node: [14.15.4]
|
||||
os: [macos-10.14, windows-latest, ubuntu-18.04]
|
||||
os: [macos-latest, windows-latest, ubuntu-18.04]
|
||||
include:
|
||||
- os: macos-10.14
|
||||
- os: macos-latest
|
||||
friendlyName: macOS
|
||||
- os: windows-latest
|
||||
friendlyName: Windows
|
||||
@@ -48,7 +45,9 @@ jobs:
|
||||
name: Install Windows arm64 node.lib
|
||||
|
||||
- name: Install and build
|
||||
run: yarn
|
||||
run: |
|
||||
yarn install
|
||||
yarn build
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
- name: Test
|
||||
|
||||
14
README.md
14
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
A cross-platform no-dependency C executable trampoline which lets GitHub Desktop
|
||||
intercede in order to provide Git with any additional info it needs (like
|
||||
credentials through `GIT_ASKPASS`).
|
||||
credentials through `GIT_ASKPASS` or `SSH_ASKPASS`).
|
||||
|
||||
The intention is to support the same platforms that
|
||||
[Electron supports](https://www.electronjs.org/docs/tutorial/support#supported-platforms).
|
||||
@@ -125,3 +125,15 @@ Thanks to this, with only one generic trampoline that forwards everything via
|
||||
that TCP socket, the implementation for every possible protocol like
|
||||
`GIT_ASKPASS` can live within the GitHub Desktop codebase instead of having
|
||||
multiple trampoline executables.
|
||||
|
||||
## SSH Wrapper
|
||||
|
||||
Along with the trampoline, an SSH wrapper is provided for macOS. The reason for
|
||||
this is macOS before Monterey include an "old" version of OpenSSH that will
|
||||
ignore the `SSH_ASKPASS` variable unless it's unable to write to a tty.
|
||||
|
||||
This SSH wrapper achieves exactly that: just runs whatever `ssh` exists in the
|
||||
path in a way that will use `SSH_ASKPASS` when necessary.
|
||||
|
||||
More recent versions of OpenSSH (starting with 8.3) don't require this wrapper,
|
||||
since they added support for a new `SSH_ASKPASS_REQUIRE` environment variable.
|
||||
|
||||
48
binding.gyp
48
binding.gyp
@@ -51,5 +51,53 @@
|
||||
}]
|
||||
]
|
||||
},
|
||||
{
|
||||
'target_name': 'ssh-wrapper',
|
||||
'defines': [
|
||||
"NAPI_VERSION=<(napi_build_version)",
|
||||
],
|
||||
'type': 'executable',
|
||||
'sources': [
|
||||
'src/ssh-wrapper.c'
|
||||
],
|
||||
'include_dirs': [
|
||||
'<!(node -p "require(\'node-addon-api\').include_dir")',
|
||||
'include'
|
||||
],
|
||||
'xcode_settings': {
|
||||
'OTHER_CFLAGS': [
|
||||
'-Wall',
|
||||
'-Werror',
|
||||
'-Werror=format-security',
|
||||
'-fPIC',
|
||||
'-D_FORTIFY_SOURCE=1',
|
||||
'-fstack-protector-strong'
|
||||
]
|
||||
},
|
||||
'cflags!': [
|
||||
'-Wall',
|
||||
'-Werror',
|
||||
'-fPIC',
|
||||
'-pie',
|
||||
'-D_FORTIFY_SOURCE=1',
|
||||
'-fstack-protector-strong',
|
||||
'-Werror=format-security',
|
||||
'-fno-exceptions'
|
||||
],
|
||||
'cflags_cc!': [ '-fno-exceptions' ],
|
||||
'ldflags!': [
|
||||
'-z relro',
|
||||
'-z now'
|
||||
],
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': { 'ExceptionHandling': 1 },
|
||||
},
|
||||
'conditions': [
|
||||
# For now only build it for macOS, since it's not needed on Windows
|
||||
['OS=="win"', {
|
||||
'defines': [ 'WINDOWS' ],
|
||||
}]
|
||||
]
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
3
index.d.ts
vendored
3
index.d.ts
vendored
@@ -1,2 +1,5 @@
|
||||
export function getDesktopTrampolinePath(): string
|
||||
export function getDesktopTrampolineFilename(): string
|
||||
|
||||
export function getSSHWrapperPath(): string
|
||||
export function getSSHWrapperFilename(): string
|
||||
|
||||
10
index.js
10
index.js
@@ -15,7 +15,17 @@ function getDesktopTrampolineFilename() {
|
||||
: 'desktop-trampoline'
|
||||
}
|
||||
|
||||
function getSSHWrapperPath() {
|
||||
return Path.join(__dirname, 'build', 'Release', getSSHWrapperFilename())
|
||||
}
|
||||
|
||||
function getSSHWrapperFilename() {
|
||||
return process.platform === 'win32' ? 'ssh-wrapper.exe' : 'ssh-wrapper'
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getDesktopTrampolinePath,
|
||||
getDesktopTrampolineFilename,
|
||||
getSSHWrapperPath,
|
||||
getSSHWrapperFilename,
|
||||
}
|
||||
|
||||
22
package.json
22
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "desktop-trampoline",
|
||||
"version": "0.9.7",
|
||||
"version": "0.9.8",
|
||||
"main": "index.js",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@@ -15,9 +15,9 @@
|
||||
"test": "jest",
|
||||
"lint": "prettier -c **/*.js **/*.md",
|
||||
"lint:fix": "prettier --write **/*.js **/*.md",
|
||||
"prebuild-napi-x64": "prebuild -t 3 -r napi -a x64 --strip --include-regex \"desktop-trampoline(\\.exe)?$\"",
|
||||
"prebuild-napi-ia32": "prebuild -t 3 -r napi -a ia32 --strip --include-regex \"desktop-trampoline(\\.exe)?$\"",
|
||||
"prebuild-napi-arm64": "prebuild -t 3 -r napi -a arm64 --strip --include-regex \"desktop-trampoline(\\.exe)?$\"",
|
||||
"prebuild-napi-x64": "prebuild -t 3 -r napi -a x64 --strip --include-regex \"(desktop-trampoline|ssh-wrapper)(\\.exe)?$\"",
|
||||
"prebuild-napi-ia32": "prebuild -t 3 -r napi -a ia32 --strip --include-regex \"(desktop-trampoline|ssh-wrapper)(\\.exe)?$\"",
|
||||
"prebuild-napi-arm64": "prebuild -t 3 -r napi -a arm64 --strip --include-regex \"(desktop-trampoline|ssh-wrapper)(\\.exe)?$\"",
|
||||
"prebuild-all": "yarn prebuild-napi-x64 && yarn prebuild-napi-ia32 && yarn prebuild-napi-arm64",
|
||||
"upload": "node ./script/upload.js"
|
||||
},
|
||||
@@ -30,15 +30,15 @@
|
||||
},
|
||||
"homepage": "https://github.com/desktop/desktop-trampoline#readme",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^3.1.0",
|
||||
"prebuild-install": "^6.0.0"
|
||||
"node-addon-api": "^4.3.0",
|
||||
"prebuild-install": "^7.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.4.2",
|
||||
"node-gyp": "^7.1.0",
|
||||
"prebuild": "^10.0.1",
|
||||
"prettier": "^2.1.2",
|
||||
"split2": "^3.2.2"
|
||||
"jest": "^27.5.0",
|
||||
"node-gyp": "^8.4.1",
|
||||
"prebuild": "^11.0.3",
|
||||
"prettier": "^2.5.1",
|
||||
"split2": "^4.1.0"
|
||||
},
|
||||
"binary": {
|
||||
"napi_versions": [
|
||||
|
||||
37
src/ssh-wrapper.c
Normal file
37
src/ssh-wrapper.c
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifdef WINDOWS
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
// Not needed on Windows, this will just create a dummy executable
|
||||
return -1;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/**
|
||||
* This is a wrapper for the ssh command. It is used to make sure ssh runs without
|
||||
* a tty on macOS, allowing GitHub Desktop to intercept different prompts from
|
||||
* ssh (e.g. passphrase, adding a host to the list of known hosts...).
|
||||
* This is not necessary on more recent versions of OpenSSH (starting with v8.3)
|
||||
* which include support for the SSH_ASKPASS_REQUIRE environment variable.
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
pid_t child = fork();
|
||||
|
||||
if (child < 0) {
|
||||
fprintf(stderr, "Failed to fork\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (child != 0) {
|
||||
// This is the parent process. Just exit.
|
||||
return 0;
|
||||
}
|
||||
|
||||
setsid();
|
||||
return execvp("ssh", argv);
|
||||
}
|
||||
|
||||
#endif
|
||||
42
test/ssh-wrapper.test.js
Normal file
42
test/ssh-wrapper.test.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const { stat, access } = require('fs').promises
|
||||
const { constants } = require('fs')
|
||||
const { execFile } = require('child_process')
|
||||
const { promisify } = require('util')
|
||||
const { getSSHWrapperPath } = require('../index')
|
||||
|
||||
const sshWrapperPath = getSSHWrapperPath()
|
||||
const run = promisify(execFile)
|
||||
|
||||
describe('ssh-wrapper', () => {
|
||||
it('exists and is a regular file', async () =>
|
||||
expect((await stat(sshWrapperPath)).isFile()).toBe(true))
|
||||
|
||||
// On Windows, the binary generated is just useless, so no point to test it.
|
||||
// Also, this won't be used on Linux (for now at least), so don't bother to
|
||||
// run the tests there.
|
||||
if (process.platform !== 'darwin') {
|
||||
return
|
||||
}
|
||||
|
||||
it('can be executed by current process', () =>
|
||||
access(sshWrapperPath, constants.X_OK))
|
||||
|
||||
it('attempts to use ssh-askpass program', async () => {
|
||||
// Try to connect to github.com with a non-existent known_hosts file to force
|
||||
// ssh to prompt the user and use askpass.
|
||||
const result = await run(
|
||||
sshWrapperPath,
|
||||
['-o', 'UserKnownHostsFile=/path/to/fake/known_hosts', 'git@github.com'],
|
||||
{
|
||||
env: {
|
||||
SSH_ASKPASS: '/path/to/fake/ssh-askpass',
|
||||
DISPLAY: '.',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(result.stderr).toMatch(
|
||||
/ssh_askpass: exec\(\/path\/to\/fake\/ssh-askpass\): No such file or directory/
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user