~le-charmers/charms/trusty/rabbitmq-server/leadership-election

« back to all changes in this revision

Viewing changes to tests/charmhelpers/contrib/ssl/service.py

  • Committer: Liam Young
  • Date: 2015-05-11 08:03:57 UTC
  • mfrom: (83.1.14 rabbitmq-server)
  • Revision ID: liam.young@canonical.com-20150511080357-3ftop9kxb6o0e3mq
Merged trunk in + LE charmhelper sync

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2014-2015 Canonical Limited.
 
2
#
 
3
# This file is part of charm-helpers.
 
4
#
 
5
# charm-helpers is free software: you can redistribute it and/or modify
 
6
# it under the terms of the GNU Lesser General Public License version 3 as
 
7
# published by the Free Software Foundation.
 
8
#
 
9
# charm-helpers is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU Lesser General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU Lesser General Public License
 
15
# along with charm-helpers.  If not, see <http://www.gnu.org/licenses/>.
 
16
 
 
17
import os
 
18
from os.path import join as path_join
 
19
from os.path import exists
 
20
import subprocess
 
21
 
 
22
from charmhelpers.core.hookenv import log, DEBUG
 
23
 
 
24
STD_CERT = "standard"
 
25
 
 
26
# Mysql server is fairly picky about cert creation
 
27
# and types, spec its creation separately for now.
 
28
MYSQL_CERT = "mysql"
 
29
 
 
30
 
 
31
class ServiceCA(object):
 
32
 
 
33
    default_expiry = str(365 * 2)
 
34
    default_ca_expiry = str(365 * 6)
 
35
 
 
36
    def __init__(self, name, ca_dir, cert_type=STD_CERT):
 
37
        self.name = name
 
38
        self.ca_dir = ca_dir
 
39
        self.cert_type = cert_type
 
40
 
 
41
    ###############
 
42
    # Hook Helper API
 
43
    @staticmethod
 
44
    def get_ca(type=STD_CERT):
 
45
        service_name = os.environ['JUJU_UNIT_NAME'].split('/')[0]
 
46
        ca_path = os.path.join(os.environ['CHARM_DIR'], 'ca')
 
47
        ca = ServiceCA(service_name, ca_path, type)
 
48
        ca.init()
 
49
        return ca
 
50
 
 
51
    @classmethod
 
52
    def get_service_cert(cls, type=STD_CERT):
 
53
        service_name = os.environ['JUJU_UNIT_NAME'].split('/')[0]
 
54
        ca = cls.get_ca()
 
55
        crt, key = ca.get_or_create_cert(service_name)
 
56
        return crt, key, ca.get_ca_bundle()
 
57
 
 
58
    ###############
 
59
 
 
60
    def init(self):
 
61
        log("initializing service ca", level=DEBUG)
 
62
        if not exists(self.ca_dir):
 
63
            self._init_ca_dir(self.ca_dir)
 
64
            self._init_ca()
 
65
 
 
66
    @property
 
67
    def ca_key(self):
 
68
        return path_join(self.ca_dir, 'private', 'cacert.key')
 
69
 
 
70
    @property
 
71
    def ca_cert(self):
 
72
        return path_join(self.ca_dir, 'cacert.pem')
 
73
 
 
74
    @property
 
75
    def ca_conf(self):
 
76
        return path_join(self.ca_dir, 'ca.cnf')
 
77
 
 
78
    @property
 
79
    def signing_conf(self):
 
80
        return path_join(self.ca_dir, 'signing.cnf')
 
81
 
 
82
    def _init_ca_dir(self, ca_dir):
 
83
        os.mkdir(ca_dir)
 
84
        for i in ['certs', 'crl', 'newcerts', 'private']:
 
85
            sd = path_join(ca_dir, i)
 
86
            if not exists(sd):
 
87
                os.mkdir(sd)
 
88
 
 
89
        if not exists(path_join(ca_dir, 'serial')):
 
90
            with open(path_join(ca_dir, 'serial'), 'w') as fh:
 
91
                fh.write('02\n')
 
92
 
 
93
        if not exists(path_join(ca_dir, 'index.txt')):
 
94
            with open(path_join(ca_dir, 'index.txt'), 'w') as fh:
 
95
                fh.write('')
 
