Merge ssl-everywhere branch
This commit is contained in:
commit
bd32a5adee
|
@ -1,8 +1,9 @@
|
|||
destination: lib/charmhelpers
|
||||
branch: lp:charm-helpers
|
||||
branch: lp:~openstack-charmers/charm-helpers/ssl-everywhere
|
||||
include:
|
||||
- fetch
|
||||
- core
|
||||
- contrib.charmsupport
|
||||
- contrib.openstack
|
||||
- contrib.storage
|
||||
- contrib.ssl
|
||||
|
|
31
config.yaml
31
config.yaml
|
@ -1,24 +1,42 @@
|
|||
options:
|
||||
ssl_enabled:
|
||||
type: boolean
|
||||
default: False
|
||||
description: enable SSL
|
||||
management_plugin:
|
||||
type: boolean
|
||||
default: False
|
||||
description: enable the management plugin
|
||||
|
||||
# SSL Configuration options
|
||||
ssl:
|
||||
type: string
|
||||
default: "off"
|
||||
description: |
|
||||
Enable SSL connections on rabbitmq, valid values are 'off', 'on', 'only'. If ssl_key,
|
||||
ssl_cert, ssl_ca are provided then then those values will be used. Otherwise
|
||||
the service will act as its own certificate authority and pass its ca cert to clients.
|
||||
For HA or clustered rabbits ssl key/cert must be provided.
|
||||
ssl_enabled:
|
||||
type: boolean
|
||||
default: False
|
||||
description: |
|
||||
(DEPRECATED see 'ssl' config option.) enable SSL
|
||||
ssl_port:
|
||||
type: int
|
||||
default: 5671
|
||||
description: SSL port
|
||||
ssl_key:
|
||||
type: string
|
||||
description: private unencrypted key in PEM format (starts "-----BEGIN RSA PRIVATE KEY-----")
|
||||
description: private unencrypted key in base64 PEM format (starts "-----BEGIN RSA PRIVATE KEY-----")
|
||||
default: ""
|
||||
ssl_cert:
|
||||
type: string
|
||||
description: X.509 certificate in PEM format (starts "-----BEGIN CERTIFICATE-----")
|
||||
description: X.509 certificate in base64 PEM format (starts "-----BEGIN CERTIFICATE-----")
|
||||
default: ""
|
||||
ssl_ca:
|
||||
type: string
|
||||
description: |
|
||||
Certificate authority cert that the cert. Optional if the ssl_cert is signed by a ca
|
||||
recognized by the os. Format is base64 PEM (concatenated certs if needed).
|
||||
default: ""
|
||||
|
||||
nagios_context:
|
||||
default: "juju"
|
||||
type: string
|
||||
|
@ -87,6 +105,7 @@ options:
|
|||
rbd pool has been created, changing this value will not have any
|
||||
effect (although it can be changed in ceph by manually configuring
|
||||
your ceph cluster).
|
||||
<<<<<<< TREE
|
||||
use-syslog:
|
||||
type: boolean
|
||||
default: False
|
||||
|
|
|
@ -163,8 +163,10 @@ def get_ceph_nodes():
|
|||
hosts = []
|
||||
for r_id in utils.relation_ids('ceph'):
|
||||
for unit in utils.relation_list(r_id):
|
||||
hosts.append(utils.relation_get('private-address',
|
||||
unit=unit, rid=r_id))
|
||||
ceph_public_addr = utils.relation_get(
|
||||
'ceph_public_addr', unit=unit, rid=r_id) or \
|
||||
utils.relation_get('private-address', unit=unit, rid=r_id)
|
||||
hosts.append(ceph_public_addr)
|
||||
return hosts
|
||||
|
||||
|
||||
|
|
|
@ -74,7 +74,11 @@ CLOUD_ARCHIVE_POCKETS = {
|
|||
'grizzly/proposed': 'precise-proposed/grizzly',
|
||||
'havana': 'precise-updates/havana',
|
||||
'havana/updates': 'precise-updates/havana',
|
||||
'havana/proposed': 'precise-proposed/havana'}
|
||||
'havana/proposed': 'precise-proposed/havana',
|
||||
'icehouse': 'precise-updates/icehouse',
|
||||
'icehouse/updates': 'precise-updates/icehouse',
|
||||
'icehouse/proposed': 'precise-proposed/icehouse',
|
||||
}
|
||||
|
||||
|
||||
def configure_source():
|
||||
|
@ -249,17 +253,15 @@ def unit_get(attribute):
|
|||
|
||||
|
||||
@cached
|
||||
def config_get(attribute):
|
||||
cmd = [
|
||||
'config-get',
|
||||
'--format',
|
||||
'json']
|
||||
out = subprocess.check_output(cmd).strip() # IGNORE:E1103
|
||||
cfg = json.loads(out)
|
||||
|
||||
def config_get(scope=None):
|
||||
"""Juju charm configuration"""
|
||||
config_cmd_line = ['config-get']
|
||||
if scope is not None:
|
||||
config_cmd_line.append(scope)
|
||||
config_cmd_line.append('--format=json')
|
||||
try:
|
||||
return cfg[attribute]
|
||||
except KeyError:
|
||||
return json.loads(subprocess.check_output(config_cmd_line))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
|
|
|
@ -241,22 +241,39 @@ def disable_plugin(plugin):
|
|||
|
||||
ssl_key_file = "/etc/rabbitmq/rabbit-server-privkey.pem"
|
||||
ssl_cert_file = "/etc/rabbitmq/rabbit-server-cert.pem"
|
||||
ssl_ca_file = "/etc/rabbitmq/rabbit-server-ca.pem"
|
||||
|
||||
|
||||
def enable_ssl(ssl_key, ssl_cert, ssl_port):
|
||||
with open(ssl_key_file, 'w') as key_file:
|
||||
key_file.write(ssl_key)
|
||||
utils.chmod(ssl_key_file, 0640)
|
||||
utils.chown(ssl_key_file, "root", RABBIT_USER)
|
||||
with open(ssl_cert_file, 'w') as cert_file:
|
||||
cert_file.write(ssl_cert)
|
||||
utils.chmod(ssl_cert_file, 0640)
|
||||
utils.chown(ssl_cert_file, "root", RABBIT_USER)
|
||||
def enable_ssl(ssl_key, ssl_cert, ssl_port,
|
||||
ssl_ca=None, ssl_only=False, ssl_client=None):
|
||||
uid = pwd.getpwnam("root").pw_uid
|
||||
gid = grp.getgrnam("rabbitmq").gr_gid
|
||||
|
||||
for contents, path in (
|
||||
(ssl_key, ssl_key_file),
|
||||
(ssl_cert, ssl_cert_file),
|
||||
(ssl_ca, ssl_ca_file)):
|
||||
if not contents:
|
||||
continue
|
||||
with open(path, 'w') as fh:
|
||||
fh.write(contents)
|
||||
os.chmod(path, 0640)
|
||||
os.chown(path, uid, gid)
|
||||
|
||||
data = {
|
||||
"ssl_port": ssl_port,
|
||||
"ssl_cert_file": ssl_cert_file,
|
||||
"ssl_key_file": ssl_key_file,
|
||||
"ssl_client": ssl_client,
|
||||
"ssl_ca_file": "",
|
||||
"ssl_only": ssl_only}
|
||||
|
||||
if ssl_ca:
|
||||
data["ssl_ca_file"] = ssl_ca_file
|
||||
|
||||
with open(RABBITMQ_CONF, 'w') as rmq_conf:
|
||||
rmq_conf.write(utils.render_template(os.path.basename(RABBITMQ_CONF),
|
||||
{"ssl_port": ssl_port,
|
||||
"ssl_cert_file": ssl_cert_file,
|
||||
"ssl_key_file": ssl_key_file}))
|
||||
rmq_conf.write(utils.render_template(
|
||||
os.path.basename(RABBITMQ_CONF), data))
|
||||
|
||||
|
||||
def execute(cmd, die=False, echo=False):
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import base64
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
@ -22,6 +22,7 @@ from charmhelpers.fetch import (
|
|||
from charmhelpers.core import hookenv
|
||||
from charmhelpers.core.host import rsync, mkdir, pwgen
|
||||
from charmhelpers.contrib.charmsupport.nrpe import NRPE
|
||||
from charmhelpers.contrib.ssl.service import ServiceCA
|
||||
|
||||
|
||||
SERVICE_NAME = os.getenv('JUJU_UNIT_NAME').split('/')[0]
|
||||
|
@ -65,18 +66,16 @@ def amqp_changed(relation_id=None, remote_unit=None):
|
|||
relation_settings = {}
|
||||
settings = hookenv.relation_get(rid=relation_id, unit=remote_unit)
|
||||
|
||||
singleset = set([
|
||||
'username',
|
||||
'vhost'])
|
||||
singleset = set(['username', 'vhost'])
|
||||
|
||||
if singleset.issubset(settings):
|
||||
if None in [settings['username'], settings['vhost']]:
|
||||
utils.juju_log('INFO', 'amqp_changed(): Relation not ready.')
|
||||
return
|
||||
|
||||
relation_settings['password'] = \
|
||||
configure_amqp(username=settings['username'],
|
||||
vhost=settings['vhost'])
|
||||
relation_settings['password'] = configure_amqp(
|
||||
username=settings['username'],
|
||||
vhost=settings['vhost'])
|
||||
else:
|
||||
queues = {}
|
||||
for k, v in settings.iteritems():
|
||||
|
@ -85,12 +84,15 @@ def amqp_changed(relation_id=None, remote_unit=None):
|
|||
if amqp not in queues:
|
||||
queues[amqp] = {}
|
||||
queues[amqp][x] = v
|
||||
relation_settings = {}
|
||||
for amqp in queues:
|
||||
if singleset.issubset(queues[amqp]):
|
||||
relation_settings['_'.join([amqp, 'password'])] = \
|
||||
configure_amqp(queues[amqp]['username'],
|
||||
queues[amqp]['vhost'])
|
||||
relation_settings[
|
||||
'_'.join([amqp, 'password'])] = configure_amqp(
|
||||
queues[amqp]['username'],
|
||||
queues[amqp]['vhost'])
|
||||
|
||||
relation_settings['hostname'] = utils.unit_get('private-address')
|
||||
configure_client_ssl(relation_settings)
|
||||
|
||||
if cluster.is_clustered():
|
||||
relation_settings['clustered'] = 'true'
|
||||
|
@ -278,6 +280,7 @@ def ha_changed():
|
|||
|
||||
def ceph_joined():
|
||||
utils.juju_log('INFO', 'Start Ceph Relation Joined')
|
||||
utils.configure_source()
|
||||
ceph.install()
|
||||
utils.juju_log('INFO', 'Finish Ceph Relation Joined')
|
||||
|
||||
|
@ -390,6 +393,104 @@ def upgrade_charm():
|
|||
MAN_PLUGIN = 'rabbitmq_management'
|
||||
|
||||
|
||||
def configure_client_ssl(relation_data):
|
||||
"""Configure client with ssl
|
||||
"""
|
||||
ssl_mode, external_ca = _get_ssl_mode()
|
||||
if ssl_mode == 'off':
|
||||
return
|
||||
relation_data['ssl_port'] = utils.config_get('ssl_port')
|
||||
if external_ca:
|
||||
if utils.config_get('ssl_ca'):
|
||||
relation_data['ssl_ca'] = base64.b64encode(
|
||||
utils.config_get('ssl_ca'))
|
||||
return
|
||||
ca = ServiceCA.get_ca()
|
||||
relation_data['ssl_ca'] = base64.b64encode(ca.get_ca_bundle())
|
||||
|
||||
|
||||
def _get_ssl_mode():
|
||||
config = utils.config_get()
|
||||
ssl_mode = config.get('ssl')
|
||||
external_ca = False
|
||||
# Legacy config boolean option
|
||||
ssl_on = config.get('ssl_enabled')
|
||||
if ssl_mode == 'off' and ssl_on is False:
|
||||
ssl_mode = 'off'
|
||||
elif ssl_mode == 'off' and ssl_on:
|
||||
ssl_mode = 'on'
|
||||
ssl_key = utils.config_get('ssl_key')
|
||||
ssl_cert = utils.config_get('ssl_cert')
|
||||
if all((ssl_key, ssl_cert)):
|
||||
external_ca = True
|
||||
return ssl_mode, external_ca
|
||||
|
||||
|
||||
def _convert_from_base64(v):
|
||||
# Rabbit originally supported pem encoded key/cert in config, play
|
||||
# nice on upgrades as we now expect base64 encoded key/cert/ca.
|
||||
if not v:
|
||||
return v
|
||||
if v.startswith('-----BEGIN'):
|
||||
return v
|
||||
try:
|
||||
return base64.b64decode(v)
|
||||
except TypeError:
|
||||
return v
|
||||
|
||||
|
||||
def reconfigure_client_ssl(ssl_enabled=False):
|
||||
ssl_config_keys = set(('ssl_key', 'ssl_cert', 'ssl_ca'))
|
||||
for rid in hookenv.relation_ids('amqp'):
|
||||
rdata = hookenv.relation_get(
|
||||
rid=rid, unit=os.environ['JUJU_UNIT_NAME'])
|
||||
if not ssl_enabled and ssl_config_keys.intersection(rdata):
|
||||
# No clean way to remove entirely, but blank them.
|
||||
utils.relation_set(
|
||||
rid=rid, ssl_key='', ssl_cert='', ssl_ca='')
|
||||
elif ssl_enabled and not ssl_config_keys.intersection(rdata):
|
||||
configure_client_ssl(rdata)
|
||||
utils.relation_set(rid=rid, **rdata)
|
||||
|
||||
|
||||
def configure_rabbit_ssl():
|
||||
"""
|
||||
The legacy config support adds some additional complications.
|
||||
|
||||
ssl_enabled = True, ssl = off -> ssl enabled
|
||||
ssl_enabled = False, ssl = on -> ssl enabled
|
||||
"""
|
||||
ssl_mode, external_ca = _get_ssl_mode()
|
||||
|
||||
if ssl_mode == 'off':
|
||||
if os.path.exists(rabbit.RABBITMQ_CONF):
|
||||
os.remove(rabbit.RABBITMQ_CONF)
|
||||
utils.close_port(utils.config_get('ssl_port'))
|
||||
reconfigure_client_ssl()
|
||||
return
|
||||
ssl_key = _convert_from_base64(utils.config_get('ssl_key'))
|
||||
ssl_cert = _convert_from_base64(utils.config_get('ssl_cert'))
|
||||
ssl_ca = _convert_from_base64(utils.config_get('ssl_ca'))
|
||||
ssl_port = utils.config_get('ssl_port')
|
||||
|
||||
# If external managed certs then we need all the fields.
|
||||
if (ssl_mode in ('on', 'only') and any((ssl_key, ssl_cert)) and
|
||||
not all((ssl_key, ssl_cert))):
|
||||
utils.juju_log(
|
||||
'ERROR',
|
||||
'If ssl_key or ssl_cert are specified both are required.')
|
||||
sys.exit(1)
|
||||
|
||||
if not external_ca:
|
||||
ssl_cert, ssl_key, ssl_ca = ServiceCA.get_service_cert()
|
||||
|
||||
rabbit.enable_ssl(
|
||||
ssl_key, ssl_cert, ssl_port, ssl_ca,
|
||||
ssl_only=(ssl_mode == "only"), ssl_client=False)
|
||||
reconfigure_client_ssl(True)
|
||||
utils.open_port(ssl_port)
|
||||
|
||||
|
||||
def config_changed():
|
||||
if utils.config_get('management_plugin') is True:
|
||||
rabbit.enable_plugin(MAN_PLUGIN)
|
||||
|
@ -398,22 +499,7 @@ def config_changed():
|
|||
rabbit.disable_plugin(MAN_PLUGIN)
|
||||
utils.close_port(55672)
|
||||
|
||||
if utils.config_get('ssl_enabled') is True:
|
||||
ssl_key = utils.config_get('ssl_key')
|
||||
ssl_cert = utils.config_get('ssl_cert')
|
||||
ssl_port = utils.config_get('ssl_port')
|
||||
if None in [ssl_key, ssl_cert, ssl_port]:
|
||||
utils.juju_log('ERROR',
|
||||
'Please provide ssl_key, ssl_cert and ssl_port'
|
||||
' config when enabling SSL support')
|
||||
sys.exit(1)
|
||||
else:
|
||||
rabbit.enable_ssl(ssl_key, ssl_cert, ssl_port)
|
||||
utils.open_port(ssl_port)
|
||||
else:
|
||||
if os.path.exists(rabbit.RABBITMQ_CONF):
|
||||
os.remove(rabbit.RABBITMQ_CONF)
|
||||
utils.close_port(utils.config_get('ssl_port'))
|
||||
configure_rabbit_ssl()
|
||||
|
||||
if cluster.eligible_leader('res_rabbitmq_vip') or \
|
||||
utils.config_get('ha-vip-only') is True:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
from base64 import b64decode
|
||||
|
||||
|
@ -113,7 +114,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
|
||||
|
@ -122,6 +124,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')
|
||||
|
@ -139,19 +142,44 @@ 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):
|
||||
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']
|
||||
|
||||
|
@ -161,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
|
||||
|
@ -186,6 +211,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()
|
||||
|
@ -196,7 +224,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):
|
||||
|
@ -213,7 +240,24 @@ 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
|
||||
|
@ -388,6 +432,8 @@ class ApacheSSLContext(OSContextGenerator):
|
|||
'private_address': unit_get('private-address'),
|
||||
'endpoints': []
|
||||
}
|
||||
if is_clustered():
|
||||
ctxt['private_address'] = config('vip')
|
||||
for api_port in self.external_ports:
|
||||
ext_port = determine_apache_port(api_port)
|
||||
int_port = determine_api_port(api_port)
|
||||
|
|
|
@ -17,8 +17,11 @@ def headers_package():
|
|||
kver = check_output(['uname', '-r']).strip()
|
||||
return 'linux-headers-%s' % kver
|
||||
|
||||
QUANTUM_CONF_DIR = '/etc/quantum'
|
||||
|
||||
# legacy
|
||||
|
||||
|
||||
def quantum_plugins():
|
||||
from charmhelpers.contrib.openstack import context
|
||||
return {
|
||||
|
@ -30,7 +33,8 @@ def quantum_plugins():
|
|||
'contexts': [
|
||||
context.SharedDBContext(user=config('neutron-database-user'),
|
||||
database=config('neutron-database'),
|
||||
relation_prefix='neutron')],
|
||||
relation_prefix='neutron',
|
||||
ssl_dir=QUANTUM_CONF_DIR)],
|
||||
'services': ['quantum-plugin-openvswitch-agent'],
|
||||
'packages': [[headers_package(), 'openvswitch-datapath-dkms'],
|
||||
['quantum-plugin-openvswitch-agent']],
|
||||
|
@ -45,7 +49,8 @@ def quantum_plugins():
|
|||
'contexts': [
|
||||
context.SharedDBContext(user=config('neutron-database-user'),
|
||||
database=config('neutron-database'),
|
||||
relation_prefix='neutron')],
|
||||
relation_prefix='neutron',
|
||||
ssl_dir=QUANTUM_CONF_DIR)],
|
||||
'services': [],
|
||||
'packages': [],
|
||||
'server_packages': ['quantum-server',
|
||||
|
@ -54,6 +59,8 @@ def quantum_plugins():
|
|||
}
|
||||
}
|
||||
|
||||
NEUTRON_CONF_DIR = '/etc/neutron'
|
||||
|
||||
|
||||
def neutron_plugins():
|
||||
from charmhelpers.contrib.openstack import context
|
||||
|
@ -66,7 +73,8 @@ def neutron_plugins():
|
|||
'contexts': [
|
||||
context.SharedDBContext(user=config('neutron-database-user'),
|
||||
database=config('neutron-database'),
|
||||
relation_prefix='neutron')],
|
||||
relation_prefix='neutron',
|
||||
ssl_dir=NEUTRON_CONF_DIR)],
|
||||
'services': ['neutron-plugin-openvswitch-agent'],
|
||||
'packages': [[headers_package(), 'openvswitch-datapath-dkms'],
|
||||
['quantum-plugin-openvswitch-agent']],
|
||||
|
@ -81,7 +89,8 @@ def neutron_plugins():
|
|||
'contexts': [
|
||||
context.SharedDBContext(user=config('neutron-database-user'),
|
||||
database=config('neutron-database'),
|
||||
relation_prefix='neutron')],
|
||||
relation_prefix='neutron',
|
||||
ssl_dir=NEUTRON_CONF_DIR)],
|
||||
'services': [],
|
||||
'packages': [],
|
||||
'server_packages': ['neutron-server',
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
import subprocess
|
||||
from charmhelpers.core import hookenv
|
||||
|
||||
|
||||
def generate_selfsigned(keyfile, certfile, keysize="1024", config=None, subject=None, cn=None):
|
||||
"""Generate selfsigned SSL keypair
|
||||
|
||||
You must provide one of the 3 optional arguments:
|
||||
config, subject or cn
|
||||
If more than one is provided the leftmost will be used
|
||||
|
||||
Arguments:
|
||||
keyfile -- (required) full path to the keyfile to be created
|
||||
certfile -- (required) full path to the certfile to be created
|
||||
keysize -- (optional) SSL key length
|
||||
config -- (optional) openssl configuration file
|
||||
subject -- (optional) dictionary with SSL subject variables
|
||||
cn -- (optional) cerfificate common name
|
||||
|
||||
Required keys in subject dict:
|
||||
cn -- Common name (eq. FQDN)
|
||||
|
||||
Optional keys in subject dict
|
||||
country -- Country Name (2 letter code)
|
||||
state -- State or Province Name (full name)
|
||||
locality -- Locality Name (eg, city)
|
||||
organization -- Organization Name (eg, company)
|
||||
organizational_unit -- Organizational Unit Name (eg, section)
|
||||
email -- Email Address
|
||||
"""
|
||||
|
||||
cmd = []
|
||||
if config:
|
||||
cmd = ["/usr/bin/openssl", "req", "-new", "-newkey",
|
||||
"rsa:{}".format(keysize), "-days", "365", "-nodes", "-x509",
|
||||
"-keyout", keyfile,
|
||||
"-out", certfile, "-config", config]
|
||||
elif subject:
|
||||
ssl_subject = ""
|
||||
if "country" in subject:
|
||||
ssl_subject = ssl_subject + "/C={}".format(subject["country"])
|
||||
if "state" in subject:
|
||||
ssl_subject = ssl_subject + "/ST={}".format(subject["state"])
|
||||
if "locality" in subject:
|
||||
ssl_subject = ssl_subject + "/L={}".format(subject["locality"])
|
||||
if "organization" in subject:
|
||||
ssl_subject = ssl_subject + "/O={}".format(subject["organization"])
|
||||
if "organizational_unit" in subject:
|
||||
ssl_subject = ssl_subject + "/OU={}".format(subject["organizational_unit"])
|
||||
if "cn" in subject:
|
||||
ssl_subject = ssl_subject + "/CN={}".format(subject["cn"])
|
||||
else:
|
||||
hookenv.log("When using \"subject\" argument you must "
|
||||
"provide \"cn\" field at very least")
|
||||
return False
|
||||
if "email" in subject:
|
||||
ssl_subject = ssl_subject + "/emailAddress={}".format(subject["email"])
|
||||
|
||||
cmd = ["/usr/bin/openssl", "req", "-new", "-newkey",
|
||||
"rsa:{}".format(keysize), "-days", "365", "-nodes", "-x509",
|
||||
"-keyout", keyfile,
|
||||
"-out", certfile, "-subj", ssl_subject]
|
||||
elif cn:
|
||||
cmd = ["/usr/bin/openssl", "req", "-new", "-newkey",
|
||||
"rsa:{}".format(keysize), "-days", "365", "-nodes", "-x509",
|
||||
"-keyout", keyfile,
|
||||
"-out", certfile, "-subj", "/CN={}".format(cn)]
|
||||
|
||||
if not cmd:
|
||||
hookenv.log("No config, subject or cn provided,"
|
||||
"unable to generate self signed SSL certificates")
|
||||
return False
|
||||
try:
|
||||
subprocess.check_call(cmd)
|
||||
return True
|
||||
except Exception as e:
|
||||
print "Execution of openssl command failed:\n{}".format(e)
|
||||
return False
|
|
@ -0,0 +1,267 @@
|
|||
import logging
|
||||
import os
|
||||
from os.path import join as path_join
|
||||
from os.path import exists
|
||||
import subprocess
|
||||
|
||||
|
||||
log = logging.getLogger("service_ca")
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
STD_CERT = "standard"
|
||||
|
||||
# Mysql server is fairly picky about cert creation
|
||||
# and types, spec its creation separately for now.
|
||||
MYSQL_CERT = "mysql"
|
||||
|
||||
|
||||
class ServiceCA(object):
|
||||
|
||||
default_expiry = str(365 * 2)
|
||||
default_ca_expiry = str(365 * 6)
|
||||
|
||||
def __init__(self, name, ca_dir, cert_type=STD_CERT):
|
||||
self.name = name
|
||||
self.ca_dir = ca_dir
|
||||
self.cert_type = cert_type
|
||||
|
||||
###############
|
||||
# Hook Helper API
|
||||
@staticmethod
|
||||
def get_ca(type=STD_CERT):
|
||||
service_name = os.environ['JUJU_UNIT_NAME'].split('/')[0]
|
||||
ca_path = os.path.join(os.environ['CHARM_DIR'], 'ca')
|
||||
ca = ServiceCA(service_name, ca_path, type)
|
||||
ca.init()
|
||||
return ca
|
||||
|
||||
@classmethod
|
||||
def get_service_cert(cls, type=STD_CERT):
|
||||
service_name = os.environ['JUJU_UNIT_NAME'].split('/')[0]
|
||||
ca = cls.get_ca()
|
||||
crt, key = ca.get_or_create_cert(service_name)
|
||||
return crt, key, ca.get_ca_bundle()
|
||||
|
||||
###############
|
||||
|
||||
def init(self):
|
||||
log.debug("initializing service ca")
|
||||
if not exists(self.ca_dir):
|
||||
self._init_ca_dir(self.ca_dir)
|
||||
self._init_ca()
|
||||
|
||||
@property
|
||||
def ca_key(self):
|
||||
return path_join(self.ca_dir, 'private', 'cacert.key')
|
||||
|
||||
@property
|
||||
def ca_cert(self):
|
||||
return path_join(self.ca_dir, 'cacert.pem')
|
||||
|
||||
@property
|
||||
def ca_conf(self):
|
||||
return path_join(self.ca_dir, 'ca.cnf')
|
||||
|
||||
@property
|
||||
def signing_conf(self):
|
||||
return path_join(self.ca_dir, 'signing.cnf')
|
||||
|
||||
def _init_ca_dir(self, ca_dir):
|
||||
os.mkdir(ca_dir)
|
||||
for i in ['certs', 'crl', 'newcerts', 'private']:
|
||||
sd = path_join(ca_dir, i)
|
||||
if not exists(sd):
|
||||
os.mkdir(sd)
|
||||
|
||||
if not exists(path_join(ca_dir, 'serial')):
|
||||
with open(path_join(ca_dir, 'serial'), 'wb') as fh:
|
||||
fh.write('02\n')
|
||||
|
||||
if not exists(path_join(ca_dir, 'index.txt')):
|
||||
with open(path_join(ca_dir, 'index.txt'), 'wb') as fh:
|
||||
fh.write('')
|
||||
|
||||
def _init_ca(self):
|
||||
"""Generate the root ca's cert and key.
|
||||
"""
|
||||
if not exists(path_join(self.ca_dir, 'ca.cnf')):
|
||||
with open(path_join(self.ca_dir, 'ca.cnf'), 'wb') as fh:
|
||||
fh.write(
|
||||
CA_CONF_TEMPLATE % (self.get_conf_variables()))
|
||||
|
||||
if not exists(path_join(self.ca_dir, 'signing.cnf')):
|
||||
with open(path_join(self.ca_dir, 'signing.cnf'), 'wb') as fh:
|
||||
fh.write(
|
||||
SIGNING_CONF_TEMPLATE % (self.get_conf_variables()))
|
||||
|
||||
if exists(self.ca_cert) or exists(self.ca_key):
|
||||
raise RuntimeError("Initialized called when CA already exists")
|
||||
cmd = ['openssl', 'req', '-config', self.ca_conf,
|
||||
'-x509', '-nodes', '-newkey', 'rsa',
|
||||
'-days', self.default_ca_expiry,
|
||||
'-keyout', self.ca_key, '-out', self.ca_cert,
|
||||
'-outform', 'PEM']
|
||||
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
||||
log.debug("CA Init:\n %s", output)
|
||||
|
||||
def get_conf_variables(self):
|
||||
return dict(
|
||||
org_name="juju",
|
||||
org_unit_name="%s service" % self.name,
|
||||
common_name=self.name,
|
||||
ca_dir=self.ca_dir)
|
||||
|
||||
def get_or_create_cert(self, common_name):
|
||||
if common_name in self:
|
||||
return self.get_certificate(common_name)
|
||||
return self.create_certificate(common_name)
|
||||
|
||||
def create_certificate(self, common_name):
|
||||
if common_name in self:
|
||||
return self.get_certificate(common_name)
|
||||
key_p = path_join(self.ca_dir, "certs", "%s.key" % common_name)
|
||||
crt_p = path_join(self.ca_dir, "certs", "%s.crt" % common_name)
|
||||
csr_p = path_join(self.ca_dir, "certs", "%s.csr" % common_name)
|
||||
self._create_certificate(common_name, key_p, csr_p, crt_p)
|
||||
return self.get_certificate(common_name)
|
||||
|
||||
def get_certificate(self, common_name):
|
||||
if not common_name in self:
|
||||
raise ValueError("No certificate for %s" % common_name)
|
||||
key_p = path_join(self.ca_dir, "certs", "%s.key" % common_name)
|
||||
crt_p = path_join(self.ca_dir, "certs", "%s.crt" % common_name)
|
||||
with open(crt_p) as fh:
|
||||
crt = fh.read()
|
||||
with open(key_p) as fh:
|
||||
key = fh.read()
|
||||
return crt, key
|
||||
|
||||
def __contains__(self, common_name):
|
||||
crt_p = path_join(self.ca_dir, "certs", "%s.crt" % common_name)
|
||||
return exists(crt_p)
|
||||
|
||||
def _create_certificate(self, common_name, key_p, csr_p, crt_p):
|
||||
template_vars = self.get_conf_variables()
|
||||
template_vars['common_name'] = common_name
|
||||
subj = '/O=%(org_name)s/OU=%(org_unit_name)s/CN=%(common_name)s' % (
|
||||
template_vars)
|
||||
|
||||
log.debug("CA Create Cert %s", common_name)
|
||||
cmd = ['openssl', 'req', '-sha1', '-newkey', 'rsa:2048',
|
||||
'-nodes', '-days', self.default_expiry,
|
||||
'-keyout', key_p, '-out', csr_p, '-subj', subj]
|
||||
subprocess.check_call(cmd)
|
||||
cmd = ['openssl', 'rsa', '-in', key_p, '-out', key_p]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
log.debug("CA Sign Cert %s", common_name)
|
||||
if self.cert_type == MYSQL_CERT:
|
||||
cmd = ['openssl', 'x509', '-req',
|
||||
'-in', csr_p, '-days', self.default_expiry,
|
||||
'-CA', self.ca_cert, '-CAkey', self.ca_key,
|
||||
'-set_serial', '01', '-out', crt_p]
|
||||
else:
|
||||
cmd = ['openssl', 'ca', '-config', self.signing_conf,
|
||||
'-extensions', 'req_extensions',
|
||||
'-days', self.default_expiry, '-notext',
|
||||
'-in', csr_p, '-out', crt_p, '-subj', subj, '-batch']
|
||||
log.debug("running %s", " ".join(cmd))
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
def get_ca_bundle(self):
|
||||
with open(self.ca_cert) as fh:
|
||||
return fh.read()
|
||||
|
||||
|
||||
CA_CONF_TEMPLATE = """
|
||||
[ ca ]
|
||||
default_ca = CA_default
|
||||
|
||||
[ CA_default ]
|
||||
dir = %(ca_dir)s
|
||||
policy = policy_match
|
||||
database = $dir/index.txt
|
||||
serial = $dir/serial
|
||||
certs = $dir/certs
|
||||
crl_dir = $dir/crl
|
||||
new_certs_dir = $dir/newcerts
|
||||
certificate = $dir/cacert.pem
|
||||
private_key = $dir/private/cacert.key
|
||||
RANDFILE = $dir/private/.rand
|
||||
default_md = default
|
||||
|
||||
[ req ]
|
||||
default_bits = 1024
|
||||
default_md = sha1
|
||||
|
||||
prompt = no
|
||||
distinguished_name = ca_distinguished_name
|
||||
|
||||
x509_extensions = ca_extensions
|
||||
|
||||
[ ca_distinguished_name ]
|
||||
organizationName = %(org_name)s
|
||||
organizationalUnitName = %(org_unit_name)s Certificate Authority
|
||||
|
||||
|
||||
[ policy_match ]
|
||||
countryName = optional
|
||||
stateOrProvinceName = optional
|
||||
organizationName = match
|
||||
organizationalUnitName = optional
|
||||
commonName = supplied
|
||||
|
||||
[ ca_extensions ]
|
||||
basicConstraints = critical,CA:true
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid:always, issuer
|
||||
keyUsage = cRLSign, keyCertSign
|
||||
"""
|
||||
|
||||
|
||||
SIGNING_CONF_TEMPLATE = """
|
||||
[ ca ]
|
||||
default_ca = CA_default
|
||||
|
||||
[ CA_default ]
|
||||
dir = %(ca_dir)s
|
||||
policy = policy_match
|
||||
database = $dir/index.txt
|
||||
serial = $dir/serial
|
||||
certs = $dir/certs
|
||||
crl_dir = $dir/crl
|
||||
new_certs_dir = $dir/newcerts
|
||||
certificate = $dir/cacert.pem
|
||||
private_key = $dir/private/cacert.key
|
||||
RANDFILE = $dir/private/.rand
|
||||
default_md = default
|
||||
|
||||
[ req ]
|
||||
default_bits = 1024
|
||||
default_md = sha1
|
||||
|
||||
prompt = no
|
||||
distinguished_name = req_distinguished_name
|
||||
|
||||
x509_extensions = req_extensions
|
||||
|
||||
[ req_distinguished_name ]
|
||||
organizationName = %(org_name)s
|
||||
organizationalUnitName = %(org_unit_name)s machine resources
|
||||
commonName = %(common_name)s
|
||||
|
||||
[ policy_match ]
|
||||
countryName = optional
|
||||
stateOrProvinceName = optional
|
||||
organizationName = match
|
||||
organizationalUnitName = optional
|
||||
commonName = supplied
|
||||
|
||||
[ req_extensions ]
|
||||
basicConstraints = CA:false
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid:always, issuer
|
||||
keyUsage = digitalSignature, keyEncipherment, keyAgreement
|
||||
extendedKeyUsage = serverAuth, clientAuth
|
||||
"""
|
|
@ -1,10 +1,21 @@
|
|||
[
|
||||
{rabbit, [
|
||||
{% if ssl_only %}
|
||||
{tcp_listeners, []},
|
||||
{% else %}
|
||||
{tcp_listeners, [5672]},
|
||||
{% endif %}
|
||||
{ssl_listeners, [{{ ssl_port }}]},
|
||||
{ssl_options, [
|
||||
{verify, verify_peer},
|
||||
{% if ssl_client %}
|
||||
{fail_if_no_peer_cert, true},
|
||||
{% else %}
|
||||
{fail_if_no_peer_cert, false},
|
||||
{% endif %}{% if ssl_ca_file %}
|
||||
{cacertfile, "{{ ssl_ca_file }}"}, {% endif %}
|
||||
{certfile, "{{ ssl_cert_file }}"},
|
||||
{keyfile, "{{ ssl_key_file }}"}
|
||||
]},
|
||||
{tcp_listeners, [5672]}
|
||||
]}
|
||||
]}
|
||||
].
|
Loading…
Reference in New Issue