config/sysinv/sysinv/sysinv/sysinv/helm/keystone.py

291 lines
12 KiB
Python

#
# Copyright (c) 2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from six.moves import configparser
import os
from sysinv.common import constants
from sysinv.common import exception
from sysinv.openstack.common import log as logging
from sysinv.helm import common
from sysinv.helm import openstack
LOG = logging.getLogger(__name__)
OPENSTACK_PASSWORD_RULES_FILE = '/etc/keystone/password-rules.conf'
class KeystoneHelm(openstack.OpenstackBaseHelm):
"""Class to encapsulate helm operations for the keystone chart"""
CHART = constants.HELM_CHART_KEYSTONE
SERVICE_NAME = constants.HELM_CHART_KEYSTONE
SERVICE_PATH = '/v3'
DEFAULT_DOMAIN_NAME = 'default'
def get_overrides(self, namespace=None):
overrides = {
common.HELM_NS_OPENSTACK: {
'pod': self._get_pod_overrides(),
'conf': self._get_conf_overrides(),
'endpoints': self._get_endpoints_overrides(),
'images': self._get_images_overrides(),
}
}
if namespace in self.SUPPORTED_NAMESPACES:
return overrides[namespace]
elif namespace:
raise exception.InvalidHelmNamespace(chart=self.CHART,
namespace=namespace)
else:
return overrides
def _get_pod_overrides(self):
overrides = {
'replicas': {
'api': self._num_controllers()
},
'lifecycle': {
'termination_grace_period': {
'api': {
'timeout': 60
}
}
}
}
if self.docker_repo_source != common.DOCKER_SRC_OSH:
overrides.update({'user': {'keystone': {'uid': 0}}})
return overrides
def _get_images_overrides(self):
heat_image = self._operator.chart_operators[
constants.HELM_CHART_HEAT].docker_image
return {
'tags': {
'bootstrap': heat_image,
'db_drop': heat_image,
'db_init': heat_image,
'keystone_api': self.docker_image,
'keystone_credential_rotate': self.docker_image,
'keystone_credential_setup': self.docker_image,
'keystone_db_sync': self.docker_image,
'keystone_domain_manage': self.docker_image,
'keystone_fernet_rotate': self.docker_image,
'keystone_fernet_setup': self.docker_image,
'ks_user': heat_image,
}
}
def _get_conf_keystone_default_overrides(self):
return {
'max_token_size': 255, # static controller.yaml => chart default
'debug': False, # static controller.yaml => chart default
'use_syslog': True, # static controller.yaml
'syslog_log_facility': 'local2', # static controller.yaml
'log_file': '/dev/null', # static controller.yaml
# 'admin_token': self._generate_random_password(length=32)
}
def _get_conf_keystone_database_overrides(self):
return {
'idle_timeout': 60, # static controller.yaml
'max_pool_size': 1, # static controller.yaml
'max_overflow': 50, # static controller.yaml
}
def _get_conf_keystone_oslo_middleware_overrides(self):
return {
'enable_proxy_headers_parsing': True # static controller.yaml
}
def _get_conf_keystone_token_overrides(self):
return {
'provider': 'fernet' # static controller.yaml => chart default
}
def _get_conf_keystone_identity_overrides(self):
return {
'driver': 'sql' # static controller.yaml
}
def _get_conf_keystone_assignment_overrides(self):
return {
'driver': 'sql' # static controller.yaml
}
@staticmethod
def _extract_openstack_password_rules_from_file(
rules_file, section="security_compliance"):
try:
config = configparser.RawConfigParser()
parsed_config = config.read(rules_file)
if not parsed_config:
msg = ("Cannot parse rules file: %s" % rules_file)
raise Exception(msg)
if not config.has_section(section):
msg = ("Required section '%s' not found in rules file" % section)
raise Exception(msg)
rules = config.items(section)
if not rules:
msg = ("section '%s' contains no configuration options" % section)
raise Exception(msg)
return dict(rules)
except Exception:
raise Exception("Failed to extract password rules from file")
def _get_password_rule(self):
password_rule = {}
if os.path.isfile(OPENSTACK_PASSWORD_RULES_FILE):
try:
passwd_rules = \
KeystoneHelm._extract_openstack_password_rules_from_file(
OPENSTACK_PASSWORD_RULES_FILE)
password_rule.update({
'unique_last_password_count':
int(passwd_rules['unique_last_password_count']),
'password_regex':
self.quoted_str(passwd_rules['password_regex']),
'password_regex_description':
self.quoted_str(
passwd_rules['password_regex_description'])
})
except Exception:
pass
return password_rule
def _get_conf_keystone_security_compliance_overrides(self):
overrides = {
'unique_last_password_count': 2, # static controller.yaml
'password_regex': self.quoted_str('^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()<>{}+=_\\\[\]\-?|~`,.;:]).{7,}$'),
'password_regex_description': self.quoted_str('Password must have a minimum length of 7 characters, and must contain at least 1 upper case, 1 lower case, 1 digit, and 1 special character'),
}
overrides.update(self._get_password_rule())
return overrides
def _get_conf_keystone_overrides(self):
return {
'DEFAULT': self._get_conf_keystone_default_overrides(),
'database': self._get_conf_keystone_database_overrides(),
'oslo_middleware': self._get_conf_keystone_oslo_middleware_overrides(),
'token': self._get_conf_keystone_token_overrides(),
'identity': self._get_conf_keystone_identity_overrides(),
'assignment': self._get_conf_keystone_assignment_overrides(),
'security_compliance': self._get_conf_keystone_security_compliance_overrides(),
}
def _get_conf_policy_overrides(self):
return {
"admin_required": "role:admin or is_admin:1",
"service_role": "role:service",
"service_or_admin": "rule:admin_required or rule:service_role",
"owner": "user_id:%(user_id)s",
"admin_or_owner": "rule:admin_required or rule:owner",
"token_subject": "user_id:%(target.token.user_id)s",
"admin_or_token_subject": "rule:admin_required or rule:token_subject",
"service_admin_or_token_subject":
"rule:service_or_admin or rule:token_subject",
"protected_domains":
"'heat':%(target.domain.name)s or 'magnum':%(target.domain.name)s",
"protected_projects":
"'admin':%(target.project.name)s or 'services':%(target.project.name)s",
"protected_admins":
"'admin':%(target.user.name)s or 'heat_admin':%(target.user.name)s"
" or 'dcmanager':%(target.user.name)s",
"protected_roles":
"'admin':%(target.role.name)s or 'heat_admin':%(target.user.name)s",
"protected_services": [
["'aodh':%(target.user.name)s"],
["'ceilometer':%(target.user.name)s"],
["'cinder':%(target.user.name)s"],
["'glance':%(target.user.name)s"],
["'heat':%(target.user.name)s"],
["'neutron':%(target.user.name)s"],
["'nova':%(target.user.name)s"],
["'patching':%(target.user.name)s"],
["'sysinv':%(target.user.name)s"],
["'mtce':%(target.user.name)s"],
["'magnum':%(target.user.name)s"],
["'murano':%(target.user.name)s"],
["'panko':%(target.user.name)s"],
["'gnocchi':%(target.user.name)s"]
],
"identity:delete_service": "rule:admin_required and not rule:protected_services",
"identity:delete_domain": "rule:admin_required and not rule:protected_domains",
"identity:delete_project": "rule:admin_required and not rule:protected_projects",
"identity:delete_user": "rule:admin_required and not (rule:protected_admins or rule:protected_services)",
"identity:change_password": "rule:admin_or_owner and not rule:protected_services",
"identity:delete_role": "rule:admin_required and not rule:protected_roles",
}
def _get_conf_overrides(self):
return {
'keystone': self._get_conf_keystone_overrides(),
'policy': self._get_conf_policy_overrides()
}
def _region_config(self):
# A wrapper over the Base region_config check.
if (self._distributed_cloud_role() ==
constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD):
return False
else:
return super(KeystoneHelm, self)._region_config()
def _get_endpoints_overrides(self):
return {
'identity': {
'auth': self._get_endpoints_identity_overrides(
self.SERVICE_NAME, []),
},
'oslo_cache': {
'auth': {
'memcached_secret_key':
self._get_common_password('auth_memcache_key')
}
},
'oslo_db': {
'auth': self._get_endpoints_oslo_db_overrides(
self.SERVICE_NAME, [self.SERVICE_NAME])
},
'oslo_messaging': {
'auth': self._get_endpoints_oslo_messaging_overrides(
self.SERVICE_NAME, [self.SERVICE_NAME])
},
}
def get_admin_user_name(self):
if self._region_config():
service_config = self._get_service_config(self.SERVICE_NAME)
if service_config is not None:
return service_config.capabilities.get('admin_user_name')
return common.USER_ADMIN
def get_admin_user_domain(self):
if self._region_config():
service_config = self._get_service_config(self.SERVICE_NAME)
if service_config is not None:
return service_config.capabilities.get('admin_user_domain')
return self.DEFAULT_DOMAIN_NAME
def get_admin_project_name(self):
if self._region_config():
service_config = self._get_service_config(self.SERVICE_NAME)
if service_config is not None:
return service_config.capabilities.get('admin_project_name')
return common.USER_ADMIN
def get_admin_project_domain(self):
if self._region_config():
service_config = self._get_service_config(self.SERVICE_NAME)
if service_config is not None:
return service_config.capabilities.get('admin_project_domain')
return self.DEFAULT_DOMAIN_NAME