add support for ldaps and starttls via config opts
* Both ldaps and ldap + StartTLS require a CA certificate configuration option; * use_tls option should only be used for StartTLS and will result in an error if used with a URL that starts with ldaps; * if a certificate is specified then LDAP backend server's certificate validation is considered mandatory ("demand" option). Depends-On: Ied4b6ed64354e3de3c78e6ac809666ee9ae29d1a Change-Id: I659683ffec91560ebbd77969840c27e3d7048689 Closes-Bug: #1728155
This commit is contained in:
parent
b5fe0ef6c9
commit
5d644391ad
|
@ -7,9 +7,15 @@ options:
|
|||
type: string
|
||||
default:
|
||||
description: |
|
||||
LDAP server URL for keystone identity backend.
|
||||
.
|
||||
Example: ldap://10.10.10.10/
|
||||
LDAP server URL for keystone LDAP identity backend.
|
||||
|
||||
Examples:
|
||||
ldap://10.10.10.10/
|
||||
ldaps://10.10.10.10/
|
||||
ldap://example.com:389,ldaps://ldaps.example.com:636
|
||||
|
||||
Usage of ldap:// urls with tls_ca_ldap option specified or certificates relation
|
||||
presence will result in mandatory StartTLS usage.
|
||||
ldap-user:
|
||||
type: string
|
||||
default:
|
||||
|
@ -42,3 +48,13 @@ options:
|
|||
type: boolean
|
||||
default: True
|
||||
description: LDAP identity server backend readonly to keystone.
|
||||
tls-ca-ldap:
|
||||
type: string
|
||||
default: null
|
||||
description: |
|
||||
This option controls which certificate (or a chain) will be used to connect
|
||||
to an ldap server(s) over TLS. Certificate contents should be either used
|
||||
directly or included via include-file://
|
||||
An LDAP url should also be considered as ldaps and StartTLS are both valid
|
||||
methods of using TLS (see RFC 4513) with StartTLS using a non-ldaps url which,
|
||||
of course, still requires a CA certificate.
|
||||
|
|
|
@ -35,6 +35,7 @@ from charms_openstack.charm.core import (
|
|||
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"
|
||||
|
||||
|
||||
|
@ -65,6 +66,15 @@ class KeystoneLDAPConfigurationAdapter(
|
|||
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):
|
||||
|
||||
|
@ -104,6 +114,7 @@ class KeystoneLDAPCharm(charms_openstack.charm.OpenStackCharm):
|
|||
'ldap_password': hookenv.config('ldap-password'),
|
||||
'ldap_suffix': hookenv.config('ldap-suffix'),
|
||||
}
|
||||
|
||||
return all(required_config.values())
|
||||
|
||||
@property
|
||||
|
@ -131,7 +142,22 @@ class KeystoneLDAPCharm(charms_openstack.charm.OpenStackCharm):
|
|||
'templates/', self.release),
|
||||
target=self.configuration_file,
|
||||
context=self.adapters_instance)
|
||||
if checksum != ch_host.file_hash(self.configuration_file):
|
||||
|
||||
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):
|
||||
|
@ -141,3 +167,7 @@ class KeystoneLDAPCharm(charms_openstack.charm.OpenStackCharm):
|
|||
"""
|
||||
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)
|
||||
|
|
|
@ -12,6 +12,12 @@ group_allow_create = {{ not options.ldap_readonly }}
|
|||
group_allow_update = {{ not options.ldap_readonly }}
|
||||
group_allow_delete = {{ not options.ldap_readonly }}
|
||||
|
||||
{% if options.tls_ca_ldap -%}
|
||||
use_tls = {{ options.use_tls }}
|
||||
tls_req_cert = demand
|
||||
tls_cacertfile = {{ options.backend_ca_file }}
|
||||
{% endif -%}
|
||||
|
||||
# User supplied configuration flags
|
||||
{% if options.ldap_options -%}
|
||||
{% for key, value in options.ldap_options.items() -%}
|
||||
|
|
|
@ -13,6 +13,7 @@ from __future__ import absolute_import
|
|||
from __future__ import print_function
|
||||
|
||||
import mock
|
||||
import textwrap
|
||||
|
||||
import charms_openstack.test_utils as test_utils
|
||||
|
||||
|
@ -155,6 +156,106 @@ class TestKeystoneLDAPCharm(Helper):
|
|||
kldap_charm.render_config(mock_trigger)
|
||||
self.assertTrue(mock_trigger.called)
|
||||
|
||||
@mock.patch('charmhelpers.core.hookenv.config')
|
||||
@mock.patch('charmhelpers.core.hookenv.service_name')
|
||||
def test_render_config_tls(self, service_name, config):
|
||||
self.patch_object(keystone_ldap.ch_host, 'file_hash')
|
||||
self.patch_object(keystone_ldap.ch_host, 'write_file')
|
||||
self.patch_object(keystone_ldap.core.templating, 'render')
|
||||
self.patch_object(keystone_ldap.core.templating, 'render')
|
||||
|
||||
reply = {
|
||||
'ldap-server': 'myserver',
|
||||
'ldap-user': 'myusername',
|
||||
'ldap-password': 'mypassword',
|
||||
'ldap-suffix': 'suffix',
|
||||
'domain-name': 'userdomain',
|
||||
'tls-ca-ldap': textwrap.dedent("""
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB
|
||||
qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
|
||||
Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
|
||||
MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
|
||||
BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw
|
||||
NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j
|
||||
LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG
|
||||
A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
|
||||
IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG
|
||||
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs
|
||||
W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta
|
||||
3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk
|
||||
6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6
|
||||
Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J
|
||||
NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA
|
||||
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP
|
||||
r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU
|
||||
DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz
|
||||
YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
|
||||
xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2
|
||||
/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/
|
||||
LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7
|
||||
jVaMaA==
|
||||
-----END CERTIFICATE-----
|
||||
""")
|
||||
}
|
||||
|
||||
def mock_config(key=None):
|
||||
if key:
|
||||
return reply.get(key)
|
||||
return reply
|
||||
config.side_effect = mock_config
|
||||
|
||||
svc_name = 'keystone_ldap'
|
||||
service_name.return_value = svc_name
|
||||
|
||||
self.file_hash.side_effect = [
|
||||
'templatehash',
|
||||
'templatehash',
|
||||
'de3d5930e6e6b3fdb385f60a05206588',
|
||||
'de3d5930e6e6b3fdb385f60a05206588',
|
||||
]
|
||||
mock_trigger = mock.MagicMock()
|
||||
|
||||
with provide_charm_instance() as kldap_charm:
|
||||
# Ensure a basic level of function from render_config
|
||||
kldap_charm.render_config(mock_trigger)
|
||||
self.render.assert_called_with(
|
||||
source=keystone_ldap.KEYSTONE_CONF_TEMPLATE,
|
||||
template_loader=mock.ANY,
|
||||
target='/etc/keystone/domains/keystone.userdomain.conf',
|
||||
context=mock.ANY
|
||||
)
|
||||
self.write_file.assert_called_with(
|
||||
keystone_ldap.BACKEND_CA_CERT.format(svc_name),
|
||||
reply['tls-ca-ldap'],
|
||||
owner='root',
|
||||
group='root',
|
||||
perms=0o644,
|
||||
)
|
||||
self.assertFalse(mock_trigger.called)
|
||||
|
||||
# template file change leads to restart without a change
|
||||
# in a cert
|
||||
self.file_hash.side_effect = [
|
||||
'oldtemplatehash',
|
||||
'newtemplatehash',
|
||||
'de3d5930e6e6b3fdb385f60a05206588',
|
||||
'de3d5930e6e6b3fdb385f60a05206588',
|
||||
]
|
||||
|
||||
kldap_charm.render_config(mock_trigger)
|
||||
self.assertTrue(mock_trigger.called)
|
||||
|
||||
# cert change without template change
|
||||
self.file_hash.side_effect = [
|
||||
'templatehash',
|
||||
'templatehash',
|
||||
'deadbeefdeadbeefdeadbeefdeadbeef',
|
||||
'de3d5930e6e6b3fdb385f60a05206588',
|
||||
]
|
||||
kldap_charm.render_config(mock_trigger)
|
||||
self.assertTrue(mock_trigger.called)
|
||||
|
||||
@mock.patch('charmhelpers.core.hookenv.config')
|
||||
@mock.patch('os.path.exists')
|
||||
@mock.patch('os.unlink')
|
||||
|
|
Loading…
Reference in New Issue