(还有另一个带有一些相关答案的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 是否会费心及时启动实例的变化不定。