diff options
Diffstat (limited to 'keystone-moon/keystone/common/openssl.py')
-rw-r--r-- | keystone-moon/keystone/common/openssl.py | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/keystone-moon/keystone/common/openssl.py b/keystone-moon/keystone/common/openssl.py new file mode 100644 index 00000000..4eb7d1d1 --- /dev/null +++ b/keystone-moon/keystone/common/openssl.py @@ -0,0 +1,347 @@ +# 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 oslo_config import cfg +from oslo_log import log + +from keystone.common import environment +from keystone.common import utils +from keystone.i18n import _LI, _LE + +LOG = log.getLogger(__name__) +CONF = cfg.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, server_conf_obj, keystone_user, + keystone_group, rebuild, **kwargs): + self.conf_dir = os.path.dirname(server_conf_obj.ca_certs) + self.use_keystone_user = keystone_user + self.use_keystone_group = keystone_group + self.rebuild = rebuild + 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': server_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': server_conf_obj.keyfile, + 'signing_cert': server_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 = [] + for cmd_part in command: + to_exec.append(cmd_part % self.ssl_dictionary) + LOG.info(_LI('Running command - %s'), ' '.join(to_exec)) + # NOTE(Jeffrey4l): Redirect both stdout and stderr to pipe, so the + # output can be captured. + # NOTE(Jeffrey4l): check_output is not compatible with Python 2.6. + # So use Popen instead. + process = environment.subprocess.Popen( + to_exec, + stdout=environment.subprocess.PIPE, + stderr=environment.subprocess.STDOUT) + output = process.communicate()[0] + retcode = process.poll() + if retcode: + LOG.error(_LE('Command %(to_exec)s exited with %(retcode)s' + '- %(output)s'), + {'to_exec': to_exec, + 'retcode': retcode, + 'output': output}) + e = environment.subprocess.CalledProcessError(retcode, to_exec[0]) + # NOTE(Jeffrey4l): Python 2.6 compatibility: + # CalledProcessError did not have output keyword argument + e.output = output + raise e + + def clean_up_existing_files(self): + files_to_clean = [self.ssl_dictionary['ca_private_key'], + self.ssl_dictionary['ca_cert'], + self.ssl_dictionary['signing_key'], + self.ssl_dictionary['signing_cert'], + ] + + existing_files = [] + + for file_path in files_to_clean: + if file_exists(file_path): + if self.rebuild: + # The file exists but the user wants to rebuild it, so blow + # it away + try: + os.remove(file_path) + except OSError as exc: + LOG.error(_LE('Failed to remove file %(file_path)r: ' + '%(error)s'), + {'file_path': file_path, + 'error': exc.strerror}) + raise + else: + existing_files.append(file_path) + + return existing_files + + 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): + try: + existing_files = self.clean_up_existing_files() + except OSError: + print('An error occurred when rebuilding cert files.') + return + if existing_files: + print('The following cert files already exist, use --rebuild to ' + 'remove the existing files before regenerating:') + for f in existing_files: + print('%s already exists' % f) + return + + 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, rebuild=False): + super(ConfigurePKI, self).__init__(CONF.signing, CONF.signing, + keystone_user, keystone_group, + rebuild=rebuild) + + +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, rebuild=False): + super(ConfigureSSL, self).__init__(CONF.ssl, CONF.eventlet_server_ssl, + keystone_user, keystone_group, + rebuild=rebuild) + + +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 +""" |