~fginther/charms/precise/nagios/add-apt-get-update

« back to all changes in this revision

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

  • Committer: matthew.bruzek at canonical
  • Date: 2014-04-11 14:06:48 UTC
  • mfrom: (10.2.14 nagios-ssl-everywhere)
  • Revision ID: matthew.bruzek@canonical.com-20140411140648-hu7qm9i68gzgxdqr
[lazypower] Enable ssl everywhere for nagios adding charm helper.
[mbruzek] Updated README.md for formatting and minor typos.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import logging
 
2
import os
 
3
from os.path import join as path_join
 
4
from os.path import exists
 
5
import subprocess
 
6
 
 
7
 
 
8
log = logging.getLogger("service_ca")
 
9
 
 
10
logging.basicConfig(level=logging.DEBUG)
 
11
 
 
12
STD_CERT = "standard"
 
13
 
 
14
# Mysql server is fairly picky about cert creation
 
15
# and types, spec its creation separately for now.
 
16
MYSQL_CERT = "mysql"
 
17
 
 
18
 
 
19
class ServiceCA(object):
 
20
 
 
21
    default_expiry = str(365 * 2)
 
22
    default_ca_expiry = str(365 * 6)
 
23
 
 
24
    def __init__(self, name, ca_dir, cert_type=STD_CERT):
 
25
        self.name = name
 
26
        self.ca_dir = ca_dir
 
27
        self.cert_type = cert_type
 
28
 
 
29
    ###############
 
30
    # Hook Helper API
 
31
    @staticmethod
 
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)
 
36
        ca.init()
 
37
        return ca
 
38
 
 
39
    @classmethod
 
40
    def get_service_cert(cls, type=STD_CERT):
 
41
        service_name = os.environ['JUJU_UNIT_NAME'].split('/')[0]
 
42
        ca = cls.get_ca()
 
43
        crt, key = ca.get_or_create_cert(service_name)
 
44
        return crt, key, ca.get_ca_bundle()
 
45
 
 
46
    ###############
 
47
 
 
48
    def init(self):
 
49
        log.debug("initializing service ca")
 
50
        if not exists(self.ca_dir):
 
51
            self._init_ca_dir(self.ca_dir)
 
52
            self._init_ca()
 
53
 
 
54
    @property
 
55
    def ca_key(self):
 
56
        return path_join(self.ca_dir, 'private', 'cacert.key')
 
57
 
 
58
    @property
 
59
    def ca_cert(self):
 
60
        return path_join(self.ca_dir, 'cacert.pem')
 
61
 
 
62
    @property
 
63
    def ca_conf(self):
 
64
        return path_join(self.ca_dir, 'ca.cnf')
 
65
 
 
66
    @property
 
67
    def signing_conf(self):
 
68
        return path_join(self.ca_dir, 'signing.cnf')
 
69
 
 
70
    def _init_ca_dir(self, ca_dir):
 
71
        os.mkdir(ca_dir)
 
72
        for i in ['certs', 'crl', 'newcerts', 'private']:
 
73
            sd = path_join(ca_dir, i)
 
74
            if not exists(sd):
 
75
                os.mkdir(sd)
 
76
 
 
77
        if not exists(path_join(ca_dir, 'serial')):
 
78
            with open(path_join(ca_dir, 'serial'), 'wb') as fh:
 
79
                fh.write('02\n')
 
80
 
 
81
        if not exists(path_join(ca_dir, 'index.txt')):
 
82
            with open(path_join(ca_dir, 'index.txt'), 'wb') as fh:
 
83
                fh.write('')
 
84
 
 
85
    def _init_ca(self):
 
86
        """Generate the root ca's cert and key.
 
87
        """
 
88
        if not exists(path_join(self.ca_dir, 'ca.cnf')):
 
89
            with open(path_join(self.ca_dir, 'ca.cnf'), 'wb') as fh:
 
90
                fh.write(
 
91
                    CA_CONF_TEMPLATE % (self.get_conf_variables()))
 
92
 
 
93
        if not exists(path_join(self.ca_dir, 'signing.cnf')):
 
94
            with open(path_join(self.ca_dir, 'signing.cnf'), 'wb') as fh:
 
95
                fh.write(
 
96
                    SIGNING_CONF_TEMPLATE % (self.get_conf_variables()))
 
97
 
 
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,
 
104
               '-outform', 'PEM']
 
105
        output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
 
106
        log.debug("CA Init:\n %s", output)
 
