# Copyright 2012 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import os from keystone.common import environment from keystone.common import utils from keystone import config from keystone.openstack.common import log LOG = log.getLogger(__name__) CONF = config.CONF PUBLIC_DIR_PERMS = 0o755 # -rwxr-xr-x PRIVATE_DIR_PERMS = 0o750 # -rwxr-x--- PUBLIC_FILE_PERMS = 0o644 # -rw-r--r-- PRIVATE_FILE_PERMS = 0o640 # -rw-r----- def file_exists(file_path): return os.path.exists(file_path) class BaseCertificateConfigure(object): """Create a certificate signing environment. This is based on a config section and reasonable OpenSSL defaults. """ def __init__(self, conf_obj, keystone_user, keystone_group, **kwargs): self.conf_dir = os.path.dirname(conf_obj.ca_certs) self.use_keystone_user = keystone_user self.use_keystone_group = keystone_group self.ssl_config_file_name = os.path.join(self.conf_dir, "openssl.conf") self.request_file_name = os.path.join(self.conf_dir, "req.pem") self.ssl_dictionary = {'conf_dir': self.conf_dir, 'ca_cert': conf_obj.ca_certs, 'default_md': 'default', 'ssl_config': self.ssl_config_file_name, 'ca_private_key': conf_obj.ca_key, 'request_file': self.request_file_name, 'signing_key': conf_obj.keyfile, 'signing_cert': conf_obj.certfile, 'key_size': int(conf_obj.key_size), 'valid_days': int(conf_obj.valid_days), 'cert_subject': conf_obj.cert_subject} try: # OpenSSL 1.0 and newer support default_md = default, olders do not openssl_ver = environment.subprocess.Popen( ['openssl', 'version'], stdout=environment.subprocess.PIPE).stdout.read() if "OpenSSL 0." in openssl_ver: self.ssl_dictionary['default_md'] = 'sha1' except OSError: LOG.warn('Failed to invoke ``openssl version``, ' 'assuming is v1.0 or newer') self.ssl_dictionary.update(kwargs) def exec_command(self, command): to_exec = command % self.ssl_dictionary LOG.info(to_exec) environment.subprocess.check_call(to_exec.rsplit(' ')) def build_ssl_config_file(self): utils.make_dirs(os.path.dirname(self.ssl_config_file_name), mode=PUBLIC_DIR_PERMS, user=self.use_keystone_user, group=self.use_keystone_group, log=LOG) if not file_exists(self.ssl_config_file_name): ssl_config_file = open(self.ssl_config_file_name, 'w') ssl_config_file.write(self.sslconfig % self.ssl_dictionary) ssl_config_file.close() utils.set_permissions(self.ssl_config_file_name, mode=PRIVATE_FILE_PERMS, user=self.use_keystone_user, group=self.use_keystone_group, log=LOG) index_file_name = os.path.join(self.conf_dir, 'index.txt') if not file_exists(index_file_name): index_file = open(index_file_name, 'w') index_file.write('') index_file.close() utils.set_permissions(index_file_name, mode=PRIVATE_FILE_PERMS, user=self.use_keystone_user, group=self.use_keystone_group, log=LOG) serial_file_name = os.path.join(self.conf_dir, 'serial') if not file_exists(serial_file_name): index_file = open(serial_file_name, 'w') index_file.write('01') index_file.close() utils.set_permissions(serial_file_name, mode=PRIVATE_FILE_PERMS, user=self.use_keystone_user, group=self.use_keystone_group, log=LOG) def build_ca_cert(self): ca_key_file = self.ssl_dictionary['ca_private_key'] utils.make_dirs(os.path.dirname(ca_key_file), mode=PRIVATE_DIR_PERMS, user=self.use_keystone_user, group=self.use_keystone_group, log=LOG) if not file_exists(ca_key_file): self.exec_command('openssl genrsa -out %(ca_private_key)s ' '%(key_size)d') utils.set_permissions(ca_key_file, mode=PRIVATE_FILE_PERMS, user=self.use_keystone_user, group=self.use_keystone_group, log=LOG) ca_cert = self.ssl_dictionary['ca_cert'] utils.make_dirs(os.path.dirname(ca_cert), mode=PUBLIC_DIR_PERMS, user=self.use_keystone_user, group=self.use_keystone_group, log=LOG) if not file_exists(ca_cert): self.exec_command('openssl req -new -x509 -extensions v3_ca ' '-key %(ca_private_key)s -out %(ca_cert)s ' '-days %(valid_days)d ' '-config %(ssl_config)s ' '-subj %(cert_subject)s') utils.set_permissions(ca_cert, mode=PUBLIC_FILE_PERMS, user=self.use_keystone_user, group=self.use_keystone_group, log=LOG) def build_private_key(self): signing_keyfile = self.ssl_dictionary['signing_key'] utils.make_dirs(os.path.dirname(signing_keyfile), mode=PRIVATE_DIR_PERMS, user=self.use_keystone_user, group=self.use_keystone_group, log=LOG) if not file_exists(signing_keyfile): self.exec_command('openssl genrsa -out %(signing_key)s ' '%(key_size)d ') utils.set_permissions(signing_keyfile, mode=PRIVATE_FILE_PERMS, user=self.use_keystone_user, group=self.use_keystone_group, log=LOG) def build_signing_cert(self): signing_cert = self.ssl_dictionary['signing_cert'] utils.make_dirs(os.path.dirname(signing_cert), mode=PUBLIC_DIR_PERMS, user=self.use_keystone_user, group=self.use_keystone_group, log=LOG) if not file_exists(signing_cert): self.exec_command('openssl req -key %(signing_key)s -new ' '-out %(request_file)s -config %(ssl_config)s ' '-subj %(cert_subject)s') self.exec_command('openssl ca -batch -out %(signing_cert)s ' '-config %(ssl_config)s -days %(valid_days)dd ' '-cert %(ca_cert)s -keyfile %(ca_private_key)s ' '-infiles %(request_file)s') def run(self): self.build_ssl_config_file() self.build_ca_cert() self.build_private_key() self.build_signing_cert() class ConfigurePKI(BaseCertificateConfigure): """Generate files for PKI signing using OpenSSL. Signed tokens require a private key and signing certificate which itself must be signed by a CA. This class generates them with workable defaults if each of the files are not present """ def __init__(self, keystone_user, keystone_group): super(ConfigurePKI, self).__init__(CONF.signing, keystone_user, keystone_group) class ConfigureSSL(BaseCertificateConfigure): """Generate files for HTTPS using OpenSSL. Creates a public/private key and certificates. If a CA is not given one will be generated using provided arguments. """ def __init__(self, keystone_user, keystone_group): super(ConfigureSSL, self).__init__(CONF.ssl, keystone_user, keystone_group) BaseCertificateConfigure.sslconfig = """ # OpenSSL configuration file. # # Establish working directory. dir = %(conf_dir)s [ ca ] default_ca = CA_default [ CA_default ] new_certs_dir = $dir serial = $dir/serial database = $dir/index.txt default_days = 365 default_md = %(default_md)s preserve = no email_in_dn = no nameopt = default_ca certopt = default_ca policy = policy_anything x509_extensions = usr_cert unique_subject = no [ policy_anything ] countryName = optional stateOrProvinceName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional [ req ] default_bits = 2048 # Size of keys default_keyfile = key.pem # name of generated keys string_mask = utf8only # permitted characters distinguished_name = req_distinguished_name req_extensions = v3_req x509_extensions = v3_ca [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) localityName = Locality Name (city, district) 0.organizationName = Organization Name (company) organizationalUnitName = Organizational Unit Name (department, division) commonName = Common Name (hostname, IP, or your name) commonName_max = 64 emailAddress = Email Address emailAddress_max = 64 [ v3_ca ] basicConstraints = CA:TRUE subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment [ usr_cert ] basicConstraints = CA:FALSE subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always """