Reputation: 115
I need to forward to multiple ports which are sits behind a server
server1(22) -> Server2(mysql, 3360) = local 3360
-> Server3(http, 8080) = local 8080
-> Server4(oracle,1234) = local 1234
I can only access Server2,3, and 4 via server1.
I am using Python ssltunnel package https://pypi.org/project/sshtunnel/
In example1&2, I can only specify one remote&local bind address. Not sure how to connect multiple servers(2,3,4)
Example1
from sshtunnel import SSHTunnelForwarder
server = SSHTunnelForwarder(
'pahaz.urfuclub.ru',
ssh_username="pahaz",
ssh_password="secret",
remote_bind_address=('127.0.0.1', 8080)
)
server.start()
print(server.local_bind_port) # show assigned local port
# work with `SECRET SERVICE` through `server.local_bind_port`.
server.stop()
Example 2
import paramiko
import sshtunnel
with sshtunnel.open_tunnel(
(REMOTE_SERVER_IP, 443),
ssh_username="",
ssh_pkey="/var/ssh/rsa_key",
ssh_private_key_password="secret",
remote_bind_address=(PRIVATE_SERVER_IP, 22),
local_bind_address=('0.0.0.0', 10022)
) as tunnel:
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('127.0.0.1', 10022)
# do some operations with client session
client.close()
print('FINISH!')
I could use any other Python package that can do the job.
Upvotes: 3
Views: 3850
Reputation: 818
I am adding this solution for multiple hops on multiple ports:
I have this setup:
the goal is to access the database by calling my machine on port 33306. This isn't possible because only gateway2 is allowed to speak to the database. We cant access gateway2 because only gateway1 is allowed to speak to it.
the following is the corresponding ssh .config file:
Host gateway1
HostName gtw1_IP_address
User gtw1_user
IdentityFile "path_to_gtw1_ssh_key"
IdentitiesOnly True
Host gateway2
User gtw2_user
Hostname gtw2_IP_address
IdentityFile "path_to_gtw2_ssh_key"
IdentitiesOnly True
# mysql
LocalForward 127.0.0.1:33306 127.0.0.1:3306
ProxyCommand ssh -W %h:%p switch-cede
this is how I reproduce it in python:
from sqlalchemy import create_engine
import config
import pandas as pd
import sshtunnel
from paramiko import SSHClient
with sshtunnel.open_tunnel(
ssh_username='gtw1_user',
ssh_address_or_host=('gtw1_IP_address', 22),
remote_bind_addresses=[('gtw2_IP_address', 22), ('gtw2_IP_address', 33306)],
local_bind_addresses=[('0.0.0.0', 22), ('0.0.0.0', 33306)], #this line is optional
ssh_pkey=path_to_gtw1_ssh_key,
) as tunnel1: # tunnel1 is the tunnel between myMachine and gateway1 I believe
print(tunnel1.local_bind_ports)
with sshtunnel.open_tunnel(
ssh_address_or_host=('localhost', tunnel1.local_bind_ports[0]),
remote_bind_addresses=[('127.0.0.1', 22),('127.0.0.1', 3306)],
local_bind_addresses=[('0.0.0.0', 22), ('127.0.0.1', 33306)],
ssh_username='gtw2_user',
ssh_pkey=path_to_gtw2_ssh_key,
) as tunnel2: # tunnel2 is the tunnel between gtw1 and gtw2 I believe
print(tunnel2.local_bind_ports)
db = create_engine(
f"mysql+pymysql://{config.USER}:{config.PASSWORD}@{config.HOST}:{config.PORT}/{config.DATABASE}")
print(db)
query = "SELECT * FROM randomTable LIMIT 10;"
df = pd.read_sql(query, db)
print(df)
# note that config is a file holding the credentials to connect to the database
Upvotes: 1
Reputation: 3100
Both examples can be modified slightly to work the way you want.
There is the singular versions of bindings (local_bind_address
& remote_bind_address
) and the plural versions of bindings (local_bind_addresses
& remote_bind_addresses
.
The singular verisons expects a tuple
containing variables for the connections, while the plural versions expects a list
of one or more tuple(s)
.
Here is a modified version of your example 2:
import paramiko
import sshtunnel
tunnels = [("172.16.0.1", 80),
("172.16.0.2", 22)]
localPorts = [("127.0.0.1", 1180),
("127.0.0.1", 10022)]
with sshtunnel.open_tunnel(
(REMOTE_SERVER_IP, 22),
ssh_username="",
ssh_pkey="/var/ssh/rsa_key",
ssh_private_key_password="secret",
remote_bind_addresses=tunnels,
local_bind_addresses=localPorts
) as tunnel:
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('127.0.0.1', 10022)
# do some operations with client session
client.close()
If the lengths of the lists are the same length, then the IP-addresses / ports will correspond with each other.
In my example above, the following is happening:
Connection: 172.16.0.1 Port: 80, Is tunneled via: 127.0.0.1 Port: 1180
Connection: 172.16.0.2 Port: 22, Is tunneled via: 127.0.0.1 Port: 10022
Upvotes: 2