107
 
 
108
    def get_conf_variables(self):
 
109
        return dict(
 
110
            org_name="juju",
 
111
            org_unit_name="%s service" % self.name,
 
112
            common_name=self.name,
 
113
            ca_dir=self.ca_dir)
 
114
 
 
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)
 
119
 
 
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)
 
128
 
 
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:
 
135
            crt = fh.read()
 
136
        with open(key_p) as fh:
 
137
            key = fh.read()
 
138
        return crt, key
 
139
 
 
140
    def __contains__(self, common_name):
 
141
        crt_p = path_join(self.ca_dir, "certs", "%s.crt" % common_name)
 
142
        return exists(crt_p)
 
143
 
 
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' % (
 
148
            template_vars)
 
149
 
 
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)
 
157
 
 
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]
 
164
        else:
 
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)
 
171
 
 
172
    def get_ca_bundle(self):
 
173
        with open(self.ca_cert) as fh:
 
174
            return fh.read()
 
175
 
 
176
 
 
177
CA_CONF_TEMPLATE = """
 
178
[ ca ]
 
179
default_ca = CA_default
 
180
 
 
181
[ CA_default ]
 
182
dir                     = %(ca_dir)s
 
183
policy                  = policy_match
 
184
database                = $dir/index.txt
 
185
serial                  = $dir/serial
 
186
certs                   = $dir/certs
 
187
crl_dir                 = $dir/crl
 
188
new_certs_dir           = $dir/newcerts
 
189
certificate             = $dir/cacert.pem
 
190
private_key             = $dir/private/cacert.key
 
191
RANDFILE                = $dir/private/.rand
 
192
default_md              = default
 
193
 
 
194
[ req ]
 
195
default_bits            = 1024
 
196
default_md              = sha1
 
197
 
 
198
prompt                  = no
 
199
distinguished_name      = ca_distinguished_name
 
200
 
 
201
x509_extensions         = ca_extensions
 
202
 
 
203
[ ca_distinguished_name ]
 
204
organizationName        = %(org_name)s
 
205
organizationalUnitName  = %(org_unit_name)s Certificate Authority
 
206
 
 
207
 
 
208
[ policy_match ]
 
209
countryName             = optional
 
210
stateOrProvinceName     = optional
 
211
organizationName        = match
 
212
organizationalUnitName  = optional
 
213
commonName              = supplied
 
214
 
 
215
[ ca_extensions ]
 
216
basicConstraints        = critical,CA:true
 
217
subjectKeyIdentifier    = hash
 
218
authorityKeyIdentifier  = keyid:always, issuer
 
219
keyUsage                = cRLSign, keyCertSign
 
220
"""
 
221
 
 
222
 
 
223
SIGNING_CONF_TEMPLATE = """
 
224
[ ca ]
 
225
default_ca = CA_default
 
226
 
 
227
[ CA_default ]
 
228
dir                     = %(ca_dir)s
 
229
policy                  = policy_match
 
230
database                = $dir/index.txt
 
231
serial                  = $dir/serial
 
232
certs                   = $dir/certs
 
233
crl_dir                 = $dir/crl
 
234
new_certs_dir           = $dir/newcerts
 
235
certificate             = $dir/cacert.pem
 
236
private_key             = $dir/private/cacert.key
 
237
RANDFILE                = $dir/private/.rand
 
238
default_md              = default
 
239
 
 
240
[ req ]
 
241
default_bits            = 1024
 
242
default_md              = sha1
 
243
 
 
244
prompt                  = no
 
245
distinguished_name      = req_distinguished_name
 
246
 
 
247
x509_extensions         = req_extensions
 
248
 
 
249
[ req_distinguished_name ]
 
250
organizationName        = %(org_name)s
 
251
organizationalUnitName  = %(org_unit_name)s machine resources
 
252
commonName              = %(common_name)s
 
253
 
 
254
[ policy_match ]
 
255
countryName             = optional
 
256
stateOrProvinceName     = optional
 
257
organizationName        = match
 
258
organizationalUnitName  = optional
 
259
commonName              = supplied
 
260
 
 
261
[ req_extensions ]
 
262
basicConstraints        = CA:false
 
263
subjectKeyIdentifier    = hash
 
264
authorityKeyIdentifier  = keyid:always, issuer
 
265
keyUsage                = digitalSignature, keyEncipherment, keyAgreement
 
266
extendedKeyUsage        = serverAuth, clientAuth
 
267
"""