Paramiko: Port Forwarding Around A NAT Router - python

Configuration
LOCAL: A local machine that will create an ssh connection and issue commands on a REMOTE box.
PROXY: An EC-2 instance with ssh access to both LOCAL and REMOTE.
REMOTE: A remote machine sitting behind a NAT Router (inaccessible by LOCAL, but will open a connection to PROXY and allow LOCAL to tunnel to it).
Port Forwarding Steps (via command line)
Create an ssh connection from REMOTE to PROXY to forward ssh traffic on port 22 on the REMOTE machine to port 8000 on the PROXY server.
# Run from the REMOTE machine
ssh -N -R 0.0.0.0:8000:localhost:22 PROXY_USER#PROXY_HOSTNAME
Create an ssh tunnel from LOCAL to PROXY and forward ssh traffic from LOCAL:1234 to PROXY:8000 (which then forwards to REMOTE:22).
# Run from LOCAL machine
ssh -L 1234:localhost:8000 PROXY_USER#PROXY_HOSTNAME
Create the forwarded ssh connection from LOCAL to REMOTE (via PROXY).
# Run from LOCAL machine in a new terminal window
ssh -p 1234 REMOTE_USER#localhost
# I have now ssh'd to the REMOTE box and can run commands
Paramiko Research
I have looked at a handful of questions related to port forwarding using Paramiko, but they don't seem to address this specific situation.
My Question
How can I use Paramiko to run steps 2 and 3 above? I essentially would like to run:
import paramiko
# Create the tunnel connection
tunnel_cli = paramiko.SSHClient()
tunnel_cli.connect(PROXY_HOSTNAME, PROXY_PORT, PROXY_USER)
# Create the forwarded connection and issue commands from LOCAL on the REMOTE box
fwd_cli = paramiko.SSHClient()
fwd_cli.connect('localhost', LOCAL_PORT, REMOTE_USER)
fwd_cli.exec_command('pwd')

A detailed explanation of what Paramiko is doing "under the hood" can be found at #bitprohet's blog here.
Assuming the configuration above, the code I have working looks something like this:
from paramiko import SSHClient
# Set up the proxy (forwarding server) credentials
proxy_hostname = 'your.proxy.hostname'
proxy_username = 'proxy-username'
proxy_port = 22
# Instantiate a client and connect to the proxy server
proxy_client = SSHClient()
proxy_client.load_host_keys('~/.ssh/known_hosts/')
proxy_client.connect(
proxy_hostname,
port=proxy_port,
username=proxy_username,
key_filename='/path/to/your/private/key/'
)
# Get the client's transport and open a `direct-tcpip` channel passing
# the destination hostname:port and the local hostname:port
transport = proxy_client.get_transport()
dest_addr = ('0.0.0.0', 8000)
local_addr = ('127.0.0.1', 1234)
channel = transport.open_channel("direct-tcpip", dest_addr, local_addr)
# Create a NEW client and pass this channel to it as the `sock` (along with
# whatever credentials you need to auth into your REMOTE box
remote_client = SSHClient()
remote_client.load_host_keys(hosts_file)
remote_client.connect('localhost', port=1234, username='remote_username', sock=channel)
# `remote_client` should now be able to issue commands to the REMOTE box
remote_client.exec_command('pwd')

Is the point solely to bounce SSH commands off PROXY or do you need to forward other, non SSH ports too?
If you just need to SSH into the REMOTE box, Paramiko supports both SSH-level gatewaying (tells the PROXY sshd to open a connection to REMOTE and forward SSH traffic on LOCAL's behalf) and ProxyCommand support (forwards all SSH traffic through a local command, which could be anything capable of talking to the remote box).
Sounds like you want the former to me, since PROXY clearly already has an sshd running. If you check out a copy of Fabric and search around for 'gateway' you will find pointers to how Fabric uses Paramiko's gateway support (I don't have time to dig up the specific spots myself right now.)

Related

Create TCP Server accessible on remote networks

