我使用boto.manage.cmdshell
创建与EC2实例的SSH连接。目前,每次用户必须输入密码才能加密密钥(例如~/.ssh/id_rsa
)。
现在我想让用户更方便工作流程并支持ssh-agent
。
到目前为止,我试过没有任何成功:
在创建ssh_key_file
时将None
设置为FakeServer
:
结果是:SSHException('Key object may not be empty')
在创建ssh_pwd
时将None
设置为SSHClient
:
结果是:paramiko.ssh_exception.PasswordRequiredException: Private key file is encrypted
有没有办法将ssh-agent
与boto.manage.cmdshell
一起使用?我知道paramiko
支持它,boto正在使用它。
答案 0 :(得分: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有关的问题:在很多情况下,连接将不立即就绪,需要在发送实际命令之前发送测试命令和checkout输出。部分原因是我们在创建和EC2或VPC实例后开始启动SSH命令(使用paramiko)正确,因此无法保证它正在侦听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脚本)鼓励编写脚本,尽管亚马逊是否已经费心去及时启动实例。
答案 1 :(得分:0)
我通过创建一个继承自SSHClientAgent
并覆盖boto.manage.cmdshell.SSHClient
的类__init__()
找到了我的问题的解决方案。在新的__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客户端。