mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
[PR #6668/f3ecf4c7 backport][stable-7] ldap: Add client certificate support (#6696)
ldap: Add client certificate support (#6668)
* 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 <felix@fontein.de>
* Remove aliases for new arguments
* Add required_together to ldap module declerations
---------
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit f3ecf4c7f8
)
Co-authored-by: Gnonthgol <gnonthgol+github@gmail.com>
This commit is contained in:
parent
7dcbb1ade4
commit
94f23ee647
12 changed files with 121 additions and 5 deletions
2
changelogs/fragments/6668-ldap-client-cert.yml
Normal file
2
changelogs/fragments/6668-ldap-client-cert.yml
Normal file
|
@ -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).
|
|
@ -29,6 +29,18 @@ options:
|
|||
- 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:
|
||||
|
|
|
@ -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
|
||||
|
@ -60,6 +66,8 @@ class LdapGeneric(object):
|
|||
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()
|
||||
|
@ -102,6 +110,10 @@ class LdapGeneric(object):
|
|||
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':
|
||||
|
|
|
@ -182,7 +182,7 @@ import traceback
|
|||
|
||||
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
|
||||
|
||||
|
@ -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:
|
||||
|
|
|
@ -151,7 +151,7 @@ import traceback
|
|||
|
||||
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:
|
||||
|
@ -255,6 +255,7 @@ def main():
|
|||
),
|
||||
required_if=[('state', 'present', ['objectClass'])],
|
||||
supports_check_mode=True,
|
||||
required_together=ldap_required_together(),
|
||||
)
|
||||
|
||||
if not HAS_LDAP:
|
||||
|
|
|
@ -72,7 +72,7 @@ modlist:
|
|||
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:
|
||||
|
@ -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:
|
||||
|
|
|
@ -121,7 +121,7 @@ import traceback
|
|||
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:
|
||||
|
@ -145,6 +145,7 @@ def main():
|
|||
base64_attributes=dict(type='list', elements='str'),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
required_together=ldap_required_together(),
|
||||
)
|
||||
|
||||
if not HAS_LDAP:
|
||||
|
|
47
tests/integration/targets/ldap_search/tasks/tests/auth.yml
Normal file
47
tests/integration/targets/ldap_search/tasks/tests/auth.yml
Normal file
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -18,6 +18,7 @@ homeDirectory: /home/ldaptest
|
|||
cn: LDAP Test
|
||||
gecos: LDAP Test
|
||||
displayName: LDAP Test
|
||||
userPassword: test1pass!
|
||||
mail: ldap.test@example.com
|
||||
sn: Test
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue