Merge "Handle user-provided TLS certificate/key for the undercloud"
This commit is contained in:
commit
1e033c98fa
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Similar to what instack-undercloud does, the containerized undercloud can
|
||||
now take user-provided certificates/keys in the bundled PEM format. This is
|
||||
done through the service_certificate option and is processed tripleoclient.
|
@ -18,3 +18,4 @@ osc-lib>=1.8.0 # Apache-2.0
|
||||
websocket-client<=0.40.0,>=0.33.0 # LGPLv2+
|
||||
tripleo-common>=7.1.0 # Apache-2.0
|
||||
python-zaqarclient>=1.0.0 # Apache-2.0
|
||||
cryptography>=1.9,!=2.0 # BSD/Apache-2.0
|
||||
|
@ -12,8 +12,18 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography import x509
|
||||
from cryptography.x509.oid import NameOID
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
import mock
|
||||
import os
|
||||
import tempfile
|
||||
import yaml
|
||||
|
||||
from tripleoclient.tests import base
|
||||
from tripleoclient.v1 import undercloud_config
|
||||
@ -90,3 +100,144 @@ class TestProcessDriversAndHardwareTypes(base.TestCase):
|
||||
'IronicEnabledRaidInterfaces': ['idrac', 'no-raid'],
|
||||
'IronicEnabledVendorInterfaces': ['idrac', 'ipmitool', 'no-vendor']
|
||||
}, env)
|
||||
|
||||
|
||||
class TestTLSSettings(base.TestCase):
|
||||
def test_public_host_with_ip_should_give_ip_endpoint_environment(self):
|
||||
expected_env_file = os.path.join(
|
||||
undercloud_config.THT_HOME,
|
||||
"environments/tls-endpoints-public-ip.yaml")
|
||||
|
||||
resulting_env_file1 = undercloud_config._get_tls_endpoint_environment(
|
||||
'127.0.0.1', undercloud_config.THT_HOME)
|
||||
|
||||
self.assertEqual(expected_env_file, resulting_env_file1)
|
||||
|
||||
resulting_env_file2 = undercloud_config._get_tls_endpoint_environment(
|
||||
'192.168.1.1', undercloud_config.THT_HOME)
|
||||
|
||||
self.assertEqual(expected_env_file, resulting_env_file2)
|
||||
|
||||
def test_public_host_with_fqdn_should_give_dns_endpoint_environment(self):
|
||||
expected_env_file = os.path.join(
|
||||
undercloud_config.THT_HOME,
|
||||
"environments/tls-endpoints-public-dns.yaml")
|
||||
|
||||
resulting_env_file1 = undercloud_config._get_tls_endpoint_environment(
|
||||
'controller-1', undercloud_config.THT_HOME)
|
||||
|
||||
self.assertEqual(expected_env_file, resulting_env_file1)
|
||||
|
||||
resulting_env_file2 = undercloud_config._get_tls_endpoint_environment(
|
||||
'controller-1.tripleodomain.com', undercloud_config.THT_HOME)
|
||||
|
||||
self.assertEqual(expected_env_file, resulting_env_file2)
|
||||
|
||||
def get_certificate_and_private_key(self):
|
||||
private_key = rsa.generate_private_key(public_exponent=3,
|
||||
key_size=1024,
|
||||
backend=default_backend())
|
||||
issuer = x509.Name([
|
||||
x509.NameAttribute(NameOID.COUNTRY_NAME, u"FI"),
|
||||
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Helsinki"),
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Some Company"),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, u"Test Certificate"),
|
||||
])
|
||||
cert_builder = x509.CertificateBuilder(
|
||||
issuer_name=issuer, subject_name=issuer,
|
||||
public_key=private_key.public_key(),
|
||||
serial_number=x509.random_serial_number(),
|
||||
not_valid_before=datetime.utcnow(),
|
||||
not_valid_after=datetime.utcnow() + timedelta(days=10)
|
||||
)
|
||||
cert = cert_builder.sign(private_key,
|
||||
hashes.SHA256(),
|
||||
default_backend())
|
||||
cert_pem = cert.public_bytes(encoding=serialization.Encoding.PEM)
|
||||
key_pem = private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption())
|
||||
return cert_pem, key_pem
|
||||
|
||||
def test_get_dict_with_cert_and_key_from_bundled_pem(self):
|
||||
cert_pem, key_pem = self.get_certificate_and_private_key()
|
||||
|
||||
with tempfile.NamedTemporaryFile() as tempbundle:
|
||||
tempbundle.write(cert_pem)
|
||||
tempbundle.write(key_pem)
|
||||
tempbundle.seek(0)
|
||||
|
||||
tls_parameters = undercloud_config._get_public_tls_parameters(
|
||||
tempbundle.name)
|
||||
|
||||
self.assertEqual(cert_pem, tls_parameters['SSLCertificate'])
|
||||
self.assertEqual(key_pem, tls_parameters['SSLKey'])
|
||||
|
||||
def test_get_tls_parameters_fails_cause_of_missing_cert(self):
|
||||
_, key_pem = self.get_certificate_and_private_key()
|
||||
|
||||
with tempfile.NamedTemporaryFile() as tempbundle:
|
||||
tempbundle.write(key_pem)
|
||||
tempbundle.seek(0)
|
||||
|
||||
self.assertRaises(ValueError,
|
||||
undercloud_config._get_public_tls_parameters,
|
||||
tempbundle.name)
|
||||
|
||||
def test_get_tls_parameters_fails_cause_of_missing_key(self):
|
||||
cert_pem, _ = self.get_certificate_and_private_key()
|
||||
|
||||
with tempfile.NamedTemporaryFile() as tempbundle:
|
||||
tempbundle.write(cert_pem)
|
||||
tempbundle.seek(0)
|
||||
|
||||
self.assertRaises(ValueError,
|
||||
undercloud_config._get_public_tls_parameters,
|
||||
tempbundle.name)
|
||||
|
||||
def test_get_tls_parameters_fails_cause_of_unexistent_file(self):
|
||||
self.assertRaises(IOError,
|
||||
undercloud_config._get_public_tls_parameters,
|
||||
'/tmp/unexistent-file-12345.pem')
|
||||
|
||||
def test_get_resource_registry_overwrites(self):
|
||||
enable_tls_yaml = {
|
||||
"parameter_defaults": {
|
||||
"SSLCertificate": "12345"
|
||||
},
|
||||
"resource_registry": {
|
||||
"registry_overwrite_key": "registry_overwrite_value"
|
||||
}
|
||||
}
|
||||
with tempfile.NamedTemporaryFile() as enable_tls_file:
|
||||
enable_tls_file.write(yaml.dump(enable_tls_yaml, encoding='utf-8'))
|
||||
enable_tls_file.seek(0)
|
||||
|
||||
overwrites = \
|
||||
undercloud_config._get_public_tls_resource_registry_overwrites(
|
||||
enable_tls_file.name)
|
||||
|
||||
self.assertEqual(enable_tls_yaml["resource_registry"], overwrites)
|
||||
|
||||
def test_get_resource_registry_overwrites_fails_cause_no_registry_entry(
|
||||
self):
|
||||
enable_tls_yaml = {
|
||||
"parameter_defaults": {
|
||||
"SSLCertificate": "12345"
|
||||
},
|
||||
}
|
||||
with tempfile.NamedTemporaryFile() as enable_tls_file:
|
||||
enable_tls_file.write(yaml.dump(enable_tls_yaml, encoding='utf-8'))
|
||||
enable_tls_file.seek(0)
|
||||
|
||||
self.assertRaises(
|
||||
RuntimeError,
|
||||
undercloud_config._get_public_tls_resource_registry_overwrites,
|
||||
enable_tls_file.name)
|
||||
|
||||
def test_get_resource_registry_overwrites_fails_cause_missing_file(self):
|
||||
self.assertRaises(
|
||||
IOError,
|
||||
undercloud_config._get_public_tls_resource_registry_overwrites,
|
||||
'/tmp/unexistent-file-12345.yaml')
|
||||
|
@ -16,6 +16,9 @@
|
||||
"""Plugin action implementation"""
|
||||
|
||||
import copy
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography import x509
|
||||
import logging
|
||||
import netaddr
|
||||
import os
|
||||
@ -452,6 +455,7 @@ def prepare_undercloud_deploy(upgrade=False, no_validations=False):
|
||||
"""Prepare Undercloud deploy command based on undercloud.conf"""
|
||||
|
||||
env_data = {}
|
||||
registry_overwrites = {}
|
||||
deploy_args = []
|
||||
_load_config()
|
||||
|
||||
@ -542,21 +546,27 @@ def prepare_undercloud_deploy(upgrade=False, no_validations=False):
|
||||
"environments/services-docker/undercloud-cinder.yaml")]
|
||||
|
||||
if CONF.get('generate_service_certificate'):
|
||||
try:
|
||||
public_host = CONF.get('undercloud_public_host')
|
||||
netaddr.IPAddress(public_host)
|
||||
endpoint_environment = os.path.join(
|
||||
tht_templates,
|
||||
"environments/tls-endpoints-public-ip.yaml")
|
||||
except netaddr.core.AddrFormatError:
|
||||
endpoint_environment = os.path.join(
|
||||
tht_templates,
|
||||
"environments/tls-endpoints-public-dns.yaml")
|
||||
endpoint_environment = _get_tls_endpoint_environment(
|
||||
CONF.get('undercloud_public_host'), tht_templates)
|
||||
|
||||
deploy_args += ['-e', os.path.join(
|
||||
tht_templates,
|
||||
"environments/public-tls-undercloud.yaml"),
|
||||
'-e', endpoint_environment]
|
||||
elif CONF.get('undercloud_service_certificate'):
|
||||
endpoint_environment = _get_tls_endpoint_environment(
|
||||
CONF.get('undercloud_public_host'), tht_templates)
|
||||
enable_tls_yaml_path = os.path.join(tht_templates,
|
||||
"environments/ssl/enable-tls.yaml")
|
||||
env_data.update(
|
||||
_get_public_tls_parameters(
|
||||
CONF.get('undercloud_service_certificate')))
|
||||
registry_overwrites.update(
|
||||
_get_public_tls_resource_registry_overwrites(enable_tls_yaml_path))
|
||||
deploy_args += [
|
||||
'-e', endpoint_environment, '-e',
|
||||
'environments/services-docker/undercloud-haproxy.yaml',
|
||||
'-e', 'environments/services-docker/undercloud-keepalived.yaml']
|
||||
|
||||
deploy_args += [
|
||||
"-e", os.path.join(tht_templates, "environments/docker.yaml"),
|
||||
@ -565,7 +575,8 @@ def prepare_undercloud_deploy(upgrade=False, no_validations=False):
|
||||
"environments/config-download-environment.yaml"),
|
||||
"-e", os.path.join(tht_templates, "environments/undercloud.yaml")]
|
||||
|
||||
env_file = _write_env_file(env_data)
|
||||
env_file = _write_env_file(
|
||||
env_data, registry_overwrites=registry_overwrites)
|
||||
deploy_args += ['-e', env_file]
|
||||
|
||||
deploy_args += ['--output-dir=%s' % os.environ.get('HOME', '')]
|
||||
@ -583,11 +594,54 @@ def prepare_undercloud_deploy(upgrade=False, no_validations=False):
|
||||
return cmd
|
||||
|
||||
|
||||
def _get_tls_endpoint_environment(public_host, tht_templates):
|
||||
try:
|
||||
netaddr.IPAddress(public_host)
|
||||
return os.path.join(tht_templates,
|
||||
"environments/tls-endpoints-public-ip.yaml")
|
||||
except netaddr.core.AddrFormatError:
|
||||
return os.path.join(tht_templates,
|
||||
"environments/tls-endpoints-public-dns.yaml")
|
||||
|
||||
|
||||
def _get_public_tls_parameters(service_certificate_path):
|
||||
with open(service_certificate_path, "rb") as pem_file:
|
||||
pem_data = pem_file.read()
|
||||
cert = x509.load_pem_x509_certificate(pem_data, default_backend())
|
||||
private_key = serialization.load_pem_private_key(
|
||||
pem_data,
|
||||
password=None,
|
||||
backend=default_backend())
|
||||
|
||||
key_pem = private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption())
|
||||
cert_pem = cert.public_bytes(serialization.Encoding.PEM)
|
||||
return {
|
||||
'SSLCertificate': cert_pem,
|
||||
'SSLKey': key_pem
|
||||
}
|
||||
|
||||
|
||||
def _get_public_tls_resource_registry_overwrites(enable_tls_yaml_path):
|
||||
with open(enable_tls_yaml_path, 'rb') as enable_tls_file:
|
||||
enable_tls_dict = yaml.load(enable_tls_file.read())
|
||||
try:
|
||||
return enable_tls_dict['resource_registry']
|
||||
except KeyError:
|
||||
raise RuntimeError('%s is malformed and is missing the resource '
|
||||
'registry.' % enable_tls_yaml_path)
|
||||
|
||||
|
||||
def _write_env_file(env_data,
|
||||
env_file="/tmp/undercloud_parameters.yaml"):
|
||||
env_file="/tmp/undercloud_parameters.yaml",
|
||||
registry_overwrites={}):
|
||||
"""Write the undercloud parameters to yaml"""
|
||||
|
||||
data = {'parameter_defaults': env_data}
|
||||
if registry_overwrites:
|
||||
data['resource_registry'] = registry_overwrites
|
||||
env_file = os.path.abspath(env_file)
|
||||
with open(env_file, "w") as f:
|
||||
try:
|
||||
|
Loading…
Reference in New Issue
Block a user