Skip to content

Commit

Permalink
ldap: Add client certificate support (#6668)
Browse files Browse the repository at this point in the history
* Set up secure ldap server

* ldap: Added client cert options

Shamelessly copied from https://github.com/andrewshulgin/ldap_search

* Added tests for ldap client authentication

* Add changelog fragment

* Make sure the openssl commands work on older versions of openssl

* Apply suggestions from code review

Co-authored-by: Felix Fontein <[email protected]>

* Remove aliases for new arguments

* Add required_together to ldap module declerations

---------

Co-authored-by: Felix Fontein <[email protected]>
  • Loading branch information
Gnonthgol and felixfontein authored Jun 15, 2023
1 parent bb21693 commit f3ecf4c
Show file tree
Hide file tree
Showing 12 changed files with 121 additions and 5 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/6668-ldap-client-cert.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- ldap_* - add new arguments ``client_cert`` and ``client_key`` to the LDAP modules in order to allow certificate authentication (https://github.com/ansible-collections/community.general/pull/6668).
12 changes: 12 additions & 0 deletions plugins/doc_fragments/ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ class ModuleDocFragment(object):
- Set the path to PEM file with CA certs.
type: path
version_added: "6.5.0"
client_cert:
type: path
description:
- PEM formatted certificate chain file to be used for SSL client authentication.
- Required if O(client_key) is defined.
version_added: "7.1.0"
client_key:
type: path
description:
- PEM formatted file that contains your private key to be used for SSL client authentication.
- Required if O(client_cert) is defined.
version_added: "7.1.0"
dn:
required: true
description:
Expand Down
12 changes: 12 additions & 0 deletions plugins/module_utils/ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,17 @@ def gen_specs(**specs):
'validate_certs': dict(default=True, type='bool'),
'sasl_class': dict(choices=['external', 'gssapi'], default='external', type='str'),
'xorder_discovery': dict(choices=['enable', 'auto', 'disable'], default='auto', type='str'),
'client_cert': dict(default=None, type='path'),
'client_key': dict(default=None, type='path'),
})

return specs


def ldap_required_together():
return [['client_cert', 'client_key']]


class LdapGeneric(object):
def __init__(self, module):
# Shortcuts
Expand All @@ -60,6 +66,8 @@ def __init__(self, module):
self.verify_cert = self.module.params['validate_certs']
self.sasl_class = self.module.params['sasl_class']
self.xorder_discovery = self.module.params['xorder_discovery']
self.client_cert = self.module.params['client_cert']
self.client_key = self.module.params['client_key']

# Establish connection
self.connection = self._connect_to_ldap()
Expand Down Expand Up @@ -102,6 +110,10 @@ def _connect_to_ldap(self):
if self.ca_path:
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.ca_path)

if self.client_cert and self.client_key:
ldap.set_option(ldap.OPT_X_TLS_CERTFILE, self.client_cert)
ldap.set_option(ldap.OPT_X_TLS_KEYFILE, self.client_key)

connection = ldap.initialize(self.server_uri)

if self.referrals_chasing == 'disabled':
Expand Down
3 changes: 2 additions & 1 deletion plugins/modules/ldap_attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@

from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.text.converters import to_native, to_bytes, to_text
from ansible_collections.community.general.plugins.module_utils.ldap import LdapGeneric, gen_specs
from ansible_collections.community.general.plugins.module_utils.ldap import LdapGeneric, gen_specs, ldap_required_together

import re

Expand Down Expand Up @@ -300,6 +300,7 @@ def main():
state=dict(type='str', default='present', choices=['absent', 'exact', 'present']),
),
supports_check_mode=True,
required_together=ldap_required_together(),
)

if not HAS_LDAP:
Expand Down
3 changes: 2 additions & 1 deletion plugins/modules/ldap_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@

from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.text.converters import to_native, to_bytes
from ansible_collections.community.general.plugins.module_utils.ldap import LdapGeneric, gen_specs
from ansible_collections.community.general.plugins.module_utils.ldap import LdapGeneric, gen_specs, ldap_required_together

LDAP_IMP_ERR = None
try:
Expand Down Expand Up @@ -255,6 +255,7 @@ def main():
),
required_if=[('state', 'present', ['objectClass'])],
supports_check_mode=True,
required_together=ldap_required_together(),
)

if not HAS_LDAP:
Expand Down
3 changes: 2 additions & 1 deletion plugins/modules/ldap_passwd.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
import traceback

from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.community.general.plugins.module_utils.ldap import LdapGeneric, gen_specs
from ansible_collections.community.general.plugins.module_utils.ldap import LdapGeneric, gen_specs, ldap_required_together

LDAP_IMP_ERR = None
try:
Expand Down Expand Up @@ -133,6 +133,7 @@ def main():
module = AnsibleModule(
argument_spec=gen_specs(passwd=dict(no_log=True)),
supports_check_mode=True,
required_together=ldap_required_together(),
)

