Store BMC password in Openstack Barbican instead of keyring.

Replacing existing mechanism of storing BMC passwords in SysInv.
Implementing access to Barbican API in SysInv and using it to write
the passwords into a Barbican secrets. Note that a Barbican cannot
change the existing password inside its secret, so we need to remove
the old secret and create a new one in case of password update.
Another thing to mention: SysInv has to create Barbican secrets in
context of "services" project in order MTCE can read them later.

Change-Id: I7102a9662f3757c062ab310737f4ba08379d0100
Story: 2003108
Task: 27700
Signed-off-by: Alex Kozyrev <alex.kozyrev@windriver.com>
This commit is contained in:
Alex Kozyrev 2019-01-18 16:04:13 -05:00
parent c34dcbb2da
commit 2efd898eb7
8 changed files with 165 additions and 116 deletions

View File

@ -546,20 +546,13 @@ fm::database_max_overflow: 20
fm::database_max_pool_size: 1 fm::database_max_pool_size: 1
# Barbican # Barbican
barbican::use_syslog: true
barbican::log_facility: 'local2'
barbican::database_idle_timeout: 60
barbican::database_max_pool_size: 1
barbican::database_max_overflow: 10
barbican::alarm_history_time_to_live: 86400
barbican::auth::auth_endpoint_type: 'internalURL'
barbican::db::sync::user: 'root'
barbican::api::enabled: false barbican::api::enabled: false
barbican::api::service_name: 'barbican-api' barbican::api::service_name: 'barbican-api'
barbican::api::enable_proxy_headers_parsing: true barbican::api::enable_proxy_headers_parsing: true
barbican::api::logging::use_syslog: true
barbican::api::logging::log_facility: 'local2'
barbican::db::sync::user: 'root'
barbican::db::database_idle_timeout: 60
barbican::db::database_max_pool_size: 1
barbican::keystone-listener::enabled: false barbican::keystone-listener::enabled: false
barbican::worker::enabled: false barbican::worker::enabled: false

View File

@ -55,12 +55,15 @@ class openstack::barbican::service
} }
$api_fqdn = $::platform::params::controller_hostname $api_fqdn = $::platform::params::controller_hostname
$url_host = "http://${api_fqdn}:${api_port}" $url_host = "http://${api_fqdn}:${api_port}"
if str2bool($::is_initial_config_primary) {
$enabled = true
} else {
$enabled = false
}
include ::platform::amqp::params include ::platform::amqp::params
class { '::barbican::api': class { '::barbican::api':
enabled => true, enabled => $enabled,
manage_service => true,
bind_host => $api_host, bind_host => $api_host,
bind_port => $api_port, bind_port => $api_port,
host_href => $url_host, host_href => $url_host,

View File

@ -1,2 +1,2 @@
SRC_DIR="sysinv" SRC_DIR="sysinv"
TIS_PATCH_VER=299 TIS_PATCH_VER=300

View File

@ -2440,15 +2440,9 @@ class HostController(rest.RestController):
"problem persists then contact your system " "problem persists then contact your system "
"administrator.")) "administrator."))
# tell conductor to delete the keystore entry associated # tell conductor to delete the barbican entry associated with this host (if present)
# with this host (if present) pecan.request.rpcapi.delete_barbican_secret(pecan.request.context,
try: host.uuid)
pecan.request.rpcapi.unconfigure_keystore_account(
pecan.request.context,
KEYRING_BM_SERVICE,
ihost.uuid)
except exception.NotFound:
pass
# Notify patching to drop the host # Notify patching to drop the host
if ihost.hostname is not None: if ihost.hostname is not None:
@ -3795,7 +3789,7 @@ class HostController(rest.RestController):
raise wsme.exc.ClientSideError( raise wsme.exc.ClientSideError(
_("Host-add Rejected: bm_ip %s already exists") % phost['bm_ip']) _("Host-add Rejected: bm_ip %s already exists") % phost['bm_ip'])
# Update keyring with updated board management credentials (if supplied) # Update barbican with updated board management credentials (if supplied)
if (ohost['bm_username'] and phost['bm_username'] and if (ohost['bm_username'] and phost['bm_username'] and
(ohost['bm_username'] != phost['bm_username'])): (ohost['bm_username'] != phost['bm_username'])):
if not password_exists: if not password_exists:
@ -3806,12 +3800,11 @@ class HostController(rest.RestController):
ohost['bm_username'], ohost['bm_username'],
phost['bm_username']))) phost['bm_username'])))
if password_exists: if password_exists and patch_bm_password:
# The conductor will handle creating the keystore acct pecan.request.rpcapi.create_barbican_secret(pecan.request.context,
pecan.request.rpcapi.configure_keystore_account(pecan.request.context, phost['uuid'],
KEYRING_BM_SERVICE, patch_bm_password)
phost['uuid'],
patch_bm_password)
LOG.info("%s bm semantic checks for user_agent %s passed" % LOG.info("%s bm semantic checks for user_agent %s passed" %
(phost['hostname'], pecan.request.user_agent)) (phost['hostname'], pecan.request.user_agent))

View File

@ -45,7 +45,6 @@ import uuid
import xml.etree.ElementTree as ElementTree import xml.etree.ElementTree as ElementTree
from contextlib import contextmanager from contextlib import contextmanager
import keyring
import tsconfig.tsconfig as tsc import tsconfig.tsconfig as tsc
from cgcs_patch.patch_verify import verify_files from cgcs_patch.patch_verify import verify_files
from controllerconfig.upgrades import management as upgrades_management from controllerconfig.upgrades import management as upgrades_management
@ -9428,38 +9427,23 @@ class ConductorManager(service.PeriodicService):
target_load = self.dbapi.load_get(host_upgrade.target_load) target_load = self.dbapi.load_get(host_upgrade.target_load)
return target_load.software_version == tsc.SW_VERSION return target_load.software_version == tsc.SW_VERSION
def configure_keystore_account(self, context, service_name, def create_barbican_secret(self, context, name, payload):
username, password): """Calls Barbican API to create a secret
"""Synchronously, have a conductor configure a ks(keyring) account.
Does the following tasks:
- call keyring API to create an account under a service.
:param context: request context. :param context: request context.
:param service_name: the keystore service. :param name: secret name
:param username: account username :param payload: secret payload
:param password: account password
""" """
if (not service_name.strip()): self._openstack.create_barbican_secret(context=context,
raise exception.SysinvException(_( name=name, payload=payload)
"Keystore service is a blank value"))
keyring.set_password(service_name, username, password) def delete_barbican_secret(self, context, name):
"""Calls Barbican API to delete a secret
def unconfigure_keystore_account(self, context, service_name, username):
"""Synchronously, have a conductor unconfigure a ks(keyring) account.
Does the following tasks:
- call keyring API to delete an account under a service.
:param context: request context. :param context: request context.
:param service_name: the keystore service. :param name: secret name
:param username: account username
""" """
try: self._openstack.delete_barbican_secret(context=context, name=name)
keyring.delete_password(service_name, username)
except keyring.errors.PasswordDeleteError:
pass
def update_snmp_config(self, context): def update_snmp_config(self, context):
"""Update the snmpd configuration""" """Update the snmpd configuration"""

View File

