From 51a68d1567eab00dc0ff5b7b6deee20bb76c5318 Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Fri, 13 Jan 2017 20:12:01 +0100 Subject: [PATCH 1/4] Do not provide the key if it comes from the SSH configuration This is a followup of #628 where this change was initially pushed but reverted because I didn't remember why I did it. Before #628, when an identity is provided in the SSH configuration, it was not copied in `_conf_ssh_private_key_file` due to a bug. After fixing the bug in #628, the key is now copied. However, the SSH configuration is provided to the `connect()` method which will use it if needed. Therefore, this is not needed. Moreover, if the key is provided by an agent and/or encrypted, this won't work as, later in the code, `allow_agent` will be set to `False` due to the presence of a private key. --- lib/jnpr/junos/device.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/jnpr/junos/device.py b/lib/jnpr/junos/device.py index 26138722b..b3ff6dfec 100644 --- a/lib/jnpr/junos/device.py +++ b/lib/jnpr/junos/device.py @@ -218,7 +218,6 @@ def _sshconf_lkup(self): self._hostname = found.get('hostname', self._hostname) self._port = found.get('port', self._port) self._conf_auth_user = found.get('user') - self._conf_ssh_private_key_file = found.get('identityfile') return sshconf_path def display_xml_rpc(self, command, format='xml'): @@ -830,15 +829,13 @@ def __init__(self, *vargs, **kvargs): # user will default to $USER self._auth_user = os.getenv('USER') self._conf_auth_user = None - self._conf_ssh_private_key_file = None # user can get updated by ssh_config self._ssh_config = kvargs.get('ssh_config') self._sshconf_lkup() # but if user or private key is explicit from call, then use it. self._auth_user = kvargs.get('user') or self._conf_auth_user or \ self._auth_user - self._ssh_private_key_file = kvargs.get('ssh_private_key_file') \ - or self._conf_ssh_private_key_file + self._ssh_private_key_file = kvargs.get('ssh_private_key_file') self._auth_password = kvargs.get( 'password') or kvargs.get('passwd') From a3686e66b67810c9d39b54f4dda2ca2ad714d4d4 Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Fri, 13 Jan 2017 19:48:09 +0100 Subject: [PATCH 2/4] scp: simplify SSH connection settings The `allow_agent` setting was used with NC but not with SCP. Store it in the device and reuse it for SCP. The private key was stored in a dictionnary, but this is not needed as Paramiko's `connect()` would default to `None` when not provided. Grab the SSH key filename from SSH configuration as Paramiko won't do it for us. For this reason, this commit is a followup to the one in #648. --- lib/jnpr/junos/device.py | 17 ++++++++--------- lib/jnpr/junos/utils/scp.py | 25 +++++++++++-------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/lib/jnpr/junos/device.py b/lib/jnpr/junos/device.py index b3ff6dfec..86577e77a 100644 --- a/lib/jnpr/junos/device.py +++ b/lib/jnpr/junos/device.py @@ -819,6 +819,7 @@ def __init__(self, *vargs, **kvargs): self._hostname = 'localhost' self._ssh_private_key_file = None self._ssh_config = None + self._allow_agent = False else: # -------------------------- # making a remote connection @@ -838,6 +839,12 @@ def __init__(self, *vargs, **kvargs): self._ssh_private_key_file = kvargs.get('ssh_private_key_file') self._auth_password = kvargs.get( 'password') or kvargs.get('passwd') + # we want to enable the ssh-agent if-and-only-if we are + # not given a password or an ssh key file. + # in this condition it means we want to query the agent + # for available ssh keys + self._allow_agent = bool((self._auth_password is None) and + (self._ssh_private_key_file is None)) # ----------------------------- # initialize instance variables @@ -904,14 +911,6 @@ def open(self, *vargs, **kvargs): try: ts_start = datetime.datetime.now() - # we want to enable the ssh-agent if-and-only-if we are - # not given a password or an ssh key file. - # in this condition it means we want to query the agent - # for available ssh keys - - allow_agent = bool((self._auth_password is None) and - (self._ssh_private_key_file is None)) - # open connection using ncclient transport self._conn = netconf_ssh.connect( host=self._hostname, @@ -920,7 +919,7 @@ def open(self, *vargs, **kvargs): password=self._auth_password, hostkey_verify=False, key_filename=self._ssh_private_key_file, - allow_agent=allow_agent, + allow_agent=self._allow_agent, ssh_config=self._sshconf_lkup(), device_params={'name': 'junos', 'local': False}) diff --git a/lib/jnpr/junos/utils/scp.py b/lib/jnpr/junos/utils/scp.py index 8a150d6cb..cd7948d09 100644 --- a/lib/jnpr/junos/utils/scp.py +++ b/lib/jnpr/junos/utils/scp.py @@ -88,29 +88,26 @@ def open(self, **scpargs): # use junos._hostname since this will be correct if we are going # through a jumphost. - config = {} - kwargs = {} - ssh_config = getattr(junos, '_sshconf_path') + # Retrieve ProxyCommand and IdentityFile + sock = None + key_file = junos._ssh_private_key_file + ssh_config = junos._sshconf_path if ssh_config: config = paramiko.SSHConfig() config.parse(open(ssh_config)) config = config.lookup(junos._hostname) - sock = None - if config.get("proxycommand"): - sock = paramiko.proxy.ProxyCommand(config.get("proxycommand")) - - if self._junos._ssh_private_key_file is not None: - kwargs['key_filename']=self._junos._ssh_private_key_file + if config.get("proxycommand"): + sock = paramiko.proxy.ProxyCommand(config.get("proxycommand")) + key_file = key_file or config.get("identityfile") self._ssh.connect(hostname=junos._hostname, - port=( - 22, int( - junos._port))[ + port=(22, int(junos._port))[ junos._hostname == 'localhost'], username=junos._auth_user, password=junos._auth_password, - sock=sock, **kwargs - ) + key_filename=key_file, + allow_agent=junos._allow_agent, + sock=sock) return SCPClient(self._ssh.get_transport(), **scpargs) def close(self): From 63ccdcc353a1a2345b4f6ec699474f1607343c8c Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Fri, 13 Jan 2017 20:36:48 +0100 Subject: [PATCH 3/4] scp: move paramiko SSH client creation into a dedicated function This is a preparation to reuse the client for other purposes, notably for starting a shell. --- lib/jnpr/junos/utils/misc.py | 34 ++++++++++++++++++++++++++++++++++ lib/jnpr/junos/utils/scp.py | 31 +++---------------------------- 2 files changed, 37 insertions(+), 28 deletions(-) create mode 100644 lib/jnpr/junos/utils/misc.py diff --git a/lib/jnpr/junos/utils/misc.py b/lib/jnpr/junos/utils/misc.py new file mode 100644 index 000000000..9b06a3996 --- /dev/null +++ b/lib/jnpr/junos/utils/misc.py @@ -0,0 +1,34 @@ +# utils/misc.py + +import paramiko + +def get_ssh_client(junos): + """Get a Paramiko SSHClient using settings from the provided device.""" + ssh = paramiko.SSHClient() + ssh.load_system_host_keys() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + # use junos._hostname since this will be correct if we are going + # through a jumphost. + + # Retrieve ProxyCommand and IdentityFile + sock = None + key_file = junos._ssh_private_key_file + ssh_config = junos._sshconf_path + if ssh_config: + config = paramiko.SSHConfig() + config.parse(open(ssh_config)) + config = config.lookup(junos._hostname) + if config.get("proxycommand"): + sock = paramiko.proxy.ProxyCommand(config.get("proxycommand")) + key_file = key_file or config.get("identityfile") + + ssh.connect(hostname=junos._hostname, + port=(22, int(junos._port))[ + junos._hostname == 'localhost'], + username=junos._auth_user, + password=junos._auth_password, + key_filename=key_file, + allow_agent=junos._allow_agent, + sock=sock) + return ssh diff --git a/lib/jnpr/junos/utils/scp.py b/lib/jnpr/junos/utils/scp.py index cd7948d09..5e502da7c 100644 --- a/lib/jnpr/junos/utils/scp.py +++ b/lib/jnpr/junos/utils/scp.py @@ -1,9 +1,10 @@ from __future__ import absolute_import import inspect -import paramiko from scp import SCPClient +from jnpr.junos.utils.misc import get_ssh_client + """ Secure Copy Utility """ @@ -81,33 +82,7 @@ def open(self, **scpargs): #@@@ should check for multi-calls to connect to ensure we don't keep #@@@ opening new connections junos = self._junos - self._ssh = paramiko.SSHClient() - self._ssh.load_system_host_keys() - self._ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - - # use junos._hostname since this will be correct if we are going - # through a jumphost. - - # Retrieve ProxyCommand and IdentityFile - sock = None - key_file = junos._ssh_private_key_file - ssh_config = junos._sshconf_path - if ssh_config: - config = paramiko.SSHConfig() - config.parse(open(ssh_config)) - config = config.lookup(junos._hostname) - if config.get("proxycommand"): - sock = paramiko.proxy.ProxyCommand(config.get("proxycommand")) - key_file = key_file or config.get("identityfile") - - self._ssh.connect(hostname=junos._hostname, - port=(22, int(junos._port))[ - junos._hostname == 'localhost'], - username=junos._auth_user, - password=junos._auth_password, - key_filename=key_file, - allow_agent=junos._allow_agent, - sock=sock) + self._ssh = get_ssh_client(junos) return SCPClient(self._ssh.get_transport(), **scpargs) def close(self): From 35d339090d7ad6c52540523e32c8bab8a160b63f Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Fri, 13 Jan 2017 20:38:44 +0100 Subject: [PATCH 4/4] start_shell: initialize SSH client the same way as for SCP --- lib/jnpr/junos/utils/start_shell.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/jnpr/junos/utils/start_shell.py b/lib/jnpr/junos/utils/start_shell.py index 07956c0ee..f2e2386e2 100644 --- a/lib/jnpr/junos/utils/start_shell.py +++ b/lib/jnpr/junos/utils/start_shell.py @@ -1,8 +1,9 @@ -import paramiko from select import select import re import datetime +from jnpr.junos.utils.misc import get_ssh_client + _JUNOS_PROMPT = '> ' _SHELL_PROMPT = '(%|#)\s' _SELECT_WAIT = 0.1 @@ -82,16 +83,7 @@ def open(self): :class:`paramiko.SSHClient` instance. """ junos = self._nc - - client = paramiko.SSHClient() - client.load_system_host_keys() - client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - client.connect(hostname=junos.hostname, - port=(22, junos._port)[junos.hostname == 'localhost'], - username=junos._auth_user, - password=junos._auth_password, - ) - + client = get_ssh_client(junos) chan = client.invoke_shell() self._client = client self._chan = chan