3
from os.path import join as path_join
4
from os.path import exists
8
log = logging.getLogger("service_ca")
10
logging.basicConfig(level=logging.DEBUG)
14
# Mysql server is fairly picky about cert creation
15
# and types, spec its creation separately for now.
19
class ServiceCA(object):
21
default_expiry = str(365 * 2)
22
default_ca_expiry = str(365 * 6)
24
def __init__(self, name, ca_dir, cert_type=STD_CERT):
27
self.cert_type = cert_type
32
def get_ca(type=STD_CERT):
33
service_name = os.environ['JUJU_UNIT_NAME'].split('/')[0]
34
ca_path = os.path.join(os.environ['CHARM_DIR'], 'ca')
35
ca = ServiceCA(service_name, ca_path, type)
40
def get_service_cert(cls, type=STD_CERT):
41
service_name = os.environ['JUJU_UNIT_NAME'].split('/')[0]
43
crt, key = ca.get_or_create_cert(service_name)
44
return crt, key, ca.get_ca_bundle()
49
log.debug("initializing service ca")
50
if not exists(self.ca_dir):
51
self._init_ca_dir(self.ca_dir)
56
return path_join(self.ca_dir, 'private', 'cacert.key')
60
return path_join(self.ca_dir, 'cacert.pem')
64
return path_join(self.ca_dir, 'ca.cnf')
67
def signing_conf(self):
68
return path_join(self.ca_dir, 'signing.cnf')
70
def _init_ca_dir(self, ca_dir):
72
for i in ['certs', 'crl', 'newcerts', 'private']:
73
sd = path_join(ca_dir, i)
77
if not exists(path_join(ca_dir, 'serial')):
78
with open(path_join(ca_dir, 'serial'), 'wb') as fh:
81
if not exists(path_join(ca_dir, 'index.txt')):
82
with open(path_join(ca_dir, 'index.txt'), 'wb') as fh:
86
"""Generate the root ca's cert and key.
88
if not exists(path_join(self.ca_dir, 'ca.cnf')):
89
with open(path_join(self.ca_dir, 'ca.cnf'), 'wb') as fh:
91
CA_CONF_TEMPLATE % (self.get_conf_variables()))
93
if not exists(path_join(self.ca_dir, 'signing.cnf')):
94
with open(path_join(self.ca_dir, 'signing.cnf'), 'wb') as fh:
96
SIGNING_CONF_TEMPLATE % (self.get_conf_variables()))
98
if exists(self.ca_cert) or exists(self.ca_key):
99
raise RuntimeError("Initialized called when CA already exists")
100
cmd = ['openssl', 'req', '-config', self.ca_conf,
101
'-x509', '-nodes', '-newkey', 'rsa',
102
'-days', self.default_ca_expiry,
103
'-keyout', self.ca_key, '-out', self.ca_cert,
105
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
106
log.debug("CA Init:\n %s", output)
108
def get_conf_variables(self):
111
org_unit_name="%s service" % self.name,
112
common_name=self.name,
115
def get_or_create_cert(self, common_name):
116
if common_name in self:
117
return self.get_certificate(common_name)
118
return self.create_certificate(common_name)
120
def create_certificate(self, common_name):
121
if common_name in self:
122
return self.get_certificate(common_name)
123
key_p = path_join(self.ca_dir, "certs", "%s.key" % common_name)
124
crt_p = path_join(self.ca_dir, "certs", "%s.crt" % common_name)
125
csr_p = path_join(self.ca_dir, "certs", "%s.csr" % common_name)
126
self._create_certificate(common_name, key_p, csr_p, crt_p)
127
return self.get_certificate(common_name)
129
def get_certificate(self, common_name):
130
if not common_name in self:
131
raise ValueError("No certificate for %s" % common_name)
132
key_p = path_join(self.ca_dir, "certs", "%s.key" % common_name)
133
crt_p = path_join(self.ca_dir, "certs", "%s.crt" % common_name)
134
with open(crt_p) as fh:
136
with open(key_p) as fh:
140
def __contains__(self, common_name):
141
crt_p = path_join(self.ca_dir, "certs", "%s.crt" % common_name)
144
def _create_certificate(self, common_name, key_p, csr_p, crt_p):
145
template_vars = self.get_conf_variables()
146
template_vars['common_name'] = common_name
147
subj = '/O=%(org_name)s/OU=%(org_unit_name)s/CN=%(common_name)s' % (
150
log.debug("CA Create Cert %s", common_name)
151
cmd = ['openssl', 'req', '-sha1', '-newkey', 'rsa:2048',
152
'-nodes', '-days', self.default_expiry,
153
'-keyout', key_p, '-out', csr_p, '-subj', subj]
154
subprocess.check_call(cmd)
155
cmd = ['openssl', 'rsa', '-in', key_p, '-out', key_p]
156
subprocess.check_call(cmd)
158
log.debug("CA Sign Cert %s", common_name)
159
if self.cert_type == MYSQL_CERT:
160
cmd = ['openssl', 'x509', '-req',
161
'-in', csr_p, '-days', self.default_expiry,
162
'-CA', self.ca_cert, '-CAkey', self.ca_key,
163
'-set_serial', '01', '-out', crt_p]
165
cmd = ['openssl', 'ca', '-config', self.signing_conf,
166
'-extensions', 'req_extensions',
167
'-days', self.default_expiry, '-notext',
168
'-in', csr_p, '-out', crt_p, '-subj', subj, '-batch']
169
log.debug("running %s", " ".join(cmd))
170
subprocess.check_call(cmd)
172
def get_ca_bundle(self):
173
with open(self.ca_cert) as fh:
177
CA_CONF_TEMPLATE = """
179
default_ca = CA_default
183
policy = policy_match
184
database = $dir/index.txt
188
new_certs_dir = $dir/newcerts
189
certificate = $dir/cacert.pem
190
private_key = $dir/private/cacert.key
191
RANDFILE = $dir/private/.rand
199
distinguished_name = ca_distinguished_name
201
x509_extensions = ca_extensions
203
[ ca_distinguished_name ]
204
organizationName = %(org_name)s
205
organizationalUnitName = %(org_unit_name)s Certificate Authority
209
countryName = optional
210
stateOrProvinceName = optional
211
organizationName = match
212
organizationalUnitName = optional
213
commonName = supplied
216
basicConstraints = critical,CA:true
217
subjectKeyIdentifier = hash
218
authorityKeyIdentifier = keyid:always, issuer
219
keyUsage = cRLSign, keyCertSign
223
SIGNING_CONF_TEMPLATE = """
225
default_ca = CA_default
229
policy = policy_match
230
database = $dir/index.txt
234
new_certs_dir = $dir/newcerts
235
certificate = $dir/cacert.pem
236
private_key = $dir/private/cacert.key
237
RANDFILE = $dir/private/.rand
245
distinguished_name = req_distinguished_name
247
x509_extensions = req_extensions
249
[ req_distinguished_name ]
250
organizationName = %(org_name)s
251
organizationalUnitName = %(org_unit_name)s machine resources
252
commonName = %(common_name)s
255
countryName = optional
256
stateOrProvinceName = optional
257
organizationName = match
258
organizationalUnitName = optional
259
commonName = supplied
262
basicConstraints = CA:false
263
subjectKeyIdentifier = hash
264
authorityKeyIdentifier = keyid:always, issuer
265
keyUsage = digitalSignature, keyEncipherment, keyAgreement
266
extendedKeyUsage = serverAuth, clientAuth