diff --git a/.travis.yml b/.travis.yml index f2cfa1c..0a9e73c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "8" + - "12" services: - docker diff --git a/README.md b/README.md index 752a9cb..63f2f95 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,24 @@ await sshConnection.forward({ }) ``` +#### Port forwarding and using a bastion/jump host with different user to jump host and end host + +```sh +$ ssh -L 9000:localhost:80 -J your-jump-user@your-jump-host.com example.com +``` + +```js +const sshConnection = new SSHConnection({ + endHost: 'example.com', + bastionHost: 'your-jump-host.com', + bastionUsername: 'your-jump-user' +}) +await sshConnection.forward({ + fromPort: 9000, + toPort: 80, +}) +``` + #### Executing a command on the remote server ```js @@ -94,6 +112,8 @@ Options are an object with following properties: * `endHost` (required): The host you want to end up on (connect to) * `endPort` (optional): Port number of the server. Needed in case the server runs on a custom port (defaults to `22`) * `bastionHost` (optional): You can specify a bastion host if you want +* `bastionUsername` (optional): You can specify the bastion username if you want, in case it is different from the user connecting to end host. +If not provided while bastionHost is specified, connection will fall back to the username. * `passphrase` (optional): You can specify the passphrase when you have an encrypted private key. If you don't specify the passphrase and you use an encrypted private key, you get prompted in the command line. * `noReadline` (optional): Don't prompt for private key passphrases using readline (eg if this is not run in an interactive session) diff --git a/src/Connection.ts b/src/Connection.ts index fc7bbb2..c903aad 100644 --- a/src/Connection.ts +++ b/src/Connection.ts @@ -29,6 +29,7 @@ interface Options { privateKey?: string | Buffer agentForward? : boolean bastionHost?: string + bastionUsername?: string passphrase?: string endPort?: number endHost: string @@ -151,7 +152,7 @@ class SSHConnection { const options = { host, port: this.options.endPort, - username: this.options.username, + username: (this.options.bastionHost && host === this.options.bastionHost && this.options.bastionUsername) ? this.options.bastionUsername : this.options.username, password: this.options.password, privateKey: this.options.privateKey } diff --git a/test/bastion-host/Dockerfile b/test/bastion-host/Dockerfile index a2dc778..a81d858 100644 --- a/test/bastion-host/Dockerfile +++ b/test/bastion-host/Dockerfile @@ -6,7 +6,7 @@ RUN apt-get -yq install openssh-server; \ mkdir /root/.ssh && chmod 700 /root/.ssh; \ touch /root/.ssh/authorized_keys -COPY ./server/sshd_config /etc/ssh/sshd_config +COPY ./bastion-host/sshd_config /etc/ssh/sshd_config COPY ./keys/authorized_keys /root/.ssh/authorized_keys diff --git a/test/bastion-host/DockerfileDiffUser b/test/bastion-host/DockerfileDiffUser new file mode 100644 index 0000000..6ea833e --- /dev/null +++ b/test/bastion-host/DockerfileDiffUser @@ -0,0 +1,20 @@ +FROM ubuntu:16.04 + +RUN apt-get update +RUN apt-get -yq install openssh-server; \ + mkdir -p /var/run/sshd; \ + useradd -r -m bastionUser; \ + cd /home/bastionUser; \ + mkdir ./.ssh && chmod 700 ./.ssh; \ + touch ./.ssh/authorized_keys + +COPY ./bastion-host/sshd_config_diffUser /etc/ssh/sshd_config + +COPY ./keys/authorized_keys /home/bastionUser/.ssh/authorized_keys + +RUN cd /home/bastionUser/.ssh; \ + chgrp -R bastionUser ./; \ + chown -R bastionUser ./ + +EXPOSE 22 23 +CMD /usr/sbin/sshd -D -p 22 -p 23 \ No newline at end of file diff --git a/test/bastion-host/sshd_config_diffUser b/test/bastion-host/sshd_config_diffUser new file mode 100644 index 0000000..3011a8c --- /dev/null +++ b/test/bastion-host/sshd_config_diffUser @@ -0,0 +1,86 @@ +# What ports, IPs and protocols we listen for +Port 22 +# Use these options to restrict which interfaces/protocols sshd will bind to +#ListenAddress :: +#ListenAddress 0.0.0.0 +Protocol 2 +# HostKeys for protocol version 2 +HostKey /etc/ssh/ssh_host_rsa_key +HostKey /etc/ssh/ssh_host_dsa_key +HostKey /etc/ssh/ssh_host_ecdsa_key +HostKey /etc/ssh/ssh_host_ed25519_key +#Privilege Separation is turned on for security +UsePrivilegeSeparation yes + +# Lifetime and size of ephemeral version 1 server key +KeyRegenerationInterval 3600 +ServerKeyBits 1024 + +# Logging +SyslogFacility AUTH +LogLevel INFO + +# Authentication: +LoginGraceTime 120 +PermitRootLogin no +StrictModes yes +AllowUsers bastionUser + +RSAAuthentication yes +PubkeyAuthentication yes +#AuthorizedKeysFile %h/.ssh/authorized_keys + +# Don't read the user's ~/.rhosts and ~/.shosts files +IgnoreRhosts yes +# For this to work you will also need host keys in /etc/ssh_known_hosts +RhostsRSAAuthentication no +# similar for protocol version 2 +HostbasedAuthentication no +# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication +#IgnoreUserKnownHosts yes + +# To enable empty passwords, change to yes (NOT RECOMMENDED) +PermitEmptyPasswords no + +# Change to yes to enable challenge-response passwords (beware issues with +# some PAM modules and threads) +ChallengeResponseAuthentication no + +# Change to no to disable tunnelled clear text passwords +PasswordAuthentication no + +# Kerberos options +#KerberosAuthentication no +#KerberosGetAFSToken no +#KerberosOrLocalPasswd yes +#KerberosTicketCleanup yes + +# GSSAPI options +#GSSAPIAuthentication no +#GSSAPICleanupCredentials yes + +X11Forwarding yes +X11DisplayOffset 10 +PrintMotd no +PrintLastLog yes +TCPKeepAlive yes +#UseLogin no + +#MaxStartups 10:30:60 +#Banner /etc/issue.net + +# Allow client to pass locale environment variables +AcceptEnv LANG LC_* + +Subsystem sftp /usr/lib/openssh/sftp-server + +# Set this to 'yes' to enable PAM authentication, account processing, +# and session processing. If this is enabled, PAM authentication will +# be allowed through the ChallengeResponseAuthentication and +# PasswordAuthentication. Depending on your PAM configuration, +# PAM authentication via ChallengeResponseAuthentication may bypass +# the setting of "PermitRootLogin without-password". +# If you just want the PAM account and session checks to run without +# PAM authentication, then enable this but set PasswordAuthentication +# and ChallengeResponseAuthentication to 'no'. +UsePAM yes diff --git a/test/client/Dockerfile b/test/client/Dockerfile index e6bbc0e..eeda5b6 100644 --- a/test/client/Dockerfile +++ b/test/client/Dockerfile @@ -10,14 +10,15 @@ RUN yarn build # adds the ssh keys COPY ./test/keys /root/.ssh/ +COPY ./test/keys /home/bastionUser/.ssh/ # add the non protected key as the standard key COPY ./test/keys/id_rsa_no_pass /root/.ssh/id_rsa +COPY ./test/keys/id_rsa_no_pass /home/bastionUser/.ssh/id_rsa COPY ./test/keys/id_rsa_no_pass.pub /root/.ssh/id_rsa.pub - +COPY ./test/keys/id_rsa_no_pass.pub /home/bastionUser/.ssh/id_rsa.pub # COPY ./dist ./dist COPY ./test ./test - -CMD ["yarn", "test"] +CMD ["yarn", "test"] \ No newline at end of file diff --git a/test/docker-compose.yml b/test/docker-compose.yml index 0d7afa2..e2eb4ac 100644 --- a/test/docker-compose.yml +++ b/test/docker-compose.yml @@ -21,6 +21,13 @@ services: build: context: ./ dockerfile: ./bastion-host/Dockerfile + + bastion-different-user: + ports: + - 22:22 + build: + context: ./ + dockerfile: ./bastion-host/DockerfileDiffUser tester: build: diff --git a/test/test.ts b/test/test.ts index d7617af..507afa5 100644 --- a/test/test.ts +++ b/test/test.ts @@ -61,5 +61,23 @@ describe('node-ssh-forward', async () => { }) await ssh.executeCommand('uptime') }) + it('with root as bastion user and default path to the private key', async () => { + const ssh = new SSHConnection({ + username: 'root', + endHost: 'server', + bastionHost: 'bastion', + bastionUsername: 'root' + }) + await ssh.executeCommand('uptime') + }) + it('with bastionUser as bastion user and default path to the private key', async () => { + const ssh = new SSHConnection({ + username: 'root', + endHost: 'server', + bastionHost: 'bastion-different-user', + bastionUsername: 'bastionUser' + }) + await ssh.executeCommand('uptime') + }) }) })