96
 
 
97
    def _init_ca(self):
 
98
        """Generate the root ca's cert and key.
 
99
        """
 
100
        if not exists(path_join(self.ca_dir, 'ca.cnf')):
 
101
            with open(path_join(self.ca_dir, 'ca.cnf'), 'w') as fh:
 
102
                fh.write(
 
103
                    CA_CONF_TEMPLATE % (self.get_conf_variables()))
 
104
 
 
105
        if not exists(path_join(self.ca_dir, 'signing.cnf')):
 
106
            with open(path_join(self.ca_dir, 'signing.cnf'), 'w') as fh:
 
107
                fh.write(
 
108
                    SIGNING_CONF_TEMPLATE % (self.get_conf_variables()))
 
109
 
 
110
        if exists(self.ca_cert) or exists(self.ca_key):
 
111
            raise RuntimeError("Initialized called when CA already exists")
 
112
        cmd = ['openssl', 'req', '-config', self.ca_conf,
 
113
               '-x509', '-nodes', '-newkey', 'rsa',
 
114
               '-days', self.default_ca_expiry,
 
115
               '-keyout', self.ca_key, '-out', self.ca_cert,
 
116
               '-outform', 'PEM']
 
117
        output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
 
118
        log("CA Init:\n %s" % output, level=DEBUG)
 
119
 
 
120
    def get_conf_variables(self):
 
121
        return dict(
 
122
            org_name="juju",
 
123
            org_unit_name="%s service" % self.name,
 
124
            common_name=self.name,
 
125
            ca_dir=self.ca_dir)
 
126
 
 
127
    def get_or_create_cert(self, common_name):
 
128
        if common_name in self:
 
129
            return self.get_certificate(common_name)
 
130
        return self.create_certificate(common_name)
 
131
 
 
132
    def create_certificate(self, common_name):
 
133
        if common_name in self:
 
134
            return self.get_certificate(common_name)
 
135
        key_p = path_join(self.ca_dir, "certs", "%s.key" % common_name)
 
136
        crt_p = path_join(self.ca_dir, "certs", "%s.crt" % common_name)
 
137
        csr_p = path_join(self.ca_dir, "certs", "%s.csr" % common_name)
 
138
        self._create_certificate(common_name, key_p, csr_p, crt_p)
 
139
        return self.get_certificate(common_name)
 
140
 
 
141
    def get_certificate(self, common_name):
 
142
        if common_name not in self:
 
143
            raise ValueError("No certificate for %s" % common_name)
 
144
        key_p = path_join(self.ca_dir, "certs", "%s.key" % common_name)
 
145
        crt_p = path_join(self.ca_dir, "certs", "%s.crt" % common_name)
 
146
        with open(crt_p) as fh:
 
147
            crt = fh.read()
 
148
        with open(key_p) as fh:
 
149
            key = fh.read()
 
150
        return crt, key
 
151
 
 
152
    def __contains__(self, common_name):
 
153
        crt_p = path_join(self.ca_dir, "certs", "%s.crt" % common_name)
 
154
        return exists(crt_p)
 
155
 
 
156
    def _create_certificate(self, common_name, key_p, csr_p, crt_p):
 
157
        template_vars = self.get_conf_variables()
 
158
        template_vars['common_name'] = common_name
 
159
        subj = '/O=%(org_name)s/OU=%(org_unit_name)s/CN=%(common_name)s' % (
 
160
            template_vars)
 
161
 
 
162
        log("CA Create Cert %s" % common_name, level=DEBUG)
 
163
        cmd = ['openssl', 'req', '-sha1', '-newkey', 'rsa:2048',
 
164
               '-nodes', '-days', self.default_expiry,
 
165
               '-keyout', key_p, '-out', csr_p, '-subj', subj]
 
166
        subprocess.check_call(cmd, stderr=subprocess.PIPE)
 
167
        cmd = ['openssl', 'rsa', '-in', key_p, '-out', key_p]
 
168
        subprocess.check_call(cmd, stderr=subprocess.PIPE)
 
169
 
 
170
        log("CA Sign Cert %s" % common_name, level=DEBUG)
 
171
        if self.cert_type == MYSQL_CERT:
 
