Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ldap: Add client certificate support #6668

Merged
merged 8 commits into from
Jun 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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