Flexible IPMI credential persistence method configuration
Instead of only file-based persistence which leaves files with credentials on the conductor disk for the duration of the session. User can now pass ``True`` to the ``store_cred_in_env`` parameter which instead stores IPMI password as an environment variable, still for the duration of the session, but limiting exposure to just the user session of ironic and anyone that has access to it. Defaults to ``False``. Closes-Bug: #2058749 Change-Id: Icd91e969e5c58bf42fc50958c3cd1acabd36ccdf
This commit is contained in:
parent
7fafe27ba7
commit
2548f022c5
@ -82,6 +82,28 @@ with an IPMItool-based driver. For example::
|
||||
--driver-info ipmi_username=<username> \
|
||||
--driver-info ipmi_password=<password>
|
||||
|
||||
|
||||
Changing The Default IPMI Credential Persistence Method
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``store_cred_in_env``: :oslo.config:option:`ipmi.store_cred_in_env`.
|
||||
|
||||
The `store_cred_in_env` configuration option allow users to switch
|
||||
between file-based and environment variable persistence methods for
|
||||
IPMI password.
|
||||
|
||||
For the temporary file option, long lived IPMI sessions, such as those for
|
||||
console support, leave files with credentials on the conductor disk for the
|
||||
duration of the session.
|
||||
|
||||
To switch to environment variable persistence, set the
|
||||
``store_cred_in_env`` parameter to ``True`` in the configuration file:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[ipmi]
|
||||
store_cred_in_env = True
|
||||
|
||||
Advanced configuration
|
||||
======================
|
||||
|
||||
|
@ -73,6 +73,11 @@ opts = [
|
||||
'additional debugging output. This is a separate '
|
||||
'option as ipmitool can log a substantial amount '
|
||||
'of misleading text when in this mode.')),
|
||||
cfg.BoolOpt('store_cred_in_env',
|
||||
default=False,
|
||||
help=_('Boolean flag to determine IPMI password persistence '
|
||||
'method. Defaults to False (file-based persistence). '
|
||||
)),
|
||||
cfg.ListOpt('cipher_suite_versions',
|
||||
default=[],
|
||||
help=_('List of possible cipher suites versions that can '
|
||||
|
@ -71,9 +71,9 @@ REQUIRED_PROPERTIES = {
|
||||
}
|
||||
OPTIONAL_PROPERTIES = {
|
||||
'ipmi_password': _("password. Optional."),
|
||||
'ipmi_hex_kg_key': _('Kg key for IPMIv2 authentication. '
|
||||
'The key is expected in hexadecimal format. '
|
||||
'Optional.'),
|
||||
'ipmi_hex_kg_key': _("Kg key for IPMIv2 authentication. "
|
||||
"The key is expected in hexadecimal format. "
|
||||
"Optional."),
|
||||
'ipmi_port': _("remote IPMI RMCP port. Optional."),
|
||||
'ipmi_priv_level': _("privilege level; default is ADMINISTRATOR. One of "
|
||||
"%s. Optional.") % ', '.join(VALID_PRIV_LEVELS),
|
||||
@ -280,18 +280,23 @@ def _console_pwfile_path(uuid):
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _make_password_file(password):
|
||||
"""Makes a temporary file that contains the password.
|
||||
def _prepare_ipmi_password(driver_info):
|
||||
"""Prepares the IPMI password by either setting it in the environment
|
||||
|
||||
:param password: the password
|
||||
:returns: the absolute pathname of the temporary file
|
||||
or creating a temporary file.
|
||||
|
||||
:param driver_info: the ipmitool parameters for accessing a node.
|
||||
:returns: the absolute pathname of the password
|
||||
:raises: PasswordFileFailedToCreate from creating or writing to the
|
||||
temporary file
|
||||
"""
|
||||
if CONF.ipmi.store_cred_in_env:
|
||||
yield _persist_ipmi_password(driver_info)
|
||||
else:
|
||||
f = None
|
||||
try:
|
||||
f = tempfile.NamedTemporaryFile(mode='w', dir=CONF.tempdir)
|
||||
f.write(str(password))
|
||||
f.write(str(driver_info['password'] or '\0'))
|
||||
f.flush()
|
||||
except (IOError, OSError) as exc:
|
||||
if f is not None:
|
||||
@ -303,11 +308,11 @@ def _make_password_file(password):
|
||||
f.close()
|
||||
|
||||
try:
|
||||
# NOTE(jlvillal): This yield can not be in the try/except block above
|
||||
# because an exception by the caller of this function would then get
|
||||
# changed to a PasswordFileFailedToCreate exception which would mislead
|
||||
# about the problem and its cause.
|
||||
yield f.name
|
||||
# NOTE(jlvillal): This yield can not be in the try/except block
|
||||
# above because an exception by the caller of this function would
|
||||
# then get changed to a PasswordFileFailedToCreate exception which
|
||||
# would mislead about the problem and its cause.
|
||||
yield ('-f', f.name)
|
||||
finally:
|
||||
if f is not None:
|
||||
f.close()
|
||||
@ -495,6 +500,25 @@ def _get_ipmitool_args(driver_info, pw_file=None):
|
||||
return args
|
||||
|
||||
|
||||
def _persist_ipmi_password(driver_info):
|
||||
"""Persists IPMI password for passing to the ipmitool
|
||||
|
||||
:param driver_info: driver info with the ipmitool parameters
|
||||
"""
|
||||
env = {}
|
||||
if CONF.ipmi.store_cred_in_env:
|
||||
password = driver_info.get('password')
|
||||
if password:
|
||||
env = {'IPMI_PASSWORD': password}
|
||||
return '-E', env
|
||||
|
||||
path = _console_pwfile_path(driver_info['uuid'])
|
||||
pw_file = console_utils.make_persistent_password_file(
|
||||
path, driver_info['password'] or '\0')
|
||||
|
||||
return '-f', pw_file
|
||||
|
||||
|
||||
def _ipmitool_timing_args():
|
||||
if not _is_option_supported('timing'):
|
||||
return []
|
||||
@ -644,10 +668,15 @@ def _exec_ipmitool(driver_info, command, check_exit_code=None,
|
||||
# 'ipmitool' command will prompt password if there is no '-f'
|
||||
# option, we set it to '\0' to write a password file to support
|
||||
# empty password
|
||||
with _make_password_file(driver_info['password'] or '\0') as pw_file:
|
||||
cmd_args.append('-f')
|
||||
cmd_args.append(pw_file)
|
||||
|
||||
with _prepare_ipmi_password(driver_info) as (flag, env_path):
|
||||
cmd_args.append(flag)
|
||||
if CONF.ipmi.store_cred_in_env:
|
||||
extra_args['env_variables'] = env_path
|
||||
else:
|
||||
cmd_args.append(env_path)
|
||||
cmd_args.extend(command.split(" "))
|
||||
|
||||
try:
|
||||
out, err = utils.execute(*cmd_args, **extra_args)
|
||||
return out, err
|
||||
@ -679,6 +708,7 @@ def _exec_ipmitool(driver_info, command, check_exit_code=None,
|
||||
'Error: %(error)s',
|
||||
{'node': driver_info['uuid'],
|
||||
'cmd': e.cmd, 'error': e})
|
||||
|
||||
finally:
|
||||
LAST_CMD_TIME[driver_info['address']] = time.time()
|
||||
|
||||
@ -1543,17 +1573,17 @@ class IPMIConsole(base.ConsoleInterface):
|
||||
created
|
||||
:raises: ConsoleSubprocessFailed when invoking the subprocess failed
|
||||
"""
|
||||
path = _console_pwfile_path(driver_info['uuid'])
|
||||
pw_file = console_utils.make_persistent_password_file(
|
||||
path, driver_info['password'] or '\0')
|
||||
ipmi_cmd = self._get_ipmi_cmd(driver_info, pw_file)
|
||||
ipmi_cmd += ' sol activate'
|
||||
|
||||
try:
|
||||
start_method(driver_info['uuid'], driver_info['port'], ipmi_cmd)
|
||||
except (exception.ConsoleError, exception.ConsoleSubprocessFailed):
|
||||
with excutils.save_and_reraise_exception():
|
||||
ironic_utils.unlink_without_raise(path)
|
||||
cmd = self._get_ipmi_cmd(driver_info, pw_file=None)
|
||||
cmd += ' sol activate'
|
||||
start_method(driver_info['uuid'], driver_info['port'], cmd)
|
||||
except (exception.ConsoleError,
|
||||
exception.ConsoleSubprocessFailed) as e:
|
||||
LOG.exception('IPMI Error while attempting "%(cmd)s" '
|
||||
'for node %(node)s. Error: %(error)s',
|
||||
{'node': driver_info['uuid'],
|
||||
'cmd': cmd, 'error': e})
|
||||
raise
|
||||
|
||||
|
||||
class IPMIShellinaboxConsole(IPMIConsole):
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds a new configuration option ``store_cred_in_env`` to allow
|
||||
switching between file-based and environment variable persistence for
|
||||
IPMI credentials. Defaults to ``False``.
|
Loading…
Reference in New Issue
Block a user