Merge pull request #517 from resin-io/ssh-proxy

resin ssh proxy support
This commit is contained in:
Tim Perry 2017-05-22 13:04:49 +02:00 committed by GitHub
commit 95fc5d7785
14 changed files with 147 additions and 89 deletions

View File

@ -10,10 +10,12 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Highlight cache usage in a local build
- Show a progress bar for upload progress
- Add ability to specify build-time variables for local builds
- Added the proxy support for the `resin ssh` command
### Fixed
- Fixed the not enough unicorns bug in resin build
- Removed the install-time warning for the `valid-email` package
## [5.9.1] - 2017-05-01

View File

@ -14,6 +14,7 @@ Requisites
- [Git](https://git-scm.com)
- The following executables should be correctly installed in your shell environment:
- `ssh`: Any recent version of the OpenSSH ssh client (required by `resin sync` and `resin ssh`)
- if you need `ssh` to work behind the proxy you also need [`proxytunnel`](http://proxytunnel.sourceforge.net/) installed (available as `proxytunnel` package for Ubuntu, for example)
- `rsync`: >= 2.6.9 (required by `resin sync`)
##### Windows Support
@ -26,6 +27,7 @@ If you still want to use `cmd.exe` you will have to use a package manager like M
2. Install the `msys-rsync` and `msys-openssh` packages.
3. Add MinGW to the `%PATH%` if this hasn't been done by the installer already. The location where the binaries are places is usually `C:\MinGW\msys\1.0\bin`, but it can vary if you selected a different location in the installer.
4. Copy your SSH keys to `%homedrive%%homepath\.ssh`.
5. If you need `ssh` to work behind the proxy you also need to install [proxytunnel](http://proxytunnel.sourceforge.net/)
Getting Started
---------------

View File

@ -94,18 +94,4 @@ exports.pipeContainerStream = Promise.method(function(arg) {
});
});
exports.getSubShellCommand = function(command) {
var os;
os = require('os');
if (os.platform() === 'win32') {
return {
program: 'cmd.exe',
args: ['/s', '/c', command]
};
} else {
return {
program: '/bin/sh',
args: ['-c', command]
};
}
};
exports.getSubShellCommand = require('../../utils/helpers');

View File

@ -15,24 +15,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var getSubShellCommand;
getSubShellCommand = function(command) {
var os;
os = require('os');
if (os.platform() === 'win32') {
return {
program: 'cmd.exe',
args: ['/s', '/c', command]
};
} else {
return {
program: '/bin/sh',
args: ['-c', command]
};
}
};
module.exports = {
signature: 'ssh [uuid]',
description: '(beta) get a shell into the running app container of a device',
@ -50,18 +32,54 @@ module.exports = {
boolean: true,
description: 'increase verbosity',
alias: 'v'
}, {
signature: 'noproxy',
boolean: true,
description: "don't use the proxy configuration for this connection. Only makes sense if you've configured proxy globally."
}
],
action: function(params, options, done) {
var Promise, child_process, patterns, resin, verbose;
var Promise, _, bash, child_process, getSshProxyCommand, getSubShellCommand, hasbin, patterns, proxyConfig, resin, useProxy, verbose;
child_process = require('child_process');
Promise = require('bluebird');
resin = require('resin-sdk-preconfigured');
_ = require('lodash');
bash = require('bash');
hasbin = require('hasbin');
getSubShellCommand = require('../utils/helpers').getSubShellCommand;
patterns = require('../utils/patterns');
if (options.port == null) {
options.port = 22;
}
verbose = options.verbose ? '-vvv' : '';
proxyConfig = global.PROXY_CONFIG;
useProxy = !!proxyConfig && !options.noproxy;
getSshProxyCommand = function(hasTunnelBin) {
var i, proxyAuth, proxyCommand, tunnelOptions;
if (!useProxy) {
return '';
}
if (!hasTunnelBin) {
console.warn('Proxy is enabled but the `proxytunnel` binary cannot be found.\nPlease install it if you want to route the `resin ssh` requests through the proxy.\nAlternatively you can pass `--noproxy` param to the `resin ssh` command to ignore the proxy config\nfor the `ssh` requests.\n\nAttemmpting the unproxied request for now.');
return '';
}
tunnelOptions = {
proxy: proxyConfig.host + ":" + proxyConfig.port,
dest: '%h:%p'
};
proxyAuth = proxyConfig.proxyAuth;
if (proxyAuth) {
i = proxyAuth.indexOf(':');
_.assign(tunnelOptions, {
user: proxyAuth.substring(0, i),
pass: proxyAuth.substring(i + 1)
});
}
proxyCommand = "proxytunnel " + (bash.args(tunnelOptions, '--', '='));
return "-o " + (bash.args({
ProxyCommand: proxyCommand
}, '', '='));
};
return Promise["try"](function() {
if (!params.uuid) {
return false;
@ -73,7 +91,7 @@ module.exports = {
}
return patterns.inferOrSelectDevice();
}).then(function(uuid) {
console.info("Connecting with: " + uuid);
console.info("Connecting to: " + uuid);
return resin.models.device.get(uuid);
}).then(function(device) {
if (!device.is_online) {
@ -83,16 +101,18 @@ module.exports = {
username: resin.auth.whoami(),
uuid: device.uuid,
containerId: resin.models.device.getApplicationInfo(device.uuid).get('containerId'),
proxyUrl: resin.settings.get('proxyUrl')
proxyUrl: resin.settings.get('proxyUrl'),
hasTunnelBin: useProxy ? hasbin('proxytunnel') : null
}).then(function(arg) {
var containerId, proxyUrl, username, uuid;
username = arg.username, uuid = arg.uuid, containerId = arg.containerId, proxyUrl = arg.proxyUrl;
var containerId, hasTunnelBin, proxyUrl, username, uuid;
username = arg.username, uuid = arg.uuid, containerId = arg.containerId, proxyUrl = arg.proxyUrl, hasTunnelBin = arg.hasTunnelBin;
if (containerId == null) {
throw new Error('Did not find running application container');
}
return Promise["try"](function() {
var command, subShellCommand;
command = "ssh " + verbose + " -t -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ControlMaster=no -p " + options.port + " " + username + "@ssh." + proxyUrl + " enter " + uuid + " " + containerId;
var command, sshProxyCommand, subShellCommand;
sshProxyCommand = getSshProxyCommand(hasTunnelBin);
command = "ssh " + verbose + " -t -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ControlMaster=no " + sshProxyCommand + " -p " + options.port + " " + username + "@ssh." + proxyUrl + " enter " + uuid + " " + containerId;
subShellCommand = getSubShellCommand(command);
return child_process.spawn(subShellCommand.program, subShellCommand.args, {
stdio: 'inherit'

View File

@ -47,6 +47,8 @@ try {
globalTunnel.initialize(proxy);
global.PROXY_CONFIG = globalTunnel.proxyConfig;
_ = require('lodash');
Promise = require('bluebird');

View File

@ -110,3 +110,19 @@ exports.getAppInfo = function(application) {
return app;
});
};
exports.getSubShellCommand = function(command) {
var os;
os = require('os');
if (os.platform() === 'win32') {
return {
program: 'cmd.exe',
args: ['/s', '/c', command]
};
} else {
return {
program: '/bin/sh',
args: ['-c', command]
};
}
};

View File

@ -17,7 +17,7 @@ limitations under the License.
*/
var validEmail;
validEmail = require('valid-email');
validEmail = require('@resin.io/valid-email');
exports.validateEmail = function(input) {
if (!validEmail(input)) {

View File

@ -786,6 +786,10 @@ ssh gateway port
increase verbosity
#### --noproxy
don't use the proxy configuration for this connection. Only makes sense if you've configured proxy globally.
# Notes
## note <|note>

View File

@ -58,21 +58,4 @@ exports.pipeContainerStream = Promise.method ({ deviceIp, name, outStream, follo
return console.log(chalk.red.bold("Container '#{name}' not found."))
throw err
# A function to reliably execute a command
# in all supported operating systems, including
# different Windows environments like `cmd.exe`
# and `Cygwin` should be encapsulated in a
# re-usable package.
exports.getSubShellCommand = (command) ->
os = require('os')
if os.platform() is 'win32'
return {
program: 'cmd.exe'
args: [ '/s', '/c', command ]
}
else
return {
program: '/bin/sh'
args: [ '-c', command ]
}
exports.getSubShellCommand = require('../../utils/helpers')

View File

@ -14,27 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
###
# TODO: A function to reliably execute a command
# in all supported operating systems, including
# different Windows environments like `cmd.exe`
# and `Cygwin` should be encapsulated in a
# re-usable package.
# This is literally copy-pasted from the `resin-sync`
# module.
getSubShellCommand = (command) ->
os = require('os')
if os.platform() is 'win32'
return {
program: 'cmd.exe'
args: [ '/s', '/c', command ]
}
else
return {
program: '/bin/sh'
args: [ '-c', command ]
}
module.exports =
signature: 'ssh [uuid]'
description: '(beta) get a shell into the running app container of a device'
@ -65,18 +44,55 @@ module.exports =
boolean: true
description: 'increase verbosity'
alias: 'v'
,
signature: 'noproxy'
boolean: true
description: "don't use the proxy configuration for this connection.
Only makes sense if you've configured proxy globally."
]
action: (params, options, done) ->
child_process = require('child_process')
Promise = require 'bluebird'
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
_ = require('lodash')
bash = require('bash')
hasbin = require('hasbin')
{ getSubShellCommand } = require('../utils/helpers')
patterns = require('../utils/patterns')
if not options.port?
options.port = 22
options.port ?= 22
verbose = if options.verbose then '-vvv' else ''
proxyConfig = global.PROXY_CONFIG
useProxy = !!proxyConfig and not options.noproxy
getSshProxyCommand = (hasTunnelBin) ->
return '' if not useProxy
if not hasTunnelBin
console.warn('''
Proxy is enabled but the `proxytunnel` binary cannot be found.
Please install it if you want to route the `resin ssh` requests through the proxy.
Alternatively you can pass `--noproxy` param to the `resin ssh` command to ignore the proxy config
for the `ssh` requests.
Attemmpting the unproxied request for now.
''')
return ''
tunnelOptions =
proxy: "#{proxyConfig.host}:#{proxyConfig.port}"
dest: '%h:%p'
{ proxyAuth } = proxyConfig
if proxyAuth
i = proxyAuth.indexOf(':')
_.assign tunnelOptions,
user: proxyAuth.substring(0, i)
pass: proxyAuth.substring(i + 1)
proxyCommand = "proxytunnel #{bash.args(tunnelOptions, '--', '=')}"
return "-o #{bash.args({ ProxyCommand: proxyCommand }, '', '=')}"
Promise.try ->
return false if not params.uuid
return resin.models.device.has(params.uuid)
@ -84,7 +100,7 @@ module.exports =
return params.uuid if uuidExists
return patterns.inferOrSelectDevice()
.then (uuid) ->
console.info("Connecting with: #{uuid}")
console.info("Connecting to: #{uuid}")
resin.models.device.get(uuid)
.then (device) ->
throw new Error('Device is not online') if not device.is_online
@ -95,14 +111,18 @@ module.exports =
# get full uuid
containerId: resin.models.device.getApplicationInfo(device.uuid).get('containerId')
proxyUrl: resin.settings.get('proxyUrl')
.then ({ username, uuid, containerId, proxyUrl }) ->
hasTunnelBin: if useProxy then hasbin('proxytunnel') else null
.then ({ username, uuid, containerId, proxyUrl, hasTunnelBin }) ->
throw new Error('Did not find running application container') if not containerId?
Promise.try ->
sshProxyCommand = getSshProxyCommand(hasTunnelBin)
command = "ssh #{verbose} -t \
-o LogLevel=ERROR \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o ControlMaster=no \
#{sshProxyCommand} \
-p #{options.port} #{username}@ssh.#{proxyUrl} enter #{uuid} #{containerId}"
subShellCommand = getSubShellCommand(command)

View File

@ -38,6 +38,9 @@ catch
# If that is not set as well the initialize will do nothing
globalTunnel.initialize(proxy)
# TODO: make this a feature of capitano https://github.com/resin-io/capitano/issues/48
global.PROXY_CONFIG = globalTunnel.proxyConfig
_ = require('lodash')
Promise = require('bluebird')
capitano = require('capitano')

View File

@ -105,3 +105,21 @@ exports.getAppInfo = (application) ->
app.arch = config.arch
return app
)
# A function to reliably execute a command
# in all supported operating systems, including
# different Windows environments like `cmd.exe`
# and `Cygwin`.
exports.getSubShellCommand = (command) ->
os = require('os')
if os.platform() is 'win32'
return {
program: 'cmd.exe'
args: [ '/s', '/c', command ]
}
else
return {
program: '/bin/sh'
args: [ '-c', command ]
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
###
validEmail = require('valid-email')
validEmail = require('@resin.io/valid-email')
exports.validateEmail = (input) ->
if not validEmail(input)

View File

@ -33,10 +33,12 @@
"gulp-shell": "^0.5.2"
},
"dependencies": {
"@resin.io/valid-email": "^0.1.0",
"ansi-escapes": "^2.0.0",
"any-promise": "^1.3.0",
"babel-preset-es2015": "^6.16.0",
"babel-register": "^6.16.3",
"bash": "0.0.1",
"bluebird": "^3.3.3",
"capitano": "^1.7.0",
"chalk": "^1.1.3",
@ -47,7 +49,8 @@
"dockerode": "^2.4.2",
"drivelist": "^5.0.16",
"etcher-image-write": "^9.0.3",
"global-tunnel-ng": "^2.0.0",
"global-tunnel-ng": "^2.1.0",
"hasbin": "^1.2.3",
"inquirer": "^3.0.6",
"is-root": "^1.0.0",
"js-yaml": "^3.7.0",
@ -85,8 +88,7 @@
"umount": "^1.1.5",
"underscore.string": "^3.1.1",
"unzip2": "^0.2.5",
"update-notifier": "^0.6.1",
"valid-email": "^0.0.2"
"update-notifier": "^0.6.1"
},
"optionalDependencies": {
"removedrive": "^1.0.0"