Optional https for containerized openstack

The certificate for openstack services are installed and stored
under /etc/ssl/private/openstack. The endpoint tls parameters are
configured by the helm overrides.

Tests performed:
AIO-SX: application apply, reapply and launch instance
AIO-DX: application apply, reapply and launch instance
Standard system: application apply, reapply and launch instance
Pause and Resume instance. Ensure that no audit error is seen.

Story: 2004433
Task: 28096

Change-Id: Ib81f9541ebf116dee817e0b55f31866ed0d283f0
Signed-off-by: Teresa Ho <teresa.ho@windriver.com>
This commit is contained in:
Teresa Ho 2019-03-08 12:11:52 -05:00
parent 3b126c2f43
commit 2336e855bd
14 changed files with 240 additions and 12 deletions

View File

@ -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

View File

@ -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

View File

@ -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 (

View File

@ -70,7 +70,7 @@ def do_certificate_list(cc, args):
@utils.arg('-m', '--mode',
metavar='<mode>',
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."""

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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(

View File

@ -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

View File

@ -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(

View File

@ -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': {

View File

@ -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):

View File

@ -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(),
},
}

View File

@ -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)