synced /next

This commit is contained in:
Edward Hope-Morley 2015-03-15 11:20:18 +01:00
commit a63b34235c
9 changed files with 391 additions and 171 deletions

View File

@ -26,6 +26,6 @@ sync: bin/charm_helpers_sync.py
@$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-hooks.yaml @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-hooks.yaml
@$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-tests.yaml @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-tests.yaml
publish: lint test publish: lint unit_test
bzr push lp:charms/keystone bzr push lp:charms/keystone
bzr push lp:charms/trusty/keystone bzr push lp:charms/trusty/keystone

View File

@ -18,6 +18,7 @@ from charmhelpers.contrib.hahelpers.cluster import (
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
log, log,
DEBUG,
INFO, INFO,
) )
@ -173,9 +174,8 @@ class KeystoneContext(context.OSContextGenerator):
def __call__(self): def __call__(self):
from keystone_utils import ( from keystone_utils import (
api_port, set_admin_token, api_port, set_admin_token, endpoint_url, resolve_address,
endpoint_url, resolve_address, PUBLIC, ADMIN, PKI_CERTS_DIR, SSH_USER, ensure_permissions,
PUBLIC, ADMIN
) )
ctxt = {} ctxt = {}
ctxt['token'] = set_admin_token(config('admin-token')) ctxt['token'] = set_admin_token(config('admin-token'))
@ -205,6 +205,31 @@ class KeystoneContext(context.OSContextGenerator):
enable_pki = config('enable-pki') enable_pki = config('enable-pki')
if enable_pki and bool_from_string(enable_pki): if enable_pki and bool_from_string(enable_pki):
ctxt['signing'] = True ctxt['signing'] = True
ctxt['token_provider'] = 'pki'
if 'token_provider' in ctxt:
log("Configuring PKI token cert paths", level=DEBUG)
certs = os.path.join(PKI_CERTS_DIR, 'certs')
privates = os.path.join(PKI_CERTS_DIR, 'privates')
for path in [PKI_CERTS_DIR, certs, privates]:
perms = 0o755
if not os.path.isdir(path):
mkdir(path=path, owner=SSH_USER, group='keystone',
perms=perms)
else:
# Ensure accessible by ssh user and group (for sync).
ensure_permissions(path, user=SSH_USER,
group='keystone', perms=perms)
signing_paths = {'certfile': os.path.join(certs,
'signing_cert.pem'),
'keyfile': os.path.join(privates,
'signing_key.pem'),
'ca_certs': os.path.join(certs, 'ca.pem'),
'ca_key': os.path.join(certs, 'ca_key.pem')}
for key, val in signing_paths.iteritems():
ctxt[key] = val
# Base endpoint URL's which are used in keystone responses # Base endpoint URL's which are used in keystone responses
# to unauthenticated requests to redirect clients to the # to unauthenticated requests to redirect clients to the

View File

@ -70,6 +70,10 @@ from keystone_utils import (
clear_ssl_synced_units, clear_ssl_synced_units,
is_db_initialised, is_db_initialised,
update_certs_if_available, update_certs_if_available,
is_pki_enabled,
ensure_ssl_dir,
ensure_pki_dir_permissions,
force_ssl_sync,
filter_null, filter_null,
ensure_ssl_dirs, ensure_ssl_dirs,
) )
@ -114,7 +118,7 @@ def install():
@hooks.hook('config-changed') @hooks.hook('config-changed')
@restart_on_change(restart_map()) @restart_on_change(restart_map())
@synchronize_ca_if_changed() @synchronize_ca_if_changed(fatal=True)
def config_changed(): def config_changed():
if config('prefer-ipv6'): if config('prefer-ipv6'):
setup_ipv6() setup_ipv6()
@ -130,18 +134,25 @@ def config_changed():
if openstack_upgrade_available('keystone'): if openstack_upgrade_available('keystone'):
do_openstack_upgrade(configs=CONFIGS) do_openstack_upgrade(configs=CONFIGS)
# Ensure ssl dir exists and is unison-accessible
ensure_ssl_dir()
check_call(['chmod', '-R', 'g+wrx', '/var/lib/keystone/']) check_call(['chmod', '-R', 'g+wrx', '/var/lib/keystone/'])
ensure_ssl_dirs() ensure_ssl_dirs()
save_script_rc() save_script_rc()
configure_https() configure_https()
update_nrpe_config() update_nrpe_config()
CONFIGS.write_all() CONFIGS.write_all()
if is_pki_enabled():
initialise_pki()
# Update relations since SSL may have been configured. If we have peer # Update relations since SSL may have been configured. If we have peer
# units we can rely on the sync to do this in cluster relation. # units we can rely on the sync to do this in cluster relation.
if is_elected_leader(CLUSTER_RES) and not peer_units(): if not peer_units():
update_all_identity_relation_units() update_all_identity_relation_units()
for rid in relation_ids('identity-admin'): for rid in relation_ids('identity-admin'):
@ -154,6 +165,22 @@ def config_changed():
ha_joined(relation_id=r_id) ha_joined(relation_id=r_id)
@synchronize_ca_if_changed(fatal=True)
def initialise_pki():
"""Create certs and keys required for PKI token signing.
NOTE: keystone.conf [signing] section must be up-to-date prior to
executing this.
"""
if is_ssl_cert_master():
log("Ensuring PKI token certs created", level=DEBUG)
cmd = ['keystone-manage', 'pki_setup', '--keystone-user', 'keystone',
'--keystone-group', 'keystone']
check_call(cmd)
ensure_pki_dir_permissions()
@hooks.hook('shared-db-relation-joined') @hooks.hook('shared-db-relation-joined')
def db_joined(): def db_joined():
if is_relation_made('pgsql-db'): if is_relation_made('pgsql-db'):
@ -294,6 +321,7 @@ def identity_changed(relation_id=None, remote_unit=None):
peerdb_settings = filter_null(peerdb_settings) peerdb_settings = filter_null(peerdb_settings)
if 'service_password' in peerdb_settings: if 'service_password' in peerdb_settings:
relation_set(relation_id=rel_id, **peerdb_settings) relation_set(relation_id=rel_id, **peerdb_settings)
log('Deferring identity_changed() to service leader.') log('Deferring identity_changed() to service leader.')
if notifications: if notifications:
@ -312,12 +340,20 @@ def send_ssl_sync_request():
""" """
unit = local_unit().replace('/', '-') unit = local_unit().replace('/', '-')
count = 0 count = 0
if bool_from_string(config('use-https')):
use_https = config('use-https')
if use_https and bool_from_string(use_https):
count += 1 count += 1
if bool_from_string(config('https-service-endpoints')): https_service_endpoints = config('https-service-endpoints')
if (https_service_endpoints and
bool_from_string(https_service_endpoints)):
count += 2 count += 2
enable_pki = config('enable-pki')
if enable_pki and bool_from_string(enable_pki):
count += 3
key = 'ssl-sync-required-%s' % (unit) key = 'ssl-sync-required-%s' % (unit)
settings = {key: count} settings = {key: count}
@ -385,23 +421,32 @@ def cluster_changed():
check_peer_actions() check_peer_actions()
if is_elected_leader(CLUSTER_RES) or is_ssl_cert_master(): if is_pki_enabled():
units = get_ssl_sync_request_units() initialise_pki()
synced_units = relation_get(attribute='ssl-synced-units',
unit=local_unit())
if synced_units:
synced_units = json.loads(synced_units)
diff = set(units).symmetric_difference(set(synced_units))
if units and (not synced_units or diff): # Figure out if we need to mandate a sync
log("New peers joined and need syncing - %s" % units = get_ssl_sync_request_units()
(', '.join(units)), level=DEBUG) synced_units = relation_get(attribute='ssl-synced-units',
update_all_identity_relation_units_force_sync() unit=local_unit())
else: diff = None
update_all_identity_relation_units() if synced_units:
synced_units = json.loads(synced_units)
diff = set(units).symmetric_difference(set(synced_units))
for rid in relation_ids('identity-admin'): if units and (not synced_units or diff):
admin_relation_changed(rid) log("New peers joined and need syncing - %s" %
(', '.join(units)), level=DEBUG)
update_all_identity_relation_units_force_sync()
else:
update_all_identity_relation_units()
for rid in relation_ids('identity-admin'):
admin_relation_changed(rid)
if not is_elected_leader(CLUSTER_RES) and is_ssl_cert_master():
# Force and sync and trigger a sync master re-election since we are not
# leader anymore.
force_ssl_sync()
else: else:
CONFIGS.write_all() CONFIGS.write_all()

