From 0a2c8a98d222d7baa322f4427d73d6bd6ff3f3b1 Mon Sep 17 00:00:00 2001 From: Kapil Thangavelu Date: Sun, 23 Feb 2014 22:48:14 -0500 Subject: [PATCH 1/7] rabbit ssl support --- charm-helpers.yaml | 2 +- hooks/ceilometer_utils.py | 5 +- .../charmhelpers/contrib/openstack/context.py | 157 +++++++++++++++--- hooks/charmhelpers/contrib/openstack/utils.py | 14 +- hooks/charmhelpers/core/hookenv.py | 6 + hooks/charmhelpers/fetch/__init__.py | 6 +- templates/havana/ceilometer.conf | 7 + 7 files changed, 160 insertions(+), 37 deletions(-) diff --git a/charm-helpers.yaml b/charm-helpers.yaml index 95bc342..d9b0881 100644 --- a/charm-helpers.yaml +++ b/charm-helpers.yaml @@ -1,4 +1,4 @@ -branch: lp:charm-helpers +branch: lp:~hazmat/charm-helpers/ssl-everywhere destination: hooks/charmhelpers include: - core diff --git a/hooks/ceilometer_utils.py b/hooks/ceilometer_utils.py index 6dc9481..3919b34 100644 --- a/hooks/ceilometer_utils.py +++ b/hooks/ceilometer_utils.py @@ -15,7 +15,8 @@ from charmhelpers.contrib.openstack.utils import ( from charmhelpers.core.hookenv import config, log from charmhelpers.fetch import apt_update, apt_install -CEILOMETER_CONF = "/etc/ceilometer/ceilometer.conf" +CEILOMETER_CONF_DIR = "/etc/ceilometer" +CEILOMETER_CONF = "%s/ceilometer.conf" % CEILOMETER_CONF_DIR CEILOMETER_SERVICES = [ 'ceilometer-agent-central', @@ -44,7 +45,7 @@ CEILOMETER_ROLE = "ResellerAdmin" CONFIG_FILES = { CEILOMETER_CONF: { 'hook_contexts': [context.IdentityServiceContext(), - context.AMQPContext(), + context.AMQPContext(ssl_dir=CEILOMETER_CONF_DIR), LoggingConfigContext(), MongoDBContext(), CeilometerContext()], diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 8a982ff..1954e93 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -1,5 +1,6 @@ import json import os +import time from base64 import b64decode @@ -23,7 +24,6 @@ from charmhelpers.core.hookenv import ( unit_get, unit_private_ip, ERROR, - WARNING, ) from charmhelpers.contrib.hahelpers.cluster import ( @@ -68,6 +68,43 @@ def context_complete(ctxt): return True +def config_flags_parser(config_flags): + if config_flags.find('==') >= 0: + log("config_flags is not in expected format (key=value)", + level=ERROR) + raise OSContextError + # strip the following from each value. + post_strippers = ' ,' + # we strip any leading/trailing '=' or ' ' from the string then + # split on '='. + split = config_flags.strip(' =').split('=') + limit = len(split) + flags = {} + for i in xrange(0, limit - 1): + current = split[i] + next = split[i + 1] + vindex = next.rfind(',') + if (i == limit - 2) or (vindex < 0): + value = next + else: + value = next[:vindex] + + if i == 0: + key = current + else: + # if this not the first entry, expect an embedded key. + index = current.rfind(',') + if index < 0: + log("invalid config value(s) at index %s" % (i), + level=ERROR) + raise OSContextError + key = current[index + 1:] + + # Add to collection. + flags[key.strip(post_strippers)] = value.rstrip(post_strippers) + return flags + + class OSContextGenerator(object): interfaces = [] @@ -78,7 +115,8 @@ class OSContextGenerator(object): class SharedDBContext(OSContextGenerator): interfaces = ['shared-db'] - def __init__(self, database=None, user=None, relation_prefix=None): + def __init__(self, + database=None, user=None, relation_prefix=None, ssl_dir=None): ''' Allows inspecting relation for settings prefixed with relation_prefix. This is useful for parsing access for multiple databases returned via @@ -87,6 +125,7 @@ class SharedDBContext(OSContextGenerator): self.relation_prefix = relation_prefix self.database = database self.user = user + self.ssl_dir = ssl_dir def __call__(self): self.database = self.database or config('database') @@ -104,19 +143,46 @@ class SharedDBContext(OSContextGenerator): for rid in relation_ids('shared-db'): for unit in related_units(rid): - passwd = relation_get(password_setting, rid=rid, unit=unit) + rdata = relation_get(rid=rid, unit=unit) ctxt = { - 'database_host': relation_get('db_host', rid=rid, - unit=unit), + 'database_host': rdata.get('db_host'), 'database': self.database, 'database_user': self.user, - 'database_password': passwd, + 'database_password': rdata.get(password_setting) } if context_complete(ctxt): + db_ssl(rdata, ctxt, self.ssl_dir) return ctxt return {} +def db_ssl(rdata, ctxt, ssl_dir, log): + ctxt.update({'database_ssl_ca': '', 'database_ssl_key': '', + 'database_ssl_cert': ''}) + if 'ssl_ca' in rdata and ssl_dir: + ca_path = os.path.join(ssl_dir, 'db-client.ca') + with open(ca_path, 'w') as fh: + fh.write(b64decode(rdata['ssl_ca'])) + ctxt['database_ssl_ca'] = ca_path + elif 'ssl_ca' in rdata: + log("Charm not setup for ssl support but ssl ca found") + return ctxt + if 'ssl_cert' in rdata: + cert_path = os.path.join( + ssl_dir, 'db-client.cert') + if not os.path.exists(cert_path): + log("Waiting 1m for ssl client cert validity") + time.sleep(60) + with open(cert_path, 'w') as fh: + fh.write(b64decode(rdata['ssl_cert'])) + ctxt['database_ssl_cert'] = cert_path + key_path = os.path.join(ssl_dir, 'db-client.key') + with open(key_path, 'w') as fh: + fh.write(b64decode(rdata['ssl_key'])) + ctxt['database_ssl_key'] = key_path + return ctxt + + class IdentityServiceContext(OSContextGenerator): interfaces = ['identity-service'] @@ -151,6 +217,9 @@ class IdentityServiceContext(OSContextGenerator): class AMQPContext(OSContextGenerator): interfaces = ['amqp'] + def __init__(self, ssl_dir=None): + self.ssl_dir = ssl_dir + def __call__(self): log('Generating template context for amqp') conf = config() @@ -161,7 +230,6 @@ class AMQPContext(OSContextGenerator): log('Could not generate shared_db context. ' 'Missing required charm config options: %s.' % e) raise OSContextError - ctxt = {} for rid in relation_ids('amqp'): for unit in related_units(rid): @@ -178,17 +246,38 @@ class AMQPContext(OSContextGenerator): unit=unit), 'rabbitmq_virtual_host': vhost, }) + ssl_port = relation_get('ssl_port', rid=rid, unit=unit) + if ssl_port: + ctxt['rabbit_ssl_port'] = ssl_port + ssl_ca = relation_get('ssl_ca', rid=rid, unit=unit) + if ssl_ca: + ctxt['rabbit_ssl_ca'] = ssl_ca + if context_complete(ctxt): + if 'rabbit_ssl_ca' in ctxt: + if not self.ssl_dir: + log(("Charm not setup for ssl support " + "but ssl ca found")) + break + ca_path = os.path.join( + self.ssl_dir, 'rabbit-client-ca.pem') + with open(ca_path, 'w') as fh: + fh.write(b64decode(ctxt['rabbit_ssl_ca'])) + ctxt['rabbit_ssl_ca'] = ca_path # Sufficient information found = break out! break # Used for active/active rabbitmq >= grizzly - ctxt['rabbitmq_hosts'] = [] - for unit in related_units(rid): - ctxt['rabbitmq_hosts'].append(relation_get('private-address', - rid=rid, unit=unit)) + if 'clustered' not in ctxt and len(related_units(rid)) > 1: + rabbitmq_hosts = [] + for unit in related_units(rid): + rabbitmq_hosts.append(relation_get('private-address', + rid=rid, unit=unit)) + ctxt['rabbitmq_hosts'] = ','.join(rabbitmq_hosts) if not context_complete(ctxt): return {} else: + ctxt.setdefault('rabbit_ssl_port', '') + ctxt.setdefault('rabbit_ssl_ca', '') return ctxt @@ -286,6 +375,7 @@ class ImageServiceContext(OSContextGenerator): class ApacheSSLContext(OSContextGenerator): + """ Generates a context for an apache vhost configuration that configures HTTPS reverse proxying for one or many endpoints. Generated context @@ -428,33 +518,37 @@ class NeutronContext(object): elif self.plugin == 'nvp': ctxt.update(self.nvp_ctxt()) + alchemy_flags = config('neutron-alchemy-flags') + if alchemy_flags: + flags = config_flags_parser(alchemy_flags) + ctxt['neutron_alchemy_flags'] = flags + self._save_flag_file() return ctxt class OSConfigFlagContext(OSContextGenerator): - ''' - Responsible adding user-defined config-flags in charm config to a - to a template context. - ''' + + """ + Responsible for adding user-defined config-flags in charm config to a + template context. + + NOTE: the value of config-flags may be a comma-separated list of + key=value pairs and some Openstack config files support + comma-separated lists as values. + """ + def __call__(self): config_flags = config('config-flags') - if not config_flags or config_flags in ['None', '']: + if not config_flags: return {} - config_flags = config_flags.split(',') - flags = {} - for flag in config_flags: - if '=' not in flag: - log('Improperly formatted config-flag, expected k=v ' - 'got %s' % flag, level=WARNING) - continue - k, v = flag.split('=') - flags[k.strip()] = v - ctxt = {'user_config_flags': flags} - return ctxt + + flags = config_flags_parser(config_flags) + return {'user_config_flags': flags} class SubordinateConfigContext(OSContextGenerator): + """ Responsible for inspecting relations to subordinates that may be exporting required config via a json blob. @@ -495,6 +589,7 @@ class SubordinateConfigContext(OSContextGenerator): } """ + def __init__(self, service, config_file, interface): """ :param service : Service name key to query in any subordinate @@ -539,3 +634,11 @@ class SubordinateConfigContext(OSContextGenerator): ctxt['sections'] = {} return ctxt + + +class SyslogContext(OSContextGenerator): + def __call__(self): + ctxt = { + 'use_syslog': config('use-syslog') + } + return ctxt diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py index 67a2337..56d0424 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/hooks/charmhelpers/contrib/openstack/utils.py @@ -415,7 +415,7 @@ def get_host_ip(hostname): return ns_query(hostname) -def get_hostname(address): +def get_hostname(address, fqdn=True): """ Resolves hostname for given IP, or returns the input if it is already a hostname. @@ -434,7 +434,11 @@ def get_hostname(address): if not result: return None - # strip trailing . - if result.endswith('.'): - return result[:-1] - return result + if fqdn: + # strip trailing . + if result.endswith('.'): + return result[:-1] + else: + return result + else: + return result.split('.')[0] diff --git a/hooks/charmhelpers/core/hookenv.py b/hooks/charmhelpers/core/hookenv.py index bb196df..505c202 100644 --- a/hooks/charmhelpers/core/hookenv.py +++ b/hooks/charmhelpers/core/hookenv.py @@ -8,6 +8,7 @@ import os import json import yaml import subprocess +import sys import UserDict from subprocess import CalledProcessError @@ -149,6 +150,11 @@ def service_name(): return local_unit().split('/')[0] +def hook_name(): + """The name of the currently executing hook""" + return os.path.basename(sys.argv[0]) + + @cached def config(scope=None): """Juju charm configuration""" diff --git a/hooks/charmhelpers/fetch/__init__.py b/hooks/charmhelpers/fetch/__init__.py index 1f4f631..07bb707 100644 --- a/hooks/charmhelpers/fetch/__init__.py +++ b/hooks/charmhelpers/fetch/__init__.py @@ -136,7 +136,7 @@ def apt_hold(packages, fatal=False): def add_source(source, key=None): if (source.startswith('ppa:') or - source.startswith('http:') or + source.startswith('http') or source.startswith('deb ') or source.startswith('cloud-archive:')): subprocess.check_call(['add-apt-repository', '--yes', source]) @@ -156,7 +156,9 @@ def add_source(source, key=None): with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt: apt.write(PROPOSED_POCKET.format(release)) if key: - subprocess.check_call(['apt-key', 'import', key]) + subprocess.check_call(['apt-key', 'adv', '--keyserver', + 'keyserver.ubuntu.com', '--recv', + key]) class SourceConfigError(Exception): diff --git a/templates/havana/ceilometer.conf b/templates/havana/ceilometer.conf index 3a629a2..854d3dc 100644 --- a/templates/havana/ceilometer.conf +++ b/templates/havana/ceilometer.conf @@ -12,6 +12,13 @@ rabbit_host = {{ rabbitmq_host }} rabbit_userid = {{ rabbitmq_user }} rabbit_password = {{ rabbitmq_password }} rabbit_virtual_host = {{ rabbitmq_virtual_host }} +{% if rabbit_ssl_port %} +rabbit_use_ssl=True +rabbit_port={{ rabbit_ssl_port }} +{% if rabbit_ssl_ca %} +kombu_ssl_ca_certs={{rabbit_ssl_ca}} +{% endif %} +{% endif %} [api] port = {{ port }} From 2948d586d73ae068cc55b6499349de3f83e3fb5d Mon Sep 17 00:00:00 2001 From: Kapil Thangavelu Date: Mon, 24 Feb 2014 14:30:48 -0500 Subject: [PATCH 2/7] sync helpers --- .../charmhelpers/contrib/hahelpers/cluster.py | 8 +++--- .../charmhelpers/contrib/openstack/context.py | 28 +++++++++---------- .../contrib/storage/linux/ceph.py | 8 ++++-- hooks/charmhelpers/core/host.py | 12 ++++++-- 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/hooks/charmhelpers/contrib/hahelpers/cluster.py b/hooks/charmhelpers/contrib/hahelpers/cluster.py index 074855f..bf832f7 100644 --- a/hooks/charmhelpers/contrib/hahelpers/cluster.py +++ b/hooks/charmhelpers/contrib/hahelpers/cluster.py @@ -126,17 +126,17 @@ def determine_api_port(public_port): return public_port - (i * 10) -def determine_haproxy_port(public_port): +def determine_apache_port(public_port): ''' - Description: Determine correct proxy listening port based on public IP + - existence of HTTPS reverse proxy. + Description: Determine correct apache listening port based on public IP + + state of the cluster. public_port: int: standard public port for given service returns: int: the correct listening port for the HAProxy service ''' i = 0 - if https(): + if len(peer_units()) > 0 or is_clustered(): i += 1 return public_port - (i * 10) diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 1954e93..38e4eac 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -27,11 +27,9 @@ from charmhelpers.core.hookenv import ( ) from charmhelpers.contrib.hahelpers.cluster import ( + determine_apache_port, determine_api_port, - determine_haproxy_port, https, - is_clustered, - peer_units, ) from charmhelpers.contrib.hahelpers.apache import ( @@ -156,9 +154,7 @@ class SharedDBContext(OSContextGenerator): return {} -def db_ssl(rdata, ctxt, ssl_dir, log): - ctxt.update({'database_ssl_ca': '', 'database_ssl_key': '', - 'database_ssl_cert': ''}) +def db_ssl(rdata, ctxt, ssl_dir): if 'ssl_ca' in rdata and ssl_dir: ca_path = os.path.join(ssl_dir, 'db-client.ca') with open(ca_path, 'w') as fh: @@ -267,7 +263,12 @@ class AMQPContext(OSContextGenerator): # Sufficient information found = break out! break # Used for active/active rabbitmq >= grizzly - if 'clustered' not in ctxt and len(related_units(rid)) > 1: + if ('clustered' not in ctxt or relation_get('ha-vip-only') == 'True') and \ + len(related_units(rid)) > 1: + if relation_get('ha_queues'): + ctxt['rabbitmq_ha_queues'] = relation_get('ha_queues') + else: + ctxt['rabbitmq_ha_queues'] = False rabbitmq_hosts = [] for unit in related_units(rid): rabbitmq_hosts.append(relation_get('private-address', @@ -276,8 +277,6 @@ class AMQPContext(OSContextGenerator): if not context_complete(ctxt): return {} else: - ctxt.setdefault('rabbit_ssl_port', '') - ctxt.setdefault('rabbit_ssl_ca', '') return ctxt @@ -298,11 +297,13 @@ class CephContext(OSContextGenerator): unit=unit)) auth = relation_get('auth', rid=rid, unit=unit) key = relation_get('key', rid=rid, unit=unit) + use_syslog = str(config('use-syslog')).lower() ctxt = { 'mon_hosts': ' '.join(mon_hosts), 'auth': auth, 'key': key, + 'use_syslog': use_syslog } if not os.path.isdir('/etc/ceph'): @@ -431,11 +432,9 @@ class ApacheSSLContext(OSContextGenerator): 'private_address': unit_get('private-address'), 'endpoints': [] } - for ext_port in self.external_ports: - if peer_units() or is_clustered(): - int_port = determine_haproxy_port(ext_port) - else: - int_port = determine_api_port(ext_port) + for api_port in self.external_ports: + ext_port = determine_apache_port(api_port) + int_port = determine_api_port(api_port) portmap = (int(ext_port), int(int_port)) ctxt['endpoints'].append(portmap) return ctxt @@ -637,6 +636,7 @@ class SubordinateConfigContext(OSContextGenerator): class SyslogContext(OSContextGenerator): + def __call__(self): ctxt = { 'use_syslog': config('use-syslog') diff --git a/hooks/charmhelpers/contrib/storage/linux/ceph.py b/hooks/charmhelpers/contrib/storage/linux/ceph.py index 69b879c..1241741 100644 --- a/hooks/charmhelpers/contrib/storage/linux/ceph.py +++ b/hooks/charmhelpers/contrib/storage/linux/ceph.py @@ -49,6 +49,9 @@ CEPH_CONF = """[global] auth supported = {auth} keyring = {keyring} mon host = {mon_hosts} + log to syslog = {use_syslog} + err to syslog = {use_syslog} + clog to syslog = {use_syslog} """ @@ -194,7 +197,7 @@ def get_ceph_nodes(): return hosts -def configure(service, key, auth): +def configure(service, key, auth, use_syslog): ''' Perform basic configuration of Ceph ''' create_keyring(service, key) create_key_file(service, key) @@ -202,7 +205,8 @@ def configure(service, key, auth): with open('/etc/ceph/ceph.conf', 'w') as ceph_conf: ceph_conf.write(CEPH_CONF.format(auth=auth, keyring=_keyring_path(service), - mon_hosts=",".join(map(str, hosts)))) + mon_hosts=",".join(map(str, hosts)), + use_syslog=use_syslog)) modprobe('rbd') diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py index c8c81b2..cfd2684 100644 --- a/hooks/charmhelpers/core/host.py +++ b/hooks/charmhelpers/core/host.py @@ -194,7 +194,7 @@ def file_hash(path): return None -def restart_on_change(restart_map): +def restart_on_change(restart_map, stopstart=False): """Restart services based on configuration files changing This function is used a decorator, for example @@ -219,8 +219,14 @@ def restart_on_change(restart_map): for path in restart_map: if checksums[path] != file_hash(path): restarts += restart_map[path] - for service_name in list(OrderedDict.fromkeys(restarts)): - service('restart', service_name) + services_list = list(OrderedDict.fromkeys(restarts)) + if not stopstart: + for service_name in services_list: + service('restart', service_name) + else: + for action in ['stop', 'start']: + for service_name in services_list: + service(action, service_name) return wrapped_f return wrap From f690acd6168bd71b975381ef08d60775fcb756df Mon Sep 17 00:00:00 2001 From: Kapil Thangavelu Date: Tue, 25 Feb 2014 09:22:26 -0500 Subject: [PATCH 3/7] sync helpers --- .../charmhelpers/contrib/hahelpers/cluster.py | 8 +++---- .../charmhelpers/contrib/openstack/context.py | 22 ++++++++----------- .../contrib/storage/linux/ceph.py | 8 ++----- hooks/charmhelpers/core/host.py | 12 +++------- 4 files changed, 18 insertions(+), 32 deletions(-) diff --git a/hooks/charmhelpers/contrib/hahelpers/cluster.py b/hooks/charmhelpers/contrib/hahelpers/cluster.py index bf832f7..074855f 100644 --- a/hooks/charmhelpers/contrib/hahelpers/cluster.py +++ b/hooks/charmhelpers/contrib/hahelpers/cluster.py @@ -126,17 +126,17 @@ def determine_api_port(public_port): return public_port - (i * 10) -def determine_apache_port(public_port): +def determine_haproxy_port(public_port): ''' - Description: Determine correct apache listening port based on public IP + - state of the cluster. + Description: Determine correct proxy listening port based on public IP + + existence of HTTPS reverse proxy. public_port: int: standard public port for given service returns: int: the correct listening port for the HAProxy service ''' i = 0 - if len(peer_units()) > 0 or is_clustered(): + if https(): i += 1 return public_port - (i * 10) diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 38e4eac..a21c0c8 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -27,9 +27,11 @@ from charmhelpers.core.hookenv import ( ) from charmhelpers.contrib.hahelpers.cluster import ( - determine_apache_port, determine_api_port, + determine_haproxy_port, https, + is_clustered, + peer_units, ) from charmhelpers.contrib.hahelpers.apache import ( @@ -263,12 +265,7 @@ class AMQPContext(OSContextGenerator): # Sufficient information found = break out! break # Used for active/active rabbitmq >= grizzly - if ('clustered' not in ctxt or relation_get('ha-vip-only') == 'True') and \ - len(related_units(rid)) > 1: - if relation_get('ha_queues'): - ctxt['rabbitmq_ha_queues'] = relation_get('ha_queues') - else: - ctxt['rabbitmq_ha_queues'] = False + if 'clustered' not in ctxt and len(related_units(rid)) > 1: rabbitmq_hosts = [] for unit in related_units(rid): rabbitmq_hosts.append(relation_get('private-address', @@ -297,13 +294,11 @@ class CephContext(OSContextGenerator): unit=unit)) auth = relation_get('auth', rid=rid, unit=unit) key = relation_get('key', rid=rid, unit=unit) - use_syslog = str(config('use-syslog')).lower() ctxt = { 'mon_hosts': ' '.join(mon_hosts), 'auth': auth, 'key': key, - 'use_syslog': use_syslog } if not os.path.isdir('/etc/ceph'): @@ -432,9 +427,11 @@ class ApacheSSLContext(OSContextGenerator): 'private_address': unit_get('private-address'), 'endpoints': [] } - for api_port in self.external_ports: - ext_port = determine_apache_port(api_port) - int_port = determine_api_port(api_port) + for ext_port in self.external_ports: + if peer_units() or is_clustered(): + int_port = determine_haproxy_port(ext_port) + else: + int_port = determine_api_port(ext_port) portmap = (int(ext_port), int(int_port)) ctxt['endpoints'].append(portmap) return ctxt @@ -636,7 +633,6 @@ class SubordinateConfigContext(OSContextGenerator): class SyslogContext(OSContextGenerator): - def __call__(self): ctxt = { 'use_syslog': config('use-syslog') diff --git a/hooks/charmhelpers/contrib/storage/linux/ceph.py b/hooks/charmhelpers/contrib/storage/linux/ceph.py index 1241741..69b879c 100644 --- a/hooks/charmhelpers/contrib/storage/linux/ceph.py +++ b/hooks/charmhelpers/contrib/storage/linux/ceph.py @@ -49,9 +49,6 @@ CEPH_CONF = """[global] auth supported = {auth} keyring = {keyring} mon host = {mon_hosts} - log to syslog = {use_syslog} - err to syslog = {use_syslog} - clog to syslog = {use_syslog} """ @@ -197,7 +194,7 @@ def get_ceph_nodes(): return hosts -def configure(service, key, auth, use_syslog): +def configure(service, key, auth): ''' Perform basic configuration of Ceph ''' create_keyring(service, key) create_key_file(service, key) @@ -205,8 +202,7 @@ def configure(service, key, auth, use_syslog): with open('/etc/ceph/ceph.conf', 'w') as ceph_conf: ceph_conf.write(CEPH_CONF.format(auth=auth, keyring=_keyring_path(service), - mon_hosts=",".join(map(str, hosts)), - use_syslog=use_syslog)) + mon_hosts=",".join(map(str, hosts)))) modprobe('rbd') diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py index cfd2684..c8c81b2 100644 --- a/hooks/charmhelpers/core/host.py +++ b/hooks/charmhelpers/core/host.py @@ -194,7 +194,7 @@ def file_hash(path): return None -def restart_on_change(restart_map, stopstart=False): +def restart_on_change(restart_map): """Restart services based on configuration files changing This function is used a decorator, for example @@ -219,14 +219,8 @@ def restart_on_change(restart_map, stopstart=False): for path in restart_map: if checksums[path] != file_hash(path): restarts += restart_map[path] - services_list = list(OrderedDict.fromkeys(restarts)) - if not stopstart: - for service_name in services_list: - service('restart', service_name) - else: - for action in ['stop', 'start']: - for service_name in services_list: - service(action, service_name) + for service_name in list(OrderedDict.fromkeys(restarts)): + service('restart', service_name) return wrapped_f return wrap From c685265ceb6d993f4b4c42757d10f4767c85744e Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 27 Feb 2014 09:47:41 +0000 Subject: [PATCH 4/7] Resync ssl-everywhere helpers, add ssl_* configuration --- charm-helpers.yaml | 2 +- config.yaml | 16 +++++ .../charmhelpers/contrib/hahelpers/apache.py | 17 ++--- .../charmhelpers/contrib/hahelpers/cluster.py | 8 +-- .../charmhelpers/contrib/openstack/context.py | 72 ++++++++++++------- .../contrib/storage/linux/ceph.py | 8 ++- hooks/charmhelpers/core/host.py | 12 +++- 7 files changed, 91 insertions(+), 44 deletions(-) diff --git a/charm-helpers.yaml b/charm-helpers.yaml index d9b0881..d76b1a0 100644 --- a/charm-helpers.yaml +++ b/charm-helpers.yaml @@ -1,4 +1,4 @@ -branch: lp:~hazmat/charm-helpers/ssl-everywhere +branch: lp:~openstack-charmers/charm-helpers/ssl-everywhere destination: hooks/charmhelpers include: - core diff --git a/config.yaml b/config.yaml index a5888c7..8c522d7 100644 --- a/config.yaml +++ b/config.yaml @@ -40,3 +40,19 @@ options: description: | By default, all services will log into their corresponding log files. Setting this to True will force all services to log to the syslog. + ssl_cert: + type: string + description: | + SSL certificate to install and use for API ports. Setting this value + and ssl_key will enable reverse proxying, point Ceilometer's entry in the + Keystone catalog to use https, and override any certficiate and key + issued by Keystone (if it is configured to do so). + ssl_key: + type: string + description: SSL key to use with certificate specified as ssl_cert. + ssl_ca: + type: string + description: | + SSL CA to use with the certificate and key provided - this is only + required if you are providing a privately signed ssl_cert and ssl_key. + diff --git a/hooks/charmhelpers/contrib/hahelpers/apache.py b/hooks/charmhelpers/contrib/hahelpers/apache.py index 3208a85..8d5fb8b 100644 --- a/hooks/charmhelpers/contrib/hahelpers/apache.py +++ b/hooks/charmhelpers/contrib/hahelpers/apache.py @@ -39,14 +39,15 @@ def get_cert(): def get_ca_cert(): - ca_cert = None - log("Inspecting identity-service relations for CA SSL certificate.", - level=INFO) - for r_id in relation_ids('identity-service'): - for unit in relation_list(r_id): - if not ca_cert: - ca_cert = relation_get('ca_cert', - rid=r_id, unit=unit) + ca_cert = config_get('ssl_ca') + if ca_cert is None: + log("Inspecting identity-service relations for CA SSL certificate.", + level=INFO) + for r_id in relation_ids('identity-service'): + for unit in relation_list(r_id): + if ca_cert is None: + ca_cert = relation_get('ca_cert', + rid=r_id, unit=unit) return ca_cert diff --git a/hooks/charmhelpers/contrib/hahelpers/cluster.py b/hooks/charmhelpers/contrib/hahelpers/cluster.py index 074855f..bf832f7 100644 --- a/hooks/charmhelpers/contrib/hahelpers/cluster.py +++ b/hooks/charmhelpers/contrib/hahelpers/cluster.py @@ -126,17 +126,17 @@ def determine_api_port(public_port): return public_port - (i * 10) -def determine_haproxy_port(public_port): +def determine_apache_port(public_port): ''' - Description: Determine correct proxy listening port based on public IP + - existence of HTTPS reverse proxy. + Description: Determine correct apache listening port based on public IP + + state of the cluster. public_port: int: standard public port for given service returns: int: the correct listening port for the HAProxy service ''' i = 0 - if https(): + if len(peer_units()) > 0 or is_clustered(): i += 1 return public_port - (i * 10) diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index a21c0c8..11ddc0b 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -27,11 +27,10 @@ from charmhelpers.core.hookenv import ( ) from charmhelpers.contrib.hahelpers.cluster import ( + determine_apache_port, determine_api_port, - determine_haproxy_port, https, - is_clustered, - peer_units, + is_clustered ) from charmhelpers.contrib.hahelpers.apache import ( @@ -190,22 +189,19 @@ class IdentityServiceContext(OSContextGenerator): for rid in relation_ids('identity-service'): for unit in related_units(rid): + rdata = relation_get(rid=rid, unit=unit) ctxt = { - 'service_port': relation_get('service_port', rid=rid, - unit=unit), - 'service_host': relation_get('service_host', rid=rid, - unit=unit), - 'auth_host': relation_get('auth_host', rid=rid, unit=unit), - 'auth_port': relation_get('auth_port', rid=rid, unit=unit), - 'admin_tenant_name': relation_get('service_tenant', - rid=rid, unit=unit), - 'admin_user': relation_get('service_username', rid=rid, - unit=unit), - 'admin_password': relation_get('service_password', rid=rid, - unit=unit), - # XXX: Hard-coded http. - 'service_protocol': 'http', - 'auth_protocol': 'http', + 'service_port': rdata.get('service_port'), + 'service_host': rdata.get('service_host'), + 'auth_host': rdata.get('auth_host'), + 'auth_port': rdata.get('auth_port'), + 'admin_tenant_name': rdata.get('service_tenant'), + 'admin_user': rdata.get('service_username'), + 'admin_password': rdata.get('service_password'), + 'service_protocol': + rdata.get('service_protocol') or 'http', + 'auth_protocol': + rdata.get('auth_protocol') or 'http', } if context_complete(ctxt): return ctxt @@ -265,7 +261,12 @@ class AMQPContext(OSContextGenerator): # Sufficient information found = break out! break # Used for active/active rabbitmq >= grizzly - if 'clustered' not in ctxt and len(related_units(rid)) > 1: + if ('clustered' not in ctxt or relation_get('ha-vip-only') == 'True') and \ + len(related_units(rid)) > 1: + if relation_get('ha_queues'): + ctxt['rabbitmq_ha_queues'] = relation_get('ha_queues') + else: + ctxt['rabbitmq_ha_queues'] = False rabbitmq_hosts = [] for unit in related_units(rid): rabbitmq_hosts.append(relation_get('private-address', @@ -284,10 +285,13 @@ class CephContext(OSContextGenerator): '''This generates context for /etc/ceph/ceph.conf templates''' if not relation_ids('ceph'): return {} + log('Generating template context for ceph') + mon_hosts = [] auth = None key = None + use_syslog = str(config('use-syslog')).lower() for rid in relation_ids('ceph'): for unit in related_units(rid): mon_hosts.append(relation_get('private-address', rid=rid, @@ -299,6 +303,7 @@ class CephContext(OSContextGenerator): 'mon_hosts': ' '.join(mon_hosts), 'auth': auth, 'key': key, + 'use_syslog': use_syslog } if not os.path.isdir('/etc/ceph'): @@ -427,17 +432,15 @@ class ApacheSSLContext(OSContextGenerator): 'private_address': unit_get('private-address'), 'endpoints': [] } - for ext_port in self.external_ports: - if peer_units() or is_clustered(): - int_port = determine_haproxy_port(ext_port) - else: - int_port = determine_api_port(ext_port) + for api_port in self.external_ports: + ext_port = determine_apache_port(api_port) + int_port = determine_api_port(api_port) portmap = (int(ext_port), int(int_port)) ctxt['endpoints'].append(portmap) return ctxt -class NeutronContext(object): +class NeutronContext(OSContextGenerator): interfaces = [] @property @@ -498,6 +501,22 @@ class NeutronContext(object): return nvp_ctxt + def neutron_ctxt(self): + if https(): + proto = 'https' + else: + proto = 'http' + if is_clustered(): + host = config('vip') + else: + host = unit_get('private-address') + url = '%s://%s:%s' % (proto, host, '9292') + ctxt = { + 'network_manager': self.network_manager, + 'neutron_url': url, + } + return ctxt + def __call__(self): self._ensure_packages() @@ -507,7 +526,7 @@ class NeutronContext(object): if not self.plugin: return {} - ctxt = {'network_manager': self.network_manager} + ctxt = self.neutron_ctxt() if self.plugin == 'ovs': ctxt.update(self.ovs_ctxt()) @@ -633,6 +652,7 @@ class SubordinateConfigContext(OSContextGenerator): class SyslogContext(OSContextGenerator): + def __call__(self): ctxt = { 'use_syslog': config('use-syslog') diff --git a/hooks/charmhelpers/contrib/storage/linux/ceph.py b/hooks/charmhelpers/contrib/storage/linux/ceph.py index 69b879c..1241741 100644 --- a/hooks/charmhelpers/contrib/storage/linux/ceph.py +++ b/hooks/charmhelpers/contrib/storage/linux/ceph.py @@ -49,6 +49,9 @@ CEPH_CONF = """[global] auth supported = {auth} keyring = {keyring} mon host = {mon_hosts} + log to syslog = {use_syslog} + err to syslog = {use_syslog} + clog to syslog = {use_syslog} """ @@ -194,7 +197,7 @@ def get_ceph_nodes(): return hosts -def configure(service, key, auth): +def configure(service, key, auth, use_syslog): ''' Perform basic configuration of Ceph ''' create_keyring(service, key) create_key_file(service, key) @@ -202,7 +205,8 @@ def configure(service, key, auth): with open('/etc/ceph/ceph.conf', 'w') as ceph_conf: ceph_conf.write(CEPH_CONF.format(auth=auth, keyring=_keyring_path(service), - mon_hosts=",".join(map(str, hosts)))) + mon_hosts=",".join(map(str, hosts)), + use_syslog=use_syslog)) modprobe('rbd') diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py index c8c81b2..cfd2684 100644 --- a/hooks/charmhelpers/core/host.py +++ b/hooks/charmhelpers/core/host.py @@ -194,7 +194,7 @@ def file_hash(path): return None -def restart_on_change(restart_map): +def restart_on_change(restart_map, stopstart=False): """Restart services based on configuration files changing This function is used a decorator, for example @@ -219,8 +219,14 @@ def restart_on_change(restart_map): for path in restart_map: if checksums[path] != file_hash(path): restarts += restart_map[path] - for service_name in list(OrderedDict.fromkeys(restarts)): - service('restart', service_name) + services_list = list(OrderedDict.fromkeys(restarts)) + if not stopstart: + for service_name in services_list: + service('restart', service_name) + else: + for action in ['stop', 'start']: + for service_name in services_list: + service(action, service_name) return wrapped_f return wrap From b9bd7ab3806599cb3bb67ad02db6f9745ba735bb Mon Sep 17 00:00:00 2001 From: Kapil Thangavelu Date: Sat, 1 Mar 2014 16:38:05 -0500 Subject: [PATCH 5/7] ceilometer passing rabbit ca to agents --- hooks/ceilometer_hooks.py | 6 ++++++ revision | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/hooks/ceilometer_hooks.py b/hooks/ceilometer_hooks.py index e57263d..427bae0 100755 --- a/hooks/ceilometer_hooks.py +++ b/hooks/ceilometer_hooks.py @@ -1,5 +1,6 @@ #!/usr/bin/python +import base64 import sys from charmhelpers.fetch import ( apt_install, filter_installed_packages, @@ -101,6 +102,11 @@ def keystone_joined(): def ceilometer_joined(): # Pass local context data onto related agent services context = get_ceilometer_context() + # This value gets tranformed to a path by the context we need to + # pass the data to agents. + if 'rabbit_ssl_ca' in context: + with open(context['rabbit_ssl_ca']) as fh: + context['rabbit_ssl_ca'] = base64.b64encode(fh.read()) for relid in relation_ids('ceilometer-service'): relation_set(relid, context) diff --git a/revision b/revision index 920a139..c739b42 100644 --- a/revision +++ b/revision @@ -1 +1 @@ -43 +44 From ba9bef7611c60e86ce4c321ba4a192b4e39cf834 Mon Sep 17 00:00:00 2001 From: Kapil Thangavelu Date: Wed, 5 Mar 2014 08:53:56 -0500 Subject: [PATCH 6/7] ssl endpoint support --- hooks/ceilometer_contexts.py | 10 ++++++- hooks/ceilometer_utils.py | 35 +++++++++++++++++++++---- revision | 2 +- templates/openstack_https_frontend | 23 ++++++++++++++++ templates/openstack_https_frontend.conf | 1 + 5 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 templates/openstack_https_frontend create mode 120000 templates/openstack_https_frontend.conf diff --git a/hooks/ceilometer_contexts.py b/hooks/ceilometer_contexts.py index 78d0709..2337bf8 100644 --- a/hooks/ceilometer_contexts.py +++ b/hooks/ceilometer_contexts.py @@ -9,7 +9,8 @@ from charmhelpers.core.hookenv import ( from charmhelpers.contrib.openstack.context import ( OSContextGenerator, - context_complete + context_complete, + ApacheSSLContext as SSLContext, ) CEILOMETER_DB = 'ceilometer' @@ -72,3 +73,10 @@ class CeilometerServiceContext(OSContextGenerator): if context_complete(conf): return conf return {} + + +class ApacheSSLContext(SSLContext): + + service_namespace = "ceilometer" + + external_ports = [CEILOMETER_PORT] diff --git a/hooks/ceilometer_utils.py b/hooks/ceilometer_utils.py index 9b84c60..e88b286 100644 --- a/hooks/ceilometer_utils.py +++ b/hooks/ceilometer_utils.py @@ -1,11 +1,16 @@ +import os + +from collections import OrderedDict + from charmhelpers.contrib.openstack import ( templating, context, ) from ceilometer_contexts import ( + ApacheSSLContext, LoggingConfigContext, MongoDBContext, - CeilometerContext + CeilometerContext, ) from charmhelpers.contrib.openstack.utils import ( get_os_codename_package, @@ -17,6 +22,9 @@ from charmhelpers.fetch import apt_update, apt_install CEILOMETER_CONF_DIR = "/etc/ceilometer" CEILOMETER_CONF = "%s/ceilometer.conf" % CEILOMETER_CONF_DIR +HTTPS_APACHE_CONF = "/etc/apache2/sites-available/openstack_https_frontend" +HTTPS_APACHE_24_CONF = "/etc/apache2/sites-available/" \ + "openstack_https_frontend.conf" CEILOMETER_SERVICES = [ 'ceilometer-agent-central', @@ -28,11 +36,13 @@ CEILOMETER_DB = "ceilometer" CEILOMETER_SERVICE = "ceilometer" CEILOMETER_PACKAGES = [ + 'apache2', 'ceilometer-agent-central', 'ceilometer-collector', 'ceilometer-api' ] + CEILOMETER_ROLE = "ResellerAdmin" #NOVA_CONF = "/etc/nova/nova.conf" @@ -42,8 +52,9 @@ CEILOMETER_ROLE = "ResellerAdmin" # ('DEFAULT', 'notification_driver', 'ceilometer.compute.nova_notifier') #] -CONFIG_FILES = { - CEILOMETER_CONF: { + +CONFIG_FILES = OrderedDict([ + (CEILOMETER_CONF, { 'hook_contexts': [context.IdentityServiceContext(), context.AMQPContext(ssl_dir=CEILOMETER_CONF_DIR), LoggingConfigContext(), @@ -51,8 +62,16 @@ CONFIG_FILES = { CeilometerContext(), context.SyslogContext()], 'services': CEILOMETER_SERVICES - } -} + }), + (HTTPS_APACHE_CONF, { + 'hook_contexts': [ApacheSSLContext()], + 'services': ['apache2'], + }), + (HTTPS_APACHE_24_CONF, { + 'hook_contexts': [ApacheSSLContext()], + 'services': ['apache2'], + }) +]) TEMPLATES = 'templates' @@ -74,6 +93,12 @@ def register_configs(): for conf in CONFIG_FILES: configs.register(conf, CONFIG_FILES[conf]['hook_contexts']) + if os.path.exists('/etc/apache2/conf-available'): + configs.register(HTTPS_APACHE_24_CONF, + CONFIG_FILES[HTTPS_APACHE_24_CONF]['hook_contexts']) + else: + configs.register(HTTPS_APACHE_CONF, + CONFIG_FILES[HTTPS_APACHE_CONF]['hook_contexts']) return configs diff --git a/revision b/revision index c739b42..9e5feb5 100644 --- a/revision +++ b/revision @@ -1 +1 @@ -44 +46 diff --git a/templates/openstack_https_frontend b/templates/openstack_https_frontend new file mode 100644 index 0000000..e833a71 --- /dev/null +++ b/templates/openstack_https_frontend @@ -0,0 +1,23 @@ +{% if endpoints %} +{% for ext, int in endpoints %} +Listen {{ ext }} +NameVirtualHost *:{{ ext }} + + ServerName {{ private_address }} + SSLEngine on + SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert + SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key + ProxyPass / http://localhost:{{ int }}/ + ProxyPassReverse / http://localhost:{{ int }}/ + ProxyPreserveHost on + + + Order deny,allow + Allow from all + + + Order allow,deny + Allow from all + +{% endfor %} +{% endif %} diff --git a/templates/openstack_https_frontend.conf b/templates/openstack_https_frontend.conf new file mode 120000 index 0000000..9a2f6f2 --- /dev/null +++ b/templates/openstack_https_frontend.conf @@ -0,0 +1 @@ +openstack_https_frontend \ No newline at end of file From af1cca24d3d109f3d4d951a99c1a1e988b7e4c6b Mon Sep 17 00:00:00 2001 From: Kapil Thangavelu Date: Wed, 5 Mar 2014 20:51:53 -0500 Subject: [PATCH 7/7] use a differnet port for apache --- hooks/ceilometer_contexts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/ceilometer_contexts.py b/hooks/ceilometer_contexts.py index 2337bf8..f09924e 100644 --- a/hooks/ceilometer_contexts.py +++ b/hooks/ceilometer_contexts.py @@ -79,4 +79,4 @@ class ApacheSSLContext(SSLContext): service_namespace = "ceilometer" - external_ports = [CEILOMETER_PORT] + external_ports = [CEILOMETER_PORT+100]