如何补偿 Paramiko 密钥更新失败?

How to compensate for Paramiko rekey failures?

短版:

我的问题:

  1. 当我拥有的是 paramiko.SSHClient() 对象时如何访问 paramiko.Packetizer.need_rekey() 的任何参考或示例?
  2. 有更好的解决问题的建议吗?

长版:

现代 SSH 会话需要在一定时间或数据通过后重新生成密钥。如果密钥更新请求没有得到足够快的响应 - "quickly enough" 意味着自请求密钥更新以来经过的时间或数据过多 - 作为安全保护机制,连接将被中止。

Paramiko 以前在重新加密请求和重新加密之间有 20 个数据包的非常窄的限制。 This patch 从 2012 年开始

Increase(d) the limit of received packets between re-key request & completion from 20 packets to 2**29 packets.

在我的应用程序中,我使用 paramiko.transport.open_channel 通过转发端口传输大型 (10-30 GB) 数据流。如果我在同一 LAN 上的两台主机之间执行此操作,则 100% 的时间都会成功。如果主机位于不同的 LAN 上,并且延迟越来越高,它就会开始出现故障 - 假设有 50% 的时间,但可能不止于此。

不用说,将 25 GB 的 SSH 连接丢失到 30 GB 的数据传输中可能会令人沮丧。

使用 OpenSSH 作为客户端,我没有这个问题,这与我读过的报告一致,即重新加密互操作性有问题。据报道,OpenSSH 可以与 自身 无缝重新加密;与 其他 ... 不太一样。但是我已经在使用 Paramiko 登录远程主机和 运行 启动数据传输的必要命令;不得不打开一个单独的 OpenSSH 会话只是为了转发数据的端口是混乱的。

正确的解决方法似乎是 Paramiko 应该在重新加密时阻止或延迟非重新加密数据包 - 这似乎是 OpenSSH 处理它的方式,尽管我还没有看到权威确认那。但这可能是一个雄心勃勃的变化,会影响很多人并需要时间。

作为临时措施,简单的修复方法是让我的脚本自我调节。我很乐意在处理重新加密时插入 sleep 调用或完全阻止数据传输。如果没有在后台进行大量数据传输,重新生成密钥应该可以在截止日期内轻松完成。

Paramiko 提供了一种查看是否已请求更新密钥的方法 - paramiko.Packetizer.need_rekey()。但是我看不到如何在我现有的 SSHClient() 对象的上下文中调用它。对于 paramiko.transport.* 方法,我可以使用 paramiko.SSHClient().get_transport() 创建一个用于调用它们的对象。似乎没有 paramiko.SSHClient().get_packetizer() 等效项来访问 Packetizer 方法。 demo/ 代码中没有 Packetizer 的示例,tests/ 下的 test_packetizer.py 文件表明它被设计为用作原始接口而不是 SSHClient

所以 - 重申上面简短版本中的问题 -

  1. 当我看到 paramiko.Packetizer.need_rekey() 为 True 时,我想我可以通过 sleep()ing 来解决这个问题,但是我看不到如何从我的上下文中访问 need_rekey() 方法有一个 SSHClient 对象。有什么想法吗?
  2. 对于这个问题,我应该考虑另一种解决方案吗?

感谢任何帮助!

版本信息仅供参考:

$ python
Python 2.7.5 (default, Apr  9 2015, 11:03:32)
[GCC 4.8.3 20140911 (Red Hat 4.8.3-9)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import paramiko
>>> paramiko.__version__
'1.15.2'
>>>

我仍然很想听到关于 #2 的好答案...

我通过 hacking 和 slashing 找到了 #1 的答案:get_transport 返回的 transport 对象包含一个 packetizer 对象,可用于寻址 need_rekey() 方法:

$ python
Python 2.7.5 (default, Apr  9 2015, 11:03:32)
[GCC 4.8.3 20140911 (Red Hat 4.8.3-9)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import paramiko
>>> ssh = paramiko.SSHClient()
>>> omkey = paramiko.RSAKey.from_private_key_file('./privkey.rsa')
>>> ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
>>> ssh.connect('gowenfawr.example.com', username='gowenfawr', pkey=omkey)
>>> transport = ssh.get_transport()
>>> transport.packetizer
<paramiko.packet.Packetizer object at 0x7fb121aa5650>
>>> transport.packetizer.need_rekey()
False
>>>

我更新了 "data read" 循环以检查 transport.packetizer.need_rekey() 是否已设置,如果已设置,则使用 time.sleep(1) 进入睡眠状态。以下代码打印出 '.'每 1/80 的数据传输进度和 'Z' 将在暂停时打印出来,因为已请求重新生成密钥:

track = 0
hash = int(size/80)
while True:
    r, w, x = select.select([chan], [], [])
    if chan in r:
        data = chan.recv(1024)
        if len(data) == 0:
            break
        os.write(fp[0], data)
        track = track + len(data)
        if transport.packetizer.need_rekey():
            sys.stdout.write('Z')
            sys.stdout.flush()
            time.sleep(1)
        if track > hash:
            sys.stdout.write('.')
            sys.stdout.flush()
            track = 0
print ''

导致这种输出

$ grabdata.py gowenfawr.example.com
target is gowenfawr.example.com
Found appropriate kernel version
Setting up data transfer
.....Z.....Z.....Z.....Z......Z.....Z.....Z.....Z.....Z......Z.....Z.....Z.....Z.....Z......Z.....Z...
Transfer complete!
$ 

这告诉我密钥更新是定期发生的(很好,考虑到这里传输的数据量)并且自更改以来我无法重现会话中止故障超过 ~10 次运行。