A project I am working on has an Android app as a front-end and a Python program that would be used as the back-end.
I want to send data from the Android app (primarily images) to the Python program, do some processing and send the result back to the Android app.
I have found numerous tutorials that suggest using the socket module in python to create the server side, but all tutorials show the server on local network only (For testing purposes I created the client side also in Python, but it would be converted to Java later on)
The server code:
from requests import get
import socket
public_ip = get('https://api.ipify.org').text
print('My public IP address is: {}'.format(public_ip))
# getting the hostname by socket.gethostname() method
hostname = socket.gethostname()
# getting the IP address using socket.gethostbyname() method
local_ip = socket.gethostbyname(hostname)
# printing the hostname and ip_address
print(f"Hostname: {hostname}")
print(f"IP Address: {local_ip}")
#
HOST = local_ip
PORT = 80 # Port to listen on (non-privileged ports are > 1023)
with socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0) as s:
s.bind((HOST, PORT))
s.listen()
conn, addr = s.accept()
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024).decode('utf-8')
if not data:
break
conn.sendall(data.encode('utf-8'))
The client code:
import socket
HOST = '…' # I modify it to the server's public IP address, as printed from the server code
PORT = 80 # The port used by the server
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
with socket.create_connection((HOST, PORT)) as s:
s.sendall(b'Hello, world')
data = s.recv(1024)
print('Received', repr(data))
Using the code above, if I try using any port other than 80 I get ConnectionRefusedError: [Errno 111] Connection refused. And for port 80, I get TimeoutError: [Errno 110] Connection timed out.
In both cases, I try to connect from a device on another network.
I tried to use the ping command in Windows CMD to check the connection to the server, and I get 'connection refused message'.
I understand that the Firewall is what probably blocks the connection, but I don't know how to bypass it. I added a new rule in the Inbound Rules section (as suggested on other websites) but for no avail… The results were the same.
How can I make the connection between remote devices on different networks?
Thanks in advance ☺
In order to connect to your server using a TCP socket connection, you need to make sure your server can listen on a port on a publically available IP address.
If the External IP address is assigned to your computer directly,
and if you run the server code on that computer, then the TCP port opened by the server code should be available on the internet.
However, IP addresses are often assigned to a modem/router in home networks,
instead of assigning them to any connected device directly.
To find out if your External IP address is assigned to the computer directly you can use tools that your OS support (eg. ipconfig on windows). If you can see the IP address returned by api.ipify.org, then it means your computer is connected directly. You can change your code to connect using publically exposed IP:
HOST = public_ip
If this is successful means your computer is assigned an external address directly. Which is highly unlikely.
There are several workarounds for this problem though:
1) Configure your router to forward port
Configure your router to forward all connections to it's external TCP port, to an internal host in your network which is assigned to your computer. Please find instructions how it is done for your router.
2) Setup a remote proxy
If you don't have permission to change your router settings you can set up a remote proxy listening on the TCP port. While there is a number of ways of doing this, very popular is to set up a remote SSH tunnel, for that you need to have a server with SSH access and an external IP. Run this command:
ssh -R 80:localhost:8080 root#your-ssh-server-host
You can also use a third-party service that exposes your private host on the internet like:
Ngrok (Commercial, with free plans)
Localtunnel (Open Source, can be self-hosted)

Connect an Unix host from Windows jump box (RDP) using Paramiko

Currently, a Unix host is connected using PuTTY which is inside the Windows Jump host. User logins to the windows jump box by providing username and password (Remote desktop connection). I referred to this question Nested SSH using Python Paramiko and tried to replace it with Jump box IP. Below is the piece of code
vm = paramiko.SSHClient()
vm.set_missing_host_key_policy(paramiko.AutoAddPolicy())
vm.connect('10.x.x.172', username='******SA', password='Jul#2021')
print('success')
I am getting the below error while the connection
WinError 10060] A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because the connected host has failed to respond
From this below code what are the dest_addr and local_addr, from where I fetch this address?
dest_addr = ('10.103.53.26', 22) #edited#
local_addr = ('192.168.115.103', 22) #edited#
vmchannel = vmtransport.open_channel("direct-tcpip", dest_addr, local_addr)
Please help.
Thanks
Krishna
If you are using Remote desktop connection to connect to the jump host, it's no surprise that you cannot connect with SSH.
You have to use a different API that your jump hosts supports, like PowerShell remoting or similar.
Or setup an SSH server on the jump host.
None of these is really a programming question, yet.

paramiko.ssh_exception.NoValidConnectionsError: [Errno None] Unable to connect to port 22 using AWS Elastic Beanstalk

This is my first post so hopefully it is appropriate and not redundant.
I have an application I have deployed on AWS Elastic Beanstalk using a Flask Dash API. I have a snippet with the API that needs to connect to my Raspberry Pi remotely (SSH) and parse out some file.
This code snippet works flawlessly on my local machine
I can easily putty/ssh into my RPi on port 22
On my 1st router I have port 22 opened tcp/udp
On my 2nd router I have a NAT Forwarding Virtual Server rule for port 22 to direct to my RPI's static IP address
import paramiko
import json
client = paramiko.client.SSHClient()
hostname='example'
port=22
username='pi'
password='masked'
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.load_system_host_keys()
print('loaded client')
client.connect(hostname, port, username, password)
sftp_client = client.open_sftp()
localFilePath='./some_file.json'
sftp_client.get('/home/pi/some_file.json', localFilePath)
sftp_client.close()
I get error paramiko.ssh_exception.NoValidConnectionsError: [Errno None] Unable to connect to port 22 on
I am assuming this is some sort of networking access issue on the AWS side or possibly my router. I can easily connect to my RPi from outside of my own network also. I tried to add some inbound/outbound rules on my EC2 load balancer, but even opening it up completely did not resolve the issue. I have raked through the webs and cannot find many answers, so I am hoping someone has suggestions which can also be beneficial to others.
Thanks!
EC2 Instance SG Rules
Xfinity has a firewall enabled that is outside the router settings. Had to log into xFi mobile app, go to More, and My Services. Disable firewall. No whitelisting option which is terrible.

Get command result from SSH/Telnet via another SSH jump server in Python

I am writing a Python code to SSH or Telnet remote hosts, execute some commands and get the result output. Here we have a jump server, so the code must be "connected" with this server and from it, Telnet or SSH the remote hosts.
All my approaches work fine within the jump server, for example I can get the output of commands inside it. The problem is when I try to remote connect to hosts from it.
import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('IP', 22, username="user", password="pass")
stdin, stdout, stderr = client.exec_command("command")
for line in stdout:
print('... ' + line.strip('\n'))
client.close()
Using the library jumpssh get same results, and I am not will able to Telnet hosts. I tried the follow approach, but get the error
Administratively prohibited.
from jumpssh import SSHSession
gateway_session = SSHSession('jumpserver','user', password='pass').open()
remote_session = gateway_session.get_remote_session('IP',password='pass')
print(gateway_session.get_cmd_output('command'))
In the last company i worked we had a license from an SSH client that supports Python scripts, and worked fine in a more "textual" treatment.
There is any way of accomplish same task natively in Python?
SSHSession is trying to open direct-tcpip port forwarding channel over the gateway_session.
"administratively prohibited" is OpenSSH sshd server message indicating that direct-tcpip port forwarding is disabled.
To enable port forwarding, set AllowTcpForwarding and DisableForwarding directives in sshd_config appropriately.
If you cannot enable the port forwarding on the server, you cannot use jumpssh library.
If you have a shell access to the server, you can use ProxyCommand-like approach instead.
See Paramiko: nest ssh session to another machine while preserving paramiko functionality (ProxyCommand).

python 3 paramiko ssh agent forward over jump host with remote command on third host

Moin!
Situation: connect to the destination.host over the jump.host and run a command on the destination.host, which connects in the background to the another.host (on this host my ssh key is needed).
Scheme: client --> jump.host --> destination.host --- remote_command with ssh key needed on the other host --> another.host
#!/usr/bin/python
import paramiko
jumpHost=paramiko.SSHClient()
sshKey = paramiko.RSAKey.from_private_key_file('path.to.key/file', password = 'the.passphrase')
jumpHost.set_missing_host_key_policy(paramiko.AutoAddPolicy())
jumpHost.connect('jump.hostname',username='foo', pkey = sshKey)
jumpHostTransport = jumpHost.get_transport()
dest_addr = ('destination.hostname', 22)
local_addr = ('jump.hostname', 22)
jumpHostChannel = jumpHostTransport.open_channel("direct-tcpip", dest_addr, local_addr)
destHost=paramiko.SSHClient()
destHost.set_missing_host_key_policy(paramiko.AutoAddPolicy())
destHost.connect('destination.hostname', username='foo', sock=jumpHostChannel, pkey=sshKey)
destHostAgentSession = destHost.get_transport().open_session()
paramiko.agent.AgentRequestHandler(destHostAgentSession)
stdin, stderr, stdout = destHost.exec_command("my.command.which.connects.to.another.host")
print(stdout.read())
print(stderr.read())
destHost.close()
jumpHost.close()
The above code works well, if run "local" commands on the destination.host - e.g. uname, whoami, hostname, ls and so on... But if i run a command, which connects in the background to another host where my ssh key is needed, the code raised in the error:
raise AuthenticationException("Unable to connect to SSH agent")
paramiko.ssh_exception.AuthenticationException: Unable to connect to SSH agent
If i connect via Putty at the same chain, it works well.
Can anyone give me a hint to resolve my problem?
Thx in advance.
Assumption: Your keys work across jump host and destination host.
Creating a local agent in that case will work. You could manually create it via shell first and test it via iPython.
eval `ssh-agent`; ssh-add <my-key-file-path>
Programmatically this can be done -
# Using shell=True is not a great idea because it is a security risk.
# Refer this post - https://security.openstack.org/guidelines/dg_avoid-shell-true.html
subprocess.check_output("eval `ssh-agent`; ssh-add <my-key-file-path>", shell=True)
I am trying to do something similar and came across this post, I will update if I find a better solution.
EDIT: I have posted the implementation over here - https://adikrishnan.in/2018/10/25/agent-forwarding-with-paramiko/

Categories