Allow to connect to SSH server using an intermediate SSH server

For debugging purposes it could be handy to execute tests
on your workstation and connect test instances via SSH passing
throw an intermediate SSH server.

This allow to configure in tempest.conf an intermediate
SSH client connection to be used from tests to create SSH
connections to VMs.

Example of configuration in tempest.conf:

  [neutron_plugin_options]
  ssh_proxy_jump_host = some.ssh.server
  ssh_proxy_jump_username = root
  # ssh_proxy_jump_password = # better using keys
  proxy_jump_keyfile = ~/.ssh/id_rsa
  proxy_jump_port = 22

Change-Id: Icae73c2cddbdcd8da2b4cdb07a7027791642c6a8
This commit is contained in:
Federico Ressi 2018-04-19 13:02:33 +02:00
parent 5fece0e41f
commit e9c89bf0ac
2 changed files with 113 additions and 5 deletions

View File

@ -12,13 +12,103 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
from oslo_log import log
from tempest.lib.common import ssh
from neutron_tempest_plugin import config
CONF = config.CONF
LOG = log.getLogger(__name__)
class Client(ssh.Client):
def __init__(self, *args, **kwargs):
if 'timeout' not in kwargs:
kwargs['timeout'] = config.CONF.validation.ssh_timeout
super(Client, self).__init__(*args, **kwargs)
timeout = CONF.validation.ssh_timeout
proxy_jump_host = CONF.neutron_plugin_options.ssh_proxy_jump_host
proxy_jump_username = CONF.neutron_plugin_options.ssh_proxy_jump_username
proxy_jump_password = CONF.neutron_plugin_options.ssh_proxy_jump_password
proxy_jump_keyfile = CONF.neutron_plugin_options.ssh_proxy_jump_keyfile
proxy_jump_port = CONF.neutron_plugin_options.ssh_proxy_jump_port
def __init__(self, host, username, password=None, timeout=None, pkey=None,
channel_timeout=10, look_for_keys=False, key_filename=None,
port=22, proxy_client=None):
timeout = timeout or self.timeout
if self.proxy_jump_host:
# Perform all SSH connections passing through configured SSH server
proxy_client = proxy_client or self.create_proxy_client(
timeout=timeout, channel_timeout=channel_timeout)
super(Client, self).__init__(
host=host, username=username, password=password, timeout=timeout,
pkey=pkey, channel_timeout=channel_timeout,
look_for_keys=look_for_keys, key_filename=key_filename, port=port,
proxy_client=proxy_client)
@classmethod
def create_proxy_client(cls, look_for_keys=True, **kwargs):
host = cls.proxy_jump_host
if not host:
# proxy_jump_host string cannot be empty or None
raise ValueError(
"'proxy_jump_host' configuration option is empty.")
# Let accept an empty string as a synonymous of default value on below
# options
password = cls.proxy_jump_password or None
key_file = cls.proxy_jump_keyfile or None
username = cls.proxy_jump_username
# Port must be a positive integer
port = cls.proxy_jump_port
if port <= 0 or port > 65535:
raise ValueError(
"Invalid value for 'proxy_jump_port' configuration option: "
"{!r}".format(port))
login = "{username}@{host}:{port}".format(username=username, host=host,
port=port)
if key_file:
# expand ~ character with user HOME directory
key_file = os.path.expanduser(key_file)
if os.path.isfile(key_file):
LOG.debug("Going to create SSH connection to %r using key "
"file: %s", login, key_file)
else:
# This message could help the user to identify a
# mis-configuration in tempest.conf
raise ValueError(
"Cannot find file specified as 'proxy_jump_keyfile' "
"option: {!r}".format(key_file))
elif password:
LOG.debug("Going to create SSH connection to %r using password.",
login)
elif look_for_keys:
# This message could help the user to identify a mis-configuration
# in tempest.conf
LOG.info("Both 'proxy_jump_password' and 'proxy_jump_keyfile' "
"options are empty. Going to create SSH connection to %r "
"looking for key file location into %r directory.",
login, os.path.expanduser('~/.ssh'))
else:
# An user that forces look_for_keys=False should really know what
# he really wants
LOG.warning("No authentication method provided to create an SSH "
"connection to %r. If it fails, then please "
"set 'proxy_jump_keyfile' to provide a valid SSH key "
"file.", login)
return ssh.Client(
host=host, username=username, password=password,
look_for_keys=look_for_keys, key_filename=key_file,
port=port, proxy_client=None, **kwargs)

View File

@ -56,7 +56,25 @@ NeutronPluginOptions = [
'"provider:network_type":<TYPE> - string '
'"mtu":<MTU> - integer '
'"cidr"<SUBNET/MASK> - string '
'"provider:segmentation_id":<VLAN_ID> - integer')
'"provider:segmentation_id":<VLAN_ID> - integer'),
# Option for feature to connect via SSH to VMs using an intermediate SSH
# server
cfg.StrOpt('ssh_proxy_jump_host',
default=None,
help='Proxy jump host used to connect via SSH to VMs..'),
cfg.StrOpt('ssh_proxy_jump_username',
default='root',
help='User name used to connect to "ssh_proxy_jump_host".'),
cfg.StrOpt('ssh_proxy_jump_password',
default=None,
help='Password used to connect to "ssh_proxy_jump_host".'),
cfg.StrOpt('ssh_proxy_jump_keyfile',
default=None,
help='Keyfile used to connect to "ssh_proxy_jump_host".'),
cfg.IntOpt('ssh_proxy_jump_port',
default=22,
help='Port used to connect to "ssh_proxy_jump_host".'),
]
# TODO(amuller): Redo configuration options registration as part of the planned