charm-keystone-ldap/src/lib/charm/openstack/keystone_ldap.py

178 lines
6.0 KiB
Python

#
# Copyright 2017 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 charmhelpers.core as core
import charmhelpers.core.host as ch_host
import charmhelpers.core.hookenv as hookenv
import charmhelpers.contrib.openstack.templating as os_templating
import charmhelpers.contrib.openstack.utils as os_utils
import charms_openstack.charm
import charms_openstack.adapters
import os
# release detection is done via keystone package given that
# openstack-origin is not present in the subordinate charm
# see https://github.com/juju/charm-helpers/issues/83
import charmhelpers.core.unitdata as unitdata
from charms_openstack.charm.core import (
register_os_release_selector
)
OPENSTACK_RELEASE_KEY = 'charmers.openstack-release-version'
DOMAIN_CONF = "/etc/keystone/domains/keystone.{}.conf"
BACKEND_CA_CERT = "/usr/share/ca-certificates/{}.crt"
KEYSTONE_CONF_TEMPLATE = "keystone.conf"
@register_os_release_selector
def select_release():
"""Determine the release based on the keystone package version.
Note that this function caches the release after the first install so
that it doesn't need to keep going and getting it from the package
information.
"""
release_version = unitdata.kv().get(OPENSTACK_RELEASE_KEY, None)
if release_version is None:
release_version = os_utils.os_release('keystone')
unitdata.kv().set(OPENSTACK_RELEASE_KEY, release_version)
return release_version
class KeystoneLDAPConfigurationAdapter(
charms_openstack.adapters.ConfigurationAdapter):
'''Charm specific configuration adapter to deal with ldap
config flag parsing
'''
@property
def ldap_options(self):
return os_utils.config_flags_parser(
hookenv.config('ldap-config-flags')
)
@property
def backend_ca_file(self):
return BACKEND_CA_CERT.format(hookenv.service_name())
@property
def use_tls(self):
ldap_srv = hookenv.config('ldap-server')
return not ldap_srv.startswith('ldaps') if ldap_srv else False
class KeystoneLDAPCharm(charms_openstack.charm.OpenStackCharm):
# Internal name of charm
service_name = name = 'keystone-ldap'
# Package to derive application version from
version_package = 'keystone'
# First release supported
release = 'mitaka'
# List of packages to install for this charm
packages = ['python-ldappool']
configuration_class = KeystoneLDAPConfigurationAdapter
@property
def domain_name(self):
"""Domain name for the running application
:returns: string: containing the current domain name for the
application
"""
return hookenv.config('domain-name') or hookenv.service_name()
@staticmethod
def configuration_complete():
"""Determine whether sufficient configuration has been provided
to configure keystone for use with a LDAP backend
:returns: boolean indicating whether configuration is complete
"""
required_config = {
'ldap_server': hookenv.config('ldap-server'),
'ldap_user': hookenv.config('ldap-user'),
'ldap_password': hookenv.config('ldap-password'),
'ldap_suffix': hookenv.config('ldap-suffix'),
}
return all(required_config.values())
@property
def configuration_file(self):
"""Configuration file for domain configuration"""
return DOMAIN_CONF.format(self.domain_name)
def assess_status(self):
"""Determine the current application status for the charm"""
hookenv.application_version_set(self.application_version)
if not self.configuration_complete():
hookenv.status_set('blocked',
'LDAP configuration incomplete')
elif os_utils.is_unit_upgrading_set():
hookenv.status_set('blocked',
'Ready for do-release-upgrade and reboot. '
'Set complete when finished.')
else:
hookenv.status_set('active',
'Unit is ready')
def render_config(self, restart_trigger):
"""Render the domain specific LDAP configuration for the application
"""
checksum = ch_host.file_hash(self.configuration_file)
core.templating.render(
source=KEYSTONE_CONF_TEMPLATE,
template_loader=os_templating.get_loader(
'templates/', self.release),
target=self.configuration_file,
context=self.adapters_instance)
tmpl_changed = (checksum !=
ch_host.file_hash(self.configuration_file))
cert = hookenv.config('tls-ca-ldap')
cert_changed = False
if cert:
ca_file = self.options.backend_ca_file
old_cert_csum = ch_host.file_hash(ca_file)
ch_host.write_file(ca_file, cert,
owner='root', group='root', perms=0o644)
cert_csum = ch_host.file_hash(ca_file)
cert_changed = (old_cert_csum != cert_csum)
if tmpl_changed or cert_changed:
restart_trigger()
def remove_config(self):
"""
Remove the domain-specific LDAP configuration file and trigger
keystone restart.
"""
if os.path.exists(self.configuration_file):
os.unlink(self.configuration_file)
if (hookenv.config('tls-ca-ldap') and
os.path.exists(self.options.backend_ca_file)):
os.unlink(self.options.backend_ca_file)