Merge lp:~josvaz/charms/trusty/bip/client_side_ssl-with_helper-lp1604894 into lp:charms/trusty/bip

Proposed by Jose L. VG
Status: Needs review
Proposed branch: lp:~josvaz/charms/trusty/bip/client_side_ssl-with_helper-lp1604894
Merge into: lp:charms/trusty/bip
Prerequisite: lp:~josvaz/charms/trusty/bip/charmhelpers-cleanup
Diff against target: 618 lines (+496/-7)
8 files modified
charm-helpers.yaml (+1/-0)
config.yaml (+4/-0)
hooks/hooks.py (+30/-6)
lib/charmhelpers/contrib/__init__.py (+13/-0)
lib/charmhelpers/contrib/ssl/__init__.py (+92/-0)
lib/charmhelpers/contrib/ssl/service.py (+277/-0)
templates/bip_conf.template (+1/-1)
tests/11-deploy-ssl (+78/-0)
To merge this branch: bzr merge lp:~josvaz/charms/trusty/bip/client_side_ssl-with_helper-lp1604894
Reviewer Review Type Date Requested Status
Review Queue (community) automated testing Approve
Brad Marshall (community) Approve
Pen Gale (community) Approve
charmers Pending
Review via email: mp+301802@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Jose L. VG (josvaz) wrote :

This has been tested on a juju setup on GCE

Revision history for this message
Pen Gale (pengale) wrote :

Code looks sensible, and does not break existing tests, but I would like to see unit or integration tests that cover the self signing code.

review: Needs Fixing
Revision history for this message
Jose L. VG (josvaz) wrote :

Would a test that calls gen_certs() and verifies the generated pem files are for a self signed cert & key suffice?

Revision history for this message
Jose L. VG (josvaz) wrote :

Also this MP depends on this other to be done first:
https://code.launchpad.net/~josvaz/charms/trusty/bip/charmhelpers-cleanup/+merge/301499

Do I need to fix anything in that one?

32. By Jose L. VG

Adding a bip ssl deploy test (validates cert & handshake)

Revision history for this message
Jose L. VG (josvaz) wrote :

Added a 11-deploy-ssl test that deploys a ssl enabled bip on port 6697, it retrieves the server certificate and then validates against it a SSL connection.

I guess this covers all the new feature code.

Please review!

Revision history for this message
Pen Gale (pengale) wrote :

Hello Jose,

Thank you for all your work on this. The new test looks great, and passes when I run bundletester. :-)

I am +1 on this.

There is an outstanding issue with merging and promulgating this, which I noted in the related PR: the bip charm is currently own by ~charmers, rather than a specific maintainer. I can't fix that myself, but I am poking people about it; I'll ping this ticket when the issue is fixed.

review: Approve
Revision history for this message
Pen Gale (pengale) wrote :

Dropping in an update here. The next steps for this change need to be:

@bradm needs to update his branch of this charm with the latest from ~charmers, then merge this PR into it. Then he needs to promulgate a charm from his namespace, and request that a charmer merge it.

That should update the charm store so that bip gets pointed at bradm's namespace (since he's the maintainer), and should allow bradm to more easily maintain the charm going forward.

@josvaz: you most likely don't need to do anything else with this PR; the maintainer (bradm) just needs to do some housekeeping to get things merged.

Revision history for this message
Jose L. VG (josvaz) wrote :

Thanks fo rthe update Pete!

Jose

On Thu, Aug 25, 2016 at 3:43 PM, petevg <email address hidden>
wrote:

