Merge lp:~hazmat/charms/precise/rabbitmq-server/ssl-everywhere into lp:charms/rabbitmq-server

Proposed by Kapil Thangavelu
Status: Superseded
Proposed branch: lp:~hazmat/charms/precise/rabbitmq-server/ssl-everywhere
Merge into: lp:charms/rabbitmq-server
Diff against target: 926 lines (+562/-105) (has conflicts)
12 files modified
Makefile (+15/-0)
charm-helpers.yaml (+2/-1)
config.yaml (+22/-4)
hooks/lib/utils.py (+8/-11)
hooks/rabbit_utils.py (+30/-15)
hooks/rabbitmq_server_relations.py (+82/-26)
lib/charmhelpers/contrib/openstack/context.py (+104/-41)
lib/charmhelpers/contrib/openstack/utils.py (+9/-5)
lib/charmhelpers/contrib/ssl/service.py (+267/-0)
lib/charmhelpers/core/hookenv.py (+6/-0)
revision (+4/-0)
templates/rabbitmq.config (+13/-2)
Text conflict in revision
To merge this branch: bzr merge lp:~hazmat/charms/precise/rabbitmq-server/ssl-everywhere
Reviewer Review Type Date Requested Status
Charles Butler (community) Approve
charmers Pending
Review via email: mp+207912@code.launchpad.net

This proposal has been superseded by a proposal from 2014-03-04.

Description of the change

SSL Support for RabbitMQ

Adds a few additional configuration options for SSL.

'ssl' is the primary one, it can be 'off', 'on', or 'only'.

If its off, no changes. If its 'on' then ssl support is
enabled and ssl_ca cert is sent to clients along the relation.

If its only, then all clients must connect using ssl.

The server cert/key and ca cert can be provided via config. If
they are not, the service will act as its own ca and provide
the ssl_ca cert to its related clients.

https://codereview.appspot.com/68140043/

To post a comment you must log in.
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

Reviewers: mp+207912_code.launchpad.net,

Message:
Please take a look.

Description:
SSL Support for RabbitMQ

Adds a few additional configuration options for SSL.

'ssl' is the primary one, it can be 'off', 'on', or 'only'.

If its off, no changes. If its 'on' then ssl support is
enabled and ssl_ca cert is sent to clients along the relation.

If its only, then all clients must connect using ssl.

The server cert/key and ca cert can be provided via config. If
they are not, the service will act as its own ca and provide
the ssl_ca cert to its related clients.

https://code.launchpad.net/~hazmat/charms/precise/rabbitmq-server/ssl-everywhere/+merge/207912

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/68140043/

Affected files (+564, -106 lines):
   A Makefile
   A [revision details]
   M charm-helpers.yaml
   M config.yaml
   M hooks/lib/utils.py
   M hooks/rabbit_utils.py
   M hooks/rabbitmq_server_relations.py
   M lib/charmhelpers/contrib/openstack/context.py
   M lib/charmhelpers/contrib/openstack/utils.py
   A lib/charmhelpers/contrib/ssl/service.py
   M lib/charmhelpers/core/hookenv.py
   M revision
   M templates/rabbitmq.config

51. By Kapil Thangavelu

sync helpers

52. By Kapil Thangavelu

incr rev for existing envs

53. By Kapil Thangavelu

sync helpers

54. By Kapil Thangavelu

sync charm-helpers

55. By Kapil Thangavelu

sync helpers

56. By Kapil Thangavelu

sync helpers

Revision history for this message
Charles Butler (lazypower) wrote :

On 2014/02/24 12:11:56, hazmat wrote:
> Please take a look.

+1 LGTM

https://codereview.appspot.com/68140043/

Revision history for this message
Charles Butler (lazypower) wrote :

+1 LGTM