View File

@ -111,15 +111,16 @@ CA_SINGLETON = []
def init_ca(ca_dir, common_name, org_name=ORG_NAME, org_unit_name=ORG_UNIT): def init_ca(ca_dir, common_name, org_name=ORG_NAME, org_unit_name=ORG_UNIT):
print 'Ensuring certificate authority exists at %s.' % ca_dir log('Ensuring certificate authority exists at %s.' % ca_dir, level=DEBUG)
if not os.path.exists(ca_dir): if not os.path.exists(ca_dir):
print 'Initializing new certificate authority at %s' % ca_dir log('Initializing new certificate authority at %s' % ca_dir,
level=DEBUG)
os.mkdir(ca_dir) os.mkdir(ca_dir)
for i in ['certs', 'crl', 'newcerts', 'private']: for i in ['certs', 'crl', 'newcerts', 'private']:
d = os.path.join(ca_dir, i) d = os.path.join(ca_dir, i)
if not os.path.exists(d): if not os.path.exists(d):
print 'Creating %s.' % d log('Creating %s.' % d, level=DEBUG)
os.mkdir(d) os.mkdir(d)
os.chmod(os.path.join(ca_dir, 'private'), 0o710) os.chmod(os.path.join(ca_dir, 'private'), 0o710)
@ -130,9 +131,11 @@ def init_ca(ca_dir, common_name, org_name=ORG_NAME, org_unit_name=ORG_UNIT):
if not os.path.isfile(os.path.join(ca_dir, 'index.txt')): if not os.path.isfile(os.path.join(ca_dir, 'index.txt')):
with open(os.path.join(ca_dir, 'index.txt'), 'wb') as out: with open(os.path.join(ca_dir, 'index.txt'), 'wb') as out:
out.write('') out.write('')
if not os.path.isfile(os.path.join(ca_dir, 'ca.cnf')):
print 'Creating new CA config in %s' % ca_dir conf = os.path.join(ca_dir, 'ca.cnf')
with open(os.path.join(ca_dir, 'ca.cnf'), 'wb') as out: if not os.path.isfile(conf):
log('Creating new CA config in %s' % ca_dir, level=DEBUG)
with open(conf, 'wb') as out:
out.write(CA_CONFIG % locals()) out.write(CA_CONFIG % locals())
@ -142,40 +145,42 @@ def root_ca_crt_key(ca_dir):
key = os.path.join(ca_dir, 'private', 'cacert.key') key = os.path.join(ca_dir, 'private', 'cacert.key')
for f in [crt, key]: for f in [crt, key]:
if not os.path.isfile(f): if not os.path.isfile(f):
print 'Missing %s, will re-initialize cert+key.' % f log('Missing %s, will re-initialize cert+key.' % f, level=DEBUG)
init = True init = True
else: else:
print 'Found %s.' % f log('Found %s.' % f, level=DEBUG)
if init: if init:
cmd = ['openssl', 'req', '-config', os.path.join(ca_dir, 'ca.cnf'), conf = os.path.join(ca_dir, 'ca.cnf')
cmd = ['openssl', 'req', '-config', conf,
'-x509', '-nodes', '-newkey', 'rsa', '-days', '21360', '-x509', '-nodes', '-newkey', 'rsa', '-days', '21360',
'-keyout', key, '-out', crt, '-outform', 'PEM'] '-keyout', key, '-out', crt, '-outform', 'PEM']
subprocess.check_call(cmd) subprocess.check_call(cmd)
return crt, key return crt, key
def intermediate_ca_csr_key(ca_dir): def intermediate_ca_csr_key(ca_dir):
print 'Creating new intermediate CSR.' log('Creating new intermediate CSR.', level=DEBUG)
key = os.path.join(ca_dir, 'private', 'cacert.key') key = os.path.join(ca_dir, 'private', 'cacert.key')
csr = os.path.join(ca_dir, 'cacert.csr') csr = os.path.join(ca_dir, 'cacert.csr')
cmd = ['openssl', 'req', '-config', os.path.join(ca_dir, 'ca.cnf'), conf = os.path.join(ca_dir, 'ca.cnf')
'-sha1', '-newkey', 'rsa', '-nodes', '-keyout', key, '-out', cmd = ['openssl', 'req', '-config', conf, '-sha1', '-newkey', 'rsa',
csr, '-outform', '-nodes', '-keyout', key, '-out', csr, '-outform', 'PEM']
'PEM']
subprocess.check_call(cmd) subprocess.check_call(cmd)
return csr, key return csr, key
def sign_int_csr(ca_dir, csr, common_name): def sign_int_csr(ca_dir, csr, common_name):
print 'Signing certificate request %s.' % csr log('Signing certificate request %s.' % csr, level=DEBUG)
crt = os.path.join(ca_dir, 'certs', crt_name = os.path.basename(csr).split('.')[0]
'%s.crt' % os.path.basename(csr).split('.')[0]) crt = os.path.join(ca_dir, 'certs', '%s.crt' % crt_name)
subj = '/O=%s/OU=%s/CN=%s' % (ORG_NAME, ORG_UNIT, common_name) subj = '/O=%s/OU=%s/CN=%s' % (ORG_NAME, ORG_UNIT, common_name)
cmd = ['openssl', 'ca', '-batch', '-config', conf = os.path.join(ca_dir, 'ca.cnf')
os.path.join(ca_dir, 'ca.cnf'), cmd = ['openssl', 'ca', '-batch', '-config', conf, '-extensions',
'-extensions', 'ca_extensions', '-days', CA_EXPIRY, '-notext', 'ca_extensions', '-days', CA_EXPIRY, '-notext', '-in', csr, '-out',
'-in', csr, '-out', crt, '-subj', subj, '-batch'] crt, '-subj', subj, '-batch']
print ' '.join(cmd) log("Executing: %s" % ' '.join(cmd), level=DEBUG)
subprocess.check_call(cmd) subprocess.check_call(cmd)
return crt return crt
@ -185,19 +190,20 @@ def init_root_ca(ca_dir, common_name):
return root_ca_crt_key(ca_dir) return root_ca_crt_key(ca_dir)
def init_intermediate_ca(ca_dir, common_name, root_ca_dir, def init_intermediate_ca(ca_dir, common_name, root_ca_dir, org_name=ORG_NAME,
org_name=ORG_NAME, org_unit_name=ORG_UNIT): org_unit_name=ORG_UNIT):
init_ca(ca_dir, common_name) init_ca(ca_dir, common_name)
if not os.path.isfile(os.path.join(ca_dir, 'cacert.pem')): if not os.path.isfile(os.path.join(ca_dir, 'cacert.pem')):
csr, key = intermediate_ca_csr_key(ca_dir) csr, key = intermediate_ca_csr_key(ca_dir)
crt = sign_int_csr(root_ca_dir, csr, common_name) crt = sign_int_csr(root_ca_dir, csr, common_name)
shutil.copy(crt, os.path.join(ca_dir, 'cacert.pem')) shutil.copy(crt, os.path.join(ca_dir, 'cacert.pem'))
else: else:
print 'Intermediate CA certificate already exists.' log('Intermediate CA certificate already exists.', level=DEBUG)
if not os.path.isfile(os.path.join(ca_dir, 'signing.cnf')): conf = os.path.join(ca_dir, 'signing.cnf')
print 'Creating new signing config in %s' % ca_dir if not os.path.isfile(conf):
with open(os.path.join(ca_dir, 'signing.cnf'), 'wb') as out: log('Creating new signing config in %s' % ca_dir, level=DEBUG)
with open(conf, 'wb') as out:
out.write(SIGNING_CONFIG % locals()) out.write(SIGNING_CONFIG % locals())
@ -210,7 +216,7 @@ def create_certificate(ca_dir, service):
key, '-out', csr, '-subj', subj] key, '-out', csr, '-subj', subj]
subprocess.check_call(cmd) subprocess.check_call(cmd)
crt = sign_int_csr(ca_dir, csr, common_name) crt = sign_int_csr(ca_dir, csr, common_name)
print 'Signed new CSR, crt @ %s' % crt log('Signed new CSR, crt @ %s' % crt, level=DEBUG)
return return
@ -219,13 +225,14 @@ def update_bundle(bundle_file, new_bundle):
if os.path.isfile(bundle_file): if os.path.isfile(bundle_file):
current = open(bundle_file, 'r').read().strip() current = open(bundle_file, 'r').read().strip()
if new_bundle == current: if new_bundle == current:
print 'CA Bundle @ %s is up to date.' % bundle_file log('CA Bundle @ %s is up to date.' % bundle_file, level=DEBUG)
return return
else:
print 'Updating CA bundle @ %s.' % bundle_file log('Updating CA bundle @ %s.' % bundle_file, level=DEBUG)
with open(bundle_file, 'wb') as out: with open(bundle_file, 'wb') as out:
out.write(new_bundle) out.write(new_bundle)
subprocess.check_call(['update-ca-certificates']) subprocess.check_call(['update-ca-certificates'])
@ -248,15 +255,19 @@ def tar_directory(path):
class JujuCA(object): class JujuCA(object):
def __init__(self, name, ca_dir, root_ca_dir, user, group): def __init__(self, name, ca_dir, root_ca_dir, user, group):
root_crt, root_key = init_root_ca(root_ca_dir, # Root CA
'%s Certificate Authority' % name) cn = '%s Certificate Authority' % name
init_intermediate_ca(ca_dir, root_crt, root_key = init_root_ca(root_ca_dir, cn)
'%s Intermediate Certificate Authority' % name, # Intermediate CA
root_ca_dir) cn = '%s Intermediate Certificate Authority' % name
init_intermediate_ca(ca_dir, cn, root_ca_dir)
# Create dirs
cmd = ['chown', '-R', '%s.%s' % (user, group), ca_dir] cmd = ['chown', '-R', '%s.%s' % (user, group), ca_dir]
subprocess.check_call(cmd) subprocess.check_call(cmd)
cmd = ['chown', '-R', '%s.%s' % (user, group), root_ca_dir] cmd = ['chown', '-R', '%s.%s' % (user, group), root_ca_dir]
subprocess.check_call(cmd) subprocess.check_call(cmd)
self.ca_dir = ca_dir self.ca_dir = ca_dir
self.root_ca_dir = root_ca_dir self.root_ca_dir = root_ca_dir
self.user = user self.user = user
@ -266,8 +277,8 @@ class JujuCA(object):
def _sign_csr(self, csr, service, common_name): def _sign_csr(self, csr, service, common_name):
subj = '/O=%s/OU=%s/CN=%s' % (ORG_NAME, ORG_UNIT, common_name) subj = '/O=%s/OU=%s/CN=%s' % (ORG_NAME, ORG_UNIT, common_name)
crt = os.path.join(self.ca_dir, 'certs', '%s.crt' % common_name) crt = os.path.join(self.ca_dir, 'certs', '%s.crt' % common_name)
cmd = ['openssl', 'ca', '-config', conf = os.path.join(self.ca_dir, 'signing.cnf')
os.path.join(self.ca_dir, 'signing.cnf'), '-extensions', cmd = ['openssl', 'ca', '-config', conf, '-extensions',
'req_extensions', '-days', '365', '-notext', '-in', csr, 'req_extensions', '-days', '365', '-notext', '-in', csr,
'-out', crt, '-batch', '-subj', subj] '-out', crt, '-batch', '-subj', subj]
subprocess.check_call(cmd) subprocess.check_call(cmd)
@ -286,10 +297,16 @@ class JujuCA(object):
log('Signed new CSR, crt @ %s' % crt, level=DEBUG) log('Signed new CSR, crt @ %s' % crt, level=DEBUG)
return crt, key return crt, key
def get_key_path(self, cn):
return os.path.join(self.ca_dir, 'certs', '%s.key' % cn)
def get_cert_path(self, cn):
return os.path.join(self.ca_dir, 'certs', '%s.crt' % cn)
def get_cert_and_key(self, common_name): def get_cert_and_key(self, common_name):
log('Getting certificate and key for %s.' % common_name, level=DEBUG) log('Getting certificate and key for %s.' % common_name, level=DEBUG)
keypath = os.path.join(self.ca_dir, 'certs', '%s.key' % common_name) keypath = self.get_key_path(common_name)
crtpath = os.path.join(self.ca_dir, 'certs', '%s.crt' % common_name) crtpath = self.get_cert_path(common_name)
if os.path.isfile(crtpath): if os.path.isfile(crtpath):
log('Found existing certificate for %s.' % common_name, log('Found existing certificate for %s.' % common_name,
level=DEBUG) level=DEBUG)
@ -300,8 +317,24 @@ class JujuCA(object):
crt, key = self._create_certificate(common_name, common_name) crt, key = self._create_certificate(common_name, common_name)
return open(crt, 'r').read(), open(key, 'r').read() return open(crt, 'r').read(), open(key, 'r').read()
@property
def ca_cert_path(self):
return os.path.join(self.ca_dir, 'cacert.pem')
@property
def ca_key_path(self):
return os.path.join(self.ca_dir, 'private', 'cacert.key')
@property
def root_ca_cert_path(self):
return os.path.join(self.root_ca_dir, 'cacert.pem')
@property
def root_ca_key_path(self):
return os.path.join(self.root_ca_dir, 'private', 'cacert.key')
def get_ca_bundle(self): def get_ca_bundle(self):
int_cert = open(os.path.join(self.ca_dir, 'cacert.pem')).read() int_cert = open(self.ca_cert_path).read()
root_cert = open(os.path.join(self.root_ca_dir, 'cacert.pem')).read() root_cert = open(self.root_ca_cert_path).read()
# NOTE: ordering of certs in bundle matters! # NOTE: ordering of certs in bundle matters!
return int_cert + root_cert return int_cert + root_cert

