From c35b159846eff947c899950d5a85a3c51be12636 Mon Sep 17 00:00:00 2001 From: Oleg Kurapov Date: Thu, 8 Nov 2018 21:26:18 +0100 Subject: [PATCH 1/2] Dockerfile slimming (#139) * Use node instead of yarn in Dockerfile. Closes #134 * Don't install SSHD in container --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2dafc6e..5beca9d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,5 +5,5 @@ RUN adduser -D -h /home/term -s /bin/sh term && \ echo "term:term" | chpasswd EXPOSE 3000 COPY . /app -RUN apk add --update build-base python openssh && yarn -CMD yarn start +RUN apk add --update build-base python openssh-client && yarn +CMD node bin From 999535875262218360bcb280cc44fbaf791fc7c2 Mon Sep 17 00:00:00 2001 From: Bertrand Roussel Date: Tue, 6 Nov 2018 14:12:38 -0800 Subject: [PATCH 2/2] Provide SSH key option to specify client private key This is to make it possible for admins to enable password-less auth at the expense of security. In that situation: Server <=> Wetty <=> Client - Link between Wetty and the SSH Server is secured by SSH. - Wetty knows a private key to connect to the SSH Server, using an SSH User. - Client only needs to know how to access Wetty to run commands on the SSH Host. - Client would not be able to retrieve the private key from Wetty to login directly to the SSH Server. This basically means that anything that reaches Wetty needs to be trusted as it would be able to execute commands on the remote SSH server, so admins should be really careful when using that feature. --- Dockerfile | 5 +++-- cli.mjs | 17 ++++++++++++++++- wetty.mjs | 26 +++++++++++++++----------- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5beca9d..0e92eb7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,8 +2,9 @@ FROM node:8-alpine MAINTAINER butlerx@notthe.cloud WORKDIR /app RUN adduser -D -h /home/term -s /bin/sh term && \ - echo "term:term" | chpasswd + ( echo "term:term" | chpasswd ) && \ + apk add --update build-base python openssh-client EXPOSE 3000 COPY . /app -RUN apk add --update build-base python openssh-client && yarn +RUN yarn CMD node bin diff --git a/cli.mjs b/cli.mjs index 7ec34f1..c3e8ea3 100644 --- a/cli.mjs +++ b/cli.mjs @@ -29,6 +29,10 @@ const opts = optimist demand : false, description: 'defaults to "password", you can use "publickey,password" instead', }, + sshkey: { + demand : false, + description: 'path to an optional client private key (connection will be password-less and insecure!)', + }, port: { demand : false, alias : 'p', @@ -51,6 +55,7 @@ const sshuser = opts.sshuser || process.env.SSHUSER || ''; const sshhost = opts.sshhost || process.env.SSHHOST || 'localhost'; const sshauth = opts.sshauth || process.env.SSHAUTH || 'password,keyboard-interactive'; const sshport = opts.sshport || process.env.SSHPORT || 22; +const sshkey = opts.sshkey || process.env.SSHKEY || ''; const port = opts.port || process.env.PORT || 3000; loadSSL(opts) @@ -62,11 +67,21 @@ loadSSL(opts) process.exit(1); }); +const sshkeyWarning = +`!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! Password-less auth enabled using private key from \'%s\'. +! This is dangerous, anything that reaches the wetty server +! will be able to run remote operations without authentication. +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`; +if(sshkey) { + console.warn(sshkeyWarning); +} + process.on('uncaughtException', err => { console.error(`Error: ${err}`); }); -const tty = wetty(port, sshuser, sshhost, sshport, sshauth, opts.ssl); +const tty = wetty(port, sshuser, sshhost, sshport, sshauth, sshkey, opts.ssl); tty.on('exit', code => { console.log(`exit with code: ${code}`); }); diff --git a/wetty.mjs b/wetty.mjs index b5181e1..2f66e29 100644 --- a/wetty.mjs +++ b/wetty.mjs @@ -36,33 +36,37 @@ function createServer(port, sslopts) { }); } -function getCommand(socket, sshuser, sshhost, sshport, sshauth) { +function getCommand(socket, sshuser, sshhost, sshport, sshauth, sshkey) { const { request } = socket; const match = request.headers.referer.match('.+/ssh/.+$'); const sshAddress = sshuser ? `${sshuser}@${sshhost}` : sshhost; const ssh = match ? `${match[0].split('/ssh/').pop()}@${sshhost}` : sshAddress; + const sshRemoteOptsBase = [ + path.join(__dirname, 'bin/ssh'), + ssh, + '-p', + sshport, + '-o', + `PreferredAuthentications=${sshauth}`, + ] + const sshRemoteOpts = sshkey ? sshRemoteOptsBase.concat(['-i', sshkey]) + : sshRemoteOptsBase return [ process.getuid() === 0 && sshhost === 'localhost' ? ['login', '-h', socket.client.conn.remoteAddress.split(':')[3]] - : [ - path.join(__dirname, 'bin/ssh'), - ssh, - '-p', - sshport, - '-o', - `PreferredAuthentications=${sshauth}`, - ], + : sshRemoteOpts + , ssh, ]; } -export default function start(port, sshuser, sshhost, sshport, sshauth, sslopts) { +export default function start(port, sshuser, sshhost, sshport, sshauth, sshkey, sslopts) { const events = new EventEmitter(); const io = server(createServer(port, sslopts), { path: '/wetty/socket.io' }); io.on('connection', socket => { console.log(`${new Date()} Connection accepted.`); - const [args, ssh] = getCommand(socket, sshuser, sshhost, sshport, sshauth); + const [args, ssh] = getCommand(socket, sshuser, sshhost, sshport, sshauth, sshkey); const term = spawn('/usr/bin/env', args, { name: 'xterm-256color', cols: 80,