Remove support for PKI tokens and legacy charm managed certificates
These features are disabled by default, a majority of our users provide certificates through configuration. At present the cluster relation carries information required for these features even when they are not enabled. This makes processing of cluster relation changes unnecessarily heavy and vulnerable to bugs. Notice of deprecation and removal in next release was given as part of the 18.05 release notes. Change-Id: I8b07c7e0d5c2c623c115c83dc8aff230b554a986 Closes-Bug: #1755897 Related-Bug: #1744990
This commit is contained in:
parent
b75a180a68
commit
17b24e7fde
|
@ -12,18 +12,9 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import json
|
||||
|
||||
from base64 import b64decode
|
||||
|
||||
from charmhelpers.core.host import (
|
||||
mkdir,
|
||||
write_file,
|
||||
service_restart,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.openstack import context
|
||||
|
||||
from charmhelpers.contrib.hahelpers.cluster import (
|
||||
|
@ -38,116 +29,13 @@ from charmhelpers.core.hookenv import (
|
|||
config,
|
||||
log,
|
||||
leader_get,
|
||||
DEBUG,
|
||||
INFO,
|
||||
related_units,
|
||||
relation_ids,
|
||||
relation_get,
|
||||
)
|
||||
|
||||
from charmhelpers.core.strutils import (
|
||||
bool_from_string,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.hahelpers.apache import install_ca_cert
|
||||
|
||||
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
|
||||
|
||||
|
||||
def is_cert_provided_in_config():
|
||||
cert = config('ssl_cert')
|
||||
key = config('ssl_key')
|
||||
return bool(cert and key)
|
||||
|
||||
|
||||
class SSLContext(context.ApacheSSLContext):
|
||||
|
||||
def configure_cert(self, cn):
|
||||
from keystone_utils import (
|
||||
SSH_USER,
|
||||
get_ca,
|
||||
ensure_permissions,
|
||||
is_ssl_cert_master,
|
||||
KEYSTONE_USER,
|
||||
)
|
||||
|
||||
# Ensure ssl dir exists whether master or not
|
||||
perms = 0o775
|
||||
mkdir(path=self.ssl_dir, owner=SSH_USER, group=KEYSTONE_USER,
|
||||
perms=perms)
|
||||
# Ensure accessible by keystone ssh user and group (for sync)
|
||||
ensure_permissions(self.ssl_dir, user=SSH_USER, group=KEYSTONE_USER,
|
||||
perms=perms)
|
||||
|
||||
if not is_cert_provided_in_config() and not is_ssl_cert_master():
|
||||
log("Not ssl-cert-master - skipping apache cert config until "
|
||||
"master is elected", level=INFO)
|
||||
return
|
||||
|
||||
log("Creating apache ssl certs in %s" % (self.ssl_dir), level=INFO)
|
||||
|
||||
cert = config('ssl_cert')
|
||||
key = config('ssl_key')
|
||||
|
||||
if not (cert and key):
|
||||
ca = get_ca(user=SSH_USER)
|
||||
cert, key = ca.get_cert_and_key(common_name=cn)
|
||||
else:
|
||||
cert = b64decode(cert)
|
||||
key = b64decode(key)
|
||||
|
||||
write_file(path=os.path.join(self.ssl_dir, 'cert_{}'.format(cn)),
|
||||
content=cert, owner=SSH_USER, group=KEYSTONE_USER,
|
||||
perms=0o640)
|
||||
write_file(path=os.path.join(self.ssl_dir, 'key_{}'.format(cn)),
|
||||
content=key, owner=SSH_USER, group=KEYSTONE_USER,
|
||||
perms=0o640)
|
||||
|
||||
def configure_ca(self):
|
||||
from keystone_utils import (
|
||||
SSH_USER,
|
||||
get_ca,
|
||||
ensure_permissions,
|
||||
is_ssl_cert_master,
|
||||
KEYSTONE_USER,
|
||||
)
|
||||
|
||||
if not is_cert_provided_in_config() and not is_ssl_cert_master():
|
||||
log("Not ssl-cert-master - skipping apache ca config until "
|
||||
"master is elected", level=INFO)
|
||||
return
|
||||
|
||||
cert = config('ssl_cert')
|
||||
key = config('ssl_key')
|
||||
|
||||
ca_cert = config('ssl_ca')
|
||||
if ca_cert:
|
||||
ca_cert = b64decode(ca_cert)
|
||||
elif not (cert and key):
|
||||
# NOTE(hopem): if a cert and key are provided as config we don't
|
||||
# mandate that a CA is also provided since it isn't necessarily
|
||||
# needed. As a result we only generate a custom CA if we are also
|
||||
# generating cert and key.
|
||||
ca = get_ca(user=SSH_USER)
|
||||
ca_cert = ca.get_ca_bundle()
|
||||
|
||||
if ca_cert:
|
||||
# Ensure accessible by keystone ssh user and group (unison)
|
||||
install_ca_cert(ca_cert)
|
||||
ensure_permissions(CA_CERT_PATH, user=SSH_USER,
|
||||
group=KEYSTONE_USER, perms=0o0644)
|
||||
|
||||
def canonical_names(self):
|
||||
addresses = self.get_network_addresses()
|
||||
addrs = []
|
||||
for address, endpoint in addresses:
|
||||
addrs.append(endpoint)
|
||||
|
||||
return list(set(addrs))
|
||||
|
||||
|
||||
class ApacheSSLContext(SSLContext):
|
||||
|
||||
class ApacheSSLContext(context.ApacheSSLContext):
|
||||
interfaces = ['https']
|
||||
external_ports = []
|
||||
service_namespace = 'keystone'
|
||||
|
@ -157,31 +45,13 @@ class ApacheSSLContext(SSLContext):
|
|||
# late import to work around circular dependency
|
||||
from keystone_utils import (
|
||||
determine_ports,
|
||||
update_hash_from_path,
|
||||
)
|
||||
|
||||
ssl_paths = [CA_CERT_PATH, self.ssl_dir]
|
||||
|
||||
self.external_ports = determine_ports()
|
||||
before = hashlib.sha256()
|
||||
for path in ssl_paths:
|
||||
update_hash_from_path(before, path)
|
||||
|
||||
ret = super(ApacheSSLContext, self).__call__()
|
||||
|
||||
after = hashlib.sha256()
|
||||
for path in ssl_paths:
|
||||
update_hash_from_path(after, path)
|
||||
|
||||
# Ensure that apache2 is restarted if these change
|
||||
if before.hexdigest() != after.hexdigest():
|
||||
service_restart('apache2')
|
||||
|
||||
return ret
|
||||
return super(ApacheSSLContext, self).__call__()
|
||||
|
||||
|
||||
class NginxSSLContext(SSLContext):
|
||||
|
||||
class NginxSSLContext(context.ApacheSSLContext):
|
||||
interfaces = ['https']
|
||||
external_ports = []
|
||||
service_namespace = 'keystone'
|
||||
|
@ -192,30 +62,14 @@ class NginxSSLContext(SSLContext):
|
|||
# late import to work around circular dependency
|
||||
from keystone_utils import (
|
||||
determine_ports,
|
||||
update_hash_from_path,
|
||||
APACHE_SSL_DIR
|
||||
)
|
||||
|
||||
ssl_paths = [CA_CERT_PATH, APACHE_SSL_DIR]
|
||||
|
||||
self.external_ports = determine_ports()
|
||||
before = hashlib.sha256()
|
||||
for path in ssl_paths:
|
||||
update_hash_from_path(before, path)
|
||||
|
||||
ret = super(NginxSSLContext, self).__call__()
|
||||
if not ret:
|
||||
log("SSL not used", level='DEBUG')
|
||||
return {}
|
||||
|
||||
after = hashlib.sha256()
|
||||
for path in ssl_paths:
|
||||
update_hash_from_path(after, path)
|
||||
|
||||
# Ensure that Nginx is restarted if these change
|
||||
if before.hexdigest() != after.hexdigest():
|
||||
service_restart('snap.keystone.nginx')
|
||||
|
||||
# Transform for use by Nginx
|
||||
"""
|
||||
{'endpoints': [(u'10.5.0.30', u'10.5.0.30', 4990, 4980),
|
||||
|
@ -294,7 +148,7 @@ class KeystoneContext(context.OSContextGenerator):
|
|||
def __call__(self):
|
||||
from keystone_utils import (
|
||||
api_port, set_admin_token, endpoint_url, resolve_address,
|
||||
PUBLIC, ADMIN, PKI_CERTS_DIR, ensure_pki_cert_paths, ADMIN_DOMAIN,
|
||||
PUBLIC, ADMIN, ADMIN_DOMAIN,
|
||||
snap_install_requested, get_api_version,
|
||||
)
|
||||
ctxt = {}
|
||||
|
@ -331,25 +185,6 @@ class KeystoneContext(context.OSContextGenerator):
|
|||
flags = context.config_flags_parser(ldap_flags)
|
||||
ctxt['ldap_config_flags'] = flags
|
||||
|
||||
enable_pki = config('enable-pki')
|
||||
if enable_pki and bool_from_string(enable_pki):
|
||||
log("Enabling PKI", level=DEBUG)
|
||||
ctxt['token_provider'] = 'pki'
|
||||
|
||||
# NOTE(jamespage): Only check PKI configuration if the PKI
|
||||
# token format is in use, which has been
|
||||
# removed as of OpenStack Ocata.
|
||||
ensure_pki_cert_paths()
|
||||
certs = os.path.join(PKI_CERTS_DIR, 'certs')
|
||||
privates = os.path.join(PKI_CERTS_DIR, 'privates')
|
||||
ctxt['enable_signing'] = True
|
||||
ctxt.update({'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')})
|
||||
else:
|
||||
ctxt['enable_signing'] = False
|
||||
|
||||
# Base endpoint URL's which are used in keystone responses
|
||||
# to unauthenticated requests to redirect clients to the
|
||||
# correct auth URL.
|
||||
|
|
|
@ -16,12 +16,10 @@
|
|||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from subprocess import check_call
|
||||
|
||||
from charmhelpers.contrib import unison
|
||||
from charmhelpers.core import unitdata
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
|
@ -29,7 +27,6 @@ from charmhelpers.core.hookenv import (
|
|||
UnregisteredHookError,
|
||||
config,
|
||||
log,
|
||||
local_unit,
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARNING,
|
||||
|
@ -44,17 +41,12 @@ from charmhelpers.core.hookenv import (
|
|||
)
|
||||
|
||||
from charmhelpers.core.host import (
|
||||
mkdir,
|
||||
service_pause,
|
||||
service_stop,
|
||||
service_start,
|
||||
service_restart,
|
||||
)
|
||||
|
||||
from charmhelpers.core.strutils import (
|
||||
bool_from_string,
|
||||
)
|
||||
|
||||
from charmhelpers.fetch import (
|
||||
apt_install, apt_update,
|
||||
filter_installed_packages
|
||||
|
@ -85,32 +77,18 @@ from keystone_utils import (
|
|||
migrate_database,
|
||||
save_script_rc,
|
||||
post_snap_install,
|
||||
synchronize_ca_if_changed,
|
||||
register_configs,
|
||||
restart_map,
|
||||
services,
|
||||
CLUSTER_RES,
|
||||
KEYSTONE_CONF,
|
||||
KEYSTONE_USER,
|
||||
POLICY_JSON,
|
||||
TOKEN_FLUSH_CRON_FILE,
|
||||
SSH_USER,
|
||||
setup_ipv6,
|
||||
send_notifications,
|
||||
check_peer_actions,
|
||||
get_ssl_sync_request_units,
|
||||
is_ssl_cert_master,
|
||||
is_db_ready,
|
||||
clear_ssl_synced_units,
|
||||
is_db_initialised,
|
||||
update_certs_if_available,
|
||||
ensure_ssl_dir,
|
||||
ensure_pki_dir_permissions,
|
||||
ensure_permissions,
|
||||
force_ssl_sync,
|
||||
filter_null,
|
||||
ensure_ssl_dirs,
|
||||
ensure_pki_cert_paths,
|
||||
is_service_present,
|
||||
delete_service_entry,
|
||||
assess_status,
|
||||
|
@ -128,7 +106,6 @@ from keystone_utils import (
|
|||
from charmhelpers.contrib.hahelpers.cluster import (
|
||||
is_elected_leader,
|
||||
get_hacluster_config,
|
||||
peer_units,
|
||||
https,
|
||||
is_clustered,
|
||||
)
|
||||
|
@ -142,7 +119,6 @@ from charmhelpers.payload.execd import execd_preinstall
|
|||
from charmhelpers.contrib.peerstorage import (
|
||||
peer_retrieve_by_prefix,
|
||||
peer_echo,
|
||||
relation_get as relation_get_and_migrate,
|
||||
)
|
||||
from charmhelpers.contrib.openstack.ip import (
|
||||
ADMIN,
|
||||
|
@ -199,13 +175,9 @@ def install():
|
|||
disable_unused_apache_sites()
|
||||
service_pause('keystone')
|
||||
|
||||
unison.ensure_user(user=SSH_USER, group=SSH_USER)
|
||||
unison.ensure_user(user=SSH_USER, group=KEYSTONE_USER)
|
||||
|
||||
|
||||
@hooks.hook('config-changed')
|
||||
@restart_on_change(restart_map(), restart_functions=restart_function_map())
|
||||
@synchronize_ca_if_changed(fatal=True)
|
||||
@harden()
|
||||
def config_changed():
|
||||
if config('prefer-ipv6'):
|
||||
|
@ -214,36 +186,21 @@ def config_changed():
|
|||
sync_db_with_multi_ipv6_addresses(config('database'),
|
||||
config('database-user'))
|
||||
|
||||
unison.ensure_user(user=SSH_USER, group=SSH_USER)
|
||||
unison.ensure_user(user=SSH_USER, group=KEYSTONE_USER)
|
||||
homedir = unison.get_homedir(SSH_USER)
|
||||
if not os.path.isdir(homedir):
|
||||
mkdir(homedir, SSH_USER, SSH_USER, 0o775)
|
||||
|
||||
if not config('action-managed-upgrade'):
|
||||
if openstack_upgrade_available('keystone'):
|
||||
status_set('maintenance', 'Running openstack upgrade')
|
||||
do_openstack_upgrade_reexec(configs=CONFIGS)
|
||||
|
||||
for r_id in relation_ids('cluster'):
|
||||
cluster_joined(rid=r_id, ssl_sync_request=False)
|
||||
cluster_joined(rid=r_id)
|
||||
|
||||
config_changed_postupgrade()
|
||||
|
||||
|
||||
@hooks.hook('config-changed-postupgrade')
|
||||
@restart_on_change(restart_map(), restart_functions=restart_function_map())
|
||||
@synchronize_ca_if_changed(fatal=True)
|
||||
@harden()
|
||||
def config_changed_postupgrade():
|
||||
# Ensure ssl dir exists and is unison-accessible
|
||||
ensure_ssl_dir()
|
||||
|
||||
if not snap_install_requested():
|
||||
check_call(['chmod', '-R', 'g+wrx', '/var/lib/keystone/'])
|
||||
|
||||
ensure_ssl_dirs()
|
||||
|
||||
save_script_rc()
|
||||
release = os_release('keystone')
|
||||
if run_in_apache(release=release):
|
||||
|
@ -275,57 +232,14 @@ def config_changed_postupgrade():
|
|||
if snap_install_requested() and not is_unit_paused_set():
|
||||
service_restart('snap.keystone.*')
|
||||
|
||||
initialise_pki()
|
||||
|
||||
update_all_identity_relation_units()
|
||||
update_all_domain_backends()
|
||||
update_all_fid_backends()
|
||||
|
||||
# Ensure sync request is sent out (needed for any/all ssl change)
|
||||
send_ssl_sync_request()
|
||||
|
||||
for r_id in relation_ids('ha'):
|
||||
ha_joined(relation_id=r_id)
|
||||
|
||||
|
||||
@synchronize_ca_if_changed(fatal=True)
|
||||
def initialise_pki():
|
||||
"""Create certs and keys required for token signing.
|
||||
|
||||
Used for PKI and signing token revocation list.
|
||||
|
||||
NOTE: keystone.conf [signing] section must be up-to-date prior to
|
||||
executing this.
|
||||
"""
|
||||
if CompareOpenStackReleases(os_release('keystone-common')) >= 'pike':
|
||||
# pike dropped support for PKI token; skip function
|
||||
return
|
||||
ensure_pki_cert_paths()
|
||||
if not peer_units() or is_ssl_cert_master():
|
||||
log("Ensuring PKI token certs created", level=DEBUG)
|
||||
if snap_install_requested():
|
||||
cmd = ['/snap/bin/keystone-manage', 'pki_setup',
|
||||
'--keystone-user', KEYSTONE_USER,
|
||||
'--keystone-group', KEYSTONE_USER]
|
||||
_log_dir = '/var/snap/keystone/common/log'
|
||||
else:
|
||||
cmd = ['keystone-manage', 'pki_setup',
|
||||
'--keystone-user', KEYSTONE_USER,
|
||||
'--keystone-group', KEYSTONE_USER]
|
||||
_log_dir = '/var/log/keystone'
|
||||
check_call(cmd)
|
||||
|
||||
# Ensure logfile has keystone perms since we may have just created it
|
||||
# with root.
|
||||
ensure_permissions(_log_dir, user=KEYSTONE_USER,
|
||||
group=KEYSTONE_USER, perms=0o744)
|
||||
ensure_permissions('{}/keystone.log'.format(_log_dir),
|
||||
user=KEYSTONE_USER, group=KEYSTONE_USER,
|
||||
perms=0o644)
|
||||
|
||||
ensure_pki_dir_permissions()
|
||||
|
||||
|
||||
@hooks.hook('shared-db-relation-joined')
|
||||
def db_joined():
|
||||
if config('prefer-ipv6'):
|
||||
|
@ -377,11 +291,6 @@ def update_all_identity_relation_units(check_db_ready=True):
|
|||
identity_credentials_changed(relation_id=rid, remote_unit=unit)
|
||||
|
||||
|
||||
@synchronize_ca_if_changed(force=True)
|
||||
def update_all_identity_relation_units_force_sync():
|
||||
update_all_identity_relation_units()
|
||||
|
||||
|
||||
def update_all_domain_backends():
|
||||
"""Re-trigger hooks for all domain-backend relations/units"""
|
||||
for rid in relation_ids('domain-backend'):
|
||||
|
@ -431,7 +340,6 @@ def leader_init_db_if_ready(use_current_context=False):
|
|||
|
||||
@hooks.hook('shared-db-relation-changed')
|
||||
@restart_on_change(restart_map(), restart_functions=restart_function_map())
|
||||
@synchronize_ca_if_changed()
|
||||
def db_changed():
|
||||
if 'shared-db' not in CONFIGS.complete_contexts():
|
||||
log('shared-db relation incomplete. Peer not ready?')
|
||||
|
@ -446,7 +354,6 @@ def db_changed():
|
|||
|
||||
@hooks.hook('identity-service-relation-changed')
|
||||
@restart_on_change(restart_map(), restart_functions=restart_function_map())
|
||||
@synchronize_ca_if_changed()
|
||||
def identity_changed(relation_id=None, remote_unit=None):
|
||||
CONFIGS.write_all()
|
||||
|
||||
|
@ -528,59 +435,8 @@ def identity_credentials_changed(relation_id=None, remote_unit=None):
|
|||
log('Deferring identity_credentials_changed() to service leader.')
|
||||
|
||||
|
||||
def send_ssl_sync_request():
|
||||
"""Set sync request on cluster relation.
|
||||
|
||||
Value set equals number of ssl configs currently enabled so that if they
|
||||
change, we ensure that certs are synced. This setting is consumed by
|
||||
cluster-relation-changed ssl master. We also clear the 'synced' set to
|
||||
guarantee that a sync will occur.
|
||||
|
||||
Note the we do nothing if the setting is already applied.
|
||||
"""
|
||||
unit = local_unit().replace('/', '-')
|
||||
# Start with core config (e.g. used for signing revoked token list)
|
||||
ssl_config = 0b1
|
||||
|
||||
use_https = config('use-https')
|
||||
if use_https and bool_from_string(use_https):
|
||||
ssl_config ^= 0b10
|
||||
|
||||
https_service_endpoints = config('https-service-endpoints')
|
||||
if (https_service_endpoints and
|
||||
bool_from_string(https_service_endpoints)):
|
||||
ssl_config ^= 0b100
|
||||
|
||||
enable_pki = config('enable-pki')
|
||||
if enable_pki and bool_from_string(enable_pki):
|
||||
ssl_config ^= 0b1000
|
||||
|
||||
key = 'ssl-sync-required-%s' % (unit)
|
||||
settings = {key: ssl_config}
|
||||
|
||||
prev = 0b0
|
||||
rid = None
|
||||
for rid in relation_ids('cluster'):
|
||||
for unit in related_units(rid):
|
||||
_prev = relation_get(rid=rid, unit=unit, attribute=key) or 0b0
|
||||
if _prev and _prev > prev:
|
||||
prev = bin(_prev)
|
||||
|
||||
if rid and prev ^ ssl_config:
|
||||
if is_leader():
|
||||
clear_ssl_synced_units()
|
||||
|
||||
log("Setting %s=%s" % (key, bin(ssl_config)), level=DEBUG)
|
||||
relation_set(relation_id=rid, relation_settings=settings)
|
||||
|
||||
|
||||
@hooks.hook('cluster-relation-joined')
|
||||
def cluster_joined(rid=None, ssl_sync_request=True):
|
||||
unison.ssh_authorized_peers(user=SSH_USER,
|
||||
group=SSH_USER,
|
||||
peer_interface='cluster',
|
||||
ensure_local_user=True)
|
||||
|
||||
def cluster_joined(rid=None):
|
||||
settings = {}
|
||||
|
||||
for addr_type in ADDRESS_TYPES:
|
||||
|
@ -594,57 +450,19 @@ def cluster_joined(rid=None, ssl_sync_request=True):
|
|||
|
||||
relation_set(relation_id=rid, relation_settings=settings)
|
||||
|
||||
if ssl_sync_request:
|
||||
send_ssl_sync_request()
|
||||
|
||||
|
||||
@hooks.hook('cluster-relation-changed')
|
||||
@restart_on_change(restart_map(), stopstart=True)
|
||||
@update_certs_if_available
|
||||
def cluster_changed():
|
||||
unison.ssh_authorized_peers(user=SSH_USER,
|
||||
group=SSH_USER,
|
||||
peer_interface='cluster',
|
||||
ensure_local_user=True)
|
||||
# NOTE(jamespage) re-echo passwords for peer storage
|
||||
echo_whitelist = ['_passwd', 'identity-service:',
|
||||
'db-initialised', 'ssl-cert-available-updates']
|
||||
# Don't echo if leader since a re-election may be in progress.
|
||||
if not is_leader():
|
||||
echo_whitelist.append('ssl-cert-master')
|
||||
echo_whitelist = ['_passwd', 'identity-service:', 'db-initialised']
|
||||
|
||||
log("Peer echo whitelist: %s" % (echo_whitelist), level=DEBUG)
|
||||
peer_echo(includes=echo_whitelist, force=True)
|
||||
|
||||
check_peer_actions()
|
||||
update_all_identity_relation_units()
|
||||
|
||||
initialise_pki()
|
||||
|
||||
if is_leader():
|
||||
# Figure out if we need to mandate a sync
|
||||
units = get_ssl_sync_request_units()
|
||||
synced_units = relation_get_and_migrate(attribute='ssl-synced-units',
|
||||
unit=local_unit())
|
||||
diff = None
|
||||
if synced_units:
|
||||
synced_units = json.loads(synced_units)
|
||||
diff = set(units).symmetric_difference(set(synced_units))
|
||||
else:
|
||||
units = None
|
||||
|
||||
if units and (not synced_units or diff):
|
||||
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()
|
||||
|
||||
if not is_leader() 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:
|
||||
CONFIGS.write_all()
|
||||
CONFIGS.write_all()
|
||||
|
||||
|
||||
@hooks.hook('leader-elected')
|
||||
|
@ -739,7 +557,6 @@ def ha_joined(relation_id=None):
|
|||
|
||||
@hooks.hook('ha-relation-changed')
|
||||
@restart_on_change(restart_map(), restart_functions=restart_function_map())
|
||||
@synchronize_ca_if_changed()
|
||||
def ha_changed():
|
||||
CONFIGS.write_all()
|
||||
|
||||
|
@ -747,10 +564,7 @@ def ha_changed():
|
|||
if clustered:
|
||||
log('Cluster configured, notifying other services and updating '
|
||||
'keystone endpoint configuration')
|
||||
if is_ssl_cert_master():
|
||||
update_all_identity_relation_units_force_sync()
|
||||
else:
|
||||
update_all_identity_relation_units()
|
||||
update_all_identity_relation_units()
|
||||
|
||||
|
||||
@hooks.hook('identity-admin-relation-changed')
|
||||
|
@ -808,7 +622,6 @@ def domain_backend_changed(relation_id=None, unit=None):
|
|||
db.flush()
|
||||
|
||||
|
||||
@synchronize_ca_if_changed(fatal=True)
|
||||
def configure_https():
|
||||
'''
|
||||
Enables SSL API Apache config if appropriate and kicks identity-service
|
||||
|
@ -831,17 +644,10 @@ def configure_https():
|
|||
|
||||
@hooks.hook('upgrade-charm')
|
||||
@restart_on_change(restart_map(), stopstart=True)
|
||||
@synchronize_ca_if_changed()
|
||||
@harden()
|
||||
def upgrade_charm():
|
||||
status_set('maintenance', 'Installing apt packages')
|
||||
apt_install(filter_installed_packages(determine_packages()))
|
||||
unison.ssh_authorized_peers(user=SSH_USER,
|
||||
group=SSH_USER,
|
||||
peer_interface='cluster',
|
||||
ensure_local_user=True)
|
||||
|
||||
ensure_ssl_dirs()
|
||||
|
||||
if run_in_apache():
|
||||
disable_unused_apache_sites()
|
||||
|
|
|
@ -1,358 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright 2016 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tarfile
|
||||
import tempfile
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
log,
|
||||
DEBUG,
|
||||
)
|
||||
|
||||
CA_EXPIRY = '365'
|
||||
ORG_NAME = 'Ubuntu'
|
||||
ORG_UNIT = 'Ubuntu Cloud'
|
||||
CA_BUNDLE = '/usr/local/share/ca-certificates/juju_ca_cert.crt'
|
||||
|
||||
CA_CONFIG = """
|
||||
[ 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
|
||||
commonName = %(common_name)s
|
||||
|
||||
[ 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_CONFIG = """
|
||||
[ 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 Server Farm
|
||||
|
||||
[ 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
|
||||
"""
|
||||
|
||||
# Instance can be appended to this list to represent a singleton
|
||||
CA_SINGLETON = []
|
||||
|
||||
|
||||
def init_ca(ca_dir, common_name, org_name=ORG_NAME, org_unit_name=ORG_UNIT):
|
||||
log('Ensuring certificate authority exists at %s.' % ca_dir, level=DEBUG)
|
||||
if not os.path.exists(ca_dir):
|
||||
log('Initializing new certificate authority at %s' % ca_dir,
|
||||
level=DEBUG)
|
||||
os.mkdir(ca_dir)
|
||||
|
||||
for i in ['certs', 'crl', 'newcerts', 'private']:
|
||||
d = os.path.join(ca_dir, i)
|
||||
if not os.path.exists(d):
|
||||
log('Creating %s.' % d, level=DEBUG)
|
||||
os.mkdir(d)
|
||||
os.chmod(os.path.join(ca_dir, 'private'), 0o710)
|
||||
|
||||
if not os.path.isfile(os.path.join(ca_dir, 'serial')):
|
||||
with open(os.path.join(ca_dir, 'serial'), 'wb') as out:
|
||||
out.write('01\n')
|
||||
|
||||
if not os.path.isfile(os.path.join(ca_dir, 'index.txt')):
|
||||
with open(os.path.join(ca_dir, 'index.txt'), 'wb') as out:
|
||||
out.write('')
|
||||
|
||||
conf = os.path.join(ca_dir, 'ca.cnf')
|
||||
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())
|
||||
|
||||
|
||||
def root_ca_crt_key(ca_dir):
|
||||
init = False
|
||||
crt = os.path.join(ca_dir, 'cacert.pem')
|
||||
key = os.path.join(ca_dir, 'private', 'cacert.key')
|
||||
for f in [crt, key]:
|
||||
if not os.path.isfile(f):
|
||||
log('Missing %s, will re-initialize cert+key.' % f, level=DEBUG)
|
||||
init = True
|
||||
else:
|
||||
log('Found %s.' % f, level=DEBUG)
|
||||
|
||||
if init:
|
||||
conf = os.path.join(ca_dir, 'ca.cnf')
|
||||
cmd = ['openssl', 'req', '-config', conf,
|
||||
'-x509', '-nodes', '-newkey', 'rsa', '-days', '21360',
|
||||
'-keyout', key, '-out', crt, '-outform', 'PEM']
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
return crt, key
|
||||
|
||||
|
||||
def intermediate_ca_csr_key(ca_dir):
|
||||
log('Creating new intermediate CSR.', level=DEBUG)
|
||||
key = os.path.join(ca_dir, 'private', 'cacert.key')
|
||||
csr = os.path.join(ca_dir, 'cacert.csr')
|
||||
conf = os.path.join(ca_dir, 'ca.cnf')
|
||||
cmd = ['openssl', 'req', '-config', conf, '-sha1', '-newkey', 'rsa',
|
||||
'-nodes', '-keyout', key, '-out', csr, '-outform', 'PEM']
|
||||
subprocess.check_call(cmd)
|
||||
return csr, key
|
||||
|
||||
|
||||
def sign_int_csr(ca_dir, csr, common_name):
|
||||
log('Signing certificate request %s.' % csr, level=DEBUG)
|
||||
crt_name = 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)
|
||||
conf = os.path.join(ca_dir, 'ca.cnf')
|
||||
cmd = ['openssl', 'ca', '-batch', '-config', conf, '-extensions',
|
||||
'ca_extensions', '-days', CA_EXPIRY, '-notext', '-in', csr, '-out',
|
||||
crt, '-subj', subj, '-batch']
|
||||
log("Executing: %s" % ' '.join(cmd), level=DEBUG)
|
||||
subprocess.check_call(cmd)
|
||||
return crt
|
||||
|
||||
|
||||
def init_root_ca(ca_dir, common_name):
|
||||
init_ca(ca_dir, common_name)
|
||||
return root_ca_crt_key(ca_dir)
|
||||
|
||||
|
||||
def init_intermediate_ca(ca_dir, common_name, root_ca_dir, org_name=ORG_NAME,
|
||||
org_unit_name=ORG_UNIT):
|
||||
init_ca(ca_dir, common_name)
|
||||
if not os.path.isfile(os.path.join(ca_dir, 'cacert.pem')):
|
||||
csr, key = intermediate_ca_csr_key(ca_dir)
|
||||
crt = sign_int_csr(root_ca_dir, csr, common_name)
|
||||
shutil.copy(crt, os.path.join(ca_dir, 'cacert.pem'))
|
||||
else:
|
||||
log('Intermediate CA certificate already exists.', level=DEBUG)
|
||||
|
||||
conf = os.path.join(ca_dir, 'signing.cnf')
|
||||
if not os.path.isfile(conf):
|
||||
log('Creating new signing config in %s' % ca_dir, level=DEBUG)
|
||||
with open(conf, 'wb') as out:
|
||||
out.write(SIGNING_CONFIG % locals())
|
||||
|
||||
|
||||
def create_certificate(ca_dir, service):
|
||||
common_name = service
|
||||
subj = '/O=%s/OU=%s/CN=%s' % (ORG_NAME, ORG_UNIT, common_name)
|
||||
csr = os.path.join(ca_dir, 'certs', '%s.csr' % service)
|
||||
key = os.path.join(ca_dir, 'certs', '%s.key' % service)
|
||||
cmd = ['openssl', 'req', '-sha1', '-newkey', 'rsa', '-nodes', '-keyout',
|
||||
key, '-out', csr, '-subj', subj]
|
||||
subprocess.check_call(cmd)
|
||||
crt = sign_int_csr(ca_dir, csr, common_name)
|
||||
log('Signed new CSR, crt @ %s' % crt, level=DEBUG)
|
||||
return
|
||||
|
||||
|
||||
def update_bundle(bundle_file, new_bundle):
|
||||
return
|
||||
if os.path.isfile(bundle_file):
|
||||
with open(bundle_file, 'r') as f:
|
||||
current = f.read().strip()
|
||||
if new_bundle == current:
|
||||
log('CA Bundle @ %s is up to date.' % bundle_file, level=DEBUG)
|
||||
return
|
||||
|
||||
log('Updating CA bundle @ %s.' % bundle_file, level=DEBUG)
|
||||
|
||||
with open(bundle_file, 'wb') as out:
|
||||
out.write(new_bundle)
|
||||
|
||||
subprocess.check_call(['update-ca-certificates'])
|
||||
|
||||
|
||||
def tar_directory(path):
|
||||
cwd = os.getcwd()
|
||||
parent = os.path.dirname(path)
|
||||
directory = os.path.basename(path)
|
||||
tmp = tempfile.TemporaryFile()
|
||||
os.chdir(parent)
|
||||
tarball = tarfile.TarFile(fileobj=tmp, mode='w')
|
||||
tarball.add(directory)
|
||||
tarball.close()
|
||||
tmp.seek(0)
|
||||
out = tmp.read()
|
||||
tmp.close()
|
||||
os.chdir(cwd)
|
||||
return out
|
||||
|
||||
|
||||
class JujuCA(object):
|
||||
|
||||
def __init__(self, name, ca_dir, root_ca_dir, user, group):
|
||||
# Root CA
|
||||
cn = '%s Certificate Authority' % name
|
||||
root_crt, root_key = init_root_ca(root_ca_dir, cn)
|
||||
# Intermediate CA
|
||||
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]
|
||||
subprocess.check_call(cmd)
|
||||
cmd = ['chown', '-R', '%s.%s' % (user, group), root_ca_dir]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
self.ca_dir = ca_dir
|
||||
self.root_ca_dir = root_ca_dir
|
||||
self.user = user
|
||||
self.group = group
|
||||
update_bundle(CA_BUNDLE, self.get_ca_bundle())
|
||||
|
||||
def _sign_csr(self, csr, service, 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)
|
||||
conf = os.path.join(self.ca_dir, 'signing.cnf')
|
||||
cmd = ['openssl', 'ca', '-config', conf, '-extensions',
|
||||
'req_extensions', '-days', '365', '-notext', '-in', csr,
|
||||
'-out', crt, '-batch', '-subj', subj]
|
||||
subprocess.check_call(cmd)
|
||||
return crt
|
||||
|
||||
def _create_certificate(self, service, common_name):
|
||||
subj = '/O=%s/OU=%s/CN=%s' % (ORG_NAME, ORG_UNIT, common_name)
|
||||
csr = os.path.join(self.ca_dir, 'certs', '%s.csr' % service)
|
||||
key = os.path.join(self.ca_dir, 'certs', '%s.key' % service)
|
||||
cmd = ['openssl', 'req', '-sha1', '-newkey', 'rsa', '-nodes',
|
||||
'-keyout', key, '-out', csr, '-subj', subj]
|
||||
subprocess.check_call(cmd)
|
||||
crt = self._sign_csr(csr, service, common_name)
|
||||
cmd = ['chown', '-R', '%s.%s' % (self.user, self.group), self.ca_dir]
|
||||
subprocess.check_call(cmd)
|
||||
log('Signed new CSR, crt @ %s' % crt, level=DEBUG)
|
||||
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):
|
||||
keypath = self.get_key_path(common_name)
|
||||
crtpath = self.get_cert_path(common_name)
|
||||
if not os.path.isfile(crtpath):
|
||||
log("Creating certificate and key for {}.".format(common_name),
|
||||
level=DEBUG)
|
||||
crtpath, keypath = self._create_certificate(common_name,
|
||||
common_name)
|
||||
|
||||
with open(crtpath, 'r') as f:
|
||||
crt = f.read()
|
||||
with open(keypath, 'r') as f:
|
||||
key = f.read()
|
||||
return crt, key
|
||||
|
||||
@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):
|
||||
with open(self.ca_cert_path) as f:
|
||||
int_cert = f.read()
|
||||
with open(self.root_ca_cert_path) as f:
|
||||
root_cert = f.read()
|
||||
# NOTE: ordering of certs in bundle matters!
|
||||
return int_cert + root_cert
|
|
@ -14,24 +14,15 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import glob
|
||||
import grp
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import pwd
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tarfile
|
||||
import threading
|
||||
import time
|
||||
import urlparse
|
||||
import uuid
|
||||
import sys
|
||||
|
||||
from itertools import chain
|
||||
from base64 import b64encode
|
||||
from collections import OrderedDict
|
||||
from copy import deepcopy
|
||||
|
||||
|
@ -39,7 +30,6 @@ from charmhelpers.contrib.hahelpers.cluster import (
|
|||
is_elected_leader,
|
||||
determine_api_port,
|
||||
https,
|
||||
peer_units,
|
||||
get_hacluster_config,
|
||||
)
|
||||
|
||||
|
@ -79,8 +69,6 @@ from charmhelpers.core.strutils import (
|
|||
bool_from_string,
|
||||
)
|
||||
|
||||
import charmhelpers.contrib.unison as unison
|
||||
|
||||
from charmhelpers.core.decorators import (
|
||||
retry_on_exception,
|
||||
)
|
||||
|
@ -98,9 +86,6 @@ from charmhelpers.core.hookenv import (
|
|||
related_units,
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARNING,
|
||||
ERROR,
|
||||
is_leader,
|
||||
)
|
||||
|
||||
from charmhelpers.fetch import (
|
||||
|
@ -111,13 +96,11 @@ from charmhelpers.fetch import (
|
|||
)
|
||||
|
||||
from charmhelpers.core.host import (
|
||||
mkdir,
|
||||
service_restart,
|
||||
service_stop,
|
||||
service_start,
|
||||
service_restart,
|
||||
pwgen,
|
||||
lsb_release,
|
||||
write_file,
|
||||
CompareHostReleases,
|
||||
)
|
||||
|
||||
|
@ -125,11 +108,9 @@ from charmhelpers.contrib.peerstorage import (
|
|||
peer_store_and_set,
|
||||
peer_store,
|
||||
peer_retrieve,
|
||||
relation_set as relation_set_and_migrate_to_leader,
|
||||
)
|
||||
|
||||
import keystone_context
|
||||
import keystone_ssl as ssl
|
||||
|
||||
|
||||
TEMPLATES = 'templates/'
|
||||
|
@ -145,7 +126,6 @@ BASE_PACKAGES = [
|
|||
'python-psycopg2',
|
||||
'python-six',
|
||||
'pwgen',
|
||||
'unison',
|
||||
'uuid',
|
||||
]
|
||||
|
||||
|
@ -154,13 +134,11 @@ BASE_PACKAGES_SNAP = [
|
|||
'openssl',
|
||||
'python-six',
|
||||
'pwgen',
|
||||
'unison',
|
||||
'uuid',
|
||||
]
|
||||
|
||||
VERSION_PACKAGE = 'keystone'
|
||||
|
||||
SSH_USER = 'juju_keystone'
|
||||
if snap_install_requested():
|
||||
SNAP_BASE_DIR = "/snap/keystone/current"
|
||||
SNAP_COMMON_DIR = "/var/snap/keystone/common"
|
||||
|
@ -182,17 +160,9 @@ if snap_install_requested():
|
|||
STORED_DEFAULT_DOMAIN_ID = ("{}/keystone.default_domain_id"
|
||||
"".format(SNAP_LIB_DIR))
|
||||
SERVICE_PASSWD_PATH = '{}/services.passwd'.format(SNAP_LIB_DIR)
|
||||
|
||||
SSH_USER_HOME = '/home/{}'.format(SSH_USER)
|
||||
SYNC_FLAGS_DIR = '{}/juju_sync_flags/'.format(SSH_USER_HOME)
|
||||
SYNC_DIR = '{}/juju_sync/'.format(SSH_USER_HOME)
|
||||
SSL_SYNC_ARCHIVE = os.path.join(SYNC_DIR, 'juju-ssl-sync.tar')
|
||||
SSL_DIR = '{}/juju_ssl/'.format(SNAP_LIB_DIR)
|
||||
PKI_CERTS_DIR = os.path.join(SSL_DIR, 'pki')
|
||||
POLICY_JSON = ('{}/keystone.conf.d/policy.json'
|
||||
''.format(SNAP_COMMON_KEYSTONE_DIR))
|
||||
BASE_SERVICES = ['snap.keystone.uwsgi', 'snap.keystone.nginx']
|
||||
APACHE_SSL_DIR = '{}/keystone'.format(SSL_DIR)
|
||||
else:
|
||||
APACHE_SSL_DIR = '/etc/apache2/ssl/keystone'
|
||||
KEYSTONE_USER = 'keystone'
|
||||
|
@ -206,12 +176,6 @@ else:
|
|||
STORED_ADMIN_DOMAIN_ID = "/var/lib/keystone/keystone.admin_domain_id"
|
||||
STORED_DEFAULT_DOMAIN_ID = "/var/lib/keystone/keystone.default_domain_id"
|
||||
SERVICE_PASSWD_PATH = '/var/lib/keystone/services.passwd'
|
||||
|
||||
SYNC_FLAGS_DIR = '/var/lib/keystone/juju_sync_flags/'
|
||||
SYNC_DIR = '/var/lib/keystone/juju_sync/'
|
||||
SSL_SYNC_ARCHIVE = os.path.join(SYNC_DIR, 'juju-ssl-sync.tar')
|
||||
SSL_DIR = '/var/lib/keystone/juju_ssl/'
|
||||
PKI_CERTS_DIR = os.path.join(SSL_DIR, 'pki')
|
||||
POLICY_JSON = '/etc/keystone/policy.json'
|
||||
BASE_SERVICES = [
|
||||
'keystone',
|
||||
|
@ -223,11 +187,7 @@ APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend'
|
|||
APACHE_24_CONF = '/etc/apache2/sites-available/openstack_https_frontend.conf'
|
||||
MEMCACHED_CONF = '/etc/memcached.conf'
|
||||
|
||||
SSL_CA_NAME = 'Ubuntu Cloud'
|
||||
CLUSTER_RES = 'grp_ks_vips'
|
||||
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
|
||||
SSL_SYNC_SEMAPHORE = threading.Semaphore()
|
||||
SSL_DIRS = [SSL_DIR, APACHE_SSL_DIR, CA_CERT_PATH]
|
||||
ADMIN_DOMAIN = 'admin_domain'
|
||||
ADMIN_PROJECT = 'admin'
|
||||
DEFAULT_DOMAIN = 'default'
|
||||
|
@ -1311,594 +1271,6 @@ def is_password_changed(username, passwd):
|
|||
return (_passwd is None or passwd != _passwd)
|
||||
|
||||
|
||||
def ensure_ssl_dirs():
|
||||
"""Ensure unison has access to these dirs."""
|
||||
for path in [SYNC_FLAGS_DIR, SYNC_DIR]:
|
||||
if not os.path.isdir(path):
|
||||
mkdir(path, SSH_USER, KEYSTONE_USER, 0o775)
|
||||
else:
|
||||
ensure_permissions(path, user=SSH_USER, group=KEYSTONE_USER,
|
||||
perms=0o775)
|
||||
|
||||
|
||||
def ensure_permissions(path, user=None, group=None, perms=None, recurse=False,
|
||||
maxdepth=50):
|
||||
"""Set chownand chmod for path
|
||||
|
||||
Note that -1 for uid or gid result in no change.
|
||||
"""
|
||||
if user:
|
||||
uid = pwd.getpwnam(user).pw_uid
|
||||
else:
|
||||
uid = -1
|
||||
|
||||
if group:
|
||||
gid = grp.getgrnam(group).gr_gid
|
||||
else:
|
||||
gid = -1
|
||||
|
||||
os.chown(path, uid, gid)
|
||||
|
||||
if perms:
|
||||
os.chmod(path, perms)
|
||||
|
||||
if recurse:
|
||||
if not maxdepth:
|
||||
log("Max recursion depth reached - skipping further recursion")
|
||||
return
|
||||
|
||||
paths = glob.glob("%s/*" % (path))
|
||||
for path in paths:
|
||||
ensure_permissions(path, user=user, group=group, perms=perms,
|
||||
recurse=recurse, maxdepth=maxdepth - 1)
|
||||
|
||||
|
||||
def check_peer_actions():
|
||||
"""Honour service action requests from sync master.
|
||||
|
||||
Check for service action request flags, perform the action then delete the
|
||||
flag.
|
||||
"""
|
||||
restart = relation_get(attribute='restart-services-trigger')
|
||||
if restart and os.path.isdir(SYNC_FLAGS_DIR):
|
||||
for flagfile in glob.glob(os.path.join(SYNC_FLAGS_DIR, '*')):
|
||||
flag = os.path.basename(flagfile)
|
||||
key = re.compile("^(.+)?\.(.+)?\.(.+)")
|
||||
res = re.search(key, flag)
|
||||
if res:
|
||||
source = res.group(1)
|
||||
service = res.group(2)
|
||||
action = res.group(3)
|
||||
else:
|
||||
key = re.compile("^(.+)?\.(.+)?")
|
||||
res = re.search(key, flag)
|
||||
source = res.group(1)
|
||||
action = res.group(2)
|
||||
|
||||
# Don't execute actions requested by this unit.
|
||||
if local_unit().replace('.', '-') != source:
|
||||
if action == 'restart':
|
||||
log("Running action='%s' on service '%s'" %
|
||||
(action, service), level=DEBUG)
|
||||
service_restart(service)
|
||||
elif action == 'start':
|
||||
log("Running action='%s' on service '%s'" %
|
||||
(action, service), level=DEBUG)
|
||||
service_start(service)
|
||||
elif action == 'stop':
|
||||
log("Running action='%s' on service '%s'" %
|
||||
(action, service), level=DEBUG)
|
||||
service_stop(service)
|
||||
elif action == 'update-ca-certificates':
|
||||
log("Running %s" % (action), level=DEBUG)
|
||||
subprocess.check_call(['update-ca-certificates'])
|
||||
elif action == 'ensure-pki-permissions':
|
||||
log("Running %s" % (action), level=DEBUG)
|
||||
ensure_pki_dir_permissions()
|
||||
else:
|
||||
log("Unknown action flag=%s" % (flag), level=WARNING)
|
||||
|
||||
try:
|
||||
os.remove(flagfile)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def create_peer_service_actions(action, services):
|
||||
"""Mark remote services for action.
|
||||
|
||||
Default action is restart. These action will be picked up by peer units
|
||||
e.g. we may need to restart services on peer units after certs have been
|
||||
synced.
|
||||
"""
|
||||
for service in services:
|
||||
flagfile = os.path.join(SYNC_FLAGS_DIR, '%s.%s.%s' %
|
||||
(local_unit().replace('/', '-'),
|
||||
service.strip(), action))
|
||||
log("Creating action %s" % (flagfile), level=DEBUG)
|
||||
write_file(flagfile, content='', owner=SSH_USER, group=KEYSTONE_USER,
|
||||
perms=0o744)
|
||||
|
||||
|
||||
def create_peer_actions(actions):
|
||||
for action in actions:
|
||||
action = "%s.%s" % (local_unit().replace('/', '-'), action)
|
||||
flagfile = os.path.join(SYNC_FLAGS_DIR, action)
|
||||
log("Creating action %s" % (flagfile), level=DEBUG)
|
||||
write_file(flagfile, content='', owner=SSH_USER, group=KEYSTONE_USER,
|
||||
perms=0o744)
|
||||
|
||||
|
||||
@retry_on_exception(3, base_delay=2, exc_type=subprocess.CalledProcessError)
|
||||
def unison_sync(paths_to_sync):
|
||||
"""Do unison sync and retry a few times if it fails since peers may not be
|
||||
ready for sync.
|
||||
|
||||
Returns list of synced units or None if one or more peers was not synced.
|
||||
"""
|
||||
log('Synchronizing CA (%s) to all peers.' % (', '.join(paths_to_sync)),
|
||||
level=INFO)
|
||||
keystone_gid = grp.getgrnam(KEYSTONE_USER).gr_gid
|
||||
|
||||
# NOTE(dosaboy): This will sync to all peers who have already provided
|
||||
# their ssh keys. If any existing peers have not provided their keys yet,
|
||||
# they will be silently ignored.
|
||||
unison.sync_to_peers(peer_interface='cluster', paths=paths_to_sync,
|
||||
user=SSH_USER, verbose=True, gid=keystone_gid,
|
||||
fatal=True)
|
||||
|
||||
synced_units = peer_units()
|
||||
if len(unison.collect_authed_hosts('cluster')) != len(synced_units):
|
||||
log("Not all peer units synced due to missing public keys", level=INFO)
|
||||
return None
|
||||
else:
|
||||
return synced_units
|
||||
|
||||
|
||||
def get_ssl_sync_request_units():
|
||||
"""Get list of units that have requested to be synced.
|
||||
|
||||
NOTE: this must be called from cluster relation context.
|
||||
"""
|
||||
units = []
|
||||
for unit in related_units():
|
||||
settings = relation_get(unit=unit) or {}
|
||||
rkeys = settings.keys()
|
||||
key = re.compile("^ssl-sync-required-(.+)")
|
||||
for rkey in rkeys:
|
||||
res = re.search(key, rkey)
|
||||
if res:
|
||||
units.append(res.group(1))
|
||||
|
||||
return units
|
||||
|
||||
|
||||
def is_ssl_cert_master(votes=None):
|
||||
"""Return True if this unit is ssl cert master."""
|
||||
|
||||
votes = votes or get_ssl_cert_master_votes()
|
||||
set_votes = set(votes)
|
||||
# Discard unknown votes
|
||||
if 'unknown' in set_votes:
|
||||
set_votes.remove('unknown')
|
||||
|
||||
# This is the elected ssl-cert-master leader
|
||||
if len(set_votes) == 1 and set_votes == set([local_unit()]):
|
||||
log("This unit is the elected ssl-cert-master "
|
||||
"{}".format(votes), level=DEBUG)
|
||||
return True
|
||||
|
||||
# Contested election
|
||||
if len(set_votes) > 1:
|
||||
log("Did not get consensus from peers on who is ssl-cert-master "
|
||||
"{}".format(votes), level=DEBUG)
|
||||
return False
|
||||
|
||||
# Neither the elected ssl-cert-master leader nor the juju leader
|
||||
if not is_leader():
|
||||
return False
|
||||
# Only the juju elected leader continues
|
||||
|
||||
# Singleton
|
||||
if not peer_units():
|
||||
log("This unit is a singleton and thefore ssl-cert-master",
|
||||
level=DEBUG)
|
||||
return True
|
||||
|
||||
# Early in the process and juju leader
|
||||
if not set_votes:
|
||||
log("This unit is the juju leader and there are no votes yet, "
|
||||
"becoming the ssl-cert-master",
|
||||
level=DEBUG)
|
||||
return True
|
||||
elif (len(set_votes) == 1 and set_votes != set([local_unit()]) and
|
||||
is_leader()):
|
||||
log("This unit is the juju leader but not yet ssl-cert-master "
|
||||
"(current votes = {})".format(set_votes), level=DEBUG)
|
||||
return False
|
||||
|
||||
# Should never reach here
|
||||
log("Could not determine the ssl-cert-master. Missing edge case. "
|
||||
"(current votes = {})".format(set_votes),
|
||||
level=ERROR)
|
||||
return False
|
||||
|
||||
|
||||
def get_ssl_cert_master_votes():
|
||||
"""Returns a list of unique votes."""
|
||||
votes = []
|
||||
# Gather election results from peers. These will need to be consistent.
|
||||
for rid in relation_ids('cluster'):
|
||||
for unit in related_units(rid):
|
||||
m = relation_get(rid=rid, unit=unit,
|
||||
attribute='ssl-cert-master')
|
||||
if m is not None:
|
||||
votes.append(m)
|
||||
|
||||
return list(set(votes))
|
||||
|
||||
|
||||
def ensure_ssl_cert_master():
|
||||
"""Ensure that an ssl cert master has been elected.
|
||||
|
||||
Normally the cluster leader will take control but we allow for this to be
|
||||
ignored since this could be called before the cluster is ready.
|
||||
"""
|
||||
master_override = False
|
||||
elect = is_elected_leader(CLUSTER_RES)
|
||||
|
||||
# If no peers we allow this unit to elect itsef as master and do
|
||||
# sync immediately.
|
||||
if not peer_units():
|
||||
elect = True
|
||||
master_override = True
|
||||
|
||||
if elect:
|
||||
votes = get_ssl_cert_master_votes()
|
||||
# We expect all peers to echo this setting
|
||||
if not votes or 'unknown' in votes:
|
||||
log("Notifying peers this unit is ssl-cert-master", level=INFO)
|
||||
for rid in relation_ids('cluster'):
|
||||
settings = {'ssl-cert-master': local_unit()}
|
||||
relation_set(relation_id=rid, relation_settings=settings)
|
||||
|
||||
# Return now and wait for cluster-relation-changed (peer_echo) for
|
||||
# sync.
|
||||
return master_override
|
||||
elif not is_ssl_cert_master(votes):
|
||||
if not master_override:
|
||||
log("Conscensus not reached - current master will need to "
|
||||
"release", level=INFO)
|
||||
|
||||
return master_override
|
||||
|
||||
if not is_ssl_cert_master():
|
||||
log("Not ssl cert master - skipping sync", level=INFO)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def stage_paths_for_sync(paths):
|
||||
shutil.rmtree(SYNC_DIR)
|
||||
ensure_ssl_dirs()
|
||||
with tarfile.open(SSL_SYNC_ARCHIVE, 'w') as fd:
|
||||
for path in paths:
|
||||
if os.path.exists(path):
|
||||
log("Adding path '%s' sync tarball" % (path), level=DEBUG)
|
||||
fd.add(path)
|
||||
else:
|
||||
log("Path '%s' does not exist - not adding to sync "
|
||||
"tarball" % (path), level=INFO)
|
||||
|
||||
ensure_permissions(SYNC_DIR, user=SSH_USER, group=KEYSTONE_USER,
|
||||
perms=0o775, 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_cert_paths():
|
||||
certs = os.path.join(PKI_CERTS_DIR, 'certs')
|
||||
privates = os.path.join(PKI_CERTS_DIR, 'privates')
|
||||
not_exists = [p for p in [PKI_CERTS_DIR, certs, privates]
|
||||
if not os.path.exists(p)]
|
||||
if not_exists:
|
||||
log("Configuring token signing cert paths", level=DEBUG)
|
||||
perms = 0o775
|
||||
for path in not_exists:
|
||||
if not os.path.isdir(path):
|
||||
mkdir(path=path, owner=SSH_USER, group=KEYSTONE_USER,
|
||||
perms=perms)
|
||||
else:
|
||||
# Ensure accessible by ssh user and group (for sync).
|
||||
ensure_permissions(path, user=SSH_USER, group=KEYSTONE_USER,
|
||||
perms=perms)
|
||||
|
||||
|
||||
def ensure_pki_dir_permissions():
|
||||
# Ensure accessible by unison user and group (for sync).
|
||||
ensure_permissions(PKI_CERTS_DIR, user=SSH_USER, group=KEYSTONE_USER,
|
||||
perms=0o775, recurse=True)
|
||||
|
||||
|
||||
def update_certs_if_available(f):
|
||||
def _inner_update_certs_if_available(*args, **kwargs):
|
||||
path = None
|
||||
for rid in relation_ids('cluster'):
|
||||
path = relation_get(attribute='ssl-cert-available-updates',
|
||||
rid=rid, unit=local_unit())
|
||||
|
||||
if path and os.path.exists(path):
|
||||
log("Updating certs from '%s'" % (path), level=DEBUG)
|
||||
with tarfile.open(path) as fd:
|
||||
files = ["/%s" % m.name for m in fd.getmembers()]
|
||||
fd.extractall(path='/')
|
||||
|
||||
for syncfile in files:
|
||||
ensure_permissions(syncfile, user=KEYSTONE_USER,
|
||||
group=KEYSTONE_USER,
|
||||
perms=0o744, recurse=True)
|
||||
|
||||
# Mark as complete
|
||||
os.rename(path, "%s.complete" % (path))
|
||||
else:
|
||||
log("No cert updates available", level=DEBUG)
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return _inner_update_certs_if_available
|
||||
|
||||
|
||||
def synchronize_ca(fatal=False):
|
||||
"""Broadcast service credentials to peers.
|
||||
|
||||
By default a failure to sync is fatal and will result in a raised
|
||||
exception.
|
||||
|
||||
This function uses a relation setting 'ssl-cert-master' to get some
|
||||
leader stickiness while synchronisation is being carried out. This ensures
|
||||
that the last host to create and broadcast cetificates has the option to
|
||||
complete actions before electing the new leader as sync master.
|
||||
|
||||
Returns a dictionary of settings to be set on the cluster relation.
|
||||
"""
|
||||
paths_to_sync = []
|
||||
peer_service_actions = {'restart': []}
|
||||
peer_actions = []
|
||||
|
||||
if bool_from_string(config('https-service-endpoints')):
|
||||
log("Syncing all endpoint certs since https-service-endpoints=True",
|
||||
level=DEBUG)
|
||||
paths_to_sync.append(SSL_DIR)
|
||||
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['restart'].append('apache2')
|
||||
peer_actions.append('update-ca-certificates')
|
||||
|
||||
if bool_from_string(config('use-https')):
|
||||
log("Syncing keystone-endpoint certs since use-https=True",
|
||||
level=DEBUG)
|
||||
paths_to_sync.append(SSL_DIR)
|
||||
paths_to_sync.append(APACHE_SSL_DIR)
|
||||
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['restart'].append('apache2')
|
||||
peer_actions.append('update-ca-certificates')
|
||||
|
||||
# NOTE: certs needed for token signing e.g. pki and revocation list query.
|
||||
log("Syncing token certs", level=DEBUG)
|
||||
# pike dropped support for PKI token; only run on releases <= pike
|
||||
if CompareOpenStackReleases(os_release('keystone-common')) <= 'pike':
|
||||
paths_to_sync.append(PKI_CERTS_DIR)
|
||||
peer_actions.append('ensure-pki-permissions')
|
||||
|
||||
if not paths_to_sync:
|
||||
log("Nothing to sync - skipping", level=DEBUG)
|
||||
return {}
|
||||
|
||||
if not os.path.isdir(SYNC_FLAGS_DIR):
|
||||
mkdir(SYNC_FLAGS_DIR, SSH_USER, KEYSTONE_USER, 0o775)
|
||||
|
||||
restart_trigger = None
|
||||
for action, services in peer_service_actions.iteritems():
|
||||
services = set(services)
|
||||
if services:
|
||||
restart_trigger = str(uuid.uuid4())
|
||||
create_peer_service_actions(action, services)
|
||||
|
||||
create_peer_actions(peer_actions)
|
||||
|
||||
paths_to_sync = list(set(paths_to_sync))
|
||||
stage_paths_for_sync(paths_to_sync)
|
||||
|
||||
hash1 = hashlib.sha256()
|
||||
for path in paths_to_sync:
|
||||
update_hash_from_path(hash1, path)
|
||||
|
||||
cluster_rel_settings = {'ssl-cert-available-updates': SSL_SYNC_ARCHIVE,
|
||||
'sync-hash': hash1.hexdigest()}
|
||||
|
||||
synced_units = unison_sync([SSL_SYNC_ARCHIVE, SYNC_FLAGS_DIR])
|
||||
if synced_units:
|
||||
# Format here needs to match that used when peers request sync
|
||||
synced_units = [u.replace('/', '-') for u in synced_units]
|
||||
ssl_synced_units = \
|
||||
json.dumps(synced_units)
|
||||
# NOTE(hopem): we pull this onto the leader settings to avoid
|
||||
# unnecessary cluster relation noise. This is possible because the
|
||||
# setting is only needed by the cert master.
|
||||
if 'ssl-synced-units' not in leader_get():
|
||||
rid = relation_ids('cluster')[0]
|
||||
relation_set_and_migrate_to_leader(relation_id=rid,
|
||||
**{'ssl-synced-units':
|
||||
ssl_synced_units})
|
||||
else:
|
||||
leader_set({'ssl-synced-units': ssl_synced_units})
|
||||
|
||||
if restart_trigger:
|
||||
log("Sending restart-services-trigger=%s to all peers" %
|
||||
(restart_trigger), level=DEBUG)
|
||||
cluster_rel_settings['restart-services-trigger'] = restart_trigger
|
||||
|
||||
log("Sync complete", level=DEBUG)
|
||||
return cluster_rel_settings
|
||||
|
||||
|
||||
def clear_ssl_synced_units():
|
||||
"""Clear the 'synced' units record on the cluster relation.
|
||||
|
||||
If new unit sync reauests are set this will ensure that a sync occurs when
|
||||
the sync master receives the requests.
|
||||
"""
|
||||
log("Clearing ssl sync units", level=DEBUG)
|
||||
for rid in relation_ids('cluster'):
|
||||
if 'ssl-synced-units' not in leader_get():
|
||||
relation_set_and_migrate_to_leader(relation_id=rid,
|
||||
**{'ssl-synced-units': None})
|
||||
else:
|
||||
leader_set({'ssl-synced-units': None})
|
||||
|
||||
|
||||
def update_hash_from_path(hash, path, recurse_depth=10):
|
||||
"""Recurse through path and update the provided hash for every file found.
|
||||
"""
|
||||
if not recurse_depth:
|
||||
log("Max recursion depth (%s) reached for update_hash_from_path() at "
|
||||
"path='%s' - not going any deeper" % (recurse_depth, path),
|
||||
level=WARNING)
|
||||
return
|
||||
|
||||
for p in glob.glob("%s/*" % path):
|
||||
if os.path.isdir(p):
|
||||
update_hash_from_path(hash, p, recurse_depth=recurse_depth - 1)
|
||||
else:
|
||||
with open(p, 'r') as fd:
|
||||
hash.update(fd.read())
|
||||
|
||||
|
||||
def synchronize_ca_if_changed(force=False, fatal=False):
|
||||
"""Decorator to perform ssl cert sync if decorated function modifies them
|
||||
in any way.
|
||||
|
||||
If force is True a sync is done regardless.
|
||||
"""
|
||||
def inner_synchronize_ca_if_changed1(f):
|
||||
def inner_synchronize_ca_if_changed2(*args, **kwargs):
|
||||
# Only sync master can do sync. Ensure (a) we are not nested and
|
||||
# (b) a master is elected and we are it.
|
||||
acquired = SSL_SYNC_SEMAPHORE.acquire(blocking=0)
|
||||
try:
|
||||
if not acquired:
|
||||
log("Nested sync - ignoring", level=DEBUG)
|
||||
return f(*args, **kwargs)
|
||||
|
||||
if not ensure_ssl_cert_master():
|
||||
log("Not ssl-cert-master - ignoring sync", level=DEBUG)
|
||||
return f(*args, **kwargs)
|
||||
|
||||
peer_settings = {}
|
||||
if not force:
|
||||
hash1 = hashlib.sha256()
|
||||
for path in SSL_DIRS:
|
||||
update_hash_from_path(hash1, path)
|
||||
|
||||
ret = f(*args, **kwargs)
|
||||
|
||||
hash2 = hashlib.sha256()
|
||||
for path in SSL_DIRS:
|
||||
update_hash_from_path(hash2, path)
|
||||
|
||||
if hash1.hexdigest() != hash2.hexdigest():
|
||||
log("SSL certs have changed - syncing peers",
|
||||
level=DEBUG)
|
||||
peer_settings = synchronize_ca(fatal=fatal)
|
||||
else:
|
||||
log("SSL certs have not changed - skipping sync",
|
||||
level=DEBUG)
|
||||
else:
|
||||
ret = f(*args, **kwargs)
|
||||
log("Doing forced ssl cert sync", level=DEBUG)
|
||||
peer_settings = synchronize_ca(fatal=fatal)
|
||||
|
||||
# If we are the sync master but not leader, ensure we have
|
||||
# relinquished master status.
|
||||
cluster_rids = relation_ids('cluster')
|
||||
if cluster_rids:
|
||||
master = relation_get('ssl-cert-master',
|
||||
rid=cluster_rids[0],
|
||||
unit=local_unit())
|
||||
if not is_leader() and master == local_unit():
|
||||
log("Re-electing ssl cert master.", level=INFO)
|
||||
peer_settings['ssl-cert-master'] = 'unknown'
|
||||
|
||||
if peer_settings:
|
||||
relation_set(relation_id=cluster_rids[0],
|
||||
relation_settings=peer_settings)
|
||||
|
||||
return ret
|
||||
finally:
|
||||
SSL_SYNC_SEMAPHORE.release()
|
||||
|
||||
return inner_synchronize_ca_if_changed2
|
||||
|
||||
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."""
|
||||
# NOTE(thedac) Snap service restarts will override permissions
|
||||
# in SNAP_LIB_DIR including SSL_DIR
|
||||
perms = 0o775
|
||||
if not os.path.isdir(SSL_DIR):
|
||||
mkdir(SSL_DIR, SSH_USER, KEYSTONE_USER, perms)
|
||||
else:
|
||||
ensure_permissions(SSL_DIR, user=SSH_USER, group=KEYSTONE_USER,
|
||||
perms=perms)
|
||||
|
||||
|
||||
def get_ca(user=KEYSTONE_USER, group=KEYSTONE_USER):
|
||||
"""Initialize a new CA object if one hasn't already been loaded.
|
||||
|
||||
This will create a new CA or load an existing one.
|
||||
"""
|
||||
if not ssl.CA_SINGLETON:
|
||||
ensure_ssl_dir()
|
||||
d_name = '_'.join(SSL_CA_NAME.lower().split(' '))
|
||||
ca = ssl.JujuCA(name=SSL_CA_NAME, user=user, group=group,
|
||||
ca_dir=os.path.join(SSL_DIR,
|
||||
'%s_intermediate_ca' % d_name),
|
||||
root_ca_dir=os.path.join(SSL_DIR,
|
||||
'%s_root_ca' % d_name))
|
||||
|
||||
# Ensure a master is elected. This should cover the following cases:
|
||||
# * single unit == 'oldest' unit is elected as master
|
||||
# * multi unit + not clustered == 'oldest' unit is elcted as master
|
||||
# * multi unit + clustered == cluster leader is elected as master
|
||||
ensure_ssl_cert_master()
|
||||
|
||||
ssl.CA_SINGLETON.append(ca)
|
||||
|
||||
return ssl.CA_SINGLETON[0]
|
||||
|
||||
|
||||
def relation_list(rid):
|
||||
cmd = [
|
||||
'relation-list',
|
||||
|
@ -2017,8 +1389,6 @@ def add_service_to_keystone(relation_id=None, remote_unit=None):
|
|||
relation_data["api_version"] = get_api_version()
|
||||
relation_data["admin_domain_id"] = leader_get(
|
||||
attribute='admin_domain_id')
|
||||
# Get and pass CA bundle settings
|
||||
relation_data.update(get_ssl_ca_settings())
|
||||
|
||||
# Allow the remote service to request creation of any additional
|
||||
# roles. Currently used by Horizon
|
||||
|
@ -2075,7 +1445,6 @@ def add_service_to_keystone(relation_id=None, remote_unit=None):
|
|||
endpoints[ep][x] = v
|
||||
|
||||
services = []
|
||||
https_cn = None
|
||||
for ep in endpoints:
|
||||
# weed out any unrelated relation stuff Juju might have added
|
||||
# by ensuring each possible endpiont has appropriate fields
|
||||
|
@ -2145,25 +1514,6 @@ def add_service_to_keystone(relation_id=None, remote_unit=None):
|
|||
"admin_domain_id": leader_get(attribute='admin_domain_id'),
|
||||
}
|
||||
|
||||
# generate or get a new cert/key for service if set to manage certs.
|
||||
https_service_endpoints = config('https-service-endpoints')
|
||||
if https_service_endpoints and bool_from_string(https_service_endpoints):
|
||||
ca = get_ca(user=SSH_USER)
|
||||
# NOTE(jamespage) may have multiple cns to deal with to iterate
|
||||
https_cns = set(https_cns)
|
||||
for https_cn in https_cns:
|
||||
cert, key = ca.get_cert_and_key(common_name=https_cn)
|
||||
relation_data['ssl_cert_{}'.format(https_cn)] = b64encode(cert)
|
||||
relation_data['ssl_key_{}'.format(https_cn)] = b64encode(key)
|
||||
|
||||
# NOTE(jamespage) for backwards compatibility
|
||||
cert, key = ca.get_cert_and_key(common_name=internal_cn)
|
||||
relation_data['ssl_cert'] = b64encode(cert)
|
||||
relation_data['ssl_key'] = b64encode(key)
|
||||
|
||||
# Get and pass CA bundle settings
|
||||
relation_data.update(get_ssl_ca_settings())
|
||||
|
||||
peer_store_and_set(relation_id=relation_id, **relation_data)
|
||||
# NOTE(dosaboy): '__null__' settings are for peer relation only so that
|
||||
# settings can flushed so we filter them out for non-peer relation.
|
||||
|
@ -2229,30 +1579,10 @@ def add_credentials_to_keystone(relation_id=None, remote_unit=None):
|
|||
}
|
||||
if domain:
|
||||
relation_data['domain'] = domain
|
||||
# Get and pass CA bundle settings
|
||||
relation_data.update(get_ssl_ca_settings())
|
||||
|
||||
peer_store_and_set(relation_id=relation_id, **relation_data)
|
||||
|
||||
|
||||
def get_ssl_ca_settings():
|
||||
""" Get the Certificate Authority settings required to use the CA
|
||||
|
||||
:returns: Dictionary with https_keystone and ca_cert set
|
||||
"""
|
||||
ca_data = {}
|
||||
https_service_endpoints = config('https-service-endpoints')
|
||||
if (https_service_endpoints and
|
||||
bool_from_string(https_service_endpoints)):
|
||||
# Pass CA cert as client will need it to
|
||||
# verify https connections
|
||||
ca = get_ca(user=SSH_USER)
|
||||
ca_bundle = ca.get_ca_bundle()
|
||||
ca_data['https_keystone'] = 'True'
|
||||
ca_data['ca_cert'] = b64encode(ca_bundle)
|
||||
return ca_data
|
||||
|
||||
|
||||
def get_protocol():
|
||||
"""Determine the http protocol
|
||||
|
||||
|
|
|
@ -37,68 +37,11 @@ class TestKeystoneContexts(CharmTestCase):
|
|||
def setUp(self):
|
||||
super(TestKeystoneContexts, self).setUp(context, TO_PATCH)
|
||||
|
||||
def test_is_cert_provided_in_config(self):
|
||||
config = {'ssl_cert': 'somecert', 'ssl_key': 'greatkey'}
|
||||
|
||||
def fake_config(key):
|
||||
return config.get(key)
|
||||
|
||||
self.config.side_effect = fake_config
|
||||
self.assertTrue(context.is_cert_provided_in_config())
|
||||
|
||||
del config['ssl_cert']
|
||||
self.assertFalse(context.is_cert_provided_in_config())
|
||||
|
||||
@patch.object(context, 'mkdir')
|
||||
@patch('keystone_utils.get_ca')
|
||||
@patch('keystone_utils.ensure_permissions')
|
||||
@patch('keystone_utils.determine_ports', lambda: None)
|
||||
@patch('keystone_utils.is_ssl_cert_master', lambda: False)
|
||||
@patch.object(context, 'is_cert_provided_in_config', lambda: False)
|
||||
@patch.object(context, 'log', lambda *args, **kwargs: None)
|
||||
def test_apache_ssl_context_ssl_not_master(self, mock_ensure_permissions,
|
||||
mock_get_ca, mock_mkdir):
|
||||
context.ApacheSSLContext().configure_cert('foo')
|
||||
context.ApacheSSLContext().configure_ca()
|
||||
self.assertTrue(mock_mkdir.called)
|
||||
self.assertTrue(mock_ensure_permissions.called)
|
||||
self.assertFalse(mock_get_ca.called)
|
||||
|
||||
@patch('keystone_utils.ensure_permissions')
|
||||
@patch.object(context, 'install_ca_cert')
|
||||
@patch.object(context, 'b64decode')
|
||||
@patch.object(context, 'mkdir', lambda *args: None)
|
||||
@patch('keystone_utils.get_ca', lambda: None)
|
||||
@patch('keystone_utils.determine_ports', lambda: None)
|
||||
@patch('keystone_utils.is_ssl_cert_master', lambda: True)
|
||||
@patch.object(context, 'log', lambda *args, **kwargs: None)
|
||||
def test_apache_ssl_context_ssl_configure_ca(self, mock_b64decode,
|
||||
mock_install_ca_cert,
|
||||
mock_ensure_permissions):
|
||||
config = {'ssl_cert': 'somecert', 'ssl_key': 'greatkey'}
|
||||
|
||||
def fake_config(key):
|
||||
return config.get(key)
|
||||
|
||||
self.config.side_effect = fake_config
|
||||
|
||||
context.ApacheSSLContext().configure_ca()
|
||||
self.assertFalse(mock_b64decode.called)
|
||||
self.assertFalse(mock_install_ca_cert.called)
|
||||
self.assertFalse(mock_ensure_permissions.called)
|
||||
|
||||
config['ssl_ca'] = 'foofoofalalala'
|
||||
context.ApacheSSLContext().configure_ca()
|
||||
self.assertTrue(mock_b64decode.called)
|
||||
self.assertTrue(mock_install_ca_cert.called)
|
||||
self.assertTrue(mock_ensure_permissions.called)
|
||||
|
||||
@patch('charmhelpers.contrib.hahelpers.cluster.relation_ids')
|
||||
@patch('charmhelpers.contrib.openstack.ip.unit_get')
|
||||
@patch('charmhelpers.contrib.openstack.ip.service_name')
|
||||
@patch('charmhelpers.contrib.openstack.ip.config')
|
||||
@patch('keystone_utils.determine_ports')
|
||||
@patch('keystone_utils.is_ssl_cert_master')
|
||||
@patch('charmhelpers.contrib.openstack.context.config')
|
||||
@patch('charmhelpers.contrib.openstack.context.is_clustered')
|
||||
@patch('charmhelpers.contrib.openstack.context.determine_apache_port')
|
||||
|
@ -113,15 +56,12 @@ class TestKeystoneContexts(CharmTestCase):
|
|||
mock_determine_apache_port,
|
||||
mock_is_clustered,
|
||||
mock_config,
|
||||
mock_is_ssl_cert_master,
|
||||
mock_determine_ports,
|
||||
mock_ip_config,
|
||||
mock_service_name,
|
||||
mock_ip_unit_get,
|
||||
mock_rel_ids,
|
||||
):
|
||||
mock_relation_ids.return_value = []
|
||||
mock_is_ssl_cert_master.return_value = True
|
||||
mock_https.return_value = True
|
||||
mock_unit_get.return_value = '1.2.3.4'
|
||||
mock_ip_unit_get.return_value = '1.2.3.4'
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import uuid
|
||||
import sys
|
||||
|
||||
from mock import call, patch, MagicMock
|
||||
|
@ -43,8 +42,6 @@ with patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec:
|
|||
with patch('keystone_utils.run_in_apache') as mock_run_in_apache:
|
||||
import keystone_hooks as hooks
|
||||
|
||||
from charmhelpers.contrib import unison
|
||||
|
||||
utils.register_configs = _reg
|
||||
utils.restart_map = _map
|
||||
|
||||
|
@ -53,7 +50,6 @@ TO_PATCH = [
|
|||
'Hooks',
|
||||
'config',
|
||||
'log',
|
||||
'local_unit',
|
||||
'filter_installed_packages',
|
||||
'relation_ids',
|
||||
'relation_set',
|
||||
|
@ -89,17 +85,13 @@ TO_PATCH = [
|
|||
'migrate_database',
|
||||
'ensure_initial_admin',
|
||||
'add_service_to_keystone',
|
||||
'synchronize_ca_if_changed',
|
||||
'update_nrpe_config',
|
||||
'ensure_ssl_dirs',
|
||||
'is_db_ready',
|
||||
'create_or_show_domain',
|
||||
'get_api_version',
|
||||
# other
|
||||
'check_call',
|
||||
'execd_preinstall',
|
||||
'mkdir',
|
||||
'os',
|
||||
# ip
|
||||
'get_iface_for_address',
|
||||
'get_netmask_for_address',
|
||||
|
@ -123,10 +115,9 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
self.snap_install_requested.return_value = False
|
||||
|
||||
@patch.object(utils, 'os_release')
|
||||
@patch.object(unison, 'ensure_user')
|
||||
@patch.object(hooks, 'service_stop', lambda *args: None)
|
||||
@patch.object(hooks, 'service_start', lambda *args: None)
|
||||
def test_install_hook(self, ensure_user, os_release):
|
||||
def test_install_hook(self, os_release):
|
||||
os_release.return_value = 'havana'
|
||||
self.run_in_apache.return_value = False
|
||||
repo = 'cloud:precise-grizzly'
|
||||
|
@ -134,33 +125,29 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
hooks.install()
|
||||
self.assertTrue(self.execd_preinstall.called)
|
||||
self.configure_installation_source.assert_called_with(repo)
|
||||
ensure_user.assert_called_with(user=self.ssh_user, group='keystone')
|
||||
self.assertTrue(self.apt_update.called)
|
||||
self.apt_install.assert_called_with(
|
||||
['apache2', 'haproxy', 'keystone', 'openssl', 'pwgen',
|
||||
'python-keystoneclient', 'python-mysqldb', 'python-psycopg2',
|
||||
'python-six', 'unison', 'uuid'], fatal=True)
|
||||
'python-six', 'uuid'], fatal=True)
|
||||
self.disable_unused_apache_sites.assert_not_called()
|
||||
|
||||
@patch.object(utils, 'os_release')
|
||||
@patch.object(unison, 'ensure_user')
|
||||
@patch.object(hooks, 'service_stop', lambda *args: None)
|
||||
@patch.object(hooks, 'service_start', lambda *args: None)
|
||||
def test_install_hook_apache2(self, ensure_user, os_release):
|
||||
def test_install_hook_apache2(self, os_release):
|
||||
os_release.return_value = 'havana'
|
||||
self.run_in_apache.return_value = True
|
||||
repo = 'cloud:xenial-newton'
|
||||
self.test_config.set('openstack-origin', repo)
|
||||
self.os.path.exists.return_value = True
|
||||
hooks.install()
|
||||
self.assertTrue(self.execd_preinstall.called)
|
||||
self.configure_installation_source.assert_called_with(repo)
|
||||
ensure_user.assert_called_with(user=self.ssh_user, group='keystone')
|
||||
self.assertTrue(self.apt_update.called)
|
||||
self.apt_install.assert_called_with(
|
||||
['apache2', 'haproxy', 'keystone', 'openssl', 'pwgen',
|
||||
'python-keystoneclient', 'python-mysqldb', 'python-psycopg2',
|
||||
'python-six', 'unison', 'uuid'], fatal=True)
|
||||
'python-six', 'uuid'], fatal=True)
|
||||
self.disable_unused_apache_sites.assert_called_with()
|
||||
|
||||
mod_ch_openstack_utils = 'charmhelpers.contrib.openstack.utils'
|
||||
|
@ -196,12 +183,9 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
hostname='192.168.20.1')
|
||||
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch.object(hooks, 'CONFIGS')
|
||||
def test_db_changed_missing_relation_data(self, configs,
|
||||
mock_ensure_ssl_cert_master,
|
||||
mock_log):
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
configs.complete_contexts = MagicMock()
|
||||
configs.complete_contexts.return_value = []
|
||||
hooks.db_changed()
|
||||
|
@ -212,20 +196,15 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
@patch.object(hooks, 'update_all_identity_relation_units')
|
||||
def _shared_db_test(self, configs, unit_name, mock_update_all):
|
||||
self.relation_get.return_value = 'keystone/0 keystone/3'
|
||||
self.local_unit.return_value = unit_name
|
||||
configs.complete_contexts = MagicMock()
|
||||
configs.complete_contexts.return_value = ['shared-db']
|
||||
configs.write = MagicMock()
|
||||
hooks.db_changed()
|
||||
|
||||
@patch.object(hooks, 'leader_init_db_if_ready')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch.object(hooks, 'CONFIGS')
|
||||
def test_db_changed(self, configs,
|
||||
mock_ensure_ssl_cert_master,
|
||||
leader_init):
|
||||
def test_db_changed(self, configs, leader_init):
|
||||
self.os_release.return_value = 'havana'
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
self._shared_db_test(configs, 'keystone/3')
|
||||
self.assertEqual([call('/etc/keystone/keystone.conf')],
|
||||
configs.write.call_args_list)
|
||||
|
@ -236,37 +215,16 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
@patch.object(hooks, 'run_in_apache')
|
||||
@patch.object(hooks, 'is_db_initialised')
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch('keystone_utils.ensure_ssl_dirs')
|
||||
@patch.object(hooks, 'ensure_permissions')
|
||||
@patch.object(hooks, 'ensure_pki_cert_paths')
|
||||
@patch.object(hooks, 'ensure_pki_dir_permissions')
|
||||
@patch.object(hooks, 'ensure_ssl_dir')
|
||||
@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, 'cluster_joined')
|
||||
@patch.object(unison, 'ensure_user')
|
||||
@patch.object(unison, 'get_homedir')
|
||||
@patch.object(hooks, 'CONFIGS')
|
||||
@patch.object(hooks, 'identity_changed')
|
||||
@patch.object(hooks, 'configure_https')
|
||||
def test_config_changed_no_upgrade_leader(self, configure_https,
|
||||
identity_changed,
|
||||
configs, get_homedir,
|
||||
ensure_user,
|
||||
configs,
|
||||
mock_cluster_joined,
|
||||
admin_relation_changed,
|
||||
mock_peer_units,
|
||||
mock_send_ssl_sync_request,
|
||||
mock_is_ssl_cert_master,
|
||||
mock_ensure_ssl_dir,
|
||||
mock_ensure_pki_cert_paths,
|
||||
mock_ensure_permissions,
|
||||
mock_ensure_pki_dir_permissions,
|
||||
mock_ensure_ssl_dirs,
|
||||
mock_ensure_ssl_cert_master,
|
||||
mock_log,
|
||||
mock_is_db_initialised,
|
||||
mock_run_in_apache,
|
||||
|
@ -282,19 +240,12 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
self.relation_ids.side_effect = fake_relation_ids
|
||||
|
||||
mock_run_in_apache.return_value = False
|
||||
mock_is_ssl_cert_master.return_value = True
|
||||
mock_is_db_initialised.return_value = True
|
||||
self.is_db_ready.return_value = True
|
||||
self.openstack_upgrade_available.return_value = False
|
||||
self.is_elected_leader.return_value = True
|
||||
# avoid having to mock syncer
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
mock_peer_units.return_value = []
|
||||
self.related_units.return_value = ['unit/0']
|
||||
|
||||
hooks.config_changed()
|
||||
ensure_user.assert_called_with(user=self.ssh_user, group='keystone')
|
||||
get_homedir.assert_called_with(self.ssh_user)
|
||||
|
||||
self.save_script_rc.assert_called_with()
|
||||
configure_https.assert_called_with()
|
||||
|
@ -309,33 +260,14 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
@patch.object(hooks, 'update_all_identity_relation_units')
|
||||
@patch.object(hooks, 'run_in_apache')
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch('keystone_utils.ensure_ssl_dirs')
|
||||
@patch.object(hooks, 'ensure_permissions')
|
||||
@patch.object(hooks, 'ensure_pki_cert_paths')
|
||||
@patch.object(hooks, 'ensure_pki_dir_permissions')
|
||||
@patch.object(hooks, 'ensure_ssl_dir')
|
||||
@patch.object(hooks, 'peer_units')
|
||||
@patch.object(hooks, 'is_ssl_cert_master')
|
||||
@patch.object(hooks, 'cluster_joined')
|
||||
@patch.object(unison, 'ensure_user')
|
||||
@patch.object(unison, 'get_homedir')
|
||||
@patch.object(hooks, 'CONFIGS')
|
||||
@patch.object(hooks, 'identity_changed')
|
||||
@patch.object(hooks, 'configure_https')
|
||||
def test_config_changed_no_upgrade_not_leader(self, configure_https,
|
||||
identity_changed,
|
||||
configs, get_homedir,
|
||||
ensure_user,
|
||||
configs,
|
||||
mock_cluster_joined,
|
||||
mock_is_ssl_cert_master,
|
||||
mock_peer_units,
|
||||
mock_ensure_ssl_dir,
|
||||
mock_ensure_permissions,
|
||||
mock_ensure_pki_cert_paths,
|
||||
mock_ensure_pki_permissions,
|
||||
ensure_ssl_dirs,
|
||||
mock_ensure_ssl_cert_master,
|
||||
mock_log,
|
||||
mock_run_in_apache, update,
|
||||
mock_update_domains):
|
||||
|
@ -349,15 +281,9 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
self.relation_ids.side_effect = fake_relation_ids
|
||||
|
||||
mock_run_in_apache.return_value = False
|
||||
mock_is_ssl_cert_master.return_value = True
|
||||
mock_peer_units.return_value = []
|
||||
self.openstack_upgrade_available.return_value = False
|
||||
self.is_elected_leader.return_value = False
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
|
||||
hooks.config_changed()
|
||||
ensure_user.assert_called_with(user=self.ssh_user, group='keystone')
|
||||
get_homedir.assert_called_with(self.ssh_user)
|
||||
|
||||
self.assertFalse(mock_cluster_joined.called)
|
||||
self.save_script_rc.assert_called_with()
|
||||
|
@ -373,36 +299,16 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
@patch.object(hooks, 'run_in_apache')
|
||||
@patch.object(hooks, 'is_db_initialised')
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch('keystone_utils.ensure_ssl_dirs')
|
||||
@patch.object(hooks, 'ensure_permissions')
|
||||
@patch.object(hooks, 'ensure_pki_cert_paths')
|
||||
@patch.object(hooks, 'ensure_pki_dir_permissions')
|
||||
@patch.object(hooks, 'ensure_ssl_dir')
|
||||
@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, 'cluster_joined')
|
||||
@patch.object(unison, 'ensure_user')
|
||||
@patch.object(unison, 'get_homedir')
|
||||
@patch.object(hooks, 'CONFIGS')
|
||||
@patch.object(hooks, 'identity_changed')
|
||||
@patch.object(hooks, 'configure_https')
|
||||
def test_config_changed_with_openstack_upgrade(self, configure_https,
|
||||
identity_changed,
|
||||
configs, get_homedir,
|
||||
ensure_user, cluster_joined,
|
||||
configs,
|
||||
cluster_joined,
|
||||
admin_relation_changed,
|
||||
mock_peer_units,
|
||||
mock_send_ssl_sync_request,
|
||||
mock_is_ssl_cert_master,
|
||||
mock_ensure_ssl_dir,
|
||||
mock_ensure_permissions,
|
||||
mock_ensure_pki_cert_paths,
|
||||
mock_ensure_pki_permissions,
|
||||
mock_ensure_ssl_dirs,
|
||||
mock_ensure_ssl_cert_master,
|
||||
mock_log,
|
||||
mock_is_db_initialised,
|
||||
mock_run_in_apache,
|
||||
|
@ -417,19 +323,12 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
self.relation_ids.side_effect = fake_relation_ids
|
||||
|
||||
mock_run_in_apache.return_value = False
|
||||
mock_is_ssl_cert_master.return_value = True
|
||||
self.is_db_ready.return_value = True
|
||||
mock_is_db_initialised.return_value = True
|
||||
self.openstack_upgrade_available.return_value = True
|
||||
self.is_elected_leader.return_value = True
|
||||
# avoid having to mock syncer
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
mock_peer_units.return_value = []
|
||||
self.related_units.return_value = ['unit/0']
|
||||
|
||||
hooks.config_changed()
|
||||
ensure_user.assert_called_with(user=self.ssh_user, group='keystone')
|
||||
get_homedir.assert_called_with(self.ssh_user)
|
||||
|
||||
self.assertTrue(self.do_openstack_upgrade_reexec.called)
|
||||
|
||||
|
@ -442,31 +341,16 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
|
||||
@patch.object(hooks, 'os_release')
|
||||
@patch.object(hooks, 'run_in_apache')
|
||||
@patch.object(hooks, 'initialise_pki')
|
||||
@patch.object(hooks, 'is_db_initialised')
|
||||
@patch.object(hooks, 'ensure_ssl_dir')
|
||||
@patch.object(hooks, 'configure_https')
|
||||
@patch.object(hooks, 'is_ssl_cert_master')
|
||||
@patch.object(hooks, 'peer_units')
|
||||
@patch.object(unison, 'get_homedir')
|
||||
@patch.object(unison, 'ensure_user')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
def test_config_changed_with_openstack_upgrade_action(self,
|
||||
ensure_ssl_cert,
|
||||
ensure_user,
|
||||
get_home,
|
||||
peer_units, is_ssl,
|
||||
config_https,
|
||||
ensure_ssl_dir,
|
||||
mock_db_init,
|
||||
mock_initialise_pki,
|
||||
mock_run_in_apache,
|
||||
os_release):
|
||||
os_release.return_value = 'ocata'
|
||||
self.enable_memcache.return_value = False
|
||||
mock_run_in_apache.return_value = False
|
||||
ensure_ssl_cert.return_value = False
|
||||
peer_units.return_value = []
|
||||
|
||||
self.openstack_upgrade_available.return_value = True
|
||||
self.test_config.set('action-managed-upgrade', True)
|
||||
|
@ -477,17 +361,18 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
|
||||
@patch.object(hooks, 'is_db_initialised')
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch.object(hooks, 'hashlib')
|
||||
@patch.object(hooks, 'send_notifications')
|
||||
def test_identity_changed_leader(self, mock_send_notifications,
|
||||
mock_hashlib, mock_ensure_ssl_cert_master,
|
||||
mock_log, mock_is_db_initialised):
|
||||
self.expect_ha.return_value = False
|
||||
mock_is_db_initialised.return_value = True
|
||||
self.is_db_ready.return_value = True
|
||||
self.is_service_present.return_value = True
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
self.relation_get.return_value = {
|
||||
'public_url': 'http://dummy.local',
|
||||
'admin_url': 'http://dummy.local',
|
||||
'internal_url': 'http://dummy.local',
|
||||
}
|
||||
hooks.identity_changed(
|
||||
relation_id='identity-service:0',
|
||||
remote_unit='unit/0')
|
||||
|
@ -500,31 +385,26 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
|
||||
@patch.object(hooks, 'is_db_initialised')
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch.object(hooks, 'hashlib')
|
||||
@patch.object(hooks, 'send_notifications')
|
||||
def test_identity_changed_leader_no_neutron(self, mock_send_notifications,
|
||||
mock_hashlib,
|
||||
mock_ensure_ssl_cert_master,
|
||||
mock_log,
|
||||
mock_is_db_initialised):
|
||||
self.expect_ha.return_value = False
|
||||
mock_is_db_initialised.return_value = True
|
||||
self.is_db_ready.return_value = True
|
||||
self.is_service_present.return_value = False
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
self.relation_get.return_value = {
|
||||
'public_url': 'http://dummy.local',
|
||||
'admin_url': 'http://dummy.local',
|
||||
'internal_url': 'http://dummy.local',
|
||||
}
|
||||
hooks.identity_changed(
|
||||
relation_id='identity-service:0',
|
||||
remote_unit='unit/0')
|
||||
self.assertFalse(self.delete_service_entry.called)
|
||||
|
||||
@patch.object(hooks, 'local_unit')
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
def test_identity_changed_no_leader(self, mock_ensure_ssl_cert_master,
|
||||
mock_log, mock_local_unit):
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
mock_local_unit.return_value = 'unit/0'
|
||||
def test_identity_changed_no_leader(self, mock_log):
|
||||
self.is_elected_leader.return_value = False
|
||||
hooks.identity_changed(
|
||||
relation_id='identity-service:0',
|
||||
|
@ -533,60 +413,18 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
self.log.assert_called_with(
|
||||
'Deferring identity_changed() to service leader.')
|
||||
|
||||
@patch.object(hooks, 'send_ssl_sync_request')
|
||||
@patch.object(hooks, 'local_unit')
|
||||
@patch.object(hooks, 'peer_units')
|
||||
@patch.object(unison, 'ssh_authorized_peers')
|
||||
def test_cluster_joined(self, ssh_authorized_peers, mock_peer_units,
|
||||
mock_local_unit, mock_send_ssl_sync_request):
|
||||
mock_local_unit.return_value = 'unit/0'
|
||||
mock_peer_units.return_value = ['unit/0']
|
||||
hooks.cluster_joined()
|
||||
ssh_authorized_peers.assert_called_with(
|
||||
user=self.ssh_user, group='juju_keystone',
|
||||
peer_interface='cluster', ensure_local_user=True)
|
||||
self.assertTrue(mock_send_ssl_sync_request.called)
|
||||
|
||||
mock_send_ssl_sync_request.reset_mock()
|
||||
hooks.cluster_joined(rid='foo:1', ssl_sync_request=True)
|
||||
self.assertTrue(mock_send_ssl_sync_request.called)
|
||||
|
||||
mock_send_ssl_sync_request.reset_mock()
|
||||
hooks.cluster_joined(rid='foo:1', ssl_sync_request=False)
|
||||
self.assertFalse(mock_send_ssl_sync_request.called)
|
||||
|
||||
@patch.object(hooks, 'relation_get_and_migrate')
|
||||
@patch.object(hooks, 'initialise_pki')
|
||||
@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, 'peer_units')
|
||||
@patch('keystone_utils.relation_ids')
|
||||
@patch('keystone_utils.config')
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch('keystone_utils.synchronize_ca')
|
||||
@patch.object(hooks, 'check_peer_actions')
|
||||
@patch.object(unison, 'ssh_authorized_peers')
|
||||
@patch.object(hooks, 'CONFIGS')
|
||||
def test_cluster_changed(self, configs, ssh_authorized_peers,
|
||||
check_peer_actions, mock_synchronize_ca,
|
||||
mock_ensure_ssl_cert_master,
|
||||
def test_cluster_changed(self, configs,
|
||||
mock_log, mock_config, mock_relation_ids,
|
||||
mock_peer_units,
|
||||
mock_is_ssl_cert_master,
|
||||
mock_get_ssl_sync_request_units,
|
||||
mock_update_all_identity_relation_units,
|
||||
mock_initialise_pki,
|
||||
mock_relation_get_and_migrate):
|
||||
mock_update_all_identity_relation_units):
|
||||
|
||||
relation_settings = {'foo_passwd': '123',
|
||||
'identity-service:16_foo': 'bar'}
|
||||
|
||||
mock_relation_get_and_migrate.return_value = None
|
||||
mock_is_ssl_cert_master.return_value = False
|
||||
mock_peer_units.return_value = ['unit/0']
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
mock_relation_ids.return_value = []
|
||||
self.is_leader.return_value = False
|
||||
|
||||
|
@ -601,13 +439,8 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
mock_config.return_value = None
|
||||
|
||||
hooks.cluster_changed()
|
||||
whitelist = ['_passwd', 'identity-service:', 'db-initialised',
|
||||
'ssl-cert-available-updates', 'ssl-cert-master']
|
||||
whitelist = ['_passwd', 'identity-service:', 'db-initialised']
|
||||
self.peer_echo.assert_called_with(force=True, includes=whitelist)
|
||||
ssh_authorized_peers.assert_called_with(
|
||||
user=self.ssh_user, group='juju_keystone',
|
||||
peer_interface='cluster', ensure_local_user=True)
|
||||
self.assertFalse(mock_synchronize_ca.called)
|
||||
self.assertTrue(configs.write_all.called)
|
||||
|
||||
@patch.object(hooks, 'update_all_identity_relation_units')
|
||||
|
@ -786,54 +619,40 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
self.assertTrue(self.update_dns_ha_resource_params.called)
|
||||
self.relation_set.assert_called_with(**args)
|
||||
|
||||
@patch.object(utils, 'peer_retrieve')
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch('keystone_utils.synchronize_ca')
|
||||
@patch.object(hooks, 'CONFIGS')
|
||||
def test_ha_relation_changed_not_clustered_not_leader(self, configs,
|
||||
mock_synchronize_ca,
|
||||
mock_is_master,
|
||||
mock_log):
|
||||
mock_is_master.return_value = False
|
||||
mock_log,
|
||||
mock_peer_retrieve):
|
||||
self.relation_get.return_value = False
|
||||
self.is_elected_leader.return_value = False
|
||||
|
||||
hooks.ha_changed()
|
||||
self.assertTrue(configs.write_all.called)
|
||||
self.assertFalse(mock_synchronize_ca.called)
|
||||
|
||||
@patch.object(hooks, 'is_ssl_cert_master')
|
||||
@patch.object(hooks, 'update_all_identity_relation_units_force_sync')
|
||||
@patch.object(hooks, 'update_all_identity_relation_units')
|
||||
@patch.object(hooks, 'is_db_initialised')
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch.object(hooks, 'identity_changed')
|
||||
@patch.object(hooks, 'CONFIGS')
|
||||
def test_ha_relation_changed_clustered_leader(self, configs,
|
||||
identity_changed,
|
||||
mock_ensure_ssl_cert_master,
|
||||
mock_log,
|
||||
mock_is_db_initialised,
|
||||
update, cert_master):
|
||||
update):
|
||||
mock_is_db_initialised.return_value = True
|
||||
self.is_db_ready.return_value = True
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
self.relation_get.return_value = True
|
||||
self.is_elected_leader.return_value = True
|
||||
self.relation_ids.return_value = ['identity-service:0']
|
||||
self.related_units.return_value = ['unit/0']
|
||||
cert_master.return_value = True
|
||||
|
||||
hooks.ha_changed()
|
||||
self.assertTrue(configs.write_all.called)
|
||||
self.assertTrue(update.called)
|
||||
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch.object(hooks, 'CONFIGS')
|
||||
def test_configure_https_enable(self, configs, mock_ensure_ssl_cert_master,
|
||||
mock_log):
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
def test_configure_https_enable(self, configs, mock_log):
|
||||
configs.complete_contexts = MagicMock()
|
||||
configs.complete_contexts.return_value = ['https']
|
||||
configs.write = MagicMock()
|
||||
|
@ -844,12 +663,8 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
self.check_call.assert_called_with(cmd)
|
||||
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch.object(hooks, 'CONFIGS')
|
||||
def test_configure_https_disable(self, configs,
|
||||
mock_ensure_ssl_cert_master,
|
||||
mock_log):
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
def test_configure_https_disable(self, configs, mock_log):
|
||||
configs.complete_contexts = MagicMock()
|
||||
configs.complete_contexts.return_value = ['']
|
||||
configs.write = MagicMock()
|
||||
|
@ -865,16 +680,7 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
@patch.object(hooks, 'is_db_initialised')
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.relation_ids')
|
||||
@patch('keystone_utils.is_elected_leader')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch('keystone_utils.update_hash_from_path')
|
||||
@patch('keystone_utils.synchronize_ca')
|
||||
@patch.object(unison, 'ssh_authorized_peers')
|
||||
def test_upgrade_charm_leader(self, ssh_authorized_peers,
|
||||
mock_synchronize_ca,
|
||||
mock_update_hash_from_path,
|
||||
mock_ensure_ssl_cert_master,
|
||||
mock_is_elected_leader,
|
||||
def test_upgrade_charm_leader(self,
|
||||
mock_relation_ids,
|
||||
mock_log,
|
||||
mock_is_db_initialised,
|
||||
|
@ -884,21 +690,11 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
os_release.return_value = 'havana'
|
||||
mock_is_db_initialised.return_value = True
|
||||
mock_is_db_ready.return_value = True
|
||||
mock_is_elected_leader.return_value = False
|
||||
mock_relation_ids.return_value = []
|
||||
mock_ensure_ssl_cert_master.return_value = True
|
||||
# Ensure always returns diff
|
||||
mock_update_hash_from_path.side_effect = \
|
||||
lambda hash, *args, **kwargs: hash.update(str(uuid.uuid4()))
|
||||
|
||||
self.is_elected_leader.return_value = True
|
||||
self.filter_installed_packages.return_value = []
|
||||
hooks.upgrade_charm()
|
||||
self.assertTrue(self.apt_install.called)
|
||||
ssh_authorized_peers.assert_called_with(
|
||||
user=self.ssh_user, group='juju_keystone',
|
||||
peer_interface='cluster', ensure_local_user=True)
|
||||
self.assertTrue(mock_synchronize_ca.called)
|
||||
self.assertTrue(update.called)
|
||||
|
||||
@patch.object(hooks, 'update_all_identity_relation_units')
|
||||
|
@ -1039,33 +835,22 @@ class KeystoneRelationTests(CharmTestCase):
|
|||
# Still updates relations
|
||||
self.assertTrue(self.relation_ids.called)
|
||||
|
||||
@patch.object(utils, 'peer_retrieve')
|
||||
@patch.object(hooks, 'update_all_identity_relation_units')
|
||||
@patch.object(utils, 'os_release')
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.relation_ids')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch('keystone_utils.update_hash_from_path')
|
||||
@patch.object(unison, 'ssh_authorized_peers')
|
||||
def test_upgrade_charm_not_leader(self, ssh_authorized_peers,
|
||||
mock_update_hash_from_path,
|
||||
mock_ensure_ssl_cert_master,
|
||||
def test_upgrade_charm_not_leader(self,
|
||||
mock_relation_ids,
|
||||
mock_log,
|
||||
os_release, update):
|
||||
os_release, update, mock_peer_retrieve):
|
||||
os_release.return_value = 'havana'
|
||||
mock_relation_ids.return_value = []
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
# Ensure always returns diff
|
||||
mock_update_hash_from_path.side_effect = \
|
||||
lambda hash, *args, **kwargs: hash.update(str(uuid.uuid4()))
|
||||
|
||||
self.is_elected_leader.return_value = False
|
||||
self.filter_installed_packages.return_value = []
|
||||
mock_peer_retrieve.return_value = 'true'
|
||||
self.is_elected_leader.return_value = False
|
||||
hooks.upgrade_charm()
|
||||
self.assertTrue(self.apt_install.called)
|
||||
ssh_authorized_peers.assert_called_with(
|
||||
user=self.ssh_user, group='juju_keystone',
|
||||
peer_interface='cluster', ensure_local_user=True)
|
||||
self.assertTrue(self.log.called)
|
||||
self.assertFalse(update.called)
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
from mock import patch, call, MagicMock
|
||||
from test_utils import CharmTestCase
|
||||
import os
|
||||
from base64 import b64encode
|
||||
import subprocess
|
||||
|
||||
os.environ['JUJU_UNIT_NAME'] = 'keystone'
|
||||
|
@ -30,7 +29,6 @@ TO_PATCH = [
|
|||
'config',
|
||||
'os_release',
|
||||
'log',
|
||||
'get_ca',
|
||||
'create_role',
|
||||
'create_service_entry',
|
||||
'create_endpoint_template',
|
||||
|
@ -41,12 +39,9 @@ TO_PATCH = [
|
|||
'get_os_codename_install_source',
|
||||
'grant_role',
|
||||
'configure_installation_source',
|
||||
'is_elected_leader',
|
||||
'is_ssl_cert_master',
|
||||
'https',
|
||||
'lsb_release',
|
||||
'peer_store_and_set',
|
||||
'service_restart',
|
||||
'service_stop',
|
||||
'service_start',
|
||||
'snap_install_requested',
|
||||
|
@ -66,7 +61,6 @@ TO_PATCH = [
|
|||
'time',
|
||||
'pwgen',
|
||||
'os_application_version_set',
|
||||
'is_leader',
|
||||
'reset_os_release',
|
||||
]
|
||||
|
||||
|
@ -174,6 +168,7 @@ class TestKeystoneUtils(CharmTestCase):
|
|||
ex = utils.BASE_PACKAGES_SNAP + ['memcached']
|
||||
self.assertEqual(set(ex), set(result))
|
||||
|
||||
@patch.object(utils, 'is_elected_leader')
|
||||
@patch.object(utils, 'disable_unused_apache_sites')
|
||||
@patch('os.path.exists')
|
||||
@patch.object(utils, 'run_in_apache')
|
||||
|
@ -181,11 +176,11 @@ class TestKeystoneUtils(CharmTestCase):
|
|||
@patch.object(utils, 'migrate_database')
|
||||
def test_openstack_upgrade_leader(
|
||||
self, migrate_database, determine_packages,
|
||||
run_in_apache, os_path_exists, disable_unused_apache_sites):
|
||||
run_in_apache, os_path_exists, disable_unused_apache_sites,
|
||||
mock_is_elected_leader):
|
||||
configs = MagicMock()
|
||||
self.test_config.set('openstack-origin', 'cloud:xenial-newton')
|
||||
determine_packages.return_value = []
|
||||
self.is_elected_leader.return_value = True
|
||||
os_path_exists.return_value = True
|
||||
run_in_apache.return_value = True
|
||||
|
||||
|
@ -231,9 +226,8 @@ class TestKeystoneUtils(CharmTestCase):
|
|||
@patch.object(utils, 'get_api_version')
|
||||
@patch.object(utils, 'get_manager')
|
||||
@patch.object(utils, 'resolve_address')
|
||||
@patch.object(utils, 'b64encode')
|
||||
def test_add_service_to_keystone_clustered_https_none_values(
|
||||
self, b64encode, _resolve_address, _get_manager,
|
||||
self, _resolve_address, _get_manager,
|
||||
_get_api_version, _leader_get):
|
||||
_get_api_version.return_value = 2
|
||||
_leader_get.return_value = None
|
||||
|
@ -241,11 +235,9 @@ class TestKeystoneUtils(CharmTestCase):
|
|||
remote_unit = 'unit/0'
|
||||
_resolve_address.return_value = '10.10.10.10'
|
||||
self.https.return_value = True
|
||||
self.test_config.set('https-service-endpoints', 'True')
|
||||
self.test_config.set('vip', '10.10.10.10')
|
||||
self.test_config.set('admin-port', 80)
|
||||
self.test_config.set('service-port', 81)
|
||||
b64encode.return_value = 'certificate'
|
||||
self.get_requested_roles.return_value = ['role1', ]
|
||||
|
||||
self.relation_get.return_value = {'service': 'keystone',
|
||||
|
@ -262,12 +254,10 @@ class TestKeystoneUtils(CharmTestCase):
|
|||
|
||||
relation_data = {'auth_host': '10.10.10.10',
|
||||
'service_host': '10.10.10.10',
|
||||
'auth_protocol': 'https',
|
||||
'service_protocol': 'https',
|
||||
'auth_port': 80,
|
||||
'auth_protocol': 'https',
|
||||
'service_port': 81,
|
||||
'https_keystone': 'True',
|
||||
'ca_cert': 'certificate',
|
||||
'region': 'RegionOne',
|
||||
'api_version': 2,
|
||||
'admin_domain_id': None}
|
||||
|
@ -649,12 +639,15 @@ class TestKeystoneUtils(CharmTestCase):
|
|||
mock_relation_set.assert_called_once_with(relation_id=relation_id,
|
||||
relation_settings=settings)
|
||||
|
||||
@patch.object(utils, 'is_elected_leader')
|
||||
@patch.object(utils, 'peer_retrieve')
|
||||
@patch.object(utils, 'peer_store')
|
||||
def test_get_admin_passwd_pwd_set(self, mock_peer_store,
|
||||
mock_peer_retrieve):
|
||||
mock_peer_retrieve,
|
||||
mock_is_elected_leader):
|
||||
mock_peer_retrieve.return_value = None
|
||||
self.test_config.set('admin-password', 'supersecret')
|
||||
mock_is_elected_leader.return_value = True
|
||||
self.assertEqual(utils.get_admin_passwd(), 'supersecret')
|
||||
mock_peer_store.assert_called_once_with('admin_passwd', 'supersecret')
|
||||
|
||||
|
@ -702,96 +695,6 @@ class TestKeystoneUtils(CharmTestCase):
|
|||
self.related_units.return_value = []
|
||||
self.assertTrue(utils.is_db_ready())
|
||||
|
||||
@patch.object(utils, 'peer_units')
|
||||
def test_ensure_ssl_cert_master_ssl_no_peers(self, mock_peer_units):
|
||||
def mock_rel_get(unit=None, **kwargs):
|
||||
return None
|
||||
|
||||
self.relation_get.side_effect = mock_rel_get
|
||||
self.relation_ids.return_value = ['cluster:0']
|
||||
self.local_unit.return_value = 'unit/0'
|
||||
self.related_units.return_value = []
|
||||
mock_peer_units.return_value = []
|
||||
# This should get ignored since we are overriding
|
||||
self.is_ssl_cert_master.return_value = False
|
||||
self.is_elected_leader.return_value = False
|
||||
self.assertTrue(utils.ensure_ssl_cert_master())
|
||||
settings = {'ssl-cert-master': 'unit/0'}
|
||||
self.relation_set.assert_called_with(relation_id='cluster:0',
|
||||
relation_settings=settings)
|
||||
|
||||
@patch.object(utils, 'peer_units')
|
||||
def test_ensure_ssl_cert_master_ssl_master_no_peers(self,
|
||||
mock_peer_units):
|
||||
def mock_rel_get(unit=None, **kwargs):
|
||||
if unit == 'unit/0':
|
||||
return 'unit/0'
|
||||
|
||||
return None
|
||||
|
||||
self.relation_get.side_effect = mock_rel_get
|
||||
self.relation_ids.return_value = ['cluster:0']
|
||||
self.local_unit.return_value = 'unit/0'
|
||||
self.related_units.return_value = []
|
||||
mock_peer_units.return_value = []
|
||||
# This should get ignored since we are overriding
|
||||
self.is_ssl_cert_master.return_value = False
|
||||
self.is_elected_leader.return_value = False
|
||||
self.assertTrue(utils.ensure_ssl_cert_master())
|
||||
settings = {'ssl-cert-master': 'unit/0'}
|
||||
self.relation_set.assert_called_with(relation_id='cluster:0',
|
||||
relation_settings=settings)
|
||||
|
||||
@patch.object(utils, 'peer_units')
|
||||
def test_ensure_ssl_cert_master_ssl_not_leader(self, mock_peer_units):
|
||||
self.relation_ids.return_value = ['cluster:0']
|
||||
self.local_unit.return_value = 'unit/0'
|
||||
mock_peer_units.return_value = ['unit/1']
|
||||
self.is_ssl_cert_master.return_value = False
|
||||
self.is_elected_leader.return_value = False
|
||||
self.assertFalse(utils.ensure_ssl_cert_master())
|
||||
self.assertFalse(self.relation_set.called)
|
||||
|
||||
@patch.object(utils, 'peer_units')
|
||||
def test_ensure_ssl_cert_master_is_leader_new_peer(self,
|
||||
mock_peer_units):
|
||||
def mock_rel_get(unit=None, **kwargs):
|
||||
if unit == 'unit/0':
|
||||
return 'unit/0'
|
||||
|
||||
return 'unknown'
|
||||
|
||||
self.relation_get.side_effect = mock_rel_get
|
||||
self.relation_ids.return_value = ['cluster:0']
|
||||
self.local_unit.return_value = 'unit/0'
|
||||
mock_peer_units.return_value = ['unit/1']
|
||||
self.related_units.return_value = ['unit/1']
|
||||
self.is_ssl_cert_master.return_value = False
|
||||
self.is_elected_leader.return_value = True
|
||||
self.assertFalse(utils.ensure_ssl_cert_master())
|
||||
settings = {'ssl-cert-master': 'unit/0'}
|
||||
self.relation_set.assert_called_with(relation_id='cluster:0',
|
||||
relation_settings=settings)
|
||||
|
||||
@patch.object(utils, 'peer_units')
|
||||
def test_ensure_ssl_cert_master_is_leader_no_new_peer(self,
|
||||
mock_peer_units):
|
||||
def mock_rel_get(unit=None, **kwargs):
|
||||
if unit == 'unit/0':
|
||||
return 'unit/0'
|
||||
|
||||
return 'unit/0'
|
||||
|
||||
self.relation_get.side_effect = mock_rel_get
|
||||
self.relation_ids.return_value = ['cluster:0']
|
||||
self.local_unit.return_value = 'unit/0'
|
||||
mock_peer_units.return_value = ['unit/1']
|
||||
self.related_units.return_value = ['unit/1']
|
||||
self.is_ssl_cert_master.return_value = False
|
||||
self.is_elected_leader.return_value = True
|
||||
self.assertFalse(utils.ensure_ssl_cert_master())
|
||||
self.assertFalse(self.relation_set.called)
|
||||
|
||||
@patch.object(utils, 'leader_set')
|
||||
@patch.object(utils, 'leader_get')
|
||||
@patch('charmhelpers.contrib.openstack.ip.unit_get')
|
||||
|
@ -826,30 +729,6 @@ class TestKeystoneUtils(CharmTestCase):
|
|||
region='RegionOne',
|
||||
)
|
||||
|
||||
@patch.object(utils, 'peer_units')
|
||||
def test_ensure_ssl_cert_master_is_leader_bad_votes(self,
|
||||
mock_peer_units):
|
||||
counter = {0: 0}
|
||||
|
||||
def mock_rel_get(unit=None, **kwargs):
|
||||
"""Returns a mix of votes."""
|
||||
if unit == 'unit/0':
|
||||
return 'unit/0'
|
||||
|
||||
ret = 'unit/%d' % (counter[0])
|
||||
counter[0] += 1
|
||||
return ret
|
||||
|
||||
self.relation_get.side_effect = mock_rel_get
|
||||
self.relation_ids.return_value = ['cluster:0']
|
||||
self.local_unit.return_value = 'unit/0'
|
||||
mock_peer_units.return_value = ['unit/1']
|
||||
self.related_units.return_value = ['unit/1']
|
||||
self.is_ssl_cert_master.return_value = False
|
||||
self.is_elected_leader.return_value = True
|
||||
self.assertFalse(utils.ensure_ssl_cert_master())
|
||||
self.assertFalse(self.relation_set.called)
|
||||
|
||||
@patch.object(utils, 'get_manager')
|
||||
def test_is_service_present(self, KeystoneManager):
|
||||
mock_keystone = MagicMock()
|
||||
|
@ -1006,16 +885,6 @@ class TestKeystoneUtils(CharmTestCase):
|
|||
protocol = utils.get_protocol()
|
||||
self.assertEqual(protocol, 'https')
|
||||
|
||||
def test_get_ssl_ca_settings(self):
|
||||
CA = MagicMock()
|
||||
CA.get_ca_bundle.return_value = 'certstring'
|
||||
self.test_config.set('https-service-endpoints', 'True')
|
||||
self.get_ca.return_value = CA
|
||||
expected_settings = {'https_keystone': 'True',
|
||||
'ca_cert': b64encode('certstring')}
|
||||
settings = utils.get_ssl_ca_settings()
|
||||
self.assertEqual(settings, expected_settings)
|
||||
|
||||
@patch.object(utils, 'get_manager')
|
||||
def test_add_credentials_keystone_not_ready(self, get_manager):
|
||||
""" Verify add_credentials_to_keystone when the relation
|
||||
|
@ -1201,67 +1070,6 @@ class TestKeystoneUtils(CharmTestCase):
|
|||
self.peer_store_and_set.assert_called_with(relation_id=relation_id,
|
||||
**relation_data)
|
||||
|
||||
@patch.object(utils, 'set_service_password')
|
||||
@patch.object(utils, 'get_service_password')
|
||||
@patch.object(utils, 'get_ssl_ca_settings')
|
||||
@patch.object(utils, 'create_user_credentials')
|
||||
@patch.object(utils, 'get_protocol')
|
||||
@patch.object(utils, 'resolve_address')
|
||||
@patch.object(utils, 'get_api_version')
|
||||
@patch.object(utils, 'get_manager')
|
||||
def test_add_credentials_keystone_ssl(self, get_manager,
|
||||
get_api_version,
|
||||
resolve_address,
|
||||
get_protocol,
|
||||
create_user_credentials,
|
||||
get_ssl_ca_settings,
|
||||
get_callback, set_callback):
|
||||
""" Verify add_credentials with SSL """
|
||||
manager = MagicMock()
|
||||
manager.resolve_tenant_id.return_value = 'abcdef0123456789'
|
||||
get_manager.return_value = manager
|
||||
remote_unit = 'unit/0'
|
||||
relation_id = 'identity-credentials:0'
|
||||
get_api_version.return_value = 2
|
||||
get_protocol.return_value = 'https'
|
||||
resolve_address.return_value = '10.10.10.10'
|
||||
create_user_credentials.return_value = 'password'
|
||||
get_ssl_ca_settings.return_value = {'https_keystone': 'True',
|
||||
'ca_cert': 'base64certstring'}
|
||||
self.relation_get.return_value = {'username': 'requester'}
|
||||
self.get_service_password.return_value = 'password'
|
||||
self.get_requested_roles.return_value = []
|
||||
self.test_config.set('admin-port', 80)
|
||||
self.test_config.set('service-port', 81)
|
||||
self.test_config.set('https-service-endpoints', 'True')
|
||||
relation_data = {'auth_host': '10.10.10.10',
|
||||
'credentials_host': '10.10.10.10',
|
||||
'credentials_port': 81,
|
||||
'auth_port': 80,
|
||||
'auth_protocol': 'https',
|
||||
'credentials_username': 'requester',
|
||||
'credentials_protocol': 'https',
|
||||
'credentials_password': 'password',
|
||||
'credentials_project': 'services',
|
||||
'credentials_project_id': 'abcdef0123456789',
|
||||
'region': 'RegionOne',
|
||||
'api_version': 2,
|
||||
'https_keystone': 'True',
|
||||
'ca_cert': 'base64certstring'}
|
||||
|
||||
utils.add_credentials_to_keystone(
|
||||
relation_id=relation_id,
|
||||
remote_unit=remote_unit)
|
||||
create_user_credentials.assert_called_with('requester',
|
||||
get_callback,
|
||||
set_callback,
|
||||
domain=None,
|
||||
new_roles=[],
|
||||
grants=['Admin'],
|
||||
tenant='services')
|
||||
self.peer_store_and_set.assert_called_with(relation_id=relation_id,
|
||||
**relation_data)
|
||||
|
||||
@patch.object(utils.os, 'remove')
|
||||
@patch.object(utils.os.path, 'exists')
|
||||
def test_disable_unused_apache_sites(self, os_path_exists, os_remove):
|
||||
|
|
Loading…
Reference in New Issue