Add support for DSA, ECDSA and Ed25519 key types
Add support default key files paths (id_dsa, id_ecdsa, id_ed25519) Change-Id: I8c754a3584249ec4fb372a3822642b7b95b2600e
This commit is contained in:
@@ -59,6 +59,7 @@ tobiko_config_path = _config.tobiko_config_path
|
|||||||
TobikoException = _exception.TobikoException
|
TobikoException = _exception.TobikoException
|
||||||
check_valid_type = _exception.check_valid_type
|
check_valid_type = _exception.check_valid_type
|
||||||
exc_info = _exception.exc_info
|
exc_info = _exception.exc_info
|
||||||
|
ExceptionInfo = _exception.ExceptionInfo
|
||||||
handle_multiple_exceptions = _exception.handle_multiple_exceptions
|
handle_multiple_exceptions = _exception.handle_multiple_exceptions
|
||||||
list_exc_infos = _exception.list_exc_infos
|
list_exc_infos = _exception.list_exc_infos
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ from __future__ import absolute_import
|
|||||||
from collections import abc
|
from collections import abc
|
||||||
import contextlib
|
import contextlib
|
||||||
import getpass
|
import getpass
|
||||||
import io
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
@@ -26,6 +25,7 @@ import threading
|
|||||||
import typing
|
import typing
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
import testtools
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import paramiko
|
import paramiko
|
||||||
from paramiko import common
|
from paramiko import common
|
||||||
@@ -578,32 +578,63 @@ def ssh_client(host, port=None, username=None, proxy_jump=None,
|
|||||||
**connect_parameters)
|
**connect_parameters)
|
||||||
|
|
||||||
|
|
||||||
def load_private_keys(key_filenames: typing.List[str]) \
|
KEY_CLASSES: typing.List[typing.Type[paramiko.PKey]] = [
|
||||||
|
paramiko.RSAKey,
|
||||||
|
paramiko.DSSKey,
|
||||||
|
paramiko.ECDSAKey,
|
||||||
|
paramiko.Ed25519Key,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def load_private_keys(key_filenames: typing.List[str],
|
||||||
|
password: str = None) \
|
||||||
-> typing.List[paramiko.PKey]:
|
-> typing.List[paramiko.PKey]:
|
||||||
pkeys: typing.List[paramiko.PKey] = []
|
pkeys: typing.List[paramiko.PKey] = []
|
||||||
for filename in key_filenames:
|
for filename in key_filenames:
|
||||||
if os.path.exists(filename):
|
if os.path.exists(filename):
|
||||||
try:
|
try:
|
||||||
with io.open(filename, 'rt') as fd:
|
pkey = load_private_key(filename, password=password)
|
||||||
pkey: paramiko.PKey = paramiko.RSAKey.from_private_key(fd)
|
except LoadPrivateKeyError as ex:
|
||||||
except Exception:
|
LOG.exception(f'Error loading key file: {ex}')
|
||||||
LOG.error('Unable to get RSAKey private key from file: '
|
|
||||||
f'{filename}', exc_info=1)
|
|
||||||
else:
|
else:
|
||||||
pkeys.append(pkey)
|
pkeys.append(pkey)
|
||||||
|
else:
|
||||||
|
LOG.debug(f'Key file not found: {filename}')
|
||||||
return pkeys
|
return pkeys
|
||||||
|
|
||||||
|
|
||||||
|
class LoadPrivateKeyError(tobiko.TobikoException):
|
||||||
|
message = "Unable to load private key from file {filename}"
|
||||||
|
|
||||||
|
|
||||||
|
def load_private_key(filename: str,
|
||||||
|
password: str = None) -> paramiko.PKey:
|
||||||
|
errors: typing.List[tobiko.ExceptionInfo] = []
|
||||||
|
for key_class in KEY_CLASSES:
|
||||||
|
try:
|
||||||
|
pkey: paramiko.PKey = key_class.from_private_key_file(
|
||||||
|
filename=filename, password=password)
|
||||||
|
except (paramiko.SSHException, ValueError):
|
||||||
|
errors.append(tobiko.exc_info())
|
||||||
|
else:
|
||||||
|
LOG.debug(f'Key file loaded: {filename} (key_class={key_class})')
|
||||||
|
return pkey
|
||||||
|
|
||||||
|
cause = testtools.MultipleExceptions(*errors)
|
||||||
|
raise LoadPrivateKeyError(filename=filename) from cause
|
||||||
|
|
||||||
|
|
||||||
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,
|
||||||
|
password: str = None,
|
||||||
**parameters):
|
**parameters):
|
||||||
client = paramiko.SSHClient()
|
client = paramiko.SSHClient()
|
||||||
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)
|
||||||
|
|
||||||
assert isinstance(key_filename, list)
|
assert isinstance(key_filename, list)
|
||||||
pkeys = load_private_keys(key_filename)
|
pkeys = load_private_keys(key_filename, password=password)
|
||||||
auth_failed: typing.Optional[Exception] = None
|
auth_failed: typing.Optional[Exception] = None
|
||||||
for attempt in tobiko.retry(count=connection_attempts,
|
for attempt in tobiko.retry(count=connection_attempts,
|
||||||
timeout=connection_timeout,
|
timeout=connection_timeout,
|
||||||
@@ -614,7 +645,7 @@ 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]:
|
for pkey in pkeys + [None]: # type: ignore
|
||||||
succeeded = False
|
succeeded = False
|
||||||
proxy_sock = ssh_proxy_sock(
|
proxy_sock = ssh_proxy_sock(
|
||||||
hostname=hostname,
|
hostname=hostname,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import os.path
|
||||||
import typing # noqa
|
import typing # noqa
|
||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
@@ -75,7 +76,8 @@ class GetSSHKeyFileFixture(tobiko.SharedFixture):
|
|||||||
return
|
return
|
||||||
|
|
||||||
key_file = tobiko.tobiko_config_path(
|
key_file = tobiko.tobiko_config_path(
|
||||||
f"~/.ssh/id_rsa-{remote_hostname}")
|
f"~/.ssh/{os.path.basename(self.remote_key_file)}-" +
|
||||||
|
remote_hostname)
|
||||||
with tobiko.open_output_file(key_file) as fd:
|
with tobiko.open_output_file(key_file) as fd:
|
||||||
fd.write(private_key.decode())
|
fd.write(private_key.decode())
|
||||||
with tobiko.open_output_file(key_file + '.pub') as fd:
|
with tobiko.open_output_file(key_file + '.pub') as fd:
|
||||||
|
|||||||
@@ -40,8 +40,12 @@ OPTIONS = [
|
|||||||
default=['ssh_config', '.ssh/config'],
|
default=['ssh_config', '.ssh/config'],
|
||||||
help="Default user SSH configuration files"),
|
help="Default user SSH configuration files"),
|
||||||
cfg.ListOpt('key_file',
|
cfg.ListOpt('key_file',
|
||||||
default=['~/.ssh/id_rsa', '.ssh/id'],
|
default=['.ssh/id',
|
||||||
help="Default SSH private key file(s)"),
|
'~/.ssh/id_dsa',
|
||||||
|
'~/.ssh/id_rsa',
|
||||||
|
'~/.ssh/id_ecdsa',
|
||||||
|
'~/.ssh/id_ed25519'],
|
||||||
|
help="Default SSH private key file(s) wildcard"),
|
||||||
cfg.BoolOpt('allow_agent',
|
cfg.BoolOpt('allow_agent',
|
||||||
default=False,
|
default=False,
|
||||||
help=("Set to False to disable connecting to the "
|
help=("Set to False to disable connecting to the "
|
||||||
@@ -97,7 +101,10 @@ def setup_tobiko_config(conf):
|
|||||||
|
|
||||||
ssh_proxy_client = _client.ssh_proxy_client()
|
ssh_proxy_client = _client.ssh_proxy_client()
|
||||||
if ssh_proxy_client:
|
if ssh_proxy_client:
|
||||||
key_file = _ssh_key_file.get_key_file(ssh_client=ssh_proxy_client)
|
key_file: str
|
||||||
|
for remote_key_file in conf.ssh.key_file:
|
||||||
|
key_file = _ssh_key_file.get_key_file(ssh_client=ssh_proxy_client,
|
||||||
|
key_file=remote_key_file)
|
||||||
if key_file and os.path.isfile(key_file):
|
if key_file and os.path.isfile(key_file):
|
||||||
LOG.info(f"Use SSH proxy server keyfile: {key_file}")
|
LOG.info(f"Use SSH proxy server keyfile: {key_file}")
|
||||||
conf.ssh.key_file.append(key_file)
|
conf.ssh.key_file.append(key_file)
|
||||||
|
|||||||
Reference in New Issue
Block a user