172
            cmd = ['openssl', 'x509', '-req',
 
173
                   '-in', csr_p, '-days', self.default_expiry,
 
174
                   '-CA', self.ca_cert, '-CAkey', self.ca_key,
 
175
                   '-set_serial', '01', '-out', crt_p]
 
176
        else:
 
177
            cmd = ['openssl', 'ca', '-config', self.signing_conf,
 
178
                   '-extensions', 'req_extensions',
 
179
                   '-days', self.default_expiry, '-notext',
 
180
                   '-in', csr_p, '-out', crt_p, '-subj', subj, '-batch']
 
181
        log("running %s" % " ".join(cmd), level=DEBUG)
 
182
        subprocess.check_call(cmd, stderr=subprocess.PIPE)
 
183
 
 
184
    def get_ca_bundle(self):
 
185
        with open(self.ca_cert) as fh:
 
186
            return fh.read()
 
187
 
 
188
 
 
189
CA_CONF_TEMPLATE = """
 
190
[ ca ]
 
191
default_ca = CA_default
 
192
 
 
193
[ CA_default ]
 
194
dir                     = %(ca_dir)s
 
195
policy                  = policy_match
 
196
database                = $dir/index.txt
 
197
serial                  = $dir/serial
 
198
certs                   = $dir/certs
 
199
crl_dir                 = $dir/crl
 
200
new_certs_dir           = $dir/newcerts
 
201
certificate             = $dir/cacert.pem
 
202
private_key             = $dir/private/cacert.key
 
203
RANDFILE                = $dir/private/.rand
 
204
default_md              = default
 
205
 
 
206
[ req ]
 
207
default_bits            = 1024
 
208
default_md              = sha1
 
209
 
 
210
prompt                  = no
 
211
distinguished_name      = ca_distinguished_name
 
212
 
 
213
x509_extensions         = ca_extensions
 
214
 
 
215
[ ca_distinguished_name ]
 
216
organizationName        = %(org_name)s
 
217
organizationalUnitName  = %(org_unit_name)s Certificate Authority
 
218
 
 
219
 
 
220
[ policy_match ]
 
221
countryName             = optional
 
222
stateOrProvinceName     = optional
 
223
organizationName        = match
 
224
organizationalUnitName  = optional
 
225
commonName              = supplied
 
226
 
 
227
[ ca_extensions ]
 
228
basicConstraints        = critical,CA:true
 
229
subjectKeyIdentifier    = hash
 
230
authorityKeyIdentifier  = keyid:always, issuer
 
231
keyUsage                = cRLSign, keyCertSign
 
232
"""
 
233
 
 
234
 
 
235
SIGNING_CONF_TEMPLATE = """
 
236
[ ca ]
 
237
default_ca = CA_default
 
238
 
 
239
[ CA_default ]
 
240
dir                     = %(ca_dir)s
 
241
policy                  = policy_match
 
242
database                = $dir/index.txt
 
243
serial                  = $dir/serial
 
244
certs                   = $dir/certs
 
245
crl_dir                 = $dir/crl
 
246
new_certs_dir           = $dir/newcerts
 
247
certificate             = $dir/cacert.pem
 
248
private_key             = $dir/private/cacert.key
 
249
RANDFILE                = $dir/private/.rand
 
250
default_md              = default
 
251
 
 
252
[ req ]
 
253
default_bits            = 1024
 
254
default_md              = sha1
 
255
 
 
256
prompt                  = no
 
257
distinguished_name      = req_distinguished_name
 
258
 
 
259
x509_extensions         = req_extensions
 
260
 
 
261
[ req_distinguished_name ]
 
262
organizationName        = %(org_name)s
 
263
organizationalUnitName  = %(org_unit_name)s machine resources
 
264
commonName              = %(common_name)s
 
265
 
 
266
[ policy_match ]
 
267
countryName             = optional
 
268
stateOrProvinceName     = optional
 
269
organizationName        = match
 
270
organizationalUnitName  = optional
 
271
commonName              = supplied
 
272
 
 
273
[ req_extensions ]
 
274
basicConstraints        = CA:false
 
275
subjectKeyIdentifier    = hash
 
276
authorityKeyIdentifier  = keyid:always, issuer
 
277
keyUsage                = digitalSignature, keyEncipherment, keyAgreement
 
278
extendedKeyUsage        = serverAuth, clientAuth
 
279
"""