@ -26,7 +26,7 @@ from keystoneclient.auth.identity import v3
from keystoneclient import session from keystoneclient import session
from sqlalchemy.orm import exc from sqlalchemy.orm import exc
from magnumclient.v1 import client as magnum_client_v1 from magnumclient.v1 import client as magnum_client_v1
from barbicanclient.v1 import client as barbican_client_v1
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -105,6 +105,9 @@ openstack_keystone_opts = [
cfg.StrOpt('magnum_region_name', cfg.StrOpt('magnum_region_name',
default='RegionOne', default='RegionOne',
help=_("Magnum Region Name")), help=_("Magnum Region Name")),
cfg.StrOpt('barbican_region_name',
default='RegionOne',
help=_("Barbican Region Name")),
cfg.StrOpt('project_name', cfg.StrOpt('project_name',
default='admin', default='admin',
help=_("keystone user project name")), help=_("keystone user project name")),
@ -129,9 +132,12 @@ class OpenStackOperator(object):
def __init__(self, dbapi): def __init__(self, dbapi):
self.dbapi = dbapi self.dbapi = dbapi
self.barbican_client = None
self.cinder_client = None self.cinder_client = None
self.keystone_client = None self.keystone_client = None
self.keystone_session = None self.keystone_session = None
self.openstack_keystone_client = None
self.openstack_keystone_session = None
self.magnum_client = None self.magnum_client = None
self.nova_client = None self.nova_client = None
self.neutron_client = None self.neutron_client = None
@ -530,29 +536,7 @@ class OpenStackOperator(object):
################# #################
# Keystone # Keystone
################# #################
def _get_keystone_session(self, service_config): def _get_keystone_password(self, service_config):
if not self.keystone_session:
if service_config == OPENSTACK_CONFIG:
password = keyring.get_password(cfg.CONF[OPENSTACK_CONFIG].
keyring_service,
cfg.CONF[OPENSTACK_CONFIG].
username)
else:
password = cfg.CONF[service_config].password
auth = v3.Password(auth_url=self._get_auth_url(service_config),
username=cfg.CONF[service_config].username,
password=password,
user_domain_name=cfg.CONF[service_config].
user_domain_name,
project_name=cfg.CONF[service_config].
project_name,
project_domain_name=cfg.CONF[service_config].
project_domain_name)
self.keystone_session = session.Session(auth=auth)
return self.keystone_session
def _get_keystoneclient(self, service_config):
if service_config == OPENSTACK_CONFIG: if service_config == OPENSTACK_CONFIG:
password = keyring.get_password(cfg.CONF[OPENSTACK_CONFIG]. password = keyring.get_password(cfg.CONF[OPENSTACK_CONFIG].
keyring_service, keyring_service,
@ -560,18 +544,70 @@ class OpenStackOperator(object):
username) username)
else: else:
password = cfg.CONF[service_config].password password = cfg.CONF[service_config].password
return password
if not self.keystone_client: # should not cache this forever def _get_new_keystone_session(self, service_config):
self.keystone_client = keystone_client.Client( auth = v3.Password(auth_url=self._get_auth_url(service_config),
username=cfg.CONF[service_config].username, username=cfg.CONF[service_config].username,
user_domain_name=cfg.CONF[service_config].user_domain_name, password=self._get_keystone_password(service_config),
project_name=cfg.CONF[service_config].project_name, user_domain_name=cfg.CONF[service_config].
project_domain_name=cfg.CONF[service_config] user_domain_name,
.project_domain_name, project_name=cfg.CONF[service_config].
password=password, project_name,
auth_url=self._get_auth_url(service_config), project_domain_name=cfg.CONF[service_config].
region_name=cfg.CONF[service_config].region_name) project_domain_name)
return self.keystone_client sess = session.Session(auth=auth)
return sess
def _get_cached_keystone_session(self, service_config):
if service_config == OPENSTACK_CONFIG:
return self.openstack_keystone_session
else:
return self.keystone_session
def _set_cached_keystone_session(self, service_config, sess):
if service_config == OPENSTACK_CONFIG:
self.openstack_keystone_session = sess
else:
self.keystone_session = sess
def _get_keystone_session(self, service_config):
sess = self._get_cached_keystone_session(service_config)
if not sess:
sess = self._get_new_keystone_session(service_config)
self._set_cached_keystone_session(service_config, sess)
return sess
def _get_new_keystone_client(self, service_config):
client = keystone_client.Client(
username=cfg.CONF[service_config].username,
user_domain_name=cfg.CONF[service_config].user_domain_name,
project_name=cfg.CONF[service_config].project_name,
project_domain_name=cfg.CONF[service_config]
.project_domain_name,
password=self._get_keystone_password(service_config),
auth_url=self._get_auth_url(service_config),
region_name=cfg.CONF[service_config].region_name)
return client
def _get_cached_keystone_client(self, service_config):
if service_config == OPENSTACK_CONFIG:
return self.openstack_keystone_client
else:
return self.keystone_client
def _set_cached_keystone_client(self, service_config, client):
if service_config == OPENSTACK_CONFIG:
self.openstack_keystone_client = client
else:
self.keystone_client = client
def _get_keystone_client(self, service_config):
client = self._get_cached_keystone_client(service_config)
if not client:
client = self._get_new_keystone_client(service_config)
self._set_cached_keystone_client(service_config, client)
return client
################# #################
# Cinder # Cinder
@ -785,6 +821,56 @@ class OpenStackOperator(object):
LOG.error("Unable to get backend list of magnum clusters") LOG.error("Unable to get backend list of magnum clusters")
return 0 return 0
#################
# Barbican
#################
def _get_barbicanclient(self):
if not self.barbican_client:
self.barbican_client = barbican_client_v1.Client(
session=self._get_keystone_session(PLATFORM_CONFIG),
interface='internalURL',
region_name=cfg.CONF[OPENSTACK_CONFIG].barbican_region_name)
return self.barbican_client
def get_barbican_secret_by_name(self, context, name):
try:
client = self._get_barbicanclient()
secret_list = client.secrets.list(name=name)
secret = next(iter(secret_list), None)
return secret
except Exception:
LOG.error("Unable to find Barbican secret %s", name)
return None
def create_barbican_secret(self, context, name, payload):
if not payload:
LOG.error("Empty password is passed to Barbican %s" % name)
return None
try:
client = self._get_barbicanclient()
secret = self.get_barbican_secret_by_name(context, name)
if secret:
client.secrets.delete(secret.secret_ref)
secret = client.secrets.create(name, payload)
secret.store()
return secret.secret_ref
except Exception:
LOG.error("Unable to create Barbican secret %s" % name)
return None
def delete_barbican_secret(self, context, name):
try:
client = self._get_barbicanclient()
secret = self.get_barbican_secret_by_name(context=context, name=name)
if not secret:
LOG.error("Unable to delete unknown Barbican secret %s" % name)
return False
client.secrets.delete(secret_ref=secret.secret_ref)
return True
except Exception:
LOG.error("Unable to delete Barbican secret %s" % name)
return False
def get_region_name(region): def get_region_name(region):
# get region name from platform.conf # get region name from platform.conf

View File

@ -1343,37 +1343,27 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy):
return self.call(context, return self.call(context,
self.make_msg('update_vswitch_type')) self.make_msg('update_vswitch_type'))
def configure_keystore_account(self, context, service_name, def create_barbican_secret(self, context, name, payload):
username, password): """Calls Barbican API to create a secret
"""Synchronously, have a conductor configure a ks(keyring) account.
Does the following tasks:
- call keyring API to create an account under a service.
:param context: request context. :param context: request context.
:param service_name: the keystore service. :param name: secret name
:param username: account username :param payload: secret payload
:param password: account password
""" """
return self.call(context, return self.call(context,
self.make_msg('configure_keystore_account', self.make_msg('create_barbican_secret',
service_name=service_name, name=name,
username=username, password=password)) payload=payload))
def unconfigure_keystore_account(self, context, service_name, username): def delete_barbican_secret(self, context, name):
"""Synchronously, have a conductor unconfigure a ks(keyring) account. """Calls Barbican API to delete a secret
Does the following tasks:
- call keyring API to delete an account under a service.
:param context: request context. :param context: request context.
:param service_name: the keystore service. :param name: secret name
:param username: account username
""" """
return self.call(context, return self.call(context,
self.make_msg('unconfigure_keystore_account', self.make_msg('delete_barbican_secret',
service_name=service_name, name=name))
username=username))
def update_snmp_config(self, context): def update_snmp_config(self, context):
"""Synchronously, have a conductor configure the SNMP configuration. """Synchronously, have a conductor configure the SNMP configuration.

View File

@ -59,7 +59,7 @@ class BarbicanPuppet(openstack.OpenstackBasePuppet):
'barbican::keystone::authtoken::project_domain_name': 'barbican::keystone::authtoken::project_domain_name':
self._get_service_project_domain_name(), self._get_service_project_domain_name(),
'barbican::keystone::authtoken::project_name': 'barbican::keystone::authtoken::project_name':
self._get_service_tenant_name(), self._get_service_project_name(),
'barbican::keystone::authtoken::region_name': 'barbican::keystone::authtoken::region_name':
self._keystone_region_name(), self._keystone_region_name(),
'barbican::keystone::authtoken::username': ksuser, 'barbican::keystone::authtoken::username': ksuser,