Load key files manually before making SSH connections

Change-Id: I63828b940d9bedcfff854f6732a78c09828666af
This commit is contained in:
Federico Ressi 2021-06-30 13:18:29 +02:00
parent 592eccd0ef
commit f526f4eae7
1 changed files with 47 additions and 30 deletions

View File

@ -18,8 +18,8 @@ from __future__ import absolute_import
import collections import collections
import contextlib import contextlib
import getpass import getpass
import io
import os import os
import socket
import subprocess import subprocess
import time import time
import threading import threading
@ -579,6 +579,21 @@ def ssh_client(host, port=None, username=None, proxy_jump=None,
**connect_parameters) **connect_parameters)
def load_private_keys(key_filenames: typing.List[str]):
pkeys = []
for filename in key_filenames:
if os.path.exists(filename):
try:
with io.open(filename, 'rt') as fd:
pkey = paramiko.RSAKey.from_private_key(fd)
except Exception:
LOG.error('Unable to get RSAKey private key from file: '
f'{filename}', exc_info=1)
else:
pkeys.append(pkey)
return pkeys
def ssh_connect(hostname, username=None, port=None, connection_interval=None, def ssh_connect(hostname, username=None, port=None, connection_interval=None,
connection_attempts=None, connection_timeout=None, connection_attempts=None, connection_timeout=None,
proxy_command=None, proxy_client=None, key_filename=None, proxy_command=None, proxy_client=None, key_filename=None,
@ -587,13 +602,9 @@ def ssh_connect(hostname, username=None, port=None, connection_interval=None,
client.set_missing_host_key_policy(paramiko.WarningPolicy()) client.set_missing_host_key_policy(paramiko.WarningPolicy())
login = _command.ssh_login(hostname=hostname, username=username, port=port) login = _command.ssh_login(hostname=hostname, username=username, port=port)
if key_filename: assert isinstance(key_filename, list)
# Ensures we try enough times to try all keys pkeys = load_private_keys(key_filename)
tobiko.check_valid_type(key_filename, list) auth_failed: typing.Optional[Exception] = None
connection_attempts = max(connection_attempts or 1,
len(key_filename),
1)
for attempt in tobiko.retry(count=connection_attempts, for attempt in tobiko.retry(count=connection_attempts,
timeout=connection_timeout, timeout=connection_timeout,
interval=connection_interval, interval=connection_interval,
@ -603,8 +614,8 @@ def ssh_connect(hostname, username=None, port=None, connection_interval=None,
LOG.debug(f"Logging in to '{login}'...\n" LOG.debug(f"Logging in to '{login}'...\n"
f" - parameters: {parameters}\n" f" - parameters: {parameters}\n"
f" - attempt: {attempt.details}\n") f" - attempt: {attempt.details}\n")
for pkey in pkeys + [None]:
try: succeeded = False
proxy_sock = ssh_proxy_sock( proxy_sock = ssh_proxy_sock(
hostname=hostname, hostname=hostname,
port=port, port=port,
@ -613,29 +624,35 @@ def ssh_connect(hostname, username=None, port=None, connection_interval=None,
timeout=connection_timeout, timeout=connection_timeout,
connection_attempts=1, connection_attempts=1,
connection_interval=connection_interval) connection_interval=connection_interval)
client.connect(hostname=hostname, try:
username=username, client.connect(hostname=hostname,
port=port, username=username,
sock=proxy_sock, port=port,
key_filename=key_filename, sock=proxy_sock,
**parameters) pkey=pkey,
except ValueError as ex: **parameters)
attempt.check_limits() except paramiko.ssh_exception.AuthenticationException as ex:
if (str(ex) == 'q must be exactly 160, 224, or 256 bits long' and if auth_failed is not None:
len(key_filename) > 1): ex.__cause__ = auth_failed
# Must try without the first key auth_failed = ex
LOG.debug(f"Retry connecting with the next key: {ex}")
key_filename = key_filename[1:]
continue continue
except Exception as ex:
LOG.debug(f"Error logging in to '{login}': {ex}", exc_info=1)
attempt.check_limits()
break
else: else:
raise LOG.debug(f"Successfully logged in to '{login}'")
except (EOFError, socket.error, socket.timeout, succeeded = True
paramiko.SSHException) as ex: return client, proxy_sock
attempt.check_limits() finally:
LOG.debug(f"Error logging in to '{login}': {ex}") if not succeeded:
try:
proxy_sock.close()
except Exception:
pass
else: else:
LOG.debug(f"Successfully logged in to '{login}'") if isinstance(auth_failed, Exception):
return client, proxy_sock raise auth_failed
def ssh_proxy_sock(hostname=None, port=None, command=None, client=None, def ssh_proxy_sock(hostname=None, port=None, command=None, client=None,