View File

@ -140,10 +140,13 @@ SYNC_FLAGS_DIR = '/var/lib/keystone/juju_sync_flags/'
SYNC_DIR = '/var/lib/keystone/juju_sync/' SYNC_DIR = '/var/lib/keystone/juju_sync/'
SSL_SYNC_ARCHIVE = os.path.join(SYNC_DIR, 'juju-ssl-sync.tar') SSL_SYNC_ARCHIVE = os.path.join(SYNC_DIR, 'juju-ssl-sync.tar')
SSL_DIR = '/var/lib/keystone/juju_ssl/' SSL_DIR = '/var/lib/keystone/juju_ssl/'
PKI_CERTS_DIR = os.path.join(SSL_DIR, 'pki')
SSL_CA_NAME = 'Ubuntu Cloud' SSL_CA_NAME = 'Ubuntu Cloud'
CLUSTER_RES = 'grp_ks_vips' CLUSTER_RES = 'grp_ks_vips'
SSH_USER = 'juju_keystone' SSH_USER = 'juju_keystone'
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
SSL_SYNC_SEMAPHORE = threading.Semaphore() SSL_SYNC_SEMAPHORE = threading.Semaphore()
SSL_DIRS = [SSL_DIR, APACHE_SSL_DIR, CA_CERT_PATH]
BASE_RESOURCE_MAP = OrderedDict([ BASE_RESOURCE_MAP = OrderedDict([
(KEYSTONE_CONF, { (KEYSTONE_CONF, {
@ -175,8 +178,6 @@ BASE_RESOURCE_MAP = OrderedDict([
}), }),
]) ])
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
valid_services = { valid_services = {
"nova": { "nova": {
"type": "compute", "type": "compute",
@ -784,6 +785,9 @@ def check_peer_actions():
elif action == 'update-ca-certificates': elif action == 'update-ca-certificates':
log("Running %s" % (action), level=DEBUG) log("Running %s" % (action), level=DEBUG)
subprocess.check_call(['update-ca-certificates']) subprocess.check_call(['update-ca-certificates'])
elif action == 'ensure-pki-permissions':
log("Running %s" % (action), level=DEBUG)
ensure_pki_dir_permissions()
else: else:
log("Unknown action flag=%s" % (flag), level=WARNING) log("Unknown action flag=%s" % (flag), level=WARNING)
@ -881,8 +885,12 @@ def is_ssl_cert_master(votes=None):
def is_ssl_enabled(): def is_ssl_enabled():
if (bool_from_string(config('use-https')) or use_https = config('use-https')
bool_from_string(config('https-service-endpoints'))): https_service_endpoints = config('https-service-endpoints')
if ((use_https and bool_from_string(use_https)) or
(https_service_endpoints and
bool_from_string(https_service_endpoints)) or
is_pki_enabled()):
log("SSL/HTTPS is enabled", level=DEBUG) log("SSL/HTTPS is enabled", level=DEBUG)
return True return True
@ -960,6 +968,20 @@ def stage_paths_for_sync(paths):
perms=0o755, recurse=True) perms=0o755, recurse=True)
def is_pki_enabled():
enable_pki = config('enable-pki')
if enable_pki and bool_from_string(enable_pki):
return True
return False
def ensure_pki_dir_permissions():
# Ensure accessible by unison user and group (for sync).
ensure_permissions(PKI_CERTS_DIR, user=SSH_USER, group='keystone',
perms=0o755, recurse=True)
def update_certs_if_available(f): def update_certs_if_available(f):
def _inner_update_certs_if_available(*args, **kwargs): def _inner_update_certs_if_available(*args, **kwargs):
path = None path = None
@ -999,12 +1021,18 @@ def synchronize_ca(fatal=False):
Returns a dictionary of settings to be set on the cluster relation. Returns a dictionary of settings to be set on the cluster relation.
""" """
paths_to_sync = [] paths_to_sync = []
peer_service_actions = []
peer_actions = []
if bool_from_string(config('https-service-endpoints')): if bool_from_string(config('https-service-endpoints')):
log("Syncing all endpoint certs since https-service-endpoints=True", log("Syncing all endpoint certs since https-service-endpoints=True",
level=DEBUG) level=DEBUG)
paths_to_sync.append(SSL_DIR) paths_to_sync.append(SSL_DIR)
paths_to_sync.append(CA_CERT_PATH) paths_to_sync.append(CA_CERT_PATH)
# We need to restart peer apache services to ensure they have picked up
# new ssl keys.
peer_service_actions.append(('restart', ('apache2')))
peer_actions.append('update-ca-certificates')
if bool_from_string(config('use-https')): if bool_from_string(config('use-https')):
log("Syncing keystone-endpoint certs since use-https=True", log("Syncing keystone-endpoint certs since use-https=True",
@ -1012,6 +1040,15 @@ def synchronize_ca(fatal=False):
paths_to_sync.append(SSL_DIR) paths_to_sync.append(SSL_DIR)
paths_to_sync.append(APACHE_SSL_DIR) paths_to_sync.append(APACHE_SSL_DIR)
paths_to_sync.append(CA_CERT_PATH) paths_to_sync.append(CA_CERT_PATH)
# We need to restart peer apache services to ensure they have picked up
# new ssl keys.
peer_service_actions.append(('restart', ('apache2')))
peer_actions.append('update-ca-certificates')
if is_pki_enabled():
log("Syncing token certs", level=DEBUG)
paths_to_sync.append(PKI_CERTS_DIR)
peer_actions.append('ensure-pki-permissions')
if not paths_to_sync: if not paths_to_sync:
log("Nothing to sync - skipping", level=DEBUG) log("Nothing to sync - skipping", level=DEBUG)
@ -1020,10 +1057,10 @@ def synchronize_ca(fatal=False):
if not os.path.isdir(SYNC_FLAGS_DIR): if not os.path.isdir(SYNC_FLAGS_DIR):
mkdir(SYNC_FLAGS_DIR, SSH_USER, 'keystone', 0o775) mkdir(SYNC_FLAGS_DIR, SSH_USER, 'keystone', 0o775)
# We need to restart peer apache services to ensure they have picked up for action, services in set(peer_service_actions):
# new ssl keys. create_peer_service_actions(action, services)
create_peer_service_actions('restart', ['apache2'])
create_peer_actions(['update-ca-certificates']) create_peer_actions(peer_actions)
paths_to_sync = list(set(paths_to_sync)) paths_to_sync = list(set(paths_to_sync))
stage_paths_for_sync(paths_to_sync) stage_paths_for_sync(paths_to_sync)
@ -1097,21 +1134,19 @@ def synchronize_ca_if_changed(force=False, fatal=False):
return f(*args, **kwargs) return f(*args, **kwargs)
if not ensure_ssl_cert_master(): if not ensure_ssl_cert_master():
log("Not leader - ignoring sync", level=DEBUG) log("Not ssl-cert-master - ignoring sync", level=DEBUG)
return f(*args, **kwargs) return f(*args, **kwargs)
peer_settings = {} peer_settings = {}
if not force: if not force:
ssl_dirs = [SSL_DIR, APACHE_SSL_DIR, CA_CERT_PATH]
hash1 = hashlib.sha256() hash1 = hashlib.sha256()
for path in ssl_dirs: for path in SSL_DIRS:
update_hash_from_path(hash1, path) update_hash_from_path(hash1, path)
ret = f(*args, **kwargs) ret = f(*args, **kwargs)
hash2 = hashlib.sha256() hash2 = hashlib.sha256()
for path in ssl_dirs: for path in SSL_DIRS:
update_hash_from_path(hash2, path) update_hash_from_path(hash2, path)
if hash1.hexdigest() != hash2.hexdigest(): if hash1.hexdigest() != hash2.hexdigest():
@ -1146,15 +1181,33 @@ def synchronize_ca_if_changed(force=False, fatal=False):
return inner_synchronize_ca_if_changed1 return inner_synchronize_ca_if_changed1
@synchronize_ca_if_changed(force=True, fatal=True)
def force_ssl_sync():
"""Force SSL sync to all peers.
This is useful if we need to relinquish ssl-cert-master status while
making sure that the new master has up-to-date certs.
"""
return
def ensure_ssl_dir():
"""Ensure juju ssl dir exists and is unsion read/writable."""
perms = 0o755
if not os.path.isdir(SSL_DIR):
mkdir(SSL_DIR, SSH_USER, 'keystone', perms)
else:
ensure_permissions(SSL_DIR, user=SSH_USER, group='keystone',
perms=perms)
def get_ca(user='keystone', group='keystone'): def get_ca(user='keystone', group='keystone'):
"""Initialize a new CA object if one hasn't already been loaded. """Initialize a new CA object if one hasn't already been loaded.
This will create a new CA or load an existing one. This will create a new CA or load an existing one.
""" """
if not ssl.CA_SINGLETON: if not ssl.CA_SINGLETON:
if not os.path.isdir(SSL_DIR): ensure_ssl_dir()
os.mkdir(SSL_DIR)
d_name = '_'.join(SSL_CA_NAME.lower().split(' ')) d_name = '_'.join(SSL_CA_NAME.lower().split(' '))
ca = ssl.JujuCA(name=SSL_CA_NAME, user=user, group=group, ca = ssl.JujuCA(name=SSL_CA_NAME, user=user, group=group,
ca_dir=os.path.join(SSL_DIR, ca_dir=os.path.join(SSL_DIR,
@ -1162,12 +1215,6 @@ def get_ca(user='keystone', group='keystone'):
root_ca_dir=os.path.join(SSL_DIR, root_ca_dir=os.path.join(SSL_DIR,
'%s_root_ca' % d_name)) '%s_root_ca' % d_name))
# SSL_DIR is synchronized via all peers over unison+ssh, need
# to ensure permissions.
subprocess.check_output(['chown', '-R', '%s.%s' % (user, group),
'%s' % SSL_DIR])
subprocess.check_output(['chmod', '-R', 'g+rwx', '%s' % SSL_DIR])
# Ensure a master is elected. This should cover the following cases: # Ensure a master is elected. This should cover the following cases:
# * single unit == 'oldest' unit is elected as master # * single unit == 'oldest' unit is elected as master
# * multi unit + not clustered == 'oldest' unit is elcted as master # * multi unit + not clustered == 'oldest' unit is elcted as master
@ -1212,9 +1259,13 @@ def add_service_to_keystone(relation_id=None, remote_unit=None):
# Some backend services advertise no endpoint but require a # Some backend services advertise no endpoint but require a
# hook execution to update auth strategy. # hook execution to update auth strategy.
relation_data = {} relation_data = {}
rel_only_data = {}
# Check if clustered and use vip + haproxy ports if so # Check if clustered and use vip + haproxy ports if so
relation_data["auth_host"] = resolve_address(ADMIN) # NOTE(hopem): don't put these on peer relation because racey
relation_data["service_host"] = resolve_address(PUBLIC) # leader election causes cluster relation to spin)
rel_only_data["auth_host"] = resolve_address(ADMIN)
rel_only_data["service_host"] = resolve_address(PUBLIC)
relation_data["auth_protocol"] = protocol relation_data["auth_protocol"] = protocol
relation_data["service_protocol"] = protocol relation_data["service_protocol"] = protocol
relation_data["auth_port"] = config('admin-port') relation_data["auth_port"] = config('admin-port')
@ -1237,8 +1288,8 @@ def add_service_to_keystone(relation_id=None, remote_unit=None):
log("Creating requested role: %s" % role) log("Creating requested role: %s" % role)
create_role(role) create_role(role)
peer_store_and_set(relation_id=relation_id, relation_set(relation_id=relation_id, **rel_only_data)
**relation_data) peer_store_and_set(relation_id=relation_id, **relation_data)
return return
else: else:
ensure_valid_service(settings['service']) ensure_valid_service(settings['service'])
@ -1342,13 +1393,16 @@ def add_service_to_keystone(relation_id=None, remote_unit=None):
# service credentials # service credentials
service_tenant = config('service-tenant') service_tenant = config('service-tenant')
# NOTE(hopem): don't put these on peer relation because racey
# leader election causes cluster relation to spin)
rel_only_data = {"auth_host": resolve_address(ADMIN),
"service_host": resolve_address(PUBLIC)}
# NOTE(dosaboy): we use __null__ to represent settings that are to be # NOTE(dosaboy): we use __null__ to represent settings that are to be
# routed to relations via the cluster relation and set to None. # routed to relations via the cluster relation and set to None.
relation_data = { relation_data = {
"admin_token": token, "admin_token": token,
"service_host": resolve_address(PUBLIC),
"service_port": config("service-port"), "service_port": config("service-port"),
"auth_host": resolve_address(ADMIN),
"auth_port": config("admin-port"), "auth_port": config("admin-port"),
"service_username": service_username, "service_username": service_username,
"service_password": service_password, "service_password": service_password,
@ -1381,6 +1435,7 @@ def add_service_to_keystone(relation_id=None, remote_unit=None):
relation_data['ca_cert'] = b64encode(ca_bundle) relation_data['ca_cert'] = b64encode(ca_bundle)
relation_data['https_keystone'] = 'True' relation_data['https_keystone'] = 'True'
relation_set(relation_id=relation_id, **rel_only_data)
# NOTE(dosaboy): '__null__' settings are for peer relation only so that # NOTE(dosaboy): '__null__' settings are for peer relation only so that
# settings can flushed so we filter them out for non-peer relation. # settings can flushed so we filter them out for non-peer relation.
filtered = filter_null(relation_data) filtered = filter_null(relation_data)

View File

@ -43,7 +43,15 @@ driver = keystone.catalog.backends.sql.Catalog
[token] [token]
driver = keystone.token.backends.sql.Token driver = keystone.token.backends.sql.Token
provider = keystone.token.providers.uuid.Provider {% if token_provider == 'pki' -%}
provider = keystone.token.providers.pki.Provider
{% elif token_provider == 'pkiz' -%}
provider = keystone.token.providers.pkiz.Provider
{% else -%}
provider = keystone.token.providers.uuid.Provider
{% endif %}
{% include "parts/section-signing" %}
[cache] [cache]
@ -58,8 +66,6 @@ driver = keystone.assignment.backends.{{ assignment_backend }}.Assignment
[oauth1] [oauth1]
[signing]
[auth] [auth]
methods = external,password,token,oauth1 methods = external,password,token,oauth1
password = keystone.auth.plugins.password.Password password = keystone.auth.plugins.password.Password

View File

@ -0,0 +1,13 @@
[signing]
{% if certfile -%}
certfile = {{ certfile }}
{% endif -%}
{% if keyfile -%}
keyfile = {{ keyfile }}
{% endif -%}
{% if ca_certs -%}
ca_certs = {{ ca_certs }}
{% endif -%}
{% if ca_key -%}
ca_key = {{ ca_key }}
{% endif -%}

View File

@ -59,6 +59,8 @@ TO_PATCH = [
'synchronize_ca_if_changed', 'synchronize_ca_if_changed',
'update_nrpe_config', 'update_nrpe_config',
'ensure_ssl_dirs', 'ensure_ssl_dirs',
'is_db_initialised',
'is_db_ready',
# other # other
'check_call', 'check_call',
'execd_preinstall', 'execd_preinstall',
@ -203,18 +205,15 @@ class KeystoneRelationTests(CharmTestCase):
configs.write = MagicMock() configs.write = MagicMock()
hooks.pgsql_db_changed() hooks.pgsql_db_changed()
@patch.object(hooks, 'is_db_initialised')
@patch.object(hooks, 'is_db_ready')
@patch('keystone_utils.log') @patch('keystone_utils.log')
@patch('keystone_utils.ensure_ssl_cert_master') @patch('keystone_utils.ensure_ssl_cert_master')
@patch.object(hooks, 'CONFIGS') @patch.object(hooks, 'CONFIGS')
@patch.object(hooks, 'identity_changed') @patch.object(hooks, 'identity_changed')
def test_db_changed_allowed(self, identity_changed, configs, def test_db_changed_allowed(self, identity_changed, configs,
mock_ensure_ssl_cert_master, mock_ensure_ssl_cert_master,
mock_log, mock_is_db_ready, mock_log):
mock_is_db_initialised): self.is_db_initialised.return_value = True
mock_is_db_initialised.return_value = True self.is_db_ready.return_value = True
mock_is_db_ready.return_value = True
mock_ensure_ssl_cert_master.return_value = False mock_ensure_ssl_cert_master.return_value = False
self.relation_ids.return_value = ['identity-service:0'] self.relation_ids.return_value = ['identity-service:0']
self.related_units.return_value = ['unit/0'] self.related_units.return_value = ['unit/0']
@ -228,15 +227,13 @@ class KeystoneRelationTests(CharmTestCase):
relation_id='identity-service:0', relation_id='identity-service:0',
remote_unit='unit/0') remote_unit='unit/0')
@patch.object(hooks, 'is_db_ready')
@patch('keystone_utils.log') @patch('keystone_utils.log')
@patch('keystone_utils.ensure_ssl_cert_master') @patch('keystone_utils.ensure_ssl_cert_master')
@patch.object(hooks, 'CONFIGS') @patch.object(hooks, 'CONFIGS')
@patch.object(hooks, 'identity_changed') @patch.object(hooks, 'identity_changed')
def test_db_changed_not_allowed(self, identity_changed, configs, def test_db_changed_not_allowed(self, identity_changed, configs,
mock_ensure_ssl_cert_master, mock_log, mock_ensure_ssl_cert_master, mock_log):
mock_is_db_ready): self.is_db_ready.return_value = False
mock_is_db_ready.return_value = False
mock_ensure_ssl_cert_master.return_value = False mock_ensure_ssl_cert_master.return_value = False
self.relation_ids.return_value = ['identity-service:0'] self.relation_ids.return_value = ['identity-service:0']
self.related_units.return_value = ['unit/0'] self.related_units.return_value = ['unit/0']
@ -250,15 +247,12 @@ class KeystoneRelationTests(CharmTestCase):
@patch('keystone_utils.log') @patch('keystone_utils.log')
@patch('keystone_utils.ensure_ssl_cert_master') @patch('keystone_utils.ensure_ssl_cert_master')
@patch.object(hooks, 'is_db_initialised')
@patch.object(hooks, 'is_db_ready')
@patch.object(hooks, 'CONFIGS') @patch.object(hooks, 'CONFIGS')
@patch.object(hooks, 'identity_changed') @patch.object(hooks, 'identity_changed')
def test_postgresql_db_changed(self, identity_changed, configs, def test_postgresql_db_changed(self, identity_changed, configs,
mock_is_db_ready, mock_is_db_initialised,
mock_ensure_ssl_cert_master, mock_log): mock_ensure_ssl_cert_master, mock_log):
mock_is_db_initialised.return_value = True self.is_db_initialised.return_value = True
mock_is_db_ready.return_value = True self.is_db_ready.return_value = True
mock_ensure_ssl_cert_master.return_value = False mock_ensure_ssl_cert_master.return_value = False
self.relation_ids.return_value = ['identity-service:0'] self.relation_ids.return_value = ['identity-service:0']
self.related_units.return_value = ['unit/0'] self.related_units.return_value = ['unit/0']
@ -274,11 +268,13 @@ class KeystoneRelationTests(CharmTestCase):
@patch('keystone_utils.log') @patch('keystone_utils.log')
@patch('keystone_utils.ensure_ssl_cert_master') @patch('keystone_utils.ensure_ssl_cert_master')
@patch.object(hooks, 'send_ssl_sync_request')
@patch.object(hooks, 'is_db_initialised')
@patch.object(hooks, 'is_db_ready')
@patch.object(hooks, 'peer_units')
@patch('keystone_utils.ensure_ssl_dirs') @patch('keystone_utils.ensure_ssl_dirs')
@patch.object(hooks, 'ensure_pki_dir_permissions')
@patch.object(hooks, 'ensure_ssl_dir')
@patch.object(hooks, 'is_pki_enabled')
@patch.object(hooks, 'is_ssl_cert_master')
@patch.object(hooks, 'send_ssl_sync_request')
@patch.object(hooks, 'peer_units')
@patch.object(hooks, 'admin_relation_changed') @patch.object(hooks, 'admin_relation_changed')
@patch.object(hooks, 'cluster_joined') @patch.object(hooks, 'cluster_joined')
@patch.object(unison, 'ensure_user') @patch.object(unison, 'ensure_user')
@ -286,15 +282,25 @@ class KeystoneRelationTests(CharmTestCase):
@patch.object(hooks, 'CONFIGS') @patch.object(hooks, 'CONFIGS')
@patch.object(hooks, 'identity_changed') @patch.object(hooks, 'identity_changed')
@patch.object(hooks, 'configure_https') @patch.object(hooks, 'configure_https')
def test_config_changed_no_openstack_upgrade_leader( def test_config_changed_no_upgrade_leader(self, configure_https,
self, configure_https, identity_changed, identity_changed,
configs, get_homedir, ensure_user, cluster_joined, configs, get_homedir,
admin_relation_changed, ensure_ssl_dirs, mock_peer_units, ensure_user,
mock_is_db_ready, mock_is_db_initialised, cluster_joined,
mock_send_ssl_sync_request, admin_relation_changed,
mock_ensure_ssl_cert_master, mock_log): mock_peer_units,
mock_is_db_initialised.return_value = True mock_send_ssl_sync_request,
mock_is_db_ready.return_value = True mock_is_ssl_cert_master,
mock_is_pki_enabled,
mock_ensure_ssl_dir,
mock_ensure_pki_dir_permissions,
mock_ensure_ssl_dirs,
mock_ensure_ssl_cert_master,
mock_log):
mock_is_pki_enabled.return_value = True
mock_is_ssl_cert_master.return_value = True
self.is_db_initialised.return_value = True
self.is_db_ready.return_value = True
self.openstack_upgrade_available.return_value = False self.openstack_upgrade_available.return_value = False
self.is_elected_leader.return_value = True self.is_elected_leader.return_value = True
# avoid having to mock syncer # avoid having to mock syncer
@ -322,17 +328,34 @@ class KeystoneRelationTests(CharmTestCase):
@patch('keystone_utils.log') @patch('keystone_utils.log')
@patch('keystone_utils.ensure_ssl_cert_master') @patch('keystone_utils.ensure_ssl_cert_master')
@patch('keystone_utils.ensure_ssl_dirs') @patch('keystone_utils.ensure_ssl_dirs')
@patch.object(hooks, 'update_all_identity_relation_units')
@patch.object(hooks, 'ensure_pki_dir_permissions')
@patch.object(hooks, 'ensure_ssl_dir')
@patch.object(hooks, 'is_pki_enabled')
@patch.object(hooks, 'peer_units')
@patch.object(hooks, 'is_ssl_cert_master')
@patch.object(hooks, 'cluster_joined') @patch.object(hooks, 'cluster_joined')
@patch.object(unison, 'ensure_user') @patch.object(unison, 'ensure_user')
@patch.object(unison, 'get_homedir') @patch.object(unison, 'get_homedir')
@patch.object(hooks, 'CONFIGS') @patch.object(hooks, 'CONFIGS')
@patch.object(hooks, 'identity_changed') @patch.object(hooks, 'identity_changed')
@patch.object(hooks, 'configure_https') @patch.object(hooks, 'configure_https')
def test_config_changed_no_openstack_upgrade_not_leader( def test_config_changed_no_upgrade_not_leader(self, configure_https,
self, configure_https, identity_changed, identity_changed,
configs, get_homedir, ensure_user, cluster_joined, configs, get_homedir,
ensure_ssl_dirs, mock_ensure_ssl_cert_master, ensure_user, cluster_joined,
mock_log): mock_is_ssl_cert_master,
mock_peer_units,
mock_is_pki_enabled,
mock_ensure_ssl_dir,
mock_ensure_pki_permissions,
mock_update_all_id_rel_units,
ensure_ssl_dirs,
mock_ensure_ssl_cert_master,
mock_log):
mock_is_pki_enabled.return_value = True
mock_is_ssl_cert_master.return_value = True
mock_peer_units.return_value = []
self.openstack_upgrade_available.return_value = False self.openstack_upgrade_available.return_value = False
self.is_elected_leader.return_value = False self.is_elected_leader.return_value = False
mock_ensure_ssl_cert_master.return_value = False mock_ensure_ssl_cert_master.return_value = False
@ -351,11 +374,13 @@ class KeystoneRelationTests(CharmTestCase):
@patch('keystone_utils.log') @patch('keystone_utils.log')
@patch('keystone_utils.ensure_ssl_cert_master') @patch('keystone_utils.ensure_ssl_cert_master')
@patch.object(hooks, 'send_ssl_sync_request')
@patch.object(hooks, 'is_db_initialised')
@patch.object(hooks, 'is_db_ready')
@patch.object(hooks, 'peer_units')
@patch('keystone_utils.ensure_ssl_dirs') @patch('keystone_utils.ensure_ssl_dirs')
@patch.object(hooks, 'ensure_pki_dir_permissions')
@patch.object(hooks, 'ensure_ssl_dir')
@patch.object(hooks, 'is_pki_enabled')
@patch.object(hooks, 'is_ssl_cert_master')
@patch.object(hooks, 'send_ssl_sync_request')
@patch.object(hooks, 'peer_units')
@patch.object(hooks, 'admin_relation_changed') @patch.object(hooks, 'admin_relation_changed')
@patch.object(hooks, 'cluster_joined') @patch.object(hooks, 'cluster_joined')
@patch.object(unison, 'ensure_user') @patch.object(unison, 'ensure_user')
@ -368,15 +393,19 @@ class KeystoneRelationTests(CharmTestCase):
configs, get_homedir, configs, get_homedir,
ensure_user, cluster_joined, ensure_user, cluster_joined,
admin_relation_changed, admin_relation_changed,
ensure_ssl_dirs,
mock_peer_units, mock_peer_units,
mock_is_db_ready,
mock_is_db_initialised,
mock_send_ssl_sync_request, mock_send_ssl_sync_request,
mock_is_ssl_cert_master,
mock_is_pki_enabled,
mock_ensure_ssl_dir,
mock_ensure_pki_permissions,
mock_ensure_ssl_dirs,
mock_ensure_ssl_cert_master, mock_ensure_ssl_cert_master,
mock_log): mock_log):
mock_is_db_ready.return_value = True mock_is_pki_enabled.return_value = True
mock_is_db_initialised.return_value = True mock_is_ssl_cert_master.return_value = True
self.is_db_ready.return_value = True
self.is_db_initialised.return_value = True
self.openstack_upgrade_available.return_value = True self.openstack_upgrade_available.return_value = True
self.is_elected_leader.return_value = True self.is_elected_leader.return_value = True
# avoid having to mock syncer # avoid having to mock syncer
@ -403,18 +432,15 @@ class KeystoneRelationTests(CharmTestCase):
remote_unit='unit/0') remote_unit='unit/0')
admin_relation_changed.assert_called_with('identity-service:0') admin_relation_changed.assert_called_with('identity-service:0')
@patch.object(hooks, 'is_db_initialised')
@patch.object(hooks, 'is_db_ready')
@patch('keystone_utils.log') @patch('keystone_utils.log')
@patch('keystone_utils.ensure_ssl_cert_master') @patch('keystone_utils.ensure_ssl_cert_master')
@patch.object(hooks, 'hashlib') @patch.object(hooks, 'hashlib')
@patch.object(hooks, 'send_notifications') @patch.object(hooks, 'send_notifications')
def test_identity_changed_leader(self, mock_send_notifications, def test_identity_changed_leader(self, mock_send_notifications,
mock_hashlib, mock_ensure_ssl_cert_master, mock_hashlib, mock_ensure_ssl_cert_master,
mock_log, mock_is_db_ready, mock_log):
mock_is_db_initialised): self.is_db_initialised.return_value = True
mock_is_db_initialised.return_value = True self.is_db_ready.return_value = True
mock_is_db_ready.return_value = True
mock_ensure_ssl_cert_master.return_value = False mock_ensure_ssl_cert_master.return_value = False
hooks.identity_changed( hooks.identity_changed(
relation_id='identity-service:0', relation_id='identity-service:0',
@ -450,9 +476,12 @@ class KeystoneRelationTests(CharmTestCase):
user=self.ssh_user, group='juju_keystone', user=self.ssh_user, group='juju_keystone',
peer_interface='cluster', ensure_local_user=True) peer_interface='cluster', ensure_local_user=True)
@patch.object(hooks, 'update_all_identity_relation_units')
@patch.object(hooks, 'get_ssl_sync_request_units')
@patch.object(hooks, 'is_ssl_cert_master') @patch.object(hooks, 'is_ssl_cert_master')
@patch.object(hooks, 'peer_units') @patch.object(hooks, 'peer_units')
@patch('keystone_utils.relation_ids') @patch('keystone_utils.relation_ids')
@patch('keystone_utils.config')
@patch('keystone_utils.log') @patch('keystone_utils.log')
@patch('keystone_utils.ensure_ssl_cert_master') @patch('keystone_utils.ensure_ssl_cert_master')
@patch('keystone_utils.synchronize_ca') @patch('keystone_utils.synchronize_ca')
@ -462,13 +491,31 @@ class KeystoneRelationTests(CharmTestCase):
def test_cluster_changed(self, configs, ssh_authorized_peers, def test_cluster_changed(self, configs, ssh_authorized_peers,
check_peer_actions, mock_synchronize_ca, check_peer_actions, mock_synchronize_ca,
mock_ensure_ssl_cert_master, mock_ensure_ssl_cert_master,
mock_log, mock_relation_ids, mock_peer_units, mock_log, mock_config, mock_relation_ids,
mock_is_ssl_cert_master): mock_peer_units,
mock_is_ssl_cert_master,
mock_get_ssl_sync_request_units,
mock_update_all_identity_relation_units):
relation_settings = {'foo_passwd': '123',
'identity-service:16_foo': 'bar'}
mock_is_ssl_cert_master.return_value = False mock_is_ssl_cert_master.return_value = False
mock_peer_units.return_value = ['unit/0'] mock_peer_units.return_value = ['unit/0']
mock_ensure_ssl_cert_master.return_value = False mock_ensure_ssl_cert_master.return_value = False
mock_relation_ids.return_value = [] mock_relation_ids.return_value = []
self.is_elected_leader.return_value = False self.is_elected_leader.return_value = False
def fake_rel_get(attribute=None, *args, **kwargs):
if not attribute:
return relation_settings
return relation_settings.get(attribute)
self.relation_get.side_effect = fake_rel_get
mock_config.return_value = None
hooks.cluster_changed() hooks.cluster_changed()
whitelist = ['_passwd', 'identity-service:', 'ssl-cert-master', whitelist = ['_passwd', 'identity-service:', 'ssl-cert-master',
'db-initialised', 'ssl-cert-available-updates'] 'db-initialised', 'ssl-cert-available-updates']
@ -572,18 +619,14 @@ class KeystoneRelationTests(CharmTestCase):
@patch('keystone_utils.log') @patch('keystone_utils.log')
@patch('keystone_utils.ensure_ssl_cert_master') @patch('keystone_utils.ensure_ssl_cert_master')
@patch.object(hooks, 'is_db_ready')
@patch.object(hooks, 'is_db_initialised')
@patch.object(hooks, 'identity_changed') @patch.object(hooks, 'identity_changed')
@patch.object(hooks, 'CONFIGS') @patch.object(hooks, 'CONFIGS')
def test_ha_relation_changed_clustered_leader(self, configs, def test_ha_relation_changed_clustered_leader(self, configs,
identity_changed, identity_changed,
mock_is_db_initialised,
mock_is_db_ready,
mock_ensure_ssl_cert_master, mock_ensure_ssl_cert_master,
mock_log): mock_log):
mock_is_db_initialised.return_value = True self.is_db_initialised.return_value = True
mock_is_db_ready.return_value = True self.is_db_ready.return_value = True
mock_ensure_ssl_cert_master.return_value = False mock_ensure_ssl_cert_master.return_value = False
self.relation_get.return_value = True self.relation_get.return_value = True
self.is_elected_leader.return_value = True self.is_elected_leader.return_value = True
@ -629,8 +672,6 @@ class KeystoneRelationTests(CharmTestCase):
cmd = ['a2dissite', 'openstack_https_frontend'] cmd = ['a2dissite', 'openstack_https_frontend']
self.check_call.assert_called_with(cmd) self.check_call.assert_called_with(cmd)
@patch.object(hooks, 'is_db_ready')
@patch.object(hooks, 'is_db_initialised')
@patch('keystone_utils.log') @patch('keystone_utils.log')
@patch('keystone_utils.relation_ids') @patch('keystone_utils.relation_ids')
@patch('keystone_utils.is_elected_leader') @patch('keystone_utils.is_elected_leader')
@ -644,11 +685,9 @@ class KeystoneRelationTests(CharmTestCase):
mock_ensure_ssl_cert_master, mock_ensure_ssl_cert_master,
mock_is_elected_leader, mock_is_elected_leader,
mock_relation_ids, mock_relation_ids,
mock_log, mock_log):
mock_is_db_ready, self.is_db_initialised.return_value = True
mock_is_db_initialised): self.is_db_ready.return_value = True
mock_is_db_initialised.return_value = True
mock_is_db_ready.return_value = True
mock_is_elected_leader.return_value = False mock_is_elected_leader.return_value = False
mock_relation_ids.return_value = [] mock_relation_ids.return_value = []
mock_ensure_ssl_cert_master.return_value = True mock_ensure_ssl_cert_master.return_value = True

View File

@ -179,18 +179,19 @@ class TestKeystoneUtils(CharmTestCase):
self.assertTrue(self.https.called) self.assertTrue(self.https.called)
self.assertTrue(self.create_role.called) self.assertTrue(self.create_role.called)
relation_data = {'auth_host': '10.10.10.10', rel_only_data = {'auth_host': '10.10.10.10',
'service_host': '10.10.10.10', 'service_host': '10.10.10.10'}
'auth_protocol': 'https', relation_data = {'auth_protocol': 'https',
'service_protocol': 'https', 'service_protocol': 'https',
'auth_port': 80, 'auth_port': 80,
'service_port': 81, 'service_port': 81,
'https_keystone': 'True', 'https_keystone': 'True',
'ca_cert': 'certificate', 'ca_cert': 'certificate',
'region': 'RegionOne'} 'region': 'RegionOne'}
self.peer_store_and_set.assert_called_with( self.relation_set.assert_called_with(relation_id=relation_id,
relation_id=relation_id, **rel_only_data)
**relation_data) self.peer_store_and_set.assert_called_with(relation_id=relation_id,
**relation_data)
@patch.object(utils, 'ensure_valid_service') @patch.object(utils, 'ensure_valid_service')
@patch.object(utils, 'add_endpoint') @patch.object(utils, 'add_endpoint')
@ -236,14 +237,15 @@ class TestKeystoneUtils(CharmTestCase):
self.grant_role.assert_called_with('keystone', 'admin', 'tenant') self.grant_role.assert_called_with('keystone', 'admin', 'tenant')
self.create_role.assert_called_with('role1', 'keystone', 'tenant') self.create_role.assert_called_with('role1', 'keystone', 'tenant')
rel_only_data = {'auth_host': '10.0.0.3',
'service_host': '10.0.0.3'}
relation_data = {'admin_token': 'token', 'service_port': 81, relation_data = {'admin_token': 'token', 'service_port': 81,
'auth_port': 80, 'service_username': 'keystone', 'auth_port': 80, 'service_username': 'keystone',
'service_password': 'password', 'service_password': 'password',
'service_tenant': 'tenant', 'service_tenant': 'tenant',
'https_keystone': '__null__', 'https_keystone': '__null__',
'ssl_cert': '__null__', 'ssl_key': '__null__', 'ssl_cert': '__null__', 'ssl_key': '__null__',
'ca_cert': '__null__', 'auth_host': '10.0.0.3', 'ca_cert': '__null__',
'service_host': '10.0.0.3',
'auth_protocol': 'http', 'service_protocol': 'http', 'auth_protocol': 'http', 'service_protocol': 'http',
'service_tenant_id': 'tenant_id'} 'service_tenant_id': 'tenant_id'}
@ -254,9 +256,11 @@ class TestKeystoneUtils(CharmTestCase):
else: else:
filtered[k] = v filtered[k] = v
call1 = call(relation_id=relation_id, **filtered) call1 = call(relation_id=relation_id, **rel_only_data)
call2 = call(relation_id='cluster/0', **relation_data) call2 = call(relation_id=relation_id, **filtered)
self.relation_set.assert_has_calls([call1, call2]) call3 = call(relation_id='cluster/0', **relation_data)
self.assertTrue(self.relation_set.called)
self.relation_set.assert_has_calls([call1, call2, call3])
@patch.object(utils, 'ensure_valid_service') @patch.object(utils, 'ensure_valid_service')
@patch.object(utils, 'add_endpoint') @patch.object(utils, 'add_endpoint')