diff --git a/controllerconfig/controllerconfig/scripts/controller_config b/controllerconfig/controllerconfig/scripts/controller_config index 89857f6539..d90bf55d44 100755 --- a/controllerconfig/controllerconfig/scripts/controller_config +++ b/controllerconfig/controllerconfig/scripts/controller_config @@ -299,6 +299,21 @@ start() fi fi + if [ -e $CONFIG_DIR/openstack ] + then + if [ ! -e /etc/ssl/private/openstack ] + then + mkdir -p /etc/ssl/private/openstack + chmod 755 /etc/ssl/private/openstack + fi + + cp -p $CONFIG_DIR/openstack/* /etc/ssl/private/openstack + if [ $? -ne 0 ] + then + fatal_error "Unable to copy openstack certificate files" + fi + fi + if [ -e $CONFIG_DIR/iptables.rules ] then cp $CONFIG_DIR/iptables.rules /etc/platform/iptables.rules diff --git a/kubernetes/applications/stx-openstack/stx-openstack-helm/stx-openstack-helm/nova-api-proxy/values.yaml b/kubernetes/applications/stx-openstack/stx-openstack-helm/stx-openstack-helm/nova-api-proxy/values.yaml index 73486bd3e1..a10da41d64 100644 --- a/kubernetes/applications/stx-openstack/stx-openstack-helm/stx-openstack-helm/nova-api-proxy/values.yaml +++ b/kubernetes/applications/stx-openstack/stx-openstack-helm/stx-openstack-helm/nova-api-proxy/values.yaml @@ -288,7 +288,10 @@ secrets: identity: admin: nova-keystone-admin nova: nova-keystone-user - + tls: + compute: + api_proxy: + public: nova-api-proxy-tls-public manifests: configmap_bin: true diff --git a/puppet-manifests/src/modules/platform/manifests/client.pp b/puppet-manifests/src/modules/platform/manifests/client.pp index 9d21c84542..7590c0084f 100644 --- a/puppet-manifests/src/modules/platform/manifests/client.pp +++ b/puppet-manifests/src/modules/platform/manifests/client.pp @@ -27,6 +27,15 @@ class platform::client mode => '0644', content => generate('/usr/bin/openstack', 'complete'), } + + if $::personality == 'controller' { + file {'/etc/ssl/private/openstack': + ensure => 'directory', + owner => 'root', + group => 'root', + mode => '0755', + } + } } class platform::client::credentials::params ( diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/certificate_shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/certificate_shell.py index 8f97fb3f83..320b9d09e7 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/certificate_shell.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/certificate_shell.py @@ -70,7 +70,7 @@ def do_certificate_list(cc, args): @utils.arg('-m', '--mode', metavar='', help="optional mode: 'tpm_mode', 'murano', 'murano_ca'," - "'docker_registry'. " + "'docker_registry, 'openstack', 'openstack_ca'. " "Default is 'ssl'.") def do_certificate_install(cc, args): """Install certificate.""" diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/certificate.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/certificate.py index f6e39b82e9..43995ff709 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/certificate.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/certificate.py @@ -269,6 +269,8 @@ class CertificateController(rest.RestController): murano: install certificate for rabbit-murano murano_ca: install ca certificate for rabbit-murano docker_registry: install certificate for docker registry + openstack: install certificate for openstack + openstack_ca: install ca certificate for openstack """ log_start = cutils.timestamped("certificate_do_post_start") @@ -306,6 +308,16 @@ class CertificateController(rest.RestController): LOG.info(msg) return dict(success="", error=msg) + if mode.startswith(constants.CERT_MODE_OPENSTACK): + try: + pecan.request.dbapi.certificate_get_by_certtype( + constants.CERT_MODE_SSL) + except exception.CertificateTypeNotFound: + msg = "No openstack certificates have been added, " \ + "platform SSL certificate is not installed." + LOG.info(msg) + return dict(success="", error=msg) + if not fileitem.filename: return dict(success="", error="Error: No file uploaded") try: diff --git a/sysinv/sysinv/sysinv/sysinv/common/constants.py b/sysinv/sysinv/sysinv/sysinv/common/constants.py index 0e9afd400a..20b3a19a04 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/constants.py +++ b/sysinv/sysinv/sysinv/sysinv/common/constants.py @@ -1348,18 +1348,35 @@ SSL_CERT_CA_DIR = "/etc/ssl/certs/" SSL_CERT_CA_FILE = os.path.join(SSL_CERT_CA_DIR, CERT_CA_FILE) SSL_CERT_CA_FILE_SHARED = os.path.join(tsc.CONFIG_PATH, CERT_CA_FILE) +CERT_OPENSTACK_DIR = "/etc/ssl/private/openstack" +CERT_OPENSTACK_SHARED_DIR = os.path.join(tsc.CONFIG_PATH, 'openstack') +OPENSTACK_CERT_FILE = os.path.join(CERT_OPENSTACK_DIR, CERT_FILE) +OPENSTACK_CERT_KEY_FILE = os.path.join(CERT_OPENSTACK_DIR, CERT_KEY_FILE) +OPENSTACK_CERT_CA_FILE = os.path.join(CERT_OPENSTACK_DIR, CERT_CA_FILE) +OPENSTACK_CERT_FILE_SHARED = os.path.join(CERT_OPENSTACK_SHARED_DIR, + CERT_FILE) +OPENSTACK_CERT_KEY_FILE_SHARED = os.path.join(CERT_OPENSTACK_SHARED_DIR, + CERT_KEY_FILE) +OPENSTACK_CERT_CA_FILE_SHARED = os.path.join(CERT_OPENSTACK_SHARED_DIR, + CERT_CA_FILE) + CERT_MODE_SSL = 'ssl' CERT_MODE_SSL_CA = 'ssl_ca' CERT_MODE_TPM = 'tpm_mode' CERT_MODE_MURANO = 'murano' CERT_MODE_MURANO_CA = 'murano_ca' CERT_MODE_DOCKER_REGISTRY = 'docker_registry' +CERT_MODE_OPENSTACK = 'openstack' +CERT_MODE_OPENSTACK_CA = 'openstack_ca' CERT_MODES_SUPPORTED = [CERT_MODE_SSL, CERT_MODE_SSL_CA, CERT_MODE_TPM, CERT_MODE_MURANO, CERT_MODE_MURANO_CA, - CERT_MODE_DOCKER_REGISTRY] + CERT_MODE_DOCKER_REGISTRY, + CERT_MODE_OPENSTACK, + CERT_MODE_OPENSTACK_CA, + ] # CONFIG file permissions CONFIG_FILE_PERMISSION_ROOT_READ_ONLY = 0o400 diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py index f67c8ecf45..eca4c81900 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py @@ -5456,9 +5456,7 @@ class ConductorManager(service.PeriodicService): "personalities": personalities, "classes": ['platform::haproxy::runtime', 'openstack::keystone::endpoint::runtime', - 'openstack::horizon::runtime', - 'openstack::nova::api::runtime', - 'openstack::heat::engine::runtime'] + 'openstack::horizon::runtime'] } config_uuid = self._config_update_hosts(context, personalities) @@ -10204,8 +10202,9 @@ class ConductorManager(service.PeriodicService): certificates = self.dbapi.certificate_get_list() for certificate in certificates: - if certificate.certtype in [ - constants.CERT_MODE_SSL, constants.CERT_MODE_TPM]: + if certificate.certtype in [constants.CERT_MODE_SSL, + constants.CERT_MODE_TPM, + constants.CERT_MODE_OPENSTACK]: self.dbapi.certificate_destroy(certificate.uuid) personalities = [constants.CONTROLLER] @@ -10275,6 +10274,7 @@ class ConductorManager(service.PeriodicService): constants.CERT_MODE_TPM, constants.CERT_MODE_MURANO, constants.CERT_MODE_DOCKER_REGISTRY, + constants.CERT_MODE_OPENSTACK, ]: private_mode = True @@ -10576,6 +10576,73 @@ class ConductorManager(service.PeriodicService): 'permissions': constants.CONFIG_FILE_PERMISSION_ROOT_READ_ONLY, } self._config_update_file(context, config_uuid, config_dict) + elif mode == constants.CERT_MODE_OPENSTACK: + config_uuid = self._config_update_hosts(context, personalities) + key_path = constants.OPENSTACK_CERT_KEY_FILE + cert_path = constants.OPENSTACK_CERT_FILE + config_dict = { + 'personalities': personalities, + 'file_names': [key_path, cert_path], + 'file_content': {key_path: private_bytes, + cert_path: public_bytes}, + 'nobackup': True, + 'permissions': constants.CONFIG_FILE_PERMISSION_ROOT_READ_ONLY, + } + self._config_update_file(context, config_uuid, config_dict) + + if not os.path.exists(constants.CERT_OPENSTACK_SHARED_DIR): + os.makedirs(constants.CERT_OPENSTACK_SHARED_DIR) + # copy the certificate to shared directory + with os.fdopen(os.open(constants.OPENSTACK_CERT_FILE_SHARED, + os.O_CREAT | os.O_WRONLY, + constants.CONFIG_FILE_PERMISSION_ROOT_READ_ONLY), + 'wb') as f: + f.write(public_bytes) + with os.fdopen(os.open(constants.OPENSTACK_CERT_KEY_FILE_SHARED, + os.O_CREAT | os.O_WRONLY, + constants.CONFIG_FILE_PERMISSION_ROOT_READ_ONLY), + 'wb') as f: + f.write(private_bytes) + + config_uuid = self._config_update_hosts(context, personalities) + config_dict = { + "personalities": personalities, + "classes": ['openstack::keystone::endpoint::runtime', + 'openstack::horizon::runtime'] + } + self._config_apply_runtime_manifest(context, + config_uuid, + config_dict) + + self._remove_certificate_file(mode, certificate_file) + + elif mode == constants.CERT_MODE_OPENSTACK_CA: + config_uuid = self._config_update_hosts(context, personalities) + file_content = public_bytes + config_dict = { + 'personalities': personalities, + 'file_names': [constants.OPENSTACK_CERT_CA_FILE], + 'file_content': file_content, + 'permissions': constants.CONFIG_FILE_PERMISSION_DEFAULT, + } + self._config_update_file(context, config_uuid, config_dict) + + # copy the certificate to shared directory + with os.fdopen(os.open(constants.OPENSTACK_CERT_CA_FILE_SHARED, + os.O_CREAT | os.O_WRONLY, + constants.CONFIG_FILE_PERMISSION_DEFAULT), + 'wb') as f: + f.write(file_content) + + config_uuid = self._config_update_hosts(context, personalities) + config_dict = { + "personalities": personalities, + "classes": ['openstack::keystone::endpoint::runtime', + 'openstack::horizon::runtime'] + } + self._config_apply_runtime_manifest(context, + config_uuid, + config_dict) else: msg = "config_certificate unexpected mode=%s" % mode LOG.warn(msg) diff --git a/sysinv/sysinv/sysinv/sysinv/helm/glance.py b/sysinv/sysinv/sysinv/sysinv/helm/glance.py index 523b048d94..a414e86b4b 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/glance.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/glance.py @@ -90,6 +90,8 @@ class GlanceHelm(openstack.OpenstackBaseHelm): 'host_fqdn_override': self._get_endpoints_host_fqdn_overrides( constants.HELM_CHART_GLANCE), + 'scheme': self._get_endpoints_scheme_public_overrides(), + 'port': self._get_endpoints_port_api_public_overrides(), }, 'identity': { 'auth': self._get_endpoints_identity_overrides( diff --git a/sysinv/sysinv/sysinv/sysinv/helm/horizon.py b/sysinv/sysinv/sysinv/sysinv/helm/horizon.py index f7d3734b6e..80837896f0 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/horizon.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/horizon.py @@ -31,7 +31,12 @@ class HorizonHelm(openstack.OpenstackBaseHelm): } } }, - 'endpoints': self._get_endpoints_overrides() + 'endpoints': self._get_endpoints_overrides(), + 'network': { + 'node_port': { + 'enabled': self._get_network_node_port_overrides() + } + } } } @@ -49,6 +54,8 @@ class HorizonHelm(openstack.OpenstackBaseHelm): 'host_fqdn_override': self._get_endpoints_host_fqdn_overrides( constants.HELM_CHART_HORIZON), + 'port': self._get_endpoints_port_api_public_overrides(), + 'scheme': self._get_endpoints_scheme_public_overrides(), }, 'oslo_db': { 'auth': self._get_endpoints_oslo_db_overrides( @@ -134,3 +141,15 @@ class HorizonHelm(openstack.OpenstackBaseHelm): return False else: return super(HorizonHelm, self)._region_config() + + def _get_network_node_port_overrides(self): + # If openstack endpoint FQDN is configured, disable node_port 31000 + # which will enable the Ingress for the horizon service + endpoint_fqdn = self._get_service_parameter( + constants.SERVICE_TYPE_OPENSTACK, + constants.SERVICE_PARAM_SECTION_OPENSTACK_HELM, + constants.SERVICE_PARAM_NAME_ENDPOINT_DOMAIN) + if endpoint_fqdn: + return False + else: + return True diff --git a/sysinv/sysinv/sysinv/sysinv/helm/keystone.py b/sysinv/sysinv/sysinv/sysinv/helm/keystone.py index 88e6b08f55..1fb3db0c8c 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/keystone.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/keystone.py @@ -248,6 +248,8 @@ class KeystoneHelm(openstack.OpenstackBaseHelm): 'host_fqdn_override': self._get_endpoints_host_fqdn_overrides( self.SERVICE_NAME), + 'port': self._get_endpoints_port_api_public_overrides(), + 'scheme': self._get_endpoints_scheme_public_overrides(), }, 'oslo_db': { 'auth': self._get_endpoints_oslo_db_overrides( diff --git a/sysinv/sysinv/sysinv/sysinv/helm/neutron.py b/sysinv/sysinv/sysinv/sysinv/helm/neutron.py index b017aa8a58..981a695ba5 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/neutron.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/neutron.py @@ -388,6 +388,8 @@ class NeutronHelm(openstack.OpenstackBaseHelm): 'host_fqdn_override': self._get_endpoints_host_fqdn_overrides( self.SERVICE_NAME), + 'port': self._get_endpoints_port_api_public_overrides(), + 'scheme': self._get_endpoints_scheme_public_overrides(), }, 'oslo_cache': { 'auth': { diff --git a/sysinv/sysinv/sysinv/sysinv/helm/nova.py b/sysinv/sysinv/sysinv/sysinv/helm/nova.py index e2edb9f1d6..513005af6d 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/nova.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/nova.py @@ -56,6 +56,7 @@ class NovaHelm(openstack.OpenstackBaseHelm): SERVICE_NAME = 'nova' AUTH_USERS = ['nova', 'placement'] SERVICE_USERS = ['neutron', 'ironic'] + NOVNCPROXY_SERVICE_NAME = 'novncproxy' def get_overrides(self, namespace=None): @@ -158,6 +159,19 @@ class NovaHelm(openstack.OpenstackBaseHelm): 'auth': self._get_endpoints_identity_overrides( self.SERVICE_NAME, self.AUTH_USERS), }, + 'compute': { + 'host_fqdn_override': + self._get_endpoints_host_fqdn_overrides(self.SERVICE_NAME), + 'port': self._get_endpoints_port_api_public_overrides(), + 'scheme': self._get_endpoints_scheme_public_overrides(), + }, + 'compute_novnc_proxy': { + 'host_fqdn_override': + self._get_endpoints_host_fqdn_overrides( + self.NOVNCPROXY_SERVICE_NAME), + 'port': self._get_endpoints_port_api_public_overrides(), + 'scheme': self._get_endpoints_scheme_public_overrides(), + }, 'oslo_cache': { 'auth': { 'memcache_secret_key': @@ -197,11 +211,14 @@ class NovaHelm(openstack.OpenstackBaseHelm): constants.SERVICE_PARAM_SECTION_OPENSTACK_HELM, constants.SERVICE_PARAM_NAME_ENDPOINT_DOMAIN) if endpoint_domain is not None: - location = "%s.%s" % (constants.HELM_CHART_HORIZON, + location = "%s.%s" % (self.NOVNCPROXY_SERVICE_NAME, str(endpoint_domain.value).lower()) else: - location = self._get_oam_address() - url = "http://%s:6080/vnc_auto.html" % location + location = self._get_service_default_dns_name( + self.NOVNCPROXY_SERVICE_NAME) + + url = "%s://%s/vnc_auto.html" % (self._get_public_protocol(), + location) return url def _get_virt_type(self): diff --git a/sysinv/sysinv/sysinv/sysinv/helm/nova_api_proxy.py b/sysinv/sysinv/sysinv/sysinv/helm/nova_api_proxy.py index 7d8c67f5d1..1cf18093d8 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/nova_api_proxy.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/nova_api_proxy.py @@ -76,5 +76,7 @@ class NovaApiProxyHelm(openstack.OpenstackBaseHelm): 'host_fqdn_override': self._get_endpoints_host_fqdn_overrides( constants.HELM_CHART_NOVA), + 'port': self._get_endpoints_port_api_public_overrides(), + 'scheme': self._get_endpoints_scheme_public_overrides(), }, } diff --git a/sysinv/sysinv/sysinv/sysinv/helm/openstack.py b/sysinv/sysinv/sysinv/sysinv/helm/openstack.py index 84cd64a7ed..9f556f4801 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/openstack.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/openstack.py @@ -5,6 +5,7 @@ # import keyring +import os import subprocess from Crypto.PublicKey import RSA @@ -191,6 +192,27 @@ class OpenstackBaseHelm(base.BaseHelm): }) return overrides + def _get_file_content(self, filename): + file_contents = '' + with open(filename) as f: + file_contents = f.read() + return file_contents + + def _get_endpoint_public_tls(self): + overrides = {} + if (os.path.exists(constants.OPENSTACK_CERT_FILE) and + os.path.exists(constants.OPENSTACK_CERT_KEY_FILE)): + overrides.update({ + 'crt': self._get_file_content(constants.OPENSTACK_CERT_FILE), + 'key': self._get_file_content( + constants.OPENSTACK_CERT_KEY_FILE), + }) + if os.path.exists(constants.OPENSTACK_CERT_CA_FILE): + overrides.update({ + 'ca': self._get_file_content(constants.OPENSTACK_CERT_CA_FILE), + }) + return overrides + def _get_endpoints_host_fqdn_overrides(self, service_name): overrides = {'public': {}} endpoint_domain = self._get_service_parameter( @@ -201,6 +223,38 @@ class OpenstackBaseHelm(base.BaseHelm): overrides['public'].update({ 'host': service_name + '.' + str(endpoint_domain.value).lower() }) + + # Get TLS certificate files if installed + cert = None + try: + cert = self.dbapi.certificate_get_by_certtype( + constants.CERT_MODE_OPENSTACK) + except exception.CertificateTypeNotFound: + pass + if cert is not None: + tls_overrides = self._get_endpoint_public_tls() + if tls_overrides: + overrides['public'].update({ + 'tls': tls_overrides + }) + return overrides + + def _get_endpoints_scheme_public_overrides(self): + overrides = {} + if self._https_enabled(): + overrides = { + 'public': 'https' + } + return overrides + + def _get_endpoints_port_api_public_overrides(self): + overrides = {} + if self._https_enabled(): + overrides = { + 'api': { + 'public': 443 + } + } return overrides def _get_endpoints_oslo_db_overrides(self, service_name, users): @@ -335,3 +389,10 @@ class OpenstackBaseHelm(base.BaseHelm): } } return override + + def _get_public_protocol(self): + return 'https' if self._https_enabled() else 'http' + + def _get_service_default_dns_name(self, service): + return "{}.{}.svc.{}".format(service, common.HELM_NS_OPENSTACK, + constants.DEFAULT_DNS_SERVICE_DOMAIN)