~junaidali/charms/trusty/neutron-api-plumgrid/optimal-helpers

« back to all changes in this revision

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

  • Committer: Junaid Ali
  • Date: 2016-04-27 12:17:56 UTC
  • Revision ID: junaidali@plumgrid.com-20160427121756-p3l3mae06r89b27j
Reduced number of helper files

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
 
"""