> Dropping in an update here. The next steps for this change need to be:
>
> @bradm needs to update his branch of this charm with the latest from
> ~charmers, then merge this PR into it. Then he needs to promulgate a charm
> from his namespace, and request that a charmer merge it.
>
> That should update the charm store so that bip gets pointed at bradm's
> namespace (since he's the maintainer), and should allow bradm to more
> easily maintain the charm going forward.
>
> @josvaz: you most likely don't need to do anything else with this PR; the
> maintainer (bradm) just needs to do some housekeeping to get things merged.
> --
> https://code.launchpad.net/~josvaz/charms/trusty/bip/
> client_side_ssl-with_helper-lp1604894/+merge/301802
> You are the owner of lp:~josvaz/charms/trusty/bip/
> client_side_ssl-with_helper-lp1604894.
>

Revision history for this message
Pen Gale (pengale) wrote :

Poked bradm about confirming that bip is moved and closing this review out.

Revision history for this message
Brad Marshall (brad-marshall) wrote :

Looks good to me, merged into my own branch and am working on getting it updated in the charmstore.

review: Approve
Revision history for this message
Jose L. VG (josvaz) wrote :

Thanks Brad, let me know if/when this MP needs to be marked Merged manually.

Revision history for this message
Review Queue (review-queue) wrote :

The results (PASS) are in and available here: http://juju-ci.vapour.ws/job/charm-bundle-test-aws/5067/

review: Approve (automated testing)
Revision history for this message
Review Queue (review-queue) wrote :

The results (PASS) are in and available here: http://juju-ci.vapour.ws/job/charm-bundle-test-lxc/4866/

review: Approve (automated testing)

Unmerged revisions

32. By Jose L. VG

Adding a bip ssl deploy test (validates cert & handshake)

31. By Jose L. VG

Added support to client_side_ssl with charmhelper contrib.ssl

30. By Jose L. VG

Got charmhelpers for fetch as apt_install is now there

29. By Jose L. VG

Moved from charm-helpers/charmhelpers to just charmhelpers (plus clean-up)

28. By Jose L. VG

Result of removing charmhelpers and re-syncing it

27. By Jose L. VG

Added charm-helpers.yaml to manage helpers with charm_helpers_sync tool

26. By Jose L. VG

Flake8 suggested fixes, including unused imports

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charm-helpers.yaml'
2--- charm-helpers.yaml 2016-08-19 17:00:35 +0000
3+++ charm-helpers.yaml 2016-08-19 17:00:35 +0000
4@@ -3,3 +3,4 @@
5 include:
6 - core
7 - fetch
8+ - contrib.ssl
9
10=== modified file 'config.yaml'
11--- config.yaml 2016-07-20 10:26:23 +0000
12+++ config.yaml 2016-08-19 17:00:35 +0000
13@@ -35,6 +35,10 @@
14 type: string
15 default: '{"oftc": {host: "irc.oftc.net", port: 6667}, "freenode": {host: "irc.freenode.net", port: 6667}}'
16 description: IRC Networks defined
17+ client_side_ssl:
18+ type: string
19+ default: "false"
20+ description: Use SSL to connect to bip from your client
21 user:
22 type: string
23 description: User definitions
24
25=== modified file 'hooks/hooks.py'
26--- hooks/hooks.py 2016-08-19 17:00:35 +0000
27+++ hooks/hooks.py 2016-08-19 17:00:35 +0000
28@@ -3,10 +3,13 @@
29 # Copyright 2013 Canonical Ltd. All rights reserved.
30 # Author: Brad Marshall <brad.marshall@canonical.com>
31
32+import glob
33+import pwd
34 import os
35 import sys
36 import json
37 import yaml
38+import subprocess
39
40 local_copy = os.path.join(
41 os.path.dirname(os.path.abspath(os.path.dirname(__file__))),
42@@ -29,6 +32,8 @@
43 open_port,
44 )
45
46+from charmhelpers.contrib import ssl
47+
48 hook_dir = os.path.abspath(os.path.dirname(__file__))
49 charm_dir = os.path.dirname(hook_dir)
50
51@@ -41,6 +46,7 @@
52 user_config = config().get('user')
53 backlog_lines = config().get('backlog_lines')
54 backlog_msg_only = config().get('backlog_msg_only')
55+client_side_ssl = config().get('client_side_ssl')
56 backlog_always = config().get('backlog_always')
57 bip_conf = '/etc/bip.conf'
58 bip_defaults = '/etc/default/bip'
59@@ -48,27 +54,44 @@
60 required_pkgs = [
61 'bip',
62 'python-jinja2',
63+ 'openssl',
64 ]
65
66 hooks = Hooks()
67
68
69+def gen_certs():
70+ """gen_certs generates bip SSL certificates with openssl CLI"""
71+ bip_dir = "/var/lib/bip"
72+ pem = "%s/bip.pem" % bip_dir
73+ ssl.generate_selfsigned(keyfile=pem, certfile=pem, keysize=4096,
74+ cn="BipServerSert")
75+ os.chmod('%s/bip.pem' % bip_dir, 0600)
76+ bip_uid = pwd.getpwnam("bip").pw_uid
77+ os.chown('%s/bip.pem' % bip_dir, bip_uid, -1)
78+
79+def setup():
80+ """setup executes post configuration actions, like certificate generation"""
81+ if client_side_ssl.lower() == 'true':
82+ gen_certs()
83+
84+
85 def update_config():
86 from jinja2 import Environment, FileSystemLoader
87 if network_config:
88- # Try originally supported JSON formatted config
89+ ## Try originally supported JSON formatted config
90 try:
91 network = json.loads(network_config)
92- # else use YAML (current):
93+ ## else use YAML (current):
94 except ValueError:
95 network = yaml.load(network_config)
96 else:
97 network = {}
98 if user_config:
99- # Try originally supported JSON formatted config
100+ ## Try originally supported JSON formatted config
101 try:
102 user = json.loads(user_config)
103- # else use YAML (current):
104+ ## else use YAML (current):
105 except ValueError:
106 user = yaml.load(user_config)
107 else:
108@@ -87,12 +110,13 @@
109 'user': user,
110 'backlog_lines': backlog_lines,
111 'backlog_msg_only': backlog_msg_only,
112+ 'client_side_ssl': client_side_ssl,
113 'backlog_always': backlog_always,
114 }
115- template = template_env.get_template('bip_conf.template').render(
116- templ_vars)
117+ template = template_env.get_template('bip_conf.template').render(templ_vars)
118 with open(bip_conf, 'w') as bip_conf_config:
119 bip_conf_config.write(str(template))
120+ setup()
121
122
123 @hooks.hook()
124
125=== added directory 'lib/charmhelpers/contrib'
126=== added file 'lib/charmhelpers/contrib/__init__.py'
127--- lib/charmhelpers/contrib/__init__.py 1970-01-01 00:00:00 +0000
128+++ lib/charmhelpers/contrib/__init__.py 2016-08-19 17:00:35 +0000
129@@ -0,0 +1,13 @@
130+# Copyright 2014-2015 Canonical Limited.
131+#
132+# Licensed under the Apache License, Version 2.0 (the "License");
133+# you may not use this file except in compliance with the License.
134+# You may obtain a copy of the License at
135+#
136+# http://www.apache.org/licenses/LICENSE-2.0
137+#
138+# Unless required by applicable law or agreed to in writing, software
139+# distributed under the License is distributed on an "AS IS" BASIS,
140+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
141+# See the License for the specific language governing permissions and
142+# limitations under the License.
143
144=== added directory 'lib/charmhelpers/contrib/ssl'
145=== added file 'lib/charmhelpers/contrib/ssl/__init__.py'
146--- lib/charmhelpers/contrib/ssl/__init__.py 1970-01-01 00:00:00 +0000
147+++ lib/charmhelpers/contrib/ssl/__init__.py 2016-08-19 17:00:35 +0000
148@@ -0,0 +1,92 @@
149+# Copyright 2014-2015 Canonical Limited.
150+#
151+# Licensed under the Apache License, Version 2.0 (the "License");
152+# you may not use this file except in compliance with the License.
153+# You may obtain a copy of the License at
154+#
155+# http://www.apache.org/licenses/LICENSE-2.0
156+#
157+# Unless required by applicable law or agreed to in writing, software
158+# distributed under the License is distributed on an "AS IS" BASIS,
159+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
160+# See the License for the specific language governing permissions and
161+# limitations under the License.
162+
163+import subprocess
164+from charmhelpers.core import hookenv
165+
166+
167+def generate_selfsigned(keyfile, certfile, keysize="1024", config=None, subject=None, cn=None):
168+ """Generate selfsigned SSL keypair
169+
170+ You must provide one of the 3 optional arguments:
171+ config, subject or cn
172+ If more than one is provided the leftmost will be used
173+
174+ Arguments:
175+ keyfile -- (required) full path to the keyfile to be created
176+ certfile -- (required) full path to the certfile to be created
177+ keysize -- (optional) SSL key length
178+ config -- (optional) openssl configuration file
179+ subject -- (optional) dictionary with SSL subject variables
180+ cn -- (optional) cerfificate common name
181+
182+ Required keys in subject dict:
183+ cn -- Common name (eq. FQDN)
184+
185+ Optional keys in subject dict
186+ country -- Country Name (2 letter code)
187+ state -- State or Province Name (full name)
188+ locality -- Locality Name (eg, city)
189+ organization -- Organization Name (eg, company)
190+ organizational_unit -- Organizational Unit Name (eg, section)
191+ email -- Email Address
192+ """
193+
194+ cmd = []
195+ if config:
196+ cmd = ["/usr/bin/openssl", "req", "-new", "-newkey",
197+ "rsa:{}".format(keysize), "-days", "365", "-nodes", "-x509",
198+ "-keyout", keyfile,
199+ "-out", certfile, "-config", config]
200+ elif subject:
201+ ssl_subject = ""
202+ if "country" in subject:
203+ ssl_subject = ssl_subject + "/C={}".format(subject["country"])
204+ if "state" in subject:
205+ ssl_subject = ssl_subject + "/ST={}".format(subject["state"])
206+ if "locality" in subject:
207+ ssl_subject = ssl_subject + "/L={}".format(subject["locality"])
208+ if "organization" in subject:
209+ ssl_subject = ssl_subject + "/O={}".format(subject["organization"])
210+ if "organizational_unit" in subject:
211+ ssl_subject = ssl_subject + "/OU={}".format(subject["organizational_unit"])
212+ if "cn" in subject:
213+ ssl_subject = ssl_subject + "/CN={}".format(subject["cn"])
214+ else:
215+ hookenv.log("When using \"subject\" argument you must "
216+ "provide \"cn\" field at very least")
217+ return False
218+ if "email" in subject:
219+ ssl_subject = ssl_subject + "/emailAddress={}".format(subject["email"])
220+
221+ cmd = ["/usr/bin/openssl", "req", "-new", "-newkey",
222+ "rsa:{}".format(keysize), "-days", "365", "-nodes", "-x509",
223+ "-keyout", keyfile,
224+ "-out", certfile, "-subj", ssl_subject]
225+ elif cn:
226+ cmd = ["/usr/bin/openssl", "req", "-new", "-newkey",
227+ "rsa:{}".format(keysize), "-days", "365", "-nodes", "-x509",
228+ "-keyout", keyfile,
229+ "-out", certfile, "-subj", "/CN={}".format(cn)]
230+
231+ if not cmd:
232+ hookenv.log("No config, subject or cn provided,"
233+ "unable to generate self signed SSL certificates")
234+ return False
235+ try:
236+ subprocess.check_call(cmd)
237+ return True
238+ except Exception as e:
239+ print("Execution of openssl command failed:\n{}".format(e))
240+ return False
241
242=== added file 'lib/charmhelpers/contrib/ssl/service.py'
243--- lib/charmhelpers/contrib/ssl/service.py 1970-01-01 00:00:00 +0000
244+++ lib/charmhelpers/contrib/ssl/service.py 2016-08-19 17:00:35 +0000
245@@ -0,0 +1,277 @@
246+# Copyright 2014-2015 Canonical Limited.
247+#
248+# Licensed under the Apache License, Version 2.0 (the "License");
249+# you may not use this file except in compliance with the License.
250+# You may obtain a copy of the License at
251+#
252+# http://www.apache.org/licenses/LICENSE-2.0
253+#
254+# Unless required by applicable law or agreed to in writing, software
255+# distributed under the License is distributed on an "AS IS" BASIS,
256+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
257+# See the License for the specific language governing permissions and
258+# limitations under the License.
259+
260+import os
261+from os.path import join as path_join
262+from os.path import exists
263+import subprocess
264+
265+from charmhelpers.core.hookenv import log, DEBUG
266+
267+STD_CERT = "standard"
268+
269+# Mysql server is fairly picky about cert creation
270+# and types, spec its creation separately for now.
271+MYSQL_CERT = "mysql"
272+
273+
274+class ServiceCA(object):
275+
276+ default_expiry = str(365 * 2)
277+ default_ca_expiry = str(365 * 6)
278+
279+ def __init__(self, name, ca_dir, cert_type=STD_CERT):
280+ self.name = name
281+ self.ca_dir = ca_dir
282+ self.cert_type = cert_type
283+
284+ ###############
285+ # Hook Helper API
286+ @staticmethod
287+ def get_ca(type=STD_CERT):
288+ service_name = os.environ['JUJU_UNIT_NAME'].split('/')[0]
289+ ca_path = os.path.join(os.environ['CHARM_DIR'], 'ca')
290+ ca = ServiceCA(service_name, ca_path, type)
291+ ca.init()
292+ return ca
293+
294+ @classmethod
295+ def get_service_cert(cls, type=STD_CERT):
296+ service_name = os.environ['JUJU_UNIT_NAME'].split('/')[0]
297+ ca = cls.get_ca()
298+ crt, key = ca.get_or_create_cert(service_name)
299+ return crt, key, ca.get_ca_bundle()
300+
301+ ###############
302+
303+ def init(self):
304+ log("initializing service ca", level=DEBUG)
305+ if not exists(self.ca_dir):
306+ self._init_ca_dir(self.ca_dir)
307+ self._init_ca()
308+
309+ @property
310+ def ca_key(self):
311+ return path_join(self.ca_dir, 'private', 'cacert.key')
312+
313+ @property
314+ def ca_cert(self):
315+ return path_join(self.ca_dir, 'cacert.pem')
316+
317+ @property
318+ def ca_conf(self):
319+ return path_join(self.ca_dir, 'ca.cnf')
320+
321+ @property
322+ def signing_conf(self):
323+ return path_join(self.ca_dir, 'signing.cnf')
324+
325+ def _init_ca_dir(self, ca_dir):
326+ os.mkdir(ca_dir)
327+ for i in ['certs', 'crl', 'newcerts', 'private']:
328+ sd = path_join(ca_dir, i)
329+ if not exists(sd):
330+ os.mkdir(sd)
331+
332+ if not exists(path_join(ca_dir, 'serial')):
333+ with open(path_join(ca_dir, 'serial'), 'w') as fh:
334+ fh.write('02\n')
335+
336+ if not exists(path_join(ca_dir, 'index.txt')):
337+ with open(path_join(ca_dir, 'index.txt'), 'w') as fh:
338+ fh.write('')
339+
340+ def _init_ca(self):
341+ """Generate the root ca's cert and key.
342+ """
343+ if not exists(path_join(self.ca_dir, 'ca.cnf')):
344+ with open(path_join(self.ca_dir, 'ca.cnf'), 'w') as fh:
345+ fh.write(
346+ CA_CONF_TEMPLATE % (self.get_conf_variables()))
347+
348+ if not exists(path_join(self.ca_dir, 'signing.cnf')):
349+ with open(path_join(self.ca_dir, 'signing.cnf'), 'w') as fh:
350+ fh.write(
351+ SIGNING_CONF_TEMPLATE % (self.get_conf_variables()))
352+
353+ if exists(self.ca_cert) or exists(self.ca_key):
354+ raise RuntimeError("Initialized called when CA already exists")
355+ cmd = ['openssl', 'req', '-config', self.ca_conf,
356+ '-x509', '-nodes', '-newkey', 'rsa',
357+ '-days', self.default_ca_expiry,
358+ '-keyout', self.ca_key, '-out', self.ca_cert,
359+ '-outform', 'PEM']
360+ output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
361+ log("CA Init:\n %s" % output, level=DEBUG)
362+
363+ def get_conf_variables(self):
364+ return dict(
365+ org_name="juju",
366+ org_unit_name="%s service" % self.name,
367+ common_name=self.name,
368+ ca_dir=self.ca_dir)
369+
370+ def get_or_create_cert(self, common_name):
371+ if common_name in self:
372+ return self.get_certificate(common_name)
373+ return self.create_certificate(common_name)
374+
375+ def create_certificate(self, common_name):
376+ if common_name in self:
377+ return self.get_certificate(common_name)
378+ key_p = path_join(self.ca_dir, "certs", "%s.key" % common_name)
379+ crt_p = path_join(self.ca_dir, "certs", "%s.crt" % common_name)
380+ csr_p = path_join(self.ca_dir, "certs", "%s.csr" % common_name)
381+ self._create_certificate(common_name, key_p, csr_p, crt_p)
382+ return self.get_certificate(common_name)
383+
384+ def get_certificate(self, common_name):
385+ if common_name not in self:
386+ raise ValueError("No certificate for %s" % common_name)
387+ key_p = path_join(self.ca_dir, "certs", "%s.key" % common_name)
388+ crt_p = path_join(self.ca_dir, "certs", "%s.crt" % common_name)
389+ with open(crt_p) as fh:
390+ crt = fh.read()
391+ with open(key_p) as fh:
392+ key = fh.read()
393+ return crt, key
394+
395+ def __contains__(self, common_name):
396+ crt_p = path_join(self.ca_dir, "certs", "%s.crt" % common_name)
397+ return exists(crt_p)
398+
399+ def _create_certificate(self, common_name, key_p, csr_p, crt_p):
400+ template_vars = self.get_conf_variables()
401+ template_vars['common_name'] = common_name
402+ subj = '/O=%(org_name)s/OU=%(org_unit_name)s/CN=%(common_name)s' % (
403+ template_vars)
404+
405+ log("CA Create Cert %s" % common_name, level=DEBUG)
406+ cmd = ['openssl', 'req', '-sha1', '-newkey', 'rsa:2048',
407+ '-nodes', '-days', self.default_expiry,
408+ '-keyout', key_p, '-out', csr_p, '-subj', subj]
409+ subprocess.check_call(cmd, stderr=subprocess.PIPE)
410+ cmd = ['openssl', 'rsa', '-in', key_p, '-out', key_p]
411+ subprocess.check_call(cmd, stderr=subprocess.PIPE)
412+
413+ log("CA Sign Cert %s" % common_name, level=DEBUG)
414+ if self.cert_type == MYSQL_CERT:
415+ cmd = ['openssl', 'x509', '-req',
416+ '-in', csr_p, '-days', self.default_expiry,
417+ '-CA', self.ca_cert, '-CAkey', self.ca_key,
418+ '-set_serial', '01', '-out', crt_p]
419+ else:
420+ cmd = ['openssl', 'ca', '-config', self.signing_conf,
421+ '-extensions', 'req_extensions',
422+ '-days', self.default_expiry, '-notext',
423+ '-in', csr_p, '-out', crt_p, '-subj', subj, '-batch']
424+ log("running %s" % " ".join(cmd), level=DEBUG)
425+ subprocess.check_call(cmd, stderr=subprocess.PIPE)
426+
427+ def get_ca_bundle(self):
428+ with open(self.ca_cert) as fh:
429+ return fh.read()
430+
431+
432+CA_CONF_TEMPLATE = """
433+[ ca ]
434+default_ca = CA_default
435+
436+[ CA_default ]
437+dir = %(ca_dir)s
438+policy = policy_match
439+database = $dir/index.txt
440+serial = $dir/serial
441+certs = $dir/certs
442+crl_dir = $dir/crl
443+new_certs_dir = $dir/newcerts
444+certificate = $dir/cacert.pem
445+private_key = $dir/private/cacert.key
446+RANDFILE = $dir/private/.rand
447+default_md = default
448+
449+[ req ]
450+default_bits = 1024
451+default_md = sha1
452+
453+prompt = no
454+distinguished_name = ca_distinguished_name
455+
456+x509_extensions = ca_extensions
457+
458+[ ca_distinguished_name ]
459+organizationName = %(org_name)s
460+organizationalUnitName = %(org_unit_name)s Certificate Authority
461+
462+
463+[ policy_match ]
464+countryName = optional
465+stateOrProvinceName = optional
466+organizationName = match
467+organizationalUnitName = optional
468+commonName = supplied
469+
470+[ ca_extensions ]
471+basicConstraints = critical,CA:true
472+subjectKeyIdentifier = hash
473+authorityKeyIdentifier = keyid:always, issuer
474+keyUsage = cRLSign, keyCertSign
475+"""
476+
477+
478+SIGNING_CONF_TEMPLATE = """
479+[ ca ]
480+default_ca = CA_default
481+
482+[ CA_default ]
483+dir = %(ca_dir)s
484+policy = policy_match
485+database = $dir/index.txt
486+serial = $dir/serial
487+certs = $dir/certs
488+crl_dir = $dir/crl
489+new_certs_dir = $dir/newcerts
490+certificate = $dir/cacert.pem
491+private_key = $dir/private/cacert.key
492+RANDFILE = $dir/private/.rand
493+default_md = default
494+
495+[ req ]
496+default_bits = 1024
497+default_md = sha1
498+
499+prompt = no
500+distinguished_name = req_distinguished_name
501+
502+x509_extensions = req_extensions
503+
504+[ req_distinguished_name ]
505+organizationName = %(org_name)s
506+organizationalUnitName = %(org_unit_name)s machine resources
507+commonName = %(common_name)s
508+
509+[ policy_match ]
510+countryName = optional
511+stateOrProvinceName = optional
512+organizationName = match
513+organizationalUnitName = optional
514+commonName = supplied
515+
516+[ req_extensions ]
517+basicConstraints = CA:false
518+subjectKeyIdentifier = hash
519+authorityKeyIdentifier = keyid:always, issuer
520+keyUsage = digitalSignature, keyEncipherment, keyAgreement
521+extendedKeyUsage = serverAuth, clientAuth
522+"""
523
524=== modified file 'templates/bip_conf.template'
525--- templates/bip_conf.template 2016-07-20 10:26:23 +0000
526+++ templates/bip_conf.template 2016-08-19 17:00:35 +0000
527@@ -2,7 +2,7 @@
528
529 port = {{ listen_port }};
530
531-client_side_ssl = false;
532+client_side_ssl = {{ client_side_ssl }};
533
534 log_level = 3;
535
536
537=== added file 'tests/11-deploy-ssl'
538--- tests/11-deploy-ssl 1970-01-01 00:00:00 +0000
539+++ tests/11-deploy-ssl 2016-08-19 17:00:35 +0000
540@@ -0,0 +1,78 @@
541+#!/usr/bin/env python3
542+
543+import amulet
544+import os
545+import pprint
546+import ssl
547+import socket
548+import unittest
549+
550+class TestSSLDeployment(unittest.TestCase):
551+ @classmethod
552+ def setUpClass(cls):
553+ cls.deployment = amulet.Deployment()
554+
555+ cls.deployment.add('bip-ssl', charm='bip')
556+
557+ # configure ssl and new port
558+ cls.deployment.configure('bip-ssl', {
559+ 'client_side_ssl': 'true',
560+ 'listen_port': '6697'
561+ })
562+
563+ cls.deployment.expose('bip-ssl')
564+
565+ try:
566+ cls.deployment.setup(timeout=900, cleanup=False)
567+ cls.deployment.sentry.wait()
568+ except amulet.helpers.TimeoutError:
569+ amulet.raise_status(amulet.SKIP,
570+ msg="Environment wasn't stood up in time")
571+ except:
572+ raise
573+
574+ cls.unit = cls.deployment.sentry['bip-ssl'][0]
575+ cls.ipaddr = cls.unit.info['public-address']
576+ cls.ports = cls.unit.info['open-ports']
577+
578+ def test_1_check_running(self):
579+ output = self.unit.run('service bip status')
580+ service_active = 'bip is running' in str(output)
581+ if service_active:
582+ print("Found running bip on %s" % self.ipaddr)
583+ #amulet.raise_status(amulet.PASS, msg=message)
584+ else:
585+ message = "Failed to find running bip"
586+ amulet.raise_status(amulet.FAIL, msg=message)
587+
588+ def test_2_check_ssl(self):
589+ #print("bip.conf: {}".format(self.unit.run('cat /etc/bip.conf')))
590+ s = socket.socket()
591+ for p in self.ports:
592+ (port, proto) = p.split("/")
593+ crt_pem = ssl.get_server_certificate((self.ipaddr, int(port)))
594+ #print(crt_pem)
595+ cert_file = "_test-self-signed-cert.pem"
596+ with open(cert_file, 'wt', encoding='utf-8') as f:
597+ f.write(crt_pem)
598+ sslsock = ssl.wrap_socket(s,
599+ ca_certs=cert_file,
600+ cert_reqs=ssl.CERT_REQUIRED)
601+ try:
602+ sslsock.connect((self.ipaddr, int(port)))
603+ #print(repr(sslsock.getpeername()))
604+ #print(sslsock.cipher())
605+ #print(pprint.pformat(sslsock.getpeercert()))
606+ message = "Connect to %s on port %s succeeded" % (
607+ self.ipaddr, port)
608+ #amulet.raise_status(amulet.PASS, msg=message)
609+ print(message)
610+ sslsock.close()
611+ except socket.error as e:
612+ message = "Connection to %s on port %s failed: %s" % (
613+ self.ipaddr, port, e)
614+ amulet.raise_status(amulet.FAIL, msg=message)
615+ os.remove(cert_file)
616+
617+if __name__ == '__main__':
618+ unittest.main()

Subscribers

People subscribed via source and target branches

to all changes: