Skip to content
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

Make SCP and StartShell behaves the same way as Device for SSH #649

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 9 additions & 13 deletions lib/jnpr/junos/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'):
Expand Down Expand Up @@ -820,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
Expand All @@ -830,17 +830,21 @@ 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')
# 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we can also have ssh key file protected by passphrase. That passphrase in passed to Device as password.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you provide a password, the agent is disabled and Paramiko will fetch the key and decrypt it. The agent doesn't allow you to provide a password for an encrypted key either (it will ask the user for that if needed). Moreover, this code is already present as is. It's just moved here to be able to reuse its result for scp.

IMO, the ability to disable the agent part is odd. The regular "ssh" client doesn't have such an option. It adds complexity and I don't know why this has been implemented. We should just get rid of that.

self._allow_agent = bool((self._auth_password is None) and
(self._ssh_private_key_file is None))

# -----------------------------
# initialize instance variables
Expand Down Expand Up @@ -907,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,
Expand All @@ -923,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})

Expand Down
34 changes: 34 additions & 0 deletions lib/jnpr/junos/utils/misc.py
Original file line number Diff line number Diff line change
@@ -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
34 changes: 3 additions & 31 deletions lib/jnpr/junos/utils/scp.py
Original file line number Diff line number Diff line change
@@ -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
"""
Expand Down Expand Up @@ -81,36 +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.

config = {}
kwargs = {}
ssh_config = getattr(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

self._ssh.connect(hostname=junos._hostname,
port=(
22, int(
junos._port))[
junos._hostname == 'localhost'],
username=junos._auth_user,
password=junos._auth_password,
sock=sock, **kwargs
)
self._ssh = get_ssh_client(junos)
return SCPClient(self._ssh.get_transport(), **scpargs)

def close(self):
Expand Down
14 changes: 3 additions & 11 deletions lib/jnpr/junos/utils/start_shell.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down