-
-
Notifications
You must be signed in to change notification settings - Fork 745
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP]Fix: Paramiko ssh config support #3032
Changes from all commits
d3ef1d1
654d16b
1064c3c
ee2c1c0
6de7445
7d5f6ad
37ae20f
e40c104
9449319
fee0ee5
787c135
0e395ea
5b914e7
7e7faea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -81,7 +81,10 @@ class ParamikoSSHClient(object): | |
# Connect socket timeout | ||
CONNECT_TIMEOUT = 60 | ||
|
||
def __init__(self, hostname, port=22, username=None, password=None, bastion_host=None, | ||
# Default SSH port | ||
SSH_PORT = 22 | ||
|
||
def __init__(self, hostname, port=SSH_PORT, username=None, password=None, bastion_host=None, | ||
key_files=None, key_material=None, timeout=None, passphrase=None): | ||
""" | ||
Authentication is always attempted in the following order: | ||
|
@@ -581,10 +584,96 @@ def _connect(self, host, socket=None): | |
|
||
client = paramiko.SSHClient() | ||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) | ||
client.connect(**conninfo) | ||
|
||
if cfg.CONF.ssh_runner.use_ssh_config: | ||
conninfo_ssh_config = self._get_conninfo_from_ssh_config_for_host(host) | ||
client = self._ssh_priority(client, conninfo, conninfo_ssh_config) | ||
|
||
else: | ||
extra = {'_conninfo': conninfo} | ||
self.logger.debug('Connection info', extra=extra) | ||
client.connect(**conninfo) | ||
|
||
return client | ||
|
||
@staticmethod | ||
def _ssh_priority(client, conninfo, conninfo_ssh_config): | ||
logger = logging.getLogger("ParamikoSSHClient") | ||
|
||
# No Action params. | ||
if conninfo['username'] == cfg.CONF.system_user.user\ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method seems unnecessary complex to me. I was envisioning something along the lines of having a method which retrieves parameters from ssh config file and then basically doing something like this: conn_info = {}
# 1. Retrieve parameters from ssh config
conn_info = self._get_parameters_from_ssh_config()
# 2. Add in arguments which are passed to the constructor. Those are action and runner parameters which have precedence over ones in the config
conn_info['username'] = username
...
client.connect(**conn_info) This seems sufficient to me and we don't need any complex if statements, etc. If I'm missing / misunderstanding something, please let me know. |
||
and conninfo['port'] == ParamikoSSHClient.SSH_PORT: | ||
|
||
if 'username' in conninfo_ssh_config: | ||
|
||
# Corner Case: Config file username or key_filename same as | ||
# system user. | ||
if conninfo_ssh_config['username'] == cfg.CONF.system_user.user\ | ||
or conninfo_ssh_config['key_filename'] ==\ | ||
cfg.CONF.system_user.ssh_key_file: | ||
conninfo.update(conninfo_ssh_config) | ||
client.connect(**conninfo) | ||
|
||
else: | ||
if 'port' in conninfo_ssh_config: | ||
conninfo_ssh_config['port'] = int(conninfo_ssh_config['port']) | ||
extra = {'_sshconfig_conninfo': conninfo_ssh_config, | ||
'_default_conninfo': conninfo} | ||
logger.debug('Connection info from config', | ||
extra=extra) | ||
client.connect(**conninfo_ssh_config) | ||
|
||
else: | ||
conninfo_ssh_config.update(conninfo) | ||
extra = {'_conninfo_ssh_config': conninfo_ssh_config} | ||
logger.debug('Connection info, no username, using sys. user', | ||
extra=extra) | ||
try: | ||
client.connect(**conninfo_ssh_config) | ||
except Exception: | ||
raise Exception('Tried with system user. Provide ' | ||
'\'User\' directive for the host in' | ||
'.ssh/config.') | ||
else: | ||
extra = {'_conninfo': conninfo} | ||
logger.debug('Connection info, action param. over ssh config', | ||
extra=extra) | ||
client.connect(**conninfo) | ||
|
||
return client | ||
|
||
@staticmethod | ||
def _get_conninfo_from_ssh_config_for_host(host): | ||
ssh_conn_info = {} | ||
ssh_config = paramiko.SSHConfig() | ||
user_config_file = os.path.expanduser(cfg.CONF.ssh_runner.ssh_config_path or | ||
"~/.ssh/config") | ||
try: | ||
with open(user_config_file) as f: | ||
ssh_config.parse(f) | ||
except IOError as e: | ||
raise Exception('Error accessing ssh config file %s. Code: %s Reason %s' % | ||
(user_config_file, e.errno, e.strerror)) | ||
|
||
user_config = ssh_config.lookup(host) | ||
for k in ('hostname', 'port'): | ||
if k in user_config: | ||
ssh_conn_info[k] = user_config[k] | ||
|
||
if 'user' in user_config: | ||
ssh_conn_info['username'] = user_config['user'] | ||
|
||
if 'proxycommand' in user_config: | ||
ssh_conn_info['sock'] = paramiko.ProxyCommand(user_config['proxycommand']) | ||
|
||
if 'identityfile' in user_config: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you check if we handle case mixup in ssh config file? |
||
ssh_conn_info['key_filename'] = user_config['identityfile'] | ||
|
||
extra = {'_get_conninfo': user_config, '_ssh_conninfo': ssh_conn_info} | ||
logger = logging.getLogger("ParamikoSSHClient") | ||
logger.debug('_get_conninfo', extra=extra) | ||
return ssh_conn_info | ||
|
||
@staticmethod | ||
def _is_key_file_needs_passphrase(file): | ||
for cls in [paramiko.RSAKey, paramiko.DSSKey, paramiko.ECDSAKey]: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
Host dummy.host.org | ||
IdentityFile ~/.ssh/id_rsa_dummy | ||
ProxyCommand ssh -q -W %h:%p dummy_bastion |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait - so do we want two config options or not -
use_ssh_config
andssh_config_path
?If we only have one (
ssh_config_path
), we should default it toNone
or similar for backward compatibility. Another option is to have two options, defaultuse_ssh_config
toFalse
(same as before) and then we can leave default value forssh_config_path
as is.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, my bad, I may have deleted the
use_ssh_config
while sorting out the merge conflicts. Fix on its way.