SSH 服务器按用户路由隧道
SSH server routes tunnel by user
我正在努力完成的图表:
$ 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" 通道,我希望转发由服务器完成,对客户端来说是不可见的。
我一直在尝试通过获取连接客户端的传输对象并代表客户端创建直接 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 的方式。(因为它们可能正在使用 gui ftp 客户端,如 filezilla。)我也是不想创建桥接接口并为所有容器分配新的 ip。因此容器与主机没有单独的 ips,它们共享同一个网络 space。
客户端容器的 sshds 将绑定到本地主机上的单独端口。这样他们就可以拥有唯一的端口,并且可以在概念上将选择哪个端口的逻辑移出到...物理主机上的简单服务器。
这更像是一个概念验证,也是我的普遍好奇心。
Attempting creation of direct-tcpip channel on client Transport
*** Caught exception: <class '[...]'>: (1, 'Administratively prohibited')
容器 ssh 服务器拒绝您的 direct-tcpip 通道请求,因为它已配置为拒绝这些请求。我收集的目的是将 SFTP 会话代理到正确的容器?我想容器 SSH 服务器已在 the usual fashion 中配置为仅允许这些人执行 SFTP? SFTP 会话通过会话通道,而不是 direct-tcpip 通道。
我不是 python 编码员,不能给你具体的 paramiko 代码,但你的中继代理应该打开一个到容器服务器的会话通道并调用 "sftp" 子系统。如果可能,您的中继代理应该只在远程客户端请求 SFTP 会话时执行此操作,而不是针对其他类型的通道请求。
正如我在上面的评论中提到的,我对 paramiko 一无所知,但我可以从 OpenSSH 的角度对此进行评论,也许您可以将这些概念转化为您需要的内容。
评论中提到了 NAT。 NAT 是在比 SSH 更低的级别上完成的,而不是在 SSH 登录的基础上设置的(尽管有 SOCK5)。您将在您的防火墙中实现它,而不是在您的 SSH 配置中。 ProxyCommand 的工作方式是协商 SSH 连接,然后将客户端交给下一跳说 "Here, negotiate with this guy too." 它是在 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 链中 "next hop" 的 SSH 访问的命令。您可以在此处 运行 任何命令,只要该命令的结果是与 SSH 守护程序的连接即可。
但是您希望 selection 端口由 GATEWAY 而不是客户端处理。其中有一点 c运行ch,因为 SSH 守护进程只使用目标用户名 select 哪个用户帐户的 authorized_keys 文件来读取。重要的是 keys,而不是用户。当服务器开始 运行 与用户关联的脚本或命令时,SSH 协商已完成,将连接转发到下一跃点为时已晚。
所以...你可以考虑让每个人都连接到一个普通用户,然后在[=51=的基础上完成端口selection ]SSH 密钥。例如,gitolite 就是这样处理用户的。在您的例子中,Joe 和 Sally 都可以使用他们的 DSA 或 RSA 密钥连接到 common@gatewayserver.horse
。
有趣的是,您的所有端口 selection 都在 "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,但由于端口 selection 发生在服务器端,基于客户端的密钥,每个人都可以使用完全相同的 ProxyCommand:
host mycontainer
ProxyCommand ssh -xaq common@gatewayserver.horse
Sally 和 Joe 将 运行 ssh-keygen
创建密钥对(如果尚未创建)。他们会向您发送 public 密钥,您将使用上述格式将其添加到 ~common/.ssh/authorized_keys。
当乔使用他的密钥连接时,ssh 服务器仅 运行s 与他的密钥关联的 nc
命令。由于 ProxyCommand,netcat 的输出被解释为 SSH 的 "next hope"。
我已经用 sftp 测试了这个(运行ning 在我的最终目标上,类似于你的容器),它似乎对我有用。
SSH 很神奇。 :-)
我正在努力完成的图表:
$ 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" 通道,我希望转发由服务器完成,对客户端来说是不可见的。 我一直在尝试通过获取连接客户端的传输对象并代表客户端创建直接 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 的方式。(因为它们可能正在使用 gui ftp 客户端,如 filezilla。)我也是不想创建桥接接口并为所有容器分配新的 ip。因此容器与主机没有单独的 ips,它们共享同一个网络 space。 客户端容器的 sshds 将绑定到本地主机上的单独端口。这样他们就可以拥有唯一的端口,并且可以在概念上将选择哪个端口的逻辑移出到...物理主机上的简单服务器。
这更像是一个概念验证,也是我的普遍好奇心。
Attempting creation of direct-tcpip channel on client Transport
*** Caught exception: <class '[...]'>: (1, 'Administratively prohibited')
容器 ssh 服务器拒绝您的 direct-tcpip 通道请求,因为它已配置为拒绝这些请求。我收集的目的是将 SFTP 会话代理到正确的容器?我想容器 SSH 服务器已在 the usual fashion 中配置为仅允许这些人执行 SFTP? SFTP 会话通过会话通道,而不是 direct-tcpip 通道。
我不是 python 编码员,不能给你具体的 paramiko 代码,但你的中继代理应该打开一个到容器服务器的会话通道并调用 "sftp" 子系统。如果可能,您的中继代理应该只在远程客户端请求 SFTP 会话时执行此操作,而不是针对其他类型的通道请求。
正如我在上面的评论中提到的,我对 paramiko 一无所知,但我可以从 OpenSSH 的角度对此进行评论,也许您可以将这些概念转化为您需要的内容。
评论中提到了 NAT。 NAT 是在比 SSH 更低的级别上完成的,而不是在 SSH 登录的基础上设置的(尽管有 SOCK5)。您将在您的防火墙中实现它,而不是在您的 SSH 配置中。 ProxyCommand 的工作方式是协商 SSH 连接,然后将客户端交给下一跳说 "Here, negotiate with this guy too." 它是在 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 链中 "next hop" 的 SSH 访问的命令。您可以在此处 运行 任何命令,只要该命令的结果是与 SSH 守护程序的连接即可。
但是您希望 selection 端口由 GATEWAY 而不是客户端处理。其中有一点 c运行ch,因为 SSH 守护进程只使用目标用户名 select 哪个用户帐户的 authorized_keys 文件来读取。重要的是 keys,而不是用户。当服务器开始 运行 与用户关联的脚本或命令时,SSH 协商已完成,将连接转发到下一跃点为时已晚。
所以...你可以考虑让每个人都连接到一个普通用户,然后在[=51=的基础上完成端口selection ]SSH 密钥。例如,gitolite 就是这样处理用户的。在您的例子中,Joe 和 Sally 都可以使用他们的 DSA 或 RSA 密钥连接到 common@gatewayserver.horse
。
有趣的是,您的所有端口 selection 都在 "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,但由于端口 selection 发生在服务器端,基于客户端的密钥,每个人都可以使用完全相同的 ProxyCommand:
host mycontainer
ProxyCommand ssh -xaq common@gatewayserver.horse
Sally 和 Joe 将 运行 ssh-keygen
创建密钥对(如果尚未创建)。他们会向您发送 public 密钥,您将使用上述格式将其添加到 ~common/.ssh/authorized_keys。
当乔使用他的密钥连接时,ssh 服务器仅 运行s 与他的密钥关联的 nc
命令。由于 ProxyCommand,netcat 的输出被解释为 SSH 的 "next hope"。
我已经用 sftp 测试了这个(运行ning 在我的最终目标上,类似于你的容器),它似乎对我有用。
SSH 很神奇。 :-)