review: Approve

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'Makefile'
2--- Makefile 1970-01-01 00:00:00 +0000
3+++ Makefile 2014-02-25 14:24:12 +0000
4@@ -0,0 +1,15 @@
5+#!/usr/bin/make
6+
7+lint:
8+ @echo -n "Running flake8 tests: "
9+ @flake8 --exclude hooks/charmhelpers hooks
10+ @flake8 unit_tests
11+ @echo "OK"
12+ @echo -n "Running charm proof: "
13+ @charm proof
14+ @echo "OK"
15+
16+sync:
17+ @charm-helper-sync -c charm-helpers.yaml
18+
19+all: test lint
20
21=== modified file 'charm-helpers.yaml'
22--- charm-helpers.yaml 2014-01-16 13:47:32 +0000
23+++ charm-helpers.yaml 2014-02-25 14:24:12 +0000
24@@ -1,6 +1,7 @@
25 destination: lib/charmhelpers
26-branch: lp:charm-helpers
27+branch: lp:~hazmat/charm-helpers/ssl-everywhere
28 include:
29 - core
30 - contrib.charmsupport
31 - contrib.openstack
32+ - contrib.ssl
33
34=== modified file 'config.yaml'
35--- config.yaml 2014-02-11 14:18:05 +0000
36+++ config.yaml 2014-02-25 14:24:12 +0000
37@@ -1,12 +1,23 @@
38 options:
39- ssl_enabled:
40- type: boolean
41- default: False
42- description: enable SSL
43 management_plugin:
44 type: boolean
45 default: False
46 description: enable the management plugin
47+
48+ # SSL Configuration options
49+ ssl:
50+ type: string
51+ default: "off"
52+ description: |
53+ Enable SSL connections on rabbitmq, valid values are 'off', 'on', 'only'. If ssl_key,
54+ ssl_cert, ssl_ca are provided then then those values will be used. Otherwise
55+ the service will act as its own certificate authority and pass its ca cert to clients.
56+ For HA or clustered rabbits ssl key/cert must be provided.
57+ ssl_enabled:
58+ type: boolean
59+ default: False
60+ description: |
61+ (DEPRECATED see 'ssl' config option.) enable SSL
62 ssl_port:
63 type: int
64 default: 5671
65@@ -19,6 +30,13 @@
66 type: string
67 description: X.509 certificate in PEM format (starts "-----BEGIN CERTIFICATE-----")
68 default: ""
69+ ssl_ca:
70+ type: string
71+ description: |
72+ Certificate authority cert that signed pem. Optional if the ssl_cert is signed by a ca
73+ recognized by the os.
74+ default: ""
75+
76 nagios_context:
77 default: "juju"
78 type: string
79
80=== modified file 'hooks/lib/utils.py'
81--- hooks/lib/utils.py 2014-01-08 15:22:21 +0000
82+++ hooks/lib/utils.py 2014-02-25 14:24:12 +0000
83@@ -261,18 +261,15 @@
84
85
86 @cached
87-def config_get(attribute):
88- cmd = [
89- 'config-get',
90- '--format',
91- 'json',
92- ]
93- out = subprocess.check_output(cmd).strip() # IGNORE:E1103
94- cfg = json.loads(out)
95-
96+def config_get(scope=None):
97+ """Juju charm configuration"""
98+ config_cmd_line = ['config-get']
99+ if scope is not None:
100+ config_cmd_line.append(scope)
101+ config_cmd_line.append('--format=json')
102 try:
103- return cfg[attribute]
104- except KeyError:
105+ return json.loads(subprocess.check_output(config_cmd_line))
106+ except ValueError:
107 return None
108
109
110
111=== modified file 'hooks/rabbit_utils.py'
112--- hooks/rabbit_utils.py 2014-01-13 12:15:05 +0000
113+++ hooks/rabbit_utils.py 2014-02-25 14:24:12 +0000
114@@ -218,24 +218,39 @@
115
116 ssl_key_file = "/etc/rabbitmq/rabbit-server-privkey.pem"
117 ssl_cert_file = "/etc/rabbitmq/rabbit-server-cert.pem"
118-
119-
120-def enable_ssl(ssl_key, ssl_cert, ssl_port):
121+ssl_ca_file = "/etc/rabbitmq/rabbit-server-ca.pem"
122+
123+
124+def enable_ssl(ssl_key, ssl_cert, ssl_port,
125+ ssl_ca=None, ssl_only=False, ssl_client=None):
126 uid = pwd.getpwnam("root").pw_uid
127 gid = grp.getgrnam("rabbitmq").gr_gid
128- with open(ssl_key_file, 'w') as key_file:
129- key_file.write(ssl_key)
130- os.chmod(ssl_key_file, 0640)
131- os.chown(ssl_key_file, uid, gid)
132- with open(ssl_cert_file, 'w') as cert_file:
133- cert_file.write(ssl_cert)
134- os.chmod(ssl_cert_file, 0640)
135- os.chown(ssl_cert_file, uid, gid)
136+
137+ for contents, path in (
138+ (ssl_key, ssl_key_file),
139+ (ssl_cert, ssl_cert_file),
140+ (ssl_ca, ssl_ca_file)):
141+ if not contents:
142+ continue
143+ with open(path, 'w') as fh:
144+ fh.write(contents)
145+ os.chmod(path, 0640)
146+ os.chown(path, uid, gid)
147+
148+ data = {
149+ "ssl_port": ssl_port,
150+ "ssl_cert_file": ssl_cert_file,
151+ "ssl_key_file": ssl_key_file,
152+ "ssl_client": ssl_client,
153+ "ssl_ca_file": "",
154+ "ssl_only": ssl_only}
155+
156+ if ssl_ca:
157+ data["ssl_ca_file"] = ssl_ca_file
158+
159 with open(RABBITMQ_CONF, 'w') as rmq_conf:
160- rmq_conf.write(utils.render_template(os.path.basename(RABBITMQ_CONF),
161- {"ssl_port": ssl_port,
162- "ssl_cert_file": ssl_cert_file,
163- "ssl_key_file": ssl_key_file}))
164+ rmq_conf.write(utils.render_template(
165+ os.path.basename(RABBITMQ_CONF), data))
166
167
168 def execute(cmd, die=False, echo=False):
169
170=== modified file 'hooks/rabbitmq_server_relations.py'
171--- hooks/rabbitmq_server_relations.py 2014-02-10 09:25:06 +0000
172+++ hooks/rabbitmq_server_relations.py 2014-02-25 14:24:12 +0000
173@@ -1,5 +1,5 @@
174 #!/usr/bin/python
175-
176+import base64
177 import os
178 import shutil
179 import sys
180@@ -20,6 +20,7 @@
181 from charmhelpers.core import hookenv
182 from charmhelpers.core.host import rsync
183 from charmhelpers.contrib.charmsupport.nrpe import NRPE
184+from charmhelpers.contrib.ssl.service import ServiceCA
185
186
187 SERVICE_NAME = os.getenv('JUJU_UNIT_NAME').split('/')[0]
188@@ -69,18 +70,16 @@
189 relation_settings = {}
190 settings = hookenv.relation_get(rid=relation_id, unit=remote_unit)
191
192- singleset = set([
193- 'username',
194- 'vhost'
195- ])
196+ singleset = set(['username', 'vhost'])
197
198 if singleset.issubset(settings):
199 if None in [settings['username'], settings['vhost']]:
200 utils.juju_log('INFO', 'amqp_changed(): Relation not ready.')
201 return
202
203- relation_settings['password'] = configure_amqp(username=settings['username'],
204- vhost=settings['vhost'])
205+ relation_settings['password'] = configure_amqp(
206+ username=settings['username'],
207+ vhost=settings['vhost'])
208 else:
209 queues = {}
210 for k, v in settings.iteritems():
211@@ -89,13 +88,15 @@
212 if amqp not in queues:
213 queues[amqp] = {}
214 queues[amqp][x] = v
215- relation_settings = {}
216 for amqp in queues:
217 if singleset.issubset(queues[amqp]):
218- relation_settings['_'.join([amqp, 'password'])] = configure_amqp(queues[amqp]['username'],
219- queues[amqp]['vhost'])
220+ relation_settings[
221+ '_'.join([amqp, 'password'])] = configure_amqp(
222+ queues[amqp]['username'],
223+ queues[amqp]['vhost'])
224
225 relation_settings['hostname'] = utils.unit_get('private-address')
226+ configure_client_ssl(relation_settings)
227
228 if cluster.is_clustered():
229 relation_settings['clustered'] = 'true'
230@@ -361,6 +362,76 @@
231 MAN_PLUGIN = 'rabbitmq_management'
232
233
234+def configure_client_ssl(relation_data):
235+ """Configure client with ssl
236+ """
237+ ssl_mode, external_ca = _get_ssl_mode()
238+ if ssl_mode == 'off':
239+ return
240+ relation_data['ssl_port'] = utils.config_get('ssl_port')
241+ if external_ca:
242+ if utils.config_get('ssl_ca'):
243+ relation_data['ssl_ca'] = base64.b64encode(
244+ utils.config_get('ssl_ca'))
245+ return
246+ ca = ServiceCA.get_ca()
247+ relation_data['ssl_ca'] = base64.b64encode(ca.get_ca_bundle())
248+
249+
250+def _get_ssl_mode():
251+ config = utils.config_get()
252+ ssl_mode = config.get('ssl')
253+ external_ca = False
254+ # Legacy config boolean option
255+ ssl_on = config.get('ssl_enabled')
256+ if ssl_mode == 'off' and ssl_on is False:
257+ ssl_mode = 'off'
258+ elif ssl_mode == 'off' and ssl_on:
259+ ssl_mode = 'on'
260+ ssl_key = utils.config_get('ssl_key')
261+ ssl_cert = utils.config_get('ssl_cert')
262+ if all((ssl_key, ssl_cert)):
263+ external_ca = True
264+ return ssl_mode, external_ca
265+
266+
267+def configure_rabbit_ssl():
268+ """
269+ The legacy config support adds some additional complications.
270+
271+ ssl_enabled = True, ssl = off -> ssl enabled
272+ ssl_enabled = False, ssl = on -> ssl enabled
273+ """
274+ ssl_mode, external_ca = _get_ssl_mode()
275+
276+ if ssl_mode == 'off':
277+ if os.path.exists(rabbit.RABBITMQ_CONF):
278+ os.remove(rabbit.RABBITMQ_CONF)
279+ utils.close_port(utils.config_get('ssl_port'))
280+ return
281+
282+ ssl_key = utils.config_get('ssl_key')
283+ ssl_cert = utils.config_get('ssl_cert')
284+ ssl_ca = utils.config_get('ssl_ca')
285+ ssl_port = utils.config_get('ssl_port')
286+
287+ # If external managed certs then we need all the fields.
288+ if (ssl_mode in ('on', 'only') and any((ssl_key, ssl_cert)) and
289+ not all((ssl_key, ssl_cert))):
290+ utils.juju_log(
291+ 'ERROR',
292+ 'If ssl_key or ssl_cert are specified both are required.')
293+ sys.exit(1)
294+
295+ if not external_ca:
296+ ssl_cert, ssl_key, ssl_ca = ServiceCA.get_service_cert()
297+
298+ rabbit.enable_ssl(
299+ ssl_key, ssl_cert, ssl_port, ssl_ca,
300+ ssl_only=(ssl_mode == "only"), ssl_client=False)
301+ utils.open_port(ssl_port)
302+
303+
304 def config_changed():
305 unison.ensure_user(user=rabbit.SSH_USER, group='rabbit')
306 ensure_unison_rabbit_permissions()
307@@ -372,22 +443,7 @@
308 rabbit.disable_plugin(MAN_PLUGIN)
309 utils.close_port(55672)
310
311- if utils.config_get('ssl_enabled') is True:
312- ssl_key = utils.config_get('ssl_key')
313- ssl_cert = utils.config_get('ssl_cert')
314- ssl_port = utils.config_get('ssl_port')
315- if None in [ssl_key, ssl_cert, ssl_port]:
316- utils.juju_log('ERROR',
317- 'Please provide ssl_key, ssl_cert and ssl_port'
318- ' config when enabling SSL support')
319- sys.exit(1)
320- else:
321- rabbit.enable_ssl(ssl_key, ssl_cert, ssl_port)
322- utils.open_port(ssl_port)
323- else:
324- if os.path.exists(rabbit.RABBITMQ_CONF):
325- os.remove(rabbit.RABBITMQ_CONF)
326- utils.close_port(utils.config_get('ssl_port'))
327+ configure_rabbit_ssl()
328
329 if cluster.eligible_leader('res_rabbitmq_vip'):
330 utils.restart('rabbitmq-server')
331
332=== modified file 'lib/charmhelpers/contrib/openstack/context.py'
333--- lib/charmhelpers/contrib/openstack/context.py 2014-01-16 13:47:32 +0000
334+++ lib/charmhelpers/contrib/openstack/context.py 2014-02-25 14:24:12 +0000
335@@ -1,5 +1,6 @@
336 import json
337 import os
338+import time
339
340 from base64 import b64decode
341
342@@ -67,6 +68,43 @@
343 return True
344
345
346+def config_flags_parser(config_flags):
347+ if config_flags.find('==') >= 0:
348+ log("config_flags is not in expected format (key=value)",
349+ level=ERROR)
350+ raise OSContextError
351+ # strip the following from each value.
352+ post_strippers = ' ,'
353+ # we strip any leading/trailing '=' or ' ' from the string then
354+ # split on '='.
355+ split = config_flags.strip(' =').split('=')
356+ limit = len(split)
357+ flags = {}
358+ for i in xrange(0, limit - 1):
359+ current = split[i]
360+ next = split[i + 1]
361+ vindex = next.rfind(',')
362+ if (i == limit - 2) or (vindex < 0):
363+ value = next
364+ else:
365+ value = next[:vindex]
366+
367+ if i == 0:
368+ key = current
369+ else:
370+ # if this not the first entry, expect an embedded key.
371+ index = current.rfind(',')
372+ if index < 0:
373+ log("invalid config value(s) at index %s" % (i),
374+ level=ERROR)
375+ raise OSContextError
376+ key = current[index + 1:]
377+
378+ # Add to collection.
379+ flags[key.strip(post_strippers)] = value.rstrip(post_strippers)
380+ return flags
381+
382+
383 class OSContextGenerator(object):
384 interfaces = []
385
386@@ -77,7 +115,8 @@
387 class SharedDBContext(OSContextGenerator):
388 interfaces = ['shared-db']
389
390- def __init__(self, database=None, user=None, relation_prefix=None):
391+ def __init__(self,
392+ database=None, user=None, relation_prefix=None, ssl_dir=None):
393 '''
394 Allows inspecting relation for settings prefixed with relation_prefix.
395 This is useful for parsing access for multiple databases returned via
396@@ -86,6 +125,7 @@
397 self.relation_prefix = relation_prefix
398 self.database = database
399 self.user = user
400+ self.ssl_dir = ssl_dir
401
402 def __call__(self):
403 self.database = self.database or config('database')
404@@ -103,19 +143,44 @@
405
406 for rid in relation_ids('shared-db'):
407 for unit in related_units(rid):
408- passwd = relation_get(password_setting, rid=rid, unit=unit)
409+ rdata = relation_get(rid=rid, unit=unit)
410 ctxt = {
411- 'database_host': relation_get('db_host', rid=rid,
412- unit=unit),
413+ 'database_host': rdata.get('db_host'),
414 'database': self.database,
415 'database_user': self.user,
416- 'database_password': passwd,
417+ 'database_password': rdata.get(password_setting)
418 }
419 if context_complete(ctxt):
420+ db_ssl(rdata, ctxt, self.ssl_dir)
421 return ctxt
422 return {}
423
424
425+def db_ssl(rdata, ctxt, ssl_dir):
426+ if 'ssl_ca' in rdata and ssl_dir:
427+ ca_path = os.path.join(ssl_dir, 'db-client.ca')
428+ with open(ca_path, 'w') as fh:
429+ fh.write(b64decode(rdata['ssl_ca']))
430+ ctxt['database_ssl_ca'] = ca_path
431+ elif 'ssl_ca' in rdata:
432+ log("Charm not setup for ssl support but ssl ca found")
433+ return ctxt
434+ if 'ssl_cert' in rdata:
435+ cert_path = os.path.join(
436+ ssl_dir, 'db-client.cert')
437+ if not os.path.exists(cert_path):
438+ log("Waiting 1m for ssl client cert validity")
439+ time.sleep(60)
440+ with open(cert_path, 'w') as fh:
441+ fh.write(b64decode(rdata['ssl_cert']))
442+ ctxt['database_ssl_cert'] = cert_path
443+ key_path = os.path.join(ssl_dir, 'db-client.key')
444+ with open(key_path, 'w') as fh:
445+ fh.write(b64decode(rdata['ssl_key']))
446+ ctxt['database_ssl_key'] = key_path
447+ return ctxt
448+
449+
450 class IdentityServiceContext(OSContextGenerator):
451 interfaces = ['identity-service']
452
453@@ -150,6 +215,9 @@
454 class AMQPContext(OSContextGenerator):
455 interfaces = ['amqp']
456
457+ def __init__(self, ssl_dir=None):
458+ self.ssl_dir = ssl_dir
459+
460 def __call__(self):
461 log('Generating template context for amqp')
462 conf = config()
463@@ -160,7 +228,6 @@
464 log('Could not generate shared_db context. '
465 'Missing required charm config options: %s.' % e)
466 raise OSContextError
467-
468 ctxt = {}
469 for rid in relation_ids('amqp'):
470 for unit in related_units(rid):
471@@ -177,7 +244,24 @@
472 unit=unit),
473 'rabbitmq_virtual_host': vhost,
474 })
475+ ssl_port = relation_get('ssl_port', rid=rid, unit=unit)
476+ if ssl_port:
477+ ctxt['rabbit_ssl_port'] = ssl_port
478+ ssl_ca = relation_get('ssl_ca', rid=rid, unit=unit)
479+ if ssl_ca:
480+ ctxt['rabbit_ssl_ca'] = ssl_ca
481+
482 if context_complete(ctxt):
483+ if 'rabbit_ssl_ca' in ctxt:
484+ if not self.ssl_dir:
485+ log(("Charm not setup for ssl support "
486+ "but ssl ca found"))
487+ break
488+ ca_path = os.path.join(
489+ self.ssl_dir, 'rabbit-client-ca.pem')
490+ with open(ca_path, 'w') as fh:
491+ fh.write(b64decode(ctxt['rabbit_ssl_ca']))
492+ ctxt['rabbit_ssl_ca'] = ca_path
493 # Sufficient information found = break out!
494 break
495 # Used for active/active rabbitmq >= grizzly
496@@ -430,6 +514,11 @@
497 elif self.plugin == 'nvp':
498 ctxt.update(self.nvp_ctxt())
499
500+ alchemy_flags = config('neutron-alchemy-flags')
501+ if alchemy_flags:
502+ flags = config_flags_parser(alchemy_flags)
503+ ctxt['neutron_alchemy_flags'] = flags
504+
505 self._save_flag_file()
506 return ctxt
507
508@@ -450,41 +539,7 @@
509 if not config_flags:
510 return {}
511
512- if config_flags.find('==') >= 0:
513- log("config_flags is not in expected format (key=value)",
514- level=ERROR)
515- raise OSContextError
516-
517- # strip the following from each value.
518- post_strippers = ' ,'
519- # we strip any leading/trailing '=' or ' ' from the string then
520- # split on '='.
521- split = config_flags.strip(' =').split('=')
522- limit = len(split)
523- flags = {}
524- for i in xrange(0, limit - 1):
525- current = split[i]
526- next = split[i + 1]
527- vindex = next.rfind(',')
528- if (i == limit - 2) or (vindex < 0):
529- value = next
530- else:
531- value = next[:vindex]
532-
533- if i == 0:
534- key = current
535- else:
536- # if this not the first entry, expect an embedded key.
537- index = current.rfind(',')
538- if index < 0:
539- log("invalid config value(s) at index %s" % (i),
540- level=ERROR)
541- raise OSContextError
542- key = current[index + 1:]
543-
544- # Add to collection.
545- flags[key.strip(post_strippers)] = value.rstrip(post_strippers)
546-
547+ flags = config_flags_parser(config_flags)
548 return {'user_config_flags': flags}
549
550
551@@ -575,3 +630,11 @@
552 ctxt['sections'] = {}
553
554 return ctxt
555+
556+
557+class SyslogContext(OSContextGenerator):
558+ def __call__(self):
559+ ctxt = {
560+ 'use_syslog': config('use-syslog')
561+ }
562+ return ctxt
563
564=== modified file 'lib/charmhelpers/contrib/openstack/utils.py'
565--- lib/charmhelpers/contrib/openstack/utils.py 2014-01-16 13:47:32 +0000
566+++ lib/charmhelpers/contrib/openstack/utils.py 2014-02-25 14:24:12 +0000
567@@ -415,7 +415,7 @@
568 return ns_query(hostname)
569
570
571-def get_hostname(address):
572+def get_hostname(address, fqdn=True):
573 """
574 Resolves hostname for given IP, or returns the input
575 if it is already a hostname.
576@@ -434,7 +434,11 @@
577 if not result:
578 return None
579
580- # strip trailing .
581- if result.endswith('.'):
582- return result[:-1]
583- return result
584+ if fqdn:
585+ # strip trailing .
586+ if result.endswith('.'):
587+ return result[:-1]
588+ else:
589+ return result
590+ else:
591+ return result.split('.')[0]
592
593=== added file 'lib/charmhelpers/contrib/ssl/service.py'
594--- lib/charmhelpers/contrib/ssl/service.py 1970-01-01 00:00:00 +0000
595+++ lib/charmhelpers/contrib/ssl/service.py 2014-02-25 14:24:12 +0000
596@@ -0,0 +1,267 @@
597+import logging
598+import os
599+from os.path import join as path_join
600+from os.path import exists
601+import subprocess
602+
603+
604+log = logging.getLogger("service_ca")
605+
606+logging.basicConfig(level=logging.DEBUG)
607+
608+STD_CERT = "standard"
609+
610+# Mysql server is fairly picky about cert creation
611+# and types, spec its creation separately for now.
612+MYSQL_CERT = "mysql"
613+
614+
615+class ServiceCA(object):
616+
617+ default_expiry = str(365 * 2)
618+ default_ca_expiry = str(365 * 6)
619+
620+ def __init__(self, name, ca_dir, cert_type=STD_CERT):
621+ self.name = name
622+ self.ca_dir = ca_dir
623+ self.cert_type = cert_type
624+
625+ ###############
626+ # Hook Helper API
627+ @staticmethod
628+ def get_ca(type=STD_CERT):
629+ service_name = os.environ['JUJU_UNIT_NAME'].split('/')[0]
630+ ca_path = os.path.join(os.environ['CHARM_DIR'], 'ca')
631+ ca = ServiceCA(service_name, ca_path, type)
632+ ca.init()
633+ return ca
634+
635+ @classmethod
636+ def get_service_cert(cls, type=STD_CERT):
637+ service_name = os.environ['JUJU_UNIT_NAME'].split('/')[0]
638+ ca = cls.get_ca()
639+ crt, key = ca.get_or_create_cert(service_name)
640+ return crt, key, ca.get_ca_bundle()
641+
642+ ###############
643+
644+ def init(self):
645+ log.debug("initializing service ca")
646+ if not exists(self.ca_dir):
647+ self._init_ca_dir(self.ca_dir)
648+ self._init_ca()
649+
650+ @property
651+ def ca_key(self):
652+ return path_join(self.ca_dir, 'private', 'cacert.key')
653+
654+ @property
655+ def ca_cert(self):
656+ return path_join(self.ca_dir, 'cacert.pem')
657+
658+ @property
659+ def ca_conf(self):
660+ return path_join(self.ca_dir, 'ca.cnf')
661+
662+ @property
663+ def signing_conf(self):
664+ return path_join(self.ca_dir, 'signing.cnf')
665+
666+ def _init_ca_dir(self, ca_dir):
667+ os.mkdir(ca_dir)
668+ for i in ['certs', 'crl', 'newcerts', 'private']:
669+ sd = path_join(ca_dir, i)
670+ if not exists(sd):
671+ os.mkdir(sd)
672+
673+ if not exists(path_join(ca_dir, 'serial')):
674+ with open(path_join(ca_dir, 'serial'), 'wb') as fh:
675+ fh.write('02\n')
676+
677+ if not exists(path_join(ca_dir, 'index.txt')):
678+ with open(path_join(ca_dir, 'index.txt'), 'wb') as fh:
679+ fh.write('')
680+
681+ def _init_ca(self):
682+ """Generate the root ca's cert and key.
683+ """
684+ if not exists(path_join(self.ca_dir, 'ca.cnf')):
685+ with open(path_join(self.ca_dir, 'ca.cnf'), 'wb') as fh:
686+ fh.write(
687+ CA_CONF_TEMPLATE % (self.get_conf_variables()))
688+
689+ if not exists(path_join(self.ca_dir, 'signing.cnf')):
690+ with open(path_join(self.ca_dir, 'signing.cnf'), 'wb') as fh:
691+ fh.write(
692+ SIGNING_CONF_TEMPLATE % (self.get_conf_variables()))
693+
694+ if exists(self.ca_cert) or exists(self.ca_key):
695+ raise RuntimeError("Initialized called when CA already exists")
696+ cmd = ['openssl', 'req', '-config', self.ca_conf,
697+ '-x509', '-nodes', '-newkey', 'rsa',
698+ '-days', self.default_ca_expiry,
699+ '-keyout', self.ca_key, '-out', self.ca_cert,
700+ '-outform', 'PEM']
701+ output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
702+ log.debug("CA Init:\n %s", output)
703+
704+ def get_conf_variables(self):
705+ return dict(
706+ org_name="juju",
707+ org_unit_name="%s service" % self.name,
708+ common_name=self.name,
709+ ca_dir=self.ca_dir)
710+
711+ def get_or_create_cert(self, common_name):
712+ if common_name in self:
713+ return self.get_certificate(common_name)
714+ return self.create_certificate(common_name)
715+
716+ def create_certificate(self, common_name):
717+ if common_name in self:
718+ return self.get_certificate(common_name)
719+ key_p = path_join(self.ca_dir, "certs", "%s.key" % common_name)
720+ crt_p = path_join(self.ca_dir, "certs", "%s.crt" % common_name)
721+ csr_p = path_join(self.ca_dir, "certs", "%s.csr" % common_name)
722+ self._create_certificate(common_name, key_p, csr_p, crt_p)
723+ return self.get_certificate(common_name)
724+
725+ def get_certificate(self, common_name):
726+ if not common_name in self:
727+ raise ValueError("No certificate for %s" % common_name)
728+ key_p = path_join(self.ca_dir, "certs", "%s.key" % common_name)
729+ crt_p = path_join(self.ca_dir, "certs", "%s.crt" % common_name)
730+ with open(crt_p) as fh:
731+ crt = fh.read()
732+ with open(key_p) as fh:
733+ key = fh.read()
734+ return crt, key
735+
736+ def __contains__(self, common_name):
737+ crt_p = path_join(self.ca_dir, "certs", "%s.crt" % common_name)
738+ return exists(crt_p)
739+
740+ def _create_certificate(self, common_name, key_p, csr_p, crt_p):
741+ template_vars = self.get_conf_variables()
742+ template_vars['common_name'] = common_name
743+ subj = '/O=%(org_name)s/OU=%(org_unit_name)s/CN=%(common_name)s' % (
744+ template_vars)
745+
746+ log.debug("CA Create Cert %s", common_name)
747+ cmd = ['openssl', 'req', '-sha1', '-newkey', 'rsa:2048',
748+ '-nodes', '-days', self.default_expiry,
749+ '-keyout', key_p, '-out', csr_p, '-subj', subj]
750+ subprocess.check_call(cmd)
751+ cmd = ['openssl', 'rsa', '-in', key_p, '-out', key_p]
752+ subprocess.check_call(cmd)
753+
754+ log.debug("CA Sign Cert %s", common_name)
755+ if self.cert_type == MYSQL_CERT:
756+ cmd = ['openssl', 'x509', '-req',
757+ '-in', csr_p, '-days', self.default_expiry,
758+ '-CA', self.ca_cert, '-CAkey', self.ca_key,
759+ '-set_serial', '01', '-out', crt_p]
760+ else:
761+ cmd = ['openssl', 'ca', '-config', self.signing_conf,
762+ '-extensions', 'req_extensions',
763+ '-days', self.default_expiry, '-notext',
764+ '-in', csr_p, '-out', crt_p, '-subj', subj, '-batch']
765+ log.debug("running %s", " ".join(cmd))
766+ subprocess.check_call(cmd)
767+
768+ def get_ca_bundle(self):
769+ with open(self.ca_cert) as fh:
770+ return fh.read()
771+
772+
773+CA_CONF_TEMPLATE = """
774+[ ca ]
775+default_ca = CA_default
776+
777+[ CA_default ]
778+dir = %(ca_dir)s
779+policy = policy_match
780+database = $dir/index.txt
781+serial = $dir/serial
782+certs = $dir/certs
783+crl_dir = $dir/crl
784+new_certs_dir = $dir/newcerts
785+certificate = $dir/cacert.pem
786+private_key = $dir/private/cacert.key
787+RANDFILE = $dir/private/.rand
788+default_md = default
789+
790+[ req ]
791+default_bits = 1024
792+default_md = sha1
793+
794+prompt = no
795+distinguished_name = ca_distinguished_name
796+
797+x509_extensions = ca_extensions
798+
799+[ ca_distinguished_name ]
800+organizationName = %(org_name)s
801+organizationalUnitName = %(org_unit_name)s Certificate Authority
802+
803+
804+[ policy_match ]
805+countryName = optional
806+stateOrProvinceName = optional
807+organizationName = match
808+organizationalUnitName = optional
809+commonName = supplied
810+
811+[ ca_extensions ]
812+basicConstraints = critical,CA:true
813+subjectKeyIdentifier = hash
814+authorityKeyIdentifier = keyid:always, issuer
815+keyUsage = cRLSign, keyCertSign
816+"""
817+
818+
819+SIGNING_CONF_TEMPLATE = """
820+[ ca ]
821+default_ca = CA_default
822+
823+[ CA_default ]
824+dir = %(ca_dir)s
825+policy = policy_match
826+database = $dir/index.txt
827+serial = $dir/serial
828+certs = $dir/certs
829+crl_dir = $dir/crl
830+new_certs_dir = $dir/newcerts
831+certificate = $dir/cacert.pem
832+private_key = $dir/private/cacert.key
833+RANDFILE = $dir/private/.rand
834+default_md = default
835+
836+[ req ]
837+default_bits = 1024
838+default_md = sha1
839+
840+prompt = no
841+distinguished_name = req_distinguished_name
842+
843+x509_extensions = req_extensions
844+
845+[ req_distinguished_name ]
846+organizationName = %(org_name)s
847+organizationalUnitName = %(org_unit_name)s machine resources
848+commonName = %(common_name)s
849+
850+[ policy_match ]
851+countryName = optional
852+stateOrProvinceName = optional
853+organizationName = match
854+organizationalUnitName = optional
855+commonName = supplied
856+
857+[ req_extensions ]
858+basicConstraints = CA:false
859+subjectKeyIdentifier = hash
860+authorityKeyIdentifier = keyid:always, issuer
861+keyUsage = digitalSignature, keyEncipherment, keyAgreement
862+extendedKeyUsage = serverAuth, clientAuth
863+"""
864
865=== modified file 'lib/charmhelpers/core/hookenv.py'
866--- lib/charmhelpers/core/hookenv.py 2013-11-15 19:15:16 +0000
867+++ lib/charmhelpers/core/hookenv.py 2014-02-25 14:24:12 +0000
868@@ -8,6 +8,7 @@
869 import json
870 import yaml
871 import subprocess
872+import sys
873 import UserDict
874 from subprocess import CalledProcessError
875
876@@ -149,6 +150,11 @@
877 return local_unit().split('/')[0]
878
879
880+def hook_name():
881+ """The name of the currently executing hook"""
882+ return os.path.basename(sys.argv[0])
883+
884+
885 @cached
886 def config(scope=None):
887 """Juju charm configuration"""
888
889=== modified file 'revision'
890--- revision 2014-02-10 09:25:06 +0000
891+++ revision 2014-02-25 14:24:12 +0000
892@@ -1,1 +1,5 @@
893+<<<<<<< TREE
894 114
895+=======
896+124
897+>>>>>>> MERGE-SOURCE
898
899=== modified file 'templates/rabbitmq.config'
900--- templates/rabbitmq.config 2013-05-21 05:59:02 +0000
901+++ templates/rabbitmq.config 2014-02-25 14:24:12 +0000
902@@ -1,10 +1,21 @@
903 [
904 {rabbit, [
905+{% if ssl_only %}
906+ {tcp_listeners, []},
907+{% else %}
908+ {tcp_listeners, [5672]},
909+{% endif %}
910 {ssl_listeners, [{{ ssl_port }}]},
911 {ssl_options, [
912+ {verify, verify_peer},
913+{% if ssl_client %}
914+ {fail_if_no_peer_cert, true},
915+{% else %}
916+ {fail_if_no_peer_cert, false},
917+{% endif %}{% if ssl_ca_file %}
918+ {cacertfile, "{{ ssl_ca_file }}"}, {% endif %}
919 {certfile, "{{ ssl_cert_file }}"},
920 {keyfile, "{{ ssl_key_file }}"}
921- ]},
922- {tcp_listeners, [5672]}
923+ ]}
924 ]}
925 ].
926\ No newline at end of file

Subscribers

People subscribed via source and target branches