November 23, 2017
Easy ssh tunneling and port forwarding
Well configured ssh environment may save a lot of your time and is less error prone. Here we are going to cover the most common case where a client needs to access an application server which is behind a gateway server/bastion host. Later on we are going to build on this setup to add port forwarding from a port on localhost to a port open in the app server.
These techniques make life of people woking with DMZ network topologies a lot easier.
Also Port forwarding wraps application traffic into ssh encrypted tunnel, providing additional layer of security.
---------------------------------------------------------------------
|
+-----------+ | +------------+ +----------+
| LOCALHOST | | | BASTION | | APP |
| LINUX | <== SSH ======>| SERVER | <== local ==>| SERVER |
------------+ | +------------+ +----------+
|
VCN (only port 22 is open)
---------------------------------------------------------------------
In this case gateway and app server are built in Oracle Cloud Infrastructure.
Bastion server accepts only connections on port 22 and has a public IP address on which it accepts external connections. All connections are open between bastion and app servers.
Localhost is Oracle Linux, bastion and app ones are CentOS 7.
Key exchange for passwordless access.
Let’s create the config on stages so you will be able to see what brings each configuration change.
For ease this POC we are going to use the same key pair (no password) in all involved hosts.This is not recommended in a production environment.
So to generate the key pairs
[oracle@locahost ~]$ ssh-keygen -t rsa
This is going to create two files in .ssh directory under your home:
- id_rsa – private key
- id_rsa.pub – public key
Copy this key pair to bastion server in ~/.ssh/ .
[oracle@locahost ~]$ scp ~/.ssh/id_rsa* <user>@<bastionIP>:.ssh/
Copy the content of the public key and paste it in bastion and app server authorized_keys:
$ vi ~/.ssh/authorized_keys
ssh-rsa CCCCB3NzaC1yc2EAAAADQAABAAABAQChX5JzDnpWniTuHwMitKlszA/fI8b71AP6bXg7btBJUEcyeT
ddLmkOSJQGDCCNVRYGN1AFd2u3PuDkCJPudzajBYFdcvHI1o5Y3CU5VPcDLQhNKlnJRQWxtCp/G0
BdgWVwKGqC5G28vFjdOcUc5HIdr8OlQj63L/tC7uHXqiPiIs2YzrLZXjaUUcT oracle@localhost
Make sure the permissions are correct in all hosts:
$ chmod 700 ~/.ssh
$ chmod 600 ~/.ssh/*
At this stage you should be able to connect to any of the hosts without password:
$ ssh opc@129.146.100.100
Last login: Wed Nov 11:34:33 2017 from xxx-117 [opc@bastion ~]$
Use ssh config file to simplify connecting.
Now we do not want to enter every time username or to remember the IP of the server. This can be achieved by adding these key parameters under .ssh/config file.
[oracle@locahost ]$ vi .ssh/config
Host bastion
Hostname 129.146.100.100
User opc
IdentityFile ~/.ssh/id_rsa
Then:
[oracle@locahost ]$ ssh bastion
Last login: xxxxx 2017 from xxxxx
[opc@bastion ~]$
Good but we still need to remember the user and IP address of the cascade appserver as the bastion host is the only one that has access to the outside world.And we do not want to waste time by logging on to the gateway and then to the final host.
Add cascade host to your configuration.
[oracle@locahost ]$ vi .ssh/config
Host bastion
Hostname 129.146.100.100
User opc
IdentityFile ~/.ssh/id_rsa
Host appserver
Hostname 129.146.100.100
User opc
IdentityFile ~/.ssh/id_rsa
ProxyCommand ssh -i ~/.ssh/id_rsa bastion -W %h:%p %r
Here we have added another host to our config file (appserver) and pointed that a proxy command should be executed over connecting. Take this as a command to be executed before the actual/final one.
Now we can connect directly to the application server:
[oracle@locahost ~]$ ssh appserver
[opc@appserver ~]$
Port forwarding.
Let’s say we have a service running in app server on port 3872 and we would like to access it directly from our local machine. The issue is that only access point is bastion server and the only port open is 22 (ssh service).
------------------------------------------------------------------------
|
+------------+ | +-----------+ +-----------+
| LOCALHOST | | | BASTION | | APP |
| LINUX | <== SSH ======> | SERVER | <== local ==> | SERVER |
+------------+ | +-----------+ +-----------+
port 3872 | | port 3872 ^
| VCN (only port 22 is open) |
| |
+------------------------------------------------------+
-------------------------------------------------------------------------
A few optional steps so to guarantee the final success of this exercise.
Check if forwarding is enabled on the gateway host:
[opc@bastion ~]$ cat /proc/sys/net/ipv4/ip_forward
1
1 -means enabled. If it returns zero – modify it with root privs to 1.
On the application server if you have enabled (it is by default) firewall – add rule(s) to open the involved port(s):
[opc@appserver work]$ sudo firewall-cmd --permanent --add-port=3872/tcp success
[opc@appserver work]$ sudo firewall-cmd --reload success
Check if the port is actually open:
[opc@appserver work]$ netstat -an|grep 3872
tcp 0 0 0.0.0.0:3872 0.0.0.0:* LISTEN
At this stage we can add port forwarding to our configuration.
[oracle@locahost ~]$ vi ~/.ssh/config
.....
Host appserver_p3872
Hostname 10.0.2.2
User opc
IdentityFile ~/.ssh/id_rsa
ProxyCommand ssh -i ~/.ssh/id_rsa bastion -W %h:%p %r
LocalForward 3872 10.0.2.2:3872
Let’s start a port forwarding session:
[oracle@locahost ~]$ ssh appserver_p3872
Last login: Wed Nov 22 11:56:39 2017 from bastion.bastion.zvcn.oraclevcn.com
[opc@appserver ~]$
Now you could try in another session from localhost if the remote port is reachable. Remember we have mapped local port 3872 to the same one on the application server.
[oracle@locahost ~]$ ssh -v 127.0.0.1 -p 3872
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips 26 Jan 2017
debug1: Reading configuration data /home/oracle/.ssh/config
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 58: Applying options for *
debug1: Connecting to 127.0.0.1 [127.0.0.1] port 3872.
debug1: Connection established.
......
debug1: ssh_exchange_identification: Hello there, appserver.internal.zvcn.oraclevcn.com,
it's now Wed Nov 00 00:07:57 2017
ssh_exchange_identification: Connection closed by remote host
[oracle@locahost ~]$
We have now the port forwarding working. It is not mandatory source and target port to match, actually it may be impossible in some cases. If you’d like you could go further and to configure alias so to have a dedicated process taking care of the tunnel:
[oracle@locahost ~]$ vi ~/.bashrc
alias appserver_p3872='ssh -f -N appserver_p3872'
Refresh the user’s env:
[oracle@locahost ~]$ exec -l $SHELL
Start the tunnel:
[oracle@locahost ~]$ appserver_p3872
[oracle@locahost ~]$
PuTTY
Putty is may be the most popular GUI ssh client around. Here is how to configure the same port forwarding with it.
First of all you need to convert your private key using Putty Key Generator, select “Conversions” then “Import key”:
Import id_rsa file then save the private key as ppk file.
I have saved my private key file as internal.ppk.
Again we would like to access application server behind bastion.Start PuTTY which brings you the configuration dialog below.
Put some descriptive name in “Saved Sessions”, bastion in our case.And DNS name or IP address of the bastion server.
In “Connection”, “Auto-login username” put the user to be authorized in bastion host: opc.
Under “Connection”,“SSH”,“Auth” put the full path to your private key.
Under “Connection”,“SSH”,“Tunnels” add “Source port” and destination in format
:.Then “Add” the record.
Finally save the configuration by returning back at top in “Session”. Then press “Open” and you should get your port forwarding in place.
Under Windows you could test the connectivity with telnet:
C:\Users\>telnet 127.0.0.1 3872
Try it yourself!