SSH服务器按用户路由隧道

时间:2015-03-26 11:40:12

标签: python ssh sftp paramiko

我想要完成的图表:

$ sftp joe@gatewayserver.horse

SSHCLIENT_JOE --------> GATEWAY_SERVER (does logic by username to determine  
                             |         socket to forward the connection to.)
                             |
                             \     127.0.0.1:1030 CONTAINRR_SSHD_SALLY
                              ---> 127.0.0.1:1031 CONTAINER_SSHD_JOE
                                   127.0.0.1:1032 CONTAINER_SSHD_MRAYMOND

这似乎与我想要做的最接近: paramiko server mode port forwarding http://bitprophet.org/blog/2012/11/05/gateway-solutions/

但是我不想客户端执行ProxyCommand或请求“direct-tcpip”通道,而是希望服务器完成转发,对客户端来说是无形的。 我一直试图通过连接客户端的Transport对象并代表客户端创建direct-tcpip通道,使用paramiko服务器执行此操作,但我遇到了障碍。 我正在使用https://github.com/paramiko/paramiko/blob/master/demos/demo_server.py作为模板

# There's a ServerInterface class definition that overrides check_channel_request
# (allowing for direct-tcpip and session), and the other expected overides like 
# check_auth_password, etc that I'm leaving out for brevity.

try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('', 2200))
except Exception as e:
    print('*** Bind failed: ' + str(e))
    traceback.print_exc()
    sys.exit(1)
try:
    sock.listen(100)
    print('Listening for connection ...')
    client, addr = sock.accept()
except Exception as e:
    print('*** Listen/accept failed: ' + str(e))
    traceback.print_exc()
    sys.exit(1)

print('Got a connection!')
try:
    t = paramiko.Transport(client)
    t.add_server_key(host_key)
    server = Server()
    try:
        t.start_server(server=server)
    except paramiko.SSHException:
        print('*** SSH negotiation failed.')
        sys.exit(1)

   # Waiting for authentication.. returns an unwanted channel object since
   # it isn't the right "kind" of channel
    unwanted_chan = t.accept(20)
    dest_addr = ("127.0.0.1", 1030)       # target container port
    local_addr = ("127.0.0.1", 1234)      # Arbitrary port on gateway server

    # Trying to put words in the client's mouth here.. fails
    # What should I do?
    print(" Attempting creation of direct-tcpip channel on client Transport")
    tunnel_chan = t.open_channel("direct-tcpip", dest_addr, local_addr)
    print("tunnel_chan created.")
    tunnel_client = SSHClient()
    tunnel_client.load_host_keys(host_key)
    print("attempting connection using tunnel_chan")
    tunnel_client.connect("127.0.0.1", port=1234, sock=tunnel_channel)
    stdin, stdout, stderr = tunnel_client.exec_command('hostname')
    print(stdout.readlines())
except Exception as e:
print('*** Caught exception: ' + str(e.__class__) + ': ' + str(e))
traceback.print_exc()
try:
    t.close()
except:
    pass
sys.exit(1)

当前输出:

Read key: bc1112352a682284d04f559b5977fb00
Listening for connection ...
Got a connection!
Auth attempt with key: 5605063f1d81253cddadc77b2a7b0273
Attempting creation of direct-tcpip channel on client Transport
*** Caught exception: <class 'paramiko.ssh_exception.ChannelException'>: (1, 'Administratively prohibited')
Traceback (most recent call last):
File "./para_server.py", line 139, in <module>
tunnel_chan = t.open_channel("direct-tcpip", dest_addr, local_addr)
File "/usr/lib/python2.6/site-packages/paramiko/transport.py", line 740, in open_channel
raise e
ChannelException: (1, 'Administratively prohibited')

我们目前有一个直接的sftp服务器,客户端连接它们,并且它们被chroot到各自的ftp目录。 我们想要将客户端移动到lxc容器中但不想改变它们连接到sftp的方式..(因为它们可能正在使用像filezilla这样的gui ftp客户端。)我也不想创建桥接接口并分配所有容器都有新的ips。因此,容器没有与主机分开的ips,它们共享相同的网络空间。 客户端容器的sshds将绑定到localhost上的不同端口。这样他们就可以拥有独特的端口,并且选择哪个端口的逻辑可以在概念上移出......物理主机上的一个简单服务器。

这更像是一种概念验证,而且是我的一般好奇心。

2 个答案:

答案 0 :(得分:1)

Attempting creation of direct-tcpip channel on client Transport
*** Caught exception: <class '[...]'>: (1, 'Administratively prohibited')

容器ssh服务器拒绝你的direct-tcpip频道请求,因为它已被配置为拒绝这些请求。我收集这里的意图是将SFTP会话代理到正确的容器?我想在the usual fashion中配置了容器SSH服务器,只允许这些人做SFTP? SFTP会话通过会话通道,而不是直接tcpip通道。

我不是python编码器,也不能给你特定的paramiko代码,但你的中继代理应该打开一个到容器服务器的会话通道并调用“sftp”子系统。如果可能,您的中继代理只应在远程客户端请求SFTP会话时执行此操作,而不应用于其他类型的通道请求。

答案 1 :(得分:1)

正如我在上面的评论中所提到的,我对paramiko一无所知,但我可以从OpenSSH的角度对此进行评论,也许你可以将概念翻译成你需要的。

评论中提到了NAT。 NAT是在比SSH更低级别完成的事情,而不是基于SSH登录设置的东西(尽管SOCK5)。您可以在防火墙中实现它,而不是在SSH配置中实现它。 ProxyCommand的工作方式是协商SSH连接,然后将客户端交给下一跳说“在这里,与这个人协商”。它是在SSH协议内部实现的。

你可能不会完全失去运气。

标准ProxyCommand设置可能如下所示,在客户端指定目标端口:

host joecontainer
    User joe
    ProxyCommand ssh -x -a -q -Wlocalhost:1031 gatewayserver.horse

旧版本可能使用过Netcat:

host joecontainer
    User joe
    ProxyCommand ssh -x -a -q gatewayserver.horse nc localhost 1031

这里的想法是nc localhost 1031是一个命令,它提供对SSH链中“下一跳”的SSH访问。只要该命令的结果是与SSH守护程序的连接,就可以在此处运行任何命令。

但是您希望端口选择由GATEWAY而不是客户端处理。其中有一点困难,因为SSH守护程序仅使用目标用户名来选择要读取的用户帐户的authorized_keys文件。它是重要的,而不是用户。当服务器开始运行与用户关联的脚本或命令时,SSH协商就完成了,将连接转发到下一跳是为时已晚。

所以...你可以考虑让每个人都连接到普通用户,然后根据 SSH密钥完成端口选择。例如,这是gitolite处理用户的方式。在您的情况下,Joe和Sally都可以使用他们的DSA或RSA密钥连接到common@gatewayserver.horse

有趣的是,所有端口选择都在“common”用户的.ssh/authorized_keys文件中处理。该文件看起来像这样:

command="/usr/bin/nc localhost 1030",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa ... sally@office
command="/usr/bin/nc localhost 1031",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa ... joe@home

您可以在the "AUTHORIZED_KEYS FILE FORMAT" section of the sshd(8) man page下阅读此内容。

要使用这种技术,我们仍然需要客户端ProxyCommand,但由于端口选择基于客户端密钥在服务器端进行,因此每个人都可以使用完全相同的ProxyCommand:

host mycontainer
    ProxyCommand ssh -xaq common@gatewayserver.horse

Sally和Joe将运行ssh-keygen来创建一个密钥对,如果他们还没有。他们会使用上面的格式向您发送您要添加到common / .ssh / authorized_keys的公钥。

当Joe使用他的密钥连接时,ssh服务器只运行与其密钥相关联的nc命令。而且由于ProxyCommand,netcat的输出被解释为SSH的“下一个希望”。

我用sftp测试了这个(在我的最终目标上运行,类似于你的容器),它似乎对我有效。

SSH很神奇。 : - )