【问题标题】:How to use boto.manage.cmdshell with ssh-agent?如何将 boto.manage.cmdshell 与 ssh-agent 一起使用?
【发布时间】:2014-09-19 02:30:03
【问题描述】:

我正在使用boto.manage.cmdshell 创建到 EC2 实例的 SSH 连接。目前每次用户都必须输入密码来加密 pkey(例如~/.ssh/id_rsa)。

现在我想让用户的工作流程更方便并支持ssh-agent。 到目前为止,我尝试过没有任何成功:

  • 在创建FakeServer时将ssh_key_file设置为None

    结果是:SSHException('Key object may not be empty')

  • 在创建SSHClient时将ssh_pwd设置为None

    结果是:paramiko.ssh_exception.PasswordRequiredException: Private key file is encrypted

有没有办法将ssh-agentboto.manage.cmdshell 一起使用?我知道paramiko 支持它,boto 正在使用它。

【问题讨论】:

    标签: python ssh boto paramiko


    【解决方案1】:

    (还有另一个带有一些相关答案的stackoverflow页面) Can't get amazon cmd shell to work through boto

    但是,使用个人 SSH 密钥肯定会更好。但如果你有这些,它们是否在目标主机的 authorized_keys 文件中?如果是这样,用户只需使用 ssh-add 正常添加他们的密钥(在 ssh-agent 会话中,通常是 Linux 中的默认设置)。您需要先用 ssh 本身进行测试,以便事先明确解决 ssh-agent/-add 问题。

    一旦确定它们可以正常使用 ssh,问题就在于 boto 是否考虑过 ssh-agent。如果我没记错的话,Paramiko 的 SSHClient() 可以——我记得的 paramiko 代码大致如下:

    paramiko.SSHClient().connect(host, timeout=10, username=user,
                                 key_filename=seckey, compress=True)
    

    seckey 是可选的,因此 key_filename 将为空,这会调用检查 ssh-agent。 Boto 的版本似乎想通过这样的显式调用来强制使用私钥文件,我认为每个实例都有一个分配的密钥和密码来解密它:

      self._pkey = paramiko.RSAKey.from_private_key_file(server.ssh_key_file,
                                                         password=ssh_pwd)
    

    如果是这样,则意味着使用 boto 直接与使用 ssh-agent 以及按用户登录和按用户记录连接的标准模型相冲突。

    paramiko.SSHClient() 功能更强大,并且明确记录了 ssh-agent 支持(来自 pydoc paramiko.SSHClient):

      Authentication is attempted in the following order of priority:
          - The C{pkey} or C{key_filename} passed in (if any)
          - Any key we can find through an SSH agent
          - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/}
          - Plain username/password auth, if a password was given
    

    基本上,您必须使用 paramiko 而不是 boto。

    我们在使用 paramiko 时遇到了一个问题:在许多情况下,连接不会立即准备好,需要在发送实际命令之前发送测试命令并检查输出。部分原因是我们会在创建 EC2 或 VPC 实例后开始触发 SSH 命令(使用 paramiko)right,因此无法保证它会监听 SSH 连接,并且 paramiko往往会丢失过早交付的命令。我们使用了一些这样的代码来应对:

    def SshCommand(**kwargs):
        '''
        Run a command on a remote host via SSH.
    
        Connect to the given host=<host-or-ip>, as user=<user> (defaulting to
        $USER), with optional seckey=<secret-key-file>, timeout=<seconds>
        (default 10), and execute a single command=<command> (assumed to be
        addressing a unix shell at the far end.
    
        Returns the exit status of the remote command (otherwise would be
        None save that an exception should be raised instead).
    
        Example: SshCommand(host=host, user=user, command=command, timeout=timeout,
                            seckey=seckey)
        '''
        remote_exit_status = None
    
        if debug:
            sys.stderr.write('SshCommand kwargs: %r\n' % (kwargs,))
    
        paranoid = True
    
        host = kwargs['host']
        user = kwargs['user'] if kwargs['user'] else os.environ['USER']
        seckey = kwargs['seckey']
        timeout = kwargs['timeout']
        command = kwargs['command']
    
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    
        time_end = time.time() + int(timeout)
    
        ssh_is_up = False
        while time.time() < time_end:
            try:
                ssh.connect(host, timeout=10, username=user, key_filename=seckey,
                            compress=True)
                if paranoid:
                    token_generator = 'echo xyz | tr a-z A-Z'
                    token_result = 'XYZ'      # possibly buried in other text
                    stdin, stdout, stderr = ssh.exec_command(token_generator)
                    lines = ''.join(stdout.readlines())
                    if re.search(token_result, lines):
                        ssh_is_up = True
                        if debug:
                            sys.stderr.write("[%d] command stream is UP!\n"
                                             % time.time())
                        break
                else:
                    ssh_is_up = True
                    break
            except paramiko.PasswordRequiredException as e:
                sys.stderr.write("usage idiom clash: %r\n" % (e,))
                return False
            except Exception as e:
                sys.stderr.write("[%d] command stream not yet available\n"
                                 % time.time())
                if debug:
                    sys.stderr.write("exception is %r\n" % (e,))
            time.sleep(1)
    
        if ssh_is_up:
            # ideally this is where Bcfg2 or Chef or such ilk get called.
    #       stdin, stdout, stderr = ssh.exec_command(command)
            chan = ssh._transport.open_session()
            chan.exec_command(command)
            # note that out/err doesn't have inter-stream ordering locked down.
            stdout = chan.makefile('rb', -1)
            stderr = chan.makefile_stderr('rb', -1)
            sys.stdout.write(''.join(stdout.readlines()))
            sys.stderr.write(''.join(stderr.readlines()))
            remote_exit_status = chan.recv_exit_status()
            if debug:
                sys.stderr.write('exit status was: %d\n' % remote_exit_status)
    
        ssh.close()
        if None == remote_exit_status:
            raise SSHException('remote command result undefined')
        return remote_exit_status
    

    我们还试图强制不直接登录 prod,因此这个特殊的包装器(一个 ssh-send-command 脚本)鼓励编写脚本,尽管 Amazon 是否会费心及时启动实例的变化不定。

    【讨论】:

    • 很好的解决方案,我真的很喜欢关于连接可能还没有准备好的问题的建议。我会把它合并到我的程序中。但我找到了一个不同的解决方案,我将继续使用它,我发布了。
    • 派生到 override init 在这种情况下非常聪明。折叠 shell-really-there 测试也是一个很好的下一步。
    【解决方案2】:

    我通过创建一个继承自boto.manage.cmdshell.SSHClient 并覆盖__init__() 的类SSHClientAgent 找到了我的问题的解决方案。在新的__init__() 中,我将对paramiko.RSAKey.from_private_key_file() 的调用替换为None

    这是我的新课程:

    class SSHClientAgent(boto.manage.cmdshell.SSHClient):
    
        def __init__(self, server,
                     host_key_file='~/.ssh/known_hosts',
                     uname='root', timeout=None, ssh_pwd=None):
            self.server = server
            self.host_key_file = host_key_file
            self.uname = uname
            self._timeout = timeout
            # replace the call to get the private key
            self._pkey = None
            self._ssh_client = paramiko.SSHClient()
            self._ssh_client.load_system_host_keys()
            self._ssh_client.load_host_keys(os.path.expanduser(host_key_file))
            self._ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            self.connect()
    

    在我创建 ssh 连接的函数中,我检查环境变量 SSH_AUTH_SOCK 并决定要创建哪个 ssh 客户端。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-16
      • 2020-07-28
      • 2023-01-13
      • 1970-01-01
      • 1970-01-01
      • 2014-08-06
      相关资源
      最近更新 更多