Generate HTTPS certificates with ssl_setup.
Extracts common OpenSSL functionality from pki_setup and adds a new cli command ssl_setup which re-uses this base to generate SSL certificates for https. Change-Id: Ia34827583bcdfbd871133250681010e642271f07 Fixes: bug 1155361
This commit is contained in:
@@ -29,41 +29,34 @@ DIR_PERMS = (stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR |
|
||||
stat.S_IROTH | stat.S_IXOTH)
|
||||
CERT_PERMS = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
PRIV_PERMS = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
|
||||
DEFAULT_SUBJECT = '/C=US/ST=Unset/L=Unset/O=Unset/CN=www.example.com'
|
||||
|
||||
|
||||
def file_exists(file_path):
|
||||
return os.path.exists(file_path)
|
||||
|
||||
|
||||
class ConfigurePKI(object):
|
||||
"""Generate files for PKI signing using OpenSSL.
|
||||
class BaseCertificateConfigure(object):
|
||||
"""Create a certificate signing environment from a config section
|
||||
and reasonable OpenSSL defaults"""
|
||||
|
||||
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, **kw):
|
||||
self.conf_dir = os.path.dirname(CONF.signing.ca_certs)
|
||||
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.ca_key_file = os.path.join(self.conf_dir, "cakey.pem")
|
||||
self.request_file_name = os.path.join(self.conf_dir, "req.pem")
|
||||
self.ssl_dictionary = {'conf_dir': self.conf_dir,
|
||||
'ca_cert': CONF.signing.ca_certs,
|
||||
'ca_cert': conf_obj.ca_certs,
|
||||
'ssl_config': self.ssl_config_file_name,
|
||||
'ca_private_key': self.ca_key_file,
|
||||
'ca_cert_cn': 'hostname',
|
||||
'ca_private_key': conf_obj.ca_key,
|
||||
'request_file': self.request_file_name,
|
||||
'signing_key': CONF.signing.keyfile,
|
||||
'signing_cert': CONF.signing.certfile,
|
||||
'default_subject': DEFAULT_SUBJECT,
|
||||
'key_size': int(CONF.signing.key_size),
|
||||
'valid_days': int(CONF.signing.valid_days),
|
||||
'ca_password': CONF.signing.ca_password}
|
||||
'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,
|
||||
'ca_password': conf_obj.ca_password}
|
||||
self.ssl_dictionary.update(kwargs)
|
||||
|
||||
def _make_dirs(self, file_name):
|
||||
dir = os.path.dirname(file_name)
|
||||
@@ -106,20 +99,25 @@ class ConfigurePKI(object):
|
||||
self._set_permissions(self.ssl_config_file_name, PRIV_PERMS)
|
||||
|
||||
def build_ca_cert(self):
|
||||
if not file_exists(CONF.signing.ca_certs):
|
||||
if not os.path.exists(self.ca_key_file):
|
||||
self._make_dirs(self.ca_key_file)
|
||||
self.exec_command('openssl genrsa -out %(ca_private_key)s '
|
||||
'%(key_size)d -config %(ssl_config)s')
|
||||
self._set_permissions(self.ssl_dictionary['ca_private_key'],
|
||||
stat.S_IRUSR)
|
||||
ca_key_file = self.ssl_dictionary['ca_private_key']
|
||||
ca_cert = self.ssl_dictionary['ca_cert']
|
||||
|
||||
if not file_exists(ca_key_file):
|
||||
self._make_dirs(ca_key_file)
|
||||
self.exec_command('openssl genrsa -out %(ca_private_key)s '
|
||||
'%(key_size)d')
|
||||
self._set_permissions(self.ssl_dictionary['ca_private_key'],
|
||||
stat.S_IRUSR)
|
||||
|
||||
if not file_exists(ca_cert):
|
||||
self._make_dirs(ca_cert)
|
||||
self.exec_command('openssl req -new -x509 -extensions v3_ca '
|
||||
'-passin pass:%(ca_password)s '
|
||||
'-key %(ca_private_key)s -out %(ca_cert)s '
|
||||
'-days %(valid_days)d '
|
||||
'-config %(ssl_config)s '
|
||||
'-subj %(default_subject)s')
|
||||
self._set_permissions(self.ssl_dictionary['ca_cert'], CERT_PERMS)
|
||||
'-subj %(cert_subject)s')
|
||||
self._set_permissions(ca_cert, CERT_PERMS)
|
||||
|
||||
def build_private_key(self):
|
||||
signing_keyfile = self.ssl_dictionary['signing_key']
|
||||
@@ -128,19 +126,23 @@ class ConfigurePKI(object):
|
||||
self._make_dirs(signing_keyfile)
|
||||
|
||||
self.exec_command('openssl genrsa -out %(signing_key)s '
|
||||
'%(key_size)d '
|
||||
'-config %(ssl_config)s')
|
||||
'%(key_size)d ')
|
||||
self._set_permissions(os.path.dirname(signing_keyfile), PRIV_PERMS)
|
||||
self._set_permissions(signing_keyfile, stat.S_IRUSR)
|
||||
|
||||
def build_signing_cert(self):
|
||||
if not file_exists(CONF.signing.certfile):
|
||||
self._make_dirs(CONF.signing.certfile)
|
||||
signing_cert = self.ssl_dictionary['signing_cert']
|
||||
|
||||
if not file_exists(signing_cert):
|
||||
self._make_dirs(signing_cert)
|
||||
|
||||
self.exec_command('openssl req -key %(signing_key)s -new -nodes '
|
||||
'-out %(request_file)s -config %(ssl_config)s '
|
||||
'-subj %(default_subject)s')
|
||||
'-subj %(cert_subject)s')
|
||||
|
||||
self.exec_command('openssl ca -batch -out %(signing_cert)s '
|
||||
'-config %(ssl_config)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):
|
||||
@@ -149,13 +151,41 @@ class ConfigurePKI(object):
|
||||
self.build_private_key()
|
||||
self.build_signing_cert()
|
||||
|
||||
sslconfig = """
|
||||
|
||||
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
|
||||
|
||||
@@ -163,55 +193,56 @@ default_ca = CA_default
|
||||
new_certs_dir = $dir
|
||||
serial = $dir/serial
|
||||
database = $dir/index.txt
|
||||
certificate = %(ca_cert)s
|
||||
private_key = %(ca_private_key)s
|
||||
default_days = 365
|
||||
default_md = md5
|
||||
default_md = sha1
|
||||
preserve = no
|
||||
email_in_dn = no
|
||||
nameopt = default_ca
|
||||
certopt = default_ca
|
||||
policy = policy_match
|
||||
[ policy_match ]
|
||||
countryName = match
|
||||
stateOrProvinceName = match
|
||||
organizationName = match
|
||||
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 = 1024 # Size of keys
|
||||
default_keyfile = key.pem # name of generated keys
|
||||
default_md = md5 # message digest algorithm
|
||||
default_bits = 1024 # Size of keys
|
||||
default_keyfile = key.pem # name of generated keys
|
||||
default_md = default # message digest algorithm
|
||||
string_mask = nombstr # permitted characters
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = v3_req
|
||||
|
||||
[ req_distinguished_name ]
|
||||
0.organizationName = Organization Name (company)
|
||||
organizationalUnitName = Organizational Unit Name (department, division)
|
||||
emailAddress = Email Address
|
||||
emailAddress_max = 40
|
||||
localityName = Locality Name (city, district)
|
||||
stateOrProvinceName = State or Province Name (full name)
|
||||
countryName = Country Name (2 letter code)
|
||||
countryName_min = 2
|
||||
countryName_max = 2
|
||||
commonName = Common Name (hostname, IP, or your name)
|
||||
commonName_max = 64
|
||||
# Default values for the above, for consistency and less typing.
|
||||
0.organizationName_default = Openstack, Inc
|
||||
localityName_default = Undefined
|
||||
stateOrProvinceName_default = Undefined
|
||||
countryName_default = US
|
||||
commonName_default = %(ca_cert_cn)s
|
||||
0.organizationName = Organization Name (company)
|
||||
organizationalUnitName = Organizational Unit Name (department, division)
|
||||
emailAddress = Email Address
|
||||
emailAddress_max = 40
|
||||
localityName = Locality Name (city, district)
|
||||
stateOrProvinceName = State or Province Name (full name)
|
||||
countryName = Country Name (2 letter code)
|
||||
countryName_min = 2
|
||||
countryName_max = 2
|
||||
commonName = Common Name (hostname, IP, or your name)
|
||||
commonName_max = 64
|
||||
|
||||
[ v3_ca ]
|
||||
basicConstraints = CA:TRUE
|
||||
subjectKeyIdentifier = hash
|
||||
basicConstraints = CA:TRUE
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid:always,issuer:always
|
||||
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
subjectKeyIdentifier = hash"""
|
||||
basicConstraints = CA:FALSE
|
||||
subjectKeyIdentifier = hash
|
||||
|
||||
[ usr_cert ]
|
||||
basicConstraints = CA:FALSE
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid:always,issuer:always
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user