keystone_manage certificate generation
Bug 1017554 paths now correspond with SSL unit test for cert generation Added mode config values Explict about umask replace string concat for paths with proper use of os.path.join Change-Id: I8b3bec82d7b72993aa69653f63ff64c3f675f716
This commit is contained in:
parent
b45c252bca
commit
5ad80860fa
@ -88,11 +88,18 @@
|
||||
#ca_certs = /etc/keystone/ssl/certs/ca.pem
|
||||
#cert_required = True
|
||||
|
||||
[signing]
|
||||
#certfile = /etc/keystone/ssl/certs/signing_cert.pem
|
||||
#keyfile = /etc/keystone/ssl/private/signing_key.pem
|
||||
#ca_certs = /etc/keystone/ssl/certs/ca.pem
|
||||
#key_size = 2048
|
||||
#valid_days = 3650
|
||||
#ca_password = None
|
||||
|
||||
[ldap]
|
||||
# url = ldap://localhost
|
||||
# user = dc=Manager,dc=example,dc=com
|
||||
# password = freeipa4all
|
||||
# password = None
|
||||
# suffix = cn=example,cn=com
|
||||
# use_dumb_member = False
|
||||
|
||||
|
@ -20,6 +20,7 @@ import sys
|
||||
import textwrap
|
||||
|
||||
from keystone import config
|
||||
from keystone.common import openssl
|
||||
from keystone.openstack.common import importutils
|
||||
from keystone.openstack.common import jsonutils
|
||||
|
||||
@ -55,6 +56,19 @@ class DbSync(BaseApp):
|
||||
driver.db_sync()
|
||||
|
||||
|
||||
class PKISetup(BaseApp):
|
||||
"""Set up Key pairs and certificates for token signing and verification."""
|
||||
|
||||
name = 'pki_setup'
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(PKISetup, self).__init__(*args, **kw)
|
||||
|
||||
def main(self):
|
||||
conf_ssl = openssl.ConfigurePKI()
|
||||
conf_ssl.run()
|
||||
|
||||
|
||||
class ImportLegacy(BaseApp):
|
||||
"""Import a legacy database."""
|
||||
|
||||
@ -110,6 +124,7 @@ CMDS = {'db_sync': DbSync,
|
||||
'import_legacy': ImportLegacy,
|
||||
'export_legacy_catalog': ExportLegacyCatalog,
|
||||
'import_nova_auth': ImportNovaAuth,
|
||||
'pki_setup': PKISetup,
|
||||
}
|
||||
|
||||
|
||||
|
214
keystone/common/openssl.py
Normal file
214
keystone/common/openssl.py
Normal file
@ -0,0 +1,214 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack LLC
|
||||
#
|
||||
# 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
|
||||
#
|
||||
|
||||
import os
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import stat
|
||||
|
||||
from keystone import config
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
DIR_PERMS = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | \
|
||||
stat.S_IRGRP | stat.S_IXGRP | \
|
||||
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):
|
||||
sys.stdout.write("Looking for %s:\t" % file_path)
|
||||
if os.path.exists(file_path):
|
||||
print("[FOUND]")
|
||||
return True
|
||||
else:
|
||||
print("[NOT FOUND]")
|
||||
return False
|
||||
|
||||
|
||||
def make_dirs(file_name):
|
||||
dir = os.path.dirname(file_name)
|
||||
if not file_exists(dir):
|
||||
os.makedirs(dir, DIR_PERMS)
|
||||
|
||||
|
||||
class ConfigurePKI(object):
|
||||
"""Generate files for PKI siginging 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, *args, **kw):
|
||||
self.conf_dir = os.path.dirname(CONF.signing.ca_certs)
|
||||
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,
|
||||
"ssl_config": self.ssl_config_file_name,
|
||||
"ca_private_key": self.ca_key_file,
|
||||
"ca_cert_cn": "hostname",
|
||||
"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
|
||||
}
|
||||
|
||||
def exec_command(self, command):
|
||||
to_exec = command % self.ssl_dictionary
|
||||
print (to_exec)
|
||||
subprocess.check_call(to_exec.rsplit(" "))
|
||||
|
||||
def build_ssl_config_file(self):
|
||||
if not file_exists(self.ssl_config_file_name):
|
||||
make_dirs(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()
|
||||
os.chmod(self.ssl_config_file_name, CERT_PERMS)
|
||||
|
||||
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()
|
||||
os.chmod(self.ssl_config_file_name, PRIV_PERMS)
|
||||
|
||||
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()
|
||||
os.chmod(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):
|
||||
make_dirs(self.ca_key_file)
|
||||
self.exec_command("openssl genrsa -out %(ca_private_key)s "\
|
||||
"%(key_size)d -config %(ssl_config)s")
|
||||
os.chmod(self.ssl_dictionary["ca_private_key"], stat.S_IRUSR)
|
||||
print("Generating CA certificate")
|
||||
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')
|
||||
os.chmod(self.ssl_dictionary["ca_cert"], CERT_PERMS)
|
||||
|
||||
def build_private_key(self):
|
||||
if not file_exists(CONF.signing.keyfile):
|
||||
make_dirs(CONF.signing.keyfile)
|
||||
|
||||
self.exec_command("openssl genrsa -out %(signing_key)s "\
|
||||
"%(key_size)d "\
|
||||
"-config %(ssl_config)s")
|
||||
os.chmod(os.path.dirname(self.ssl_dictionary["signing_key"]),
|
||||
PRIV_PERMS)
|
||||
os.chmod(self.ssl_dictionary["signing_key"], stat.S_IRUSR)
|
||||
|
||||
def build_signing_cert(self):
|
||||
if not file_exists(CONF.signing.certfile):
|
||||
make_dirs(CONF.signing.certfile)
|
||||
self.exec_command("openssl req -key %(signing_key)s -new -nodes "\
|
||||
"-out %(request_file)s -config %(ssl_config)s "\
|
||||
"-subj %(default_subject)s")
|
||||
self.exec_command("openssl ca -batch -out %(signing_cert)s "\
|
||||
"-config %(ssl_config)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()
|
||||
|
||||
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
|
||||
certificate = %(ca_cert)s
|
||||
private_key = %(ca_private_key)s
|
||||
default_days = 365
|
||||
default_md = md5
|
||||
preserve = no
|
||||
email_in_dn = no
|
||||
nameopt = default_ca
|
||||
certopt = default_ca
|
||||
policy = policy_match
|
||||
[ policy_match ]
|
||||
countryName = match
|
||||
stateOrProvinceName = match
|
||||
organizationName = match
|
||||
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
|
||||
string_mask = nombstr # permitted characters
|
||||
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
|
||||
|
||||
[ v3_ca ]
|
||||
basicConstraints = CA:TRUE
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid:always,issuer:always
|
||||
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
subjectKeyIdentifier = hash"""
|
@ -124,6 +124,16 @@ register_str('certfile', group='ssl', default=None)
|
||||
register_str('keyfile', group='ssl', default=None)
|
||||
register_str('ca_certs', group='ssl', default=None)
|
||||
register_bool('cert_required', group='ssl', default=False)
|
||||
#signing options
|
||||
register_str('certfile', group='signing',
|
||||
default="/etc/keystone/ssl/certs/signing_cert.pem")
|
||||
register_str('keyfile', group='signing',
|
||||
default="/etc/keystone/ssl/private/signing_key.pem")
|
||||
register_str('ca_certs', group='signing',
|
||||
default="/etc/keystone/ssl/certs/ca.pem")
|
||||
register_int('key_size', group='signing', default=2048)
|
||||
register_int('valid_days', group='signing', default=3650)
|
||||
register_str('ca_password', group='signing', default=None)
|
||||
|
||||
# sql options
|
||||
register_str('connection', group='sql', default='sqlite:///keystone.db')
|
||||
|
52
tests/test_cert_setup.py
Normal file
52
tests/test_cert_setup.py
Normal file
@ -0,0 +1,52 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack LLC
|
||||
#
|
||||
# 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
|
||||
import unittest2 as test
|
||||
import shutil
|
||||
|
||||
from keystone import config
|
||||
from keystone.common import openssl
|
||||
|
||||
ROOTDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
SSLDIR = "%s/tests/ssl/" % ROOTDIR
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
def rootdir(*p):
|
||||
return os.path.join(SSLDIR, *p)
|
||||
|
||||
|
||||
CERTDIR = rootdir("certs")
|
||||
KEYDIR = rootdir("private")
|
||||
|
||||
CONF.signing.certfile = os.path.join(CERTDIR, 'signing_cert.pem')
|
||||
CONF.signing.ca_certs = os.path.join(CERTDIR, "ca.pem")
|
||||
CONF.signing.keyfile = os.path.join(KEYDIR, "signing_key.pem")
|
||||
|
||||
|
||||
class CertSetupTestCase(test.TestCase):
|
||||
|
||||
def test_create_certs(self):
|
||||
ssl = openssl.ConfigurePKI()
|
||||
ssl.run()
|
||||
self.assertTrue(os.path.exists(CONF.signing.certfile))
|
||||
self.assertTrue(os.path.exists(CONF.signing.ca_certs))
|
||||
self.assertTrue(os.path.exists(CONF.signing.keyfile))
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(rootdir(SSLDIR))
|
Loading…
x
Reference in New Issue
Block a user