if not HAS_LDAP:
Expand Down
3 changes: 2 additions & 1 deletion plugins/modules/ldap_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
from ansible.module_utils.six import string_types, text_type
from ansible_collections.community.general.plugins.module_utils.ldap import LdapGeneric, gen_specs
from ansible_collections.community.general.plugins.module_utils.ldap import LdapGeneric, gen_specs, ldap_required_together

LDAP_IMP_ERR = None
try:
Expand All @@ -136,6 +136,7 @@ def main():
base64_attributes=dict(type='list', elements='str'),
),
supports_check_mode=True,
required_together=ldap_required_together(),
)

if not HAS_LDAP:
Expand Down
47 changes: 47 additions & 0 deletions tests/integration/targets/ldap_search/tasks/tests/auth.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

- debug:
msg: Running tests/auth.yml

####################################################################
## Search ##########################################################
####################################################################
- name: Test simple search for password authenticated user
ldap_search:
dn: "ou=users,dc=example,dc=com"
scope: "onelevel"
filter: "(uid=ldaptest)"
bind_dn: "uid=ldaptest,ou=users,dc=example,dc=com"
bind_pw: "test1pass!"
ignore_errors: true
register: output

- name: assert that test LDAP user can read its password
assert:
that:
- output is not failed
- output.results | length == 1
- output.results.0.userPassword is defined

- name: Test simple search for cert authenticated user
ldap_search:
dn: "ou=users,dc=example,dc=com"
server_uri: "ldap://localhost/"
start_tls: true
ca_path: /usr/local/share/ca-certificates/ca.crt
scope: "onelevel"
filter: "(uid=ldaptest)"
client_cert: "/root/user.crt"
client_key: "/root/user.key"
ignore_errors: true
register: output

- name: assert that test LDAP user can read its password
assert:
that:
- output is not failed
- output.results | length == 1
- output.results.0.userPassword is defined
15 changes: 15 additions & 0 deletions tests/integration/targets/setup_openldap/files/cert_cnconfig.ldif
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
dn: cn=config
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /usr/local/share/ca-certificates/ca.crt
-
add: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ldap/localhost.crt
-
add: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ldap/localhost.key
-
add: olcAuthzRegexp
olcAuthzRegexp: {0}"UID=([^,]*)" uid=$1,ou=users,dc=example,dc=com
-
add: olcTLSVerifyClient
olcTLSVerifyClient: allow
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-FileCopyrightText: Ansible Project
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ homeDirectory: /home/ldaptest
cn: LDAP Test
gecos: LDAP Test
displayName: LDAP Test
userPassword: test1pass!
mail: [email protected]
sn: Test
22 changes: 21 additions & 1 deletion tests/integration/targets/setup_openldap/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@
cmd: "export DEBIAN_FRONTEND=noninteractive; cat /root/debconf-slapd.conf | debconf-set-selections; dpkg-reconfigure -f noninteractive slapd"
creates: "/root/slapd_configured"

- name: Enable secure ldap
lineinfile:
path: /etc/default/slapd
regexp: "^SLAPD_SERVICES"
line: 'SLAPD_SERVICES="ldap:/// ldaps:/// ldapi:///"'

- name: Create certificates
shell: |
openssl req -x509 -batch -sha256 -days 1825 -newkey rsa:2048 -nodes -keyout /root/ca.key -out /usr/local/share/ca-certificates/ca.crt
openssl req -batch -sha256 -days 365 -newkey rsa:2048 -subj "/CN=$(hostname)" -addext "subjectAltName = DNS:localhost" -nodes -keyout /etc/ldap/localhost.key -out /etc/ldap/localhost.csr
openssl x509 -req -CA /usr/local/share/ca-certificates/ca.crt -CAkey /root/ca.key -CAcreateserial -in /etc/ldap/localhost.csr -out /etc/ldap/localhost.crt
chgrp openldap /etc/ldap/localhost.key
chmod 0640 /etc/ldap/localhost.key
openssl req -batch -sha256 -days 365 -newkey rsa:2048 -subj "/UID=ldaptest" -nodes -keyout /root/user.key -out /root/user.csr
openssl x509 -req -CA /usr/local/share/ca-certificates/ca.crt -CAkey /root/ca.key -CAcreateserial -in /root/user.csr -out /root/user.crt
- name: Start OpenLDAP service
become: true
service:
Expand All @@ -61,10 +77,14 @@
mode: '0644'
loop:
- rootpw_cnconfig.ldif
- cert_cnconfig.ldif
- initial_config.ldif

- name: Configure admin password for cn=config
shell: "ldapmodify -Y EXTERNAL -H ldapi:/// -f /tmp/rootpw_cnconfig.ldif"
shell: "ldapmodify -Y EXTERNAL -H ldapi:/// -f /tmp/{{ item }}"
loop:
- rootpw_cnconfig.ldif
- cert_cnconfig.ldif

- name: Add initial config
become: true
Expand Down

0 comments on commit f3ecf4c

Please sign in to comment.