]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Break up LDAP howto pages into yet smaller pages
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 21 Oct 2019 02:18:30 +0000 (22:18 -0400)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 21 Oct 2019 02:18:30 +0000 (22:18 -0400)
13 files changed:
doc/antora/modules/concepts/pages/modules/ldap/authentication.adoc
doc/antora/modules/howto/nav.adoc
doc/antora/modules/howto/pages/modules/ldap/authorization.adoc
doc/antora/modules/howto/pages/modules/ldap/bootstrap_openldap/docker.adoc [new file with mode: 0644]
doc/antora/modules/howto/pages/modules/ldap/bootstrap_openldap/index.adoc [new file with mode: 0644]
doc/antora/modules/howto/pages/modules/ldap/bootstrap_openldap/packages.adoc [moved from doc/antora/modules/howto/pages/modules/ldap/bootstrap_openldap.adoc with 66% similarity]
doc/antora/modules/howto/pages/modules/ldap/configuration.adoc
doc/antora/modules/howto/pages/modules/ldap/index.adoc
doc/antora/modules/howto/pages/modules/ldap/ldapsearch/before_starting.adoc [new file with mode: 0644]
doc/antora/modules/howto/pages/modules/ldap/ldapsearch/connection_parameters.adoc [new file with mode: 0644]
doc/antora/modules/howto/pages/modules/ldap/ldapsearch/index.adoc [new file with mode: 0644]
doc/antora/modules/howto/pages/modules/ldap/ldapsearch/locating_objects.adoc [new file with mode: 0644]
doc/antora/modules/howto/pages/modules/ldap/ldapsearch/translating_to_the_ldap_module.adoc [new file with mode: 0644]

index bc35ac6eee6bea3a54dbf9870ad1016da5deefb7..9766d09d35f83a29d5ec38ed1cffb77984b046f3 100644 (file)
@@ -94,7 +94,7 @@ involves giving the FreeRADIUS "read-only" user permission to read the
 `userPassword` field.
 
 Again, the best method is to test authentication is with the
-xref:howto:modules/ldap/ldapsearch.adoc[ldapsearch] tool.
+xref:howto:modules/ldap/ldapsearch/index.adoc[ldapsearch] tool.
 These tests *must* be run prior to configuring FreeRADIUS.  We strongly
 recommend having the LDAP database return the `userPassword` field to
 FreeRADIUS, so that FreeRADIUS can authenticate the user.
index 473b19d528db33707df20863076fa7773004d478..51810822168bdea38c8d72552fb84598bba314c3 100644 (file)
@@ -5,14 +5,24 @@
 *** xref:modules/eap/index.adoc[EAP]
 *** xref:modules/expiration/index.adoc[Expiration]
 *** xref:modules/krb5/index.adoc[Krb5]
+
 *** xref:modules/ldap/index.adoc[LDAP]
-**** xref:modules/ldap/bootstrap_openldap.adoc[Installing and Configuring OpenLDAP]
-**** xref:modules/ldap/ldapsearch.adoc[Mapping and testing with ldapsearch]
+
+**** xref:modules/ldap/bootstrap_openldap/index.adoc[Installing and Configuring OpenLDAP]
+***** xref:modules/ldap/bootstrap_openldap/docker.adoc[Docker]
+***** xref:modules/ldap/bootstrap_openldap/packages.adoc[Packages]
+
+**** xref:modules/ldap/ldapsearch/index.adoc[Mapping and testing with ldapsearch]
+***** xref:modules/ldap/ldapsearch/before_starting.adoc[Before starting]
+***** xref:modules/ldap/ldapsearch/connection_parameters.adoc[Determining connection parameters]
+***** xref:modules/ldap/ldapsearch/locating_objects.adoc[Locating objects]
+***** xref:modules/ldap/ldapsearch/translating_to_the_ldap_module.adoc[Translating ldapsearch arguments to LDAP configuration items]
 
 **** xref:modules/ldap/configuration.adoc[Enabling the LDAP module]
 **** xref:modules/ldap/authorization.adoc[Configuring authorization]
 **** xref:modules/ldap/authentication.adoc[Configuring authentication]
 **** xref:modules/ldap/accounting.adoc[Configuring accounting]
+
 *** xref:modules/mschap/index.adoc[MS-CHAP]
 *** xref:modules/pam/index.adoc[PAM]
 *** xref:modules/passwd/index.adoc[Passwd]
index 010b3a211c8f446a8049bc988ca872289d1d5cfb..22222c8bff3b57604174973c9a287c8c26dfd526 100644 (file)
@@ -1,5 +1,45 @@
 = Configuring Authorization
-=== Group Membership
+
+The `authorize` method of the LDAP module is responsible for locating the
+authenticating user's LDAP object.
+
+In addition to determining where the user is, the authorize method also
+performs LDAP to FreeRADIUS attribute mappings,
+
+== 1. Locating the user
+
+No matter how the LDAP module is called (via its `authorize`, `authenticate`,
+`accounting` methods or an `LDAP-Group` comparison) the first operation the
+module performs it to populate `&control:LDAP-UserDN` with the location of
+the authenticating user's object in LDAP.
+
+How the LDAP-UserDN value is discovered is determined by the contents of the
+`user { ... }` section of the LDAP module instance.
+
+Below is an example of configuring a user section for OpenLDAP, with callouts
+explaining how the different configuration items are used. See
+xref:modules/ldap/ldapsearch/index.adoc[ldapsearch] for how to determine the
+appropriate values for your directory.
+
+[source,unlang]
+----
+user {
+       base_dn = "ou=people,${..base_dn}"      <1>
+       filter =
+}
+----
+
+== 2. Disambiguating users
+
+dn: olcOverlay=sssvlv,olcDatabase={1}mdb,cn=config
+> objectClass: olcSssVlvConfig
+> olcOverlay: sssvlv
+> olcSssVlvMax: 10
+> olcSssVlvMaxKeys: 5
+
+== 2. Configuring the enable/disable attribute
+
+== Group Membership
 
 Depending on the LDAP vendor you use, LDAP group membership is either handled
 using the LDAP attributes:
diff --git a/doc/antora/modules/howto/pages/modules/ldap/bootstrap_openldap/docker.adoc b/doc/antora/modules/howto/pages/modules/ldap/bootstrap_openldap/docker.adoc
new file mode 100644 (file)
index 0000000..63e0ef6
--- /dev/null
@@ -0,0 +1,132 @@
+= Using OpenLDAP in a docker container
+
+== Bootstrap
+
+https://github.com/osixia[Osixia!] provides a
+https://github.com/osixia/docker-openldap[fully functionally OpenLDAP container]
+which can be instantiated using the docker invocation below.
+
+This docker invocation also sets up a readonly user, and loads the custom
+FreeRADIUS schemas required for RADIUS to LDAP attribute mapping, dynamic client
+definitions, and attribute profiles.
+
+=== Define site specific variables
+Change the values here to match local paths and your site specific
+configuration.
+
+[source,shell]
+----
+# Where to store a temporary shallow clone of the FreeRADIUS source
+# Or the path to an existing copy of the FreeRADIUS source
+FREERADIUS_SRC="/tmp/freeradius-src"
+
+# Base DN
+LDAP_BASE_DN="dc=example,dc=com"
+
+# Domain
+LDAP_DOMAIN="example.com"
+
+# DN Suffix
+LDAP_BASE_DN="dc=example,dc=com"
+
+# Password for the administrative user
+LDAP_ADMIN_PASSWORD="secret"
+
+# Password for the read only user
+LDAP_READONLY_PASSWORD="readonly"
+----
+
+=== Instantiate an OpenLDAP docker container
+
+[source,shell]
+----
+if [ ! -d "${FREERADIUS_SRC}" ]; then
+       git clone --depth 1 https://github.com/FreeRADIUS/freeradius-server.git "${FREERADIUS_SRC}"
+fi
+docker run -it --rm -p 389:389 \
+       --env LDAP_DOMAIN=${LDAP_DOMAIN} \
+       --env LDAP_ADMIN_PASSWORD=${LDAP_ADMIN_PASSWORD} \
+       --env LDAP_READONLY_USER=true \
+       --env LDAP_READONLY_USER_PASSWORD=${LDAP_READONLY_PASSWORD} \
+       --volume "${FREERADIUS_SRC}/doc/schemas/ldap/openldap/freeradius.schema:/container/service/slapd/assets/config/bootstrap/schema/mmc/radius.schema:ro" \
+       --volume "${FREERADIUS_SRC}/doc/schemas/ldap/openldap/freeradius-clients.schema:/container/service/slapd/assets/config/bootstrap/schema/mmc/freeradius-clients.schema:ro" \
+       osixia/openldap:1.2.5 --copy-service
+----
+
+== Populate with test data
+Once docker is running, there should now be an LDAP server running on
+`localhost`, it can now be populated with test data.
+
+For test data we will be using the object definitions from the LDAP
+module's CIT (Continuous Integration Testing) script.
+
+These object definitions have been designed to exercise all features
+of the FreeRADIUS LDAP module.
+
+[source,shell]
+----
+sed -e '1,/^description:/ d' "${FREERADIUS_SRC}/src/tests/modules/ldap/example.com.ldif" \
+    | ldapadd -H ldap://localhost -x -D cn=admin,${LDAP_BASE_DN} -w admin
+----
+
+There should be no errors, and the console with the running LDAP
+server should show requests being handled.
+
+If the LDAP server server is stopped, as with `CTRL-C`, then the database
+contents will be lost.  All of the steps above will have to be re-done the next
+time the server is started.
+
+In order to make the LDAP database persistent, see the
+https://github.com/osixia/docker-openldap[osixia/openldap
+instructions].
+
+== Check it works
+
+As a final step you should verify that test data has been loaded correctly.
+This can be done using the `ldapsearch` utility using the LDAP read only user.
+
+The command below will retrieve the entry for one of the test `radiusClient`
+entries. You should see a single search result returned if everything worked
+correctly.
+
+[source,shell]
+----
+ldapsearch -LLL -H ldap://localhost -x -D cn=readonly,<base_dn> -w <readonly_password> -b <base_dn> '(&(objectClass=radiusClient)(radiusClientShortname=client2))'
+----
+
+.Searching for a RADIUS Client
+====
+[source,shell]
+----
+ldapsearch -LLL -H ldap://localhost -x -D cn=readonly,dc=example,dc=com -w readonly -b dc=example,dc=com '(&(objectClass=radiusClient)(radiusClientShortname=client2))'
+----
+
+.Expected output
+[source,ldiff]
+----
+# extended LDIF
+#
+# LDAPv3
+# base <dc=example,dc=com> with scope subtree
+# filter: (&(objectClass=radiusClient)(radiusClientShortname=client2))
+# requesting: ALL
+#
+
+# 2.2.2.2, clients, example.com
+dn: radiusClientIdentifier=2.2.2.2,ou=clients,dc=example,dc=com
+objectClass: radiusClient
+radiusClientIdentifier: 2.2.2.2
+radiusClientSecret: 123secret
+radiusClientShortname: client2
+radiusClientType: cisco
+radiusClientRequireMa: TRUE
+radiusClientComment: Another test client
+
+# search result
+search: 2
+result: 0 Success
+
+# numResponses: 2
+# numEntries: 1
+----
+====
diff --git a/doc/antora/modules/howto/pages/modules/ldap/bootstrap_openldap/index.adoc b/doc/antora/modules/howto/pages/modules/ldap/bootstrap_openldap/index.adoc
new file mode 100644 (file)
index 0000000..3d28f0e
--- /dev/null
@@ -0,0 +1,22 @@
+= Installing and Configuring OpenLDAP
+
+This section of the howto describes two methods of creating an OpenLDAP instance for
+testing purposes.
+
+== xref:modules/ldap/bootstrap_openldap/docker.adoc[Docker]
+
+If you're looking to create a temporary instance of OpenLDAP and spin it up as
+quickly as possible, you should consider using the
+https://docs.docker.com/install/[Docker containerisation service].
+
+A pre-configured docker container allow you to create a self-contained OpenLDAP
+instance with a minimum amount of effort.
+
+== xref:modules/ldap/bootstrap_openldap/packages.adoc[Packages]
+
+If you're looking to create a more permanent installation of OpenLDAP or
+are not comfortable using docker, then you may wish to install OpenLDAP from packages.
+
+OpenLDAP is available pre-packaged for many distributions.  We recommend
+using the OpenLDAP LTB packages available under the
+"Packaging and OpenLDAP extensions" heading https://ltb-project.org/documentation[here].
similarity index 66%
rename from doc/antora/modules/howto/pages/modules/ldap/bootstrap_openldap.adoc
rename to doc/antora/modules/howto/pages/modules/ldap/bootstrap_openldap/packages.adoc
index 54830265b5efba1aede09da28e99d077c7552646..38a72feaeb011401d4fde4948a7d8fbd49ced71f 100644 (file)
@@ -1,113 +1,6 @@
-= Installing and Configuring OpenLDAP
+= Installing OpenLDAP from packages
 
-This page describes two methods (using <<bootstrap_with_docker,Docker>> and via
-<<bootstrap_with_packages,Packages>>) of creating an OpenLDAP instance for
-testing purposes.
-
-If you're looking to create a temporary instance of OpenLDAP and spin it up as
-quickly as possible, you should consider using the
-https://docs.docker.com/install/[Docker containerisation service].
-
-A pre-configured docker container allow you to create a self-contained OpenLDAP
-instance with a minimum amount of effort.
-
-If you're looking to create a more permanent installation of OpenLDAP or
-are not comfortable using docker, then you may wish to install OpenLDAP from packages.
-
-OpenLDAP is available pre-packaged for many distributions.  We recommend
-using the OpenLDAP LTB packages available under the
-"Packaging and OpenLDAP extensions" heading https://ltb-project.org/documentation[here].
-
-[#bootstrap_with_docker]
-== Using OpenLDAP in a docker container
-
-=== Bootstrap
-
-https://github.com/osixia[Osixia!] provides a
-https://github.com/osixia/docker-openldap[fully functionally OpenLDAP container]
-which can be instantiated using the docker invocation below.
-
-This docker invocation also sets up a readonly user, and loads the custom
-FreeRADIUS schemas required for RADIUS to LDAP attribute mapping, dynamic client
-definitions, and attribute profiles.
-
-==== Define site specific variables
-Change the values here to match local paths and your site specific
-configuration.
-
-[source,shell]
-----
-# Where to store a temporary shallow clone of the FreeRADIUS source
-# Or the path to an existing copy of the FreeRADIUS source
-FREERADIUS_SRC="/tmp/freeradius-src"
-
-# Base DN
-LDAP_BASE_DN="dc=example,dc=com"
-
-# Domain
-LDAP_DOMAIN="example.com"
-
-# DN Suffix
-LDAP_BASE_DN="dc=example,dc=com"
-
-# Password for the administrative user
-LDAP_ADMIN_PASSWORD="secret"
-
-# Password for the read only user
-LDAP_READONLY_PASSWORD="readonly"
-----
-
-==== Instantiate an OpenLDAP docker container
-
-[source,shell]
-----
-if [ ! -d "${FREERADIUS_SRC}" ]; then
-       git clone --depth 1 https://github.com/FreeRADIUS/freeradius-server.git "${FREERADIUS_SRC}"
-fi
-docker run -it --rm -p 389:389 \
-       --env LDAP_DOMAIN=${LDAP_DOMAIN} \
-       --env LDAP_ADMIN_PASSWORD=${LDAP_ADMIN_PASSWORD} \
-       --env LDAP_READONLY_USER=true \
-       --env LDAP_READONLY_USER_PASSWORD=${LDAP_READONLY_PASSWORD} \
-       --volume "${FREERADIUS_SRC}/doc/schemas/ldap/openldap/freeradius.schema:/container/service/slapd/assets/config/bootstrap/schema/mmc/radius.schema:ro" \
-       --volume "${FREERADIUS_SRC}/doc/schemas/ldap/openldap/freeradius-clients.schema:/container/service/slapd/assets/config/bootstrap/schema/mmc/freeradius-clients.schema:ro" \
-       osixia/openldap:1.2.5 --copy-service
-----
-
-=== Test data
-Once docker is running, there should now be an LDAP server running on
-`localhost`, it can now be populated with test data.
-
-For test data we will be using the object definitions from the LDAP
-module's CIT (Continuous Integration Testing) script.
-
-These object definitions have been designed to exercise all features
-of the FreeRADIUS LDAP module.
-
-[source,shell]
-----
-sed -e '1,/^description:/ d' "${FREERADIUS_SRC}/src/tests/modules/ldap/example.com.ldif" \
-    | ldapadd -H ldap://localhost -x -D cn=admin,${LDAP_BASE_DN} -w admin
-----
-
-There should be no errors, and the console with the running LDAP
-server should show requests being handled.
-
-If the LDAP server server is stopped, as with `CTRL-C`, then the database
-contents will be lost.  All of the steps above will have to be re-done the next
-time the server is started.
-
-In order to make the LDAP database persistent, see the
-https://github.com/osixia/docker-openldap[osixia/openldap
-instructions].
-
-As a final step you should <<verifying_openldap_setup,verify OpenLDAP is
-setup correctly>>.
-
-[#bootstrap_with_packages]
-== Installing OpenLDAP from packages
-=== Bootstrap
-==== Switch to privileged user
+== Switch to privileged user
 
 [source,shell]
 ----
@@ -122,7 +15,9 @@ For convenience we will configure the OLC database to be modifiable only by the
 root user (`UID=0`, `GID=0`).  When configuring OpenLDAP via OLC the `ldapadd`
 utility will be run as root so our changes are authorised.
 
-==== Install packages
+== Bootstrap
+
+=== Install packages
 
 OpenLDAP LTB packages available under the
 "Packaging and OpenLDAP extensions" heading https://ltb-project.org/documentation[here].
@@ -130,7 +25,7 @@ OpenLDAP LTB packages available under the
 You should add the appropriate repository definitions and install the `openldap`
 package (`yum install openldap` or `apt-get install openldap`).
 
-==== Define site specific variables
+=== Define site specific variables
 Change the values here to match local paths and your site specific
 configuration.
 
@@ -153,7 +48,7 @@ LDAP_ADMIN_PASSWORD="secret"
 LDAP_READONLY_PASSWORD="readonly"
 ----
 
-==== Switch OpenLDAP from using static configuration files to OLC
+=== Switch OpenLDAP from using static configuration files to OLC
 After installing OpenLDAP, `slapd` (the main OpenLDAP daemon) needs to be
 switched to using OLC (on-line configuration).
 
@@ -201,7 +96,7 @@ sed -ie 's#^SLAPD_CONF=.*#SLAPD_CONF=""#;s#^SLAPD_CONF_DIR=.*#SLAPD_CONF_DIR="$S
 systemctl start slapd
 ----
 
-==== Define a database and set appropriate ACLs
+=== Define a database and set appropriate ACLs
 
 [source,shell]
 ----
@@ -224,8 +119,9 @@ olcAccess: to dn.base="" by * read
 EOF
 ----
 
-.Interpreting OpenLDAP ACLs
-****
+.Wondering what those ACLs mean?
+[%collapsible]
+====
 
 The OpenLDAP ACL syntax can be difficult to understand for new users. To help
 with implementing site-specific ACLs, the humanly readable translation of the
@@ -242,10 +138,9 @@ base ACLs in the above example is included below.
 * `to dn.base="" by * read`
 ** Any user may access the metadata at the top of the directory.  The is useful for the
    autodiscovery functionality in LDAP browsers.
+====
 
-****
-
-==== Populate the top level object, and add credentials for the readonly user
+=== Populate the top level object, and add credentials for the readonly user
 
 [source,shell]
 ----
@@ -267,7 +162,7 @@ description: LDAP read only user
 EOF
 ----
 
-=== Loading schemas
+== Load schemas
 
 For our tests we need to load some basic bundled OpenLDAP schemas and some
 FreeRADIUS specific schemas for defining profiles, RADIUS to LDAP mappings and
@@ -290,7 +185,7 @@ for i in freeradius.ldif freeradius-clients.ldif; do
 done
 ----
 
-=== Test data
+== Populate with test data
 Once slapd is running and appropriately configured with database definitions an
 admin user, a readonly user, and the prerequisite schemas, it can now be
 populated with test data.
@@ -307,7 +202,7 @@ sed -e '1,/^description:/ d' ${FREERADIUS_SRC}/src/tests/modules/ldap/example.co
     | ldapadd -Y EXTERNAL -H ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi
 ----
 
-=== Switch to an unprivileged user
+== Switch to an unprivileged user
 [source,shell]
 ----
 exit
@@ -320,8 +215,7 @@ If, however, you need to make further changes to OpenLDAP's configuration, you
 should note that the `ldapadd`, `ldapmodify` commands must be called as root
 with the `-Y EXTERNAL` argument.
 
-[#verifying_openldap_setup]
-== Verifying OpenLDAP setup
+== Check it works
 
 As a final step you should verify that test data has been loaded correctly.
 This can be done using the `ldapsearch` utility using the LDAP read only user.
index 20eda3c839785a658ac2efea829341e59cdca585..0abee266aed1ce28d9fca97ece7c86ba4db9f12e 100644 (file)
@@ -1,7 +1,7 @@
 = Enabling the LDAP module
 
 The configuration step should be little more than
-xref:modules/ldap/ldapsearch.adoc#_translating_ldapsearch_arguments_to_rlm_ldap_configuration_items[copying the parameters used by ldapsearch]
+xref:modules/ldap/ldapsearch/index.adoc#_translating_ldapsearch_arguments_to_rlm_ldap_configuration_items[copying the parameters used by ldapsearch]
 to the xref:raddb:mods-available/ldap.adoc[ldap module] configuration file.
 
 The xref:raddb:mods-available/ldap.adoc[ldap module] configuration file
@@ -16,15 +16,15 @@ possible in the default configuration.  The defaults are usually close
 to being correct.  All that is necessary is to make minor changes, and
 _test_ them.  FreeRADIUS should look for data.
 
-If the xref:modules/ldap/search[ldapsearch] tests above pass,
+If the xref:modules/ldap/ldapsearch/index.adoc[ldapsearch] tests above pass,
 then the LDAP module configuration can be copied directly from the
 command-line options to that tool using the
-xref:modules/ldap/ldapsearch.adoc#_translating_ldapsearch_arguments_to_rlm_ldap_configuration_items[translation table] on that page.
+xref:modules/ldap/ldapsearch/index.adoc#_translating_ldapsearch_arguments_to_rlm_ldap_configuration_items[translation table] on that page.
 
 At a minimum you will need to set the following configuration items
 in in xref:raddb:mods-available/ldap.adoc[mods-available/ldap] using
 the values you discovered
-running the xref:modules/ldap/search[ldapsearch] tests.
+running the xref:modules/ldap/ldapsearch/index.adoc[ldapsearch] tests.
 
 [source,config]
 ----
@@ -36,7 +36,7 @@ ldap {
 
        user {
                base_dn = "ou=people,${..base_dn}"  <5>
-               filter = '(objectClass=posixUser)'  <6>
+               filter = '(&(uid=%{%{Stripped-User-Name}:-%{User-Name}})(objectClass=posixUser))'  <6>
        }
 }
 ----
@@ -51,7 +51,7 @@ ldap {
     to reduce network overhead.
 <4> The object containing all other objects we're interested in.
 <5> The object containing all the users we need to perform AAA functions for.
-<6> Filter to select only valid user objects.
+<6> Filter to select the object matching the authenticating user.
 
 We do _not_ recommend immediately configuring TLS unless you are testing against
 a production server.  The best approach is to test one piece in isolation,
index 614201a38b75ca91e10a94b975209857f91cf3eb..7f0bef1a61a4658e3dbaed7944c1cd6427375249 100644 (file)
@@ -45,14 +45,14 @@ where a high level of redundancy is required.
 === 2. Testing
 
 Once an LDAP server is available, it should be tested via the command-line
-xref:modules/ldap/ldapsearch.adoc[ldapsearch] tool.  This is to ensure that
+xref:modules/ldap/ldapsearch/index.adoc[ldapsearch] tool.  This is to ensure that
 the LDAP server has been configured correctly.  If testing via `ldapsearch`
 fails, then that *MUST* those issues must be resolved before configuring
 FreeRADIUS.
 
 === 3. Configuring the LDAP module
 
-Once the xref:modules/ldap/ldapsearch.adoc[ldapsearch] validation tests pass, the
+Once the xref:modules/ldap/ldapsearch/index.adoc[ldapsearch] validation tests pass, the
 next step is to xref:modules/ldap/configuration.adoc[configure the LDAP module].
 
 OpenLDAP configuration examples detailing how to install appropriate schemas and
diff --git a/doc/antora/modules/howto/pages/modules/ldap/ldapsearch/before_starting.adoc b/doc/antora/modules/howto/pages/modules/ldap/ldapsearch/before_starting.adoc
new file mode 100644 (file)
index 0000000..3f89b3b
--- /dev/null
@@ -0,0 +1,20 @@
+= Before starting
+
+Try to determine answers to the following questions:
+
+* What's the IP address of the LDAP server being tested?
+** If using an FQDN does the FQDN resolve to this IP address?
+* Does the LDAP server requires encrypted connections?
+** If yes:
+*** Does the LDAP server use the StartTLS extension to transition to
+encrypted communication, or is communication encrypted from the start (LDAPS)?
+*** What are the necessary root certificates and intermediary certificates
+needed to validate the identity of the LDAP server?
+* What port is used to connect to the LDAP server?  Usually this will be `389`
+(unencrypted or StartTLS) or `636` (LDAPS).
+* Do connections need to be bound to perform searches on the LDAP directory?
+** If yes:
+*** Determine what credentials are needed for binding, and whether a client
+certificate is required.
+* Determine the Base DN, the object deepest in the tree that contains the object
+representing users, groups, clients etc...
diff --git a/doc/antora/modules/howto/pages/modules/ldap/ldapsearch/connection_parameters.adoc b/doc/antora/modules/howto/pages/modules/ldap/ldapsearch/connection_parameters.adoc
new file mode 100644 (file)
index 0000000..85242d1
--- /dev/null
@@ -0,0 +1,223 @@
+= Determining connection parameters
+
+`ldapsearch` accepts a large number of different arguments, and allow relatively
+complex commands to be sent to the LDAP server.
+
+It will likely take a number of attempts to find the correct `ldapsearch` invocation.
+If you have no prior information about the LDAP server follow the examples below,
+adding progressively more connection parameters until `ldapsearch` returns a positive
+result (`$? == 0` and one or more entries written to stdout).
+
+[source,ldif]
+----
+dn: uid=john,ou=people,dc=example,dc=com
+objectClass: inetOrgPerson
+uid: john
+userPassword: password
+----
+
+If the `ldapsearch` program fails to return anything useful, then additional
+arguments should be added until the search succeeds. Unfortunately, every piece
+of advice in this section is site-specific, and is independent of RADIUS.
+
+== Search without bind (anonymous)
+At a minimum, unless you have defaults set in your local `ldap.conf` file,
+you will need to provide the following arguments:
+
+- `-z 1` - Limit the number of results to 1.
+- `-x` - Use simple authentication instead of SASL (correct for the majority of servers).
+- `-H <ldap uri>` - The LDAP server to connect to.
+- `-b <dn>` - A point in the LDAP tree to start the search from.
+
+.Performing an anonymous search
+====
+[source,shell]
+----
+ldapsearch -z 1 -x \
+       -H ldap://ldap.example.com:389 \
+       -b "cn=freeradius,dc=example,dc=com"
+----
+====
+
+== Failure - Bad FQDN/IP or attempt blocked by firewall
+
+```
+ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)
+```
+
+* Verify (using `dig` or similar) that the FQDN of the LDAP
+server can be resolved.
+* Verify that there is no firewall blocking access.  Some techniques to check this
+which _may_ work.
+** `sudo traceroute -Tn -p 636 ldap.example.com` will be successful (and not
+continue indefinitely) when there is _no_ firewall blocking.
+** `netcat -vz -w3 ldap.example.com 636` will return `succeeded` when there is no
+firewall blocking access.
+
+== Failure - Bind required
+
+```
+ldap_bind: Server is unwilling to perform (53)
+       additional info: unauthenticated bind (DN with no password) disallowed
+```
+
+The majority of LDAP servers will also require the LDAP client to identify itself,
+this is done with a bind operation.
+
+If a message similar to the one above is returned, it means a bind operation
+must be performed before attempting any operations on the LDAP server.
+
+The following arguments should be added to identify the LDAP client to the LDAP
+server:
+
+- `-D <dn>` - The bind DN.  A unique identifier for the user being bound.
+- `-w <password>` - The bind password.
+
+.Performing a search with a bound user
+====
+[source,shell]
+----
+ldapsearch -z 1 -x \
+       -H ldap://ldap.example.com:389 \
+       -D "cn=freeradius,dc=example,dc=com" \
+       -w mypassword \
+       -b "dc=example,dc=com"
+----
+====
+
+== Failure - Encryption (confidentiality) required
+
+If a message similar to the one below is returned by `ldapsearch`.  TLS must
+be enabled on the connection.
+
+```
+ldap_bind: Confidentiality required (13)
+        additional info: TLS confidentiality required
+```
+
+If you're not sure which encryption method is used (LDAPS or StartTLS) try
+each of them in turn.
+
+In order to configure either encryption method you will need to have acces
+to the certificate chain for the certificate the LDAP server presents.
+
+See the section below on retrieving certificate chains if you don't have
+access to these certificates.
+
+=== Common parameters for both LDAPS and StartTLS)
+
+- `LDAPTLS_CACERT=<ca_cert_and_intermediaries.pem>` - An environmental variable
+specifying a PEM file containing CA and chain of trusted certificates, that
+joins with the chain presented by the LDAP server.  These certificates must be
+provided in order to validate the LDAP server's certificate.
+
+- `LDAPTLS_REQCERT=hard` - An environmental variable requiring the LDAP server
+provide a certificate.
+
+=== Encryption with LDAPS
+
+LDAPS is configured by changing the URI scheme passed as the value to `-H`.
+
+Where an unencrypted or StartTLS connection uses `ldap://`, LDAPS requires
+`ldaps://`.
+
+The port for LDAPS is different than for unencrypted communication. Where an
+unencrypted or StartTLS connection normally uses `TCP/389`, LDAPS normally uses
+`TCP/636`.
+
+For LDAPS an unencrypted or StartTLS URI is changed from:
+```
+-H ldap://<fqdn>:389
+```
+
+to
+
+```
+-H ldaps://<fqdn>:636
+```
+
+.Performing a search with a bound user with LDAPS
+====
+[source,shell]
+----
+LDAPTLS_CACERT=cert_bundle.pem LDAPTLS_REQCERT=hard \
+       ldapsearch -z 1 -x \
+               -H ldaps://ldap.example.com:636 \
+               -D "cn=freeradius,dc=example,dc=com" \
+               -w mypassword \
+               -b "dc=example,dc=com"
+----
+====
+
+=== Encryption with StartTLS
+
+StartTLS connections runs on the same port as unencrypted LDAP.  StartTLS
+is an LDAP
+
+- `-ZZ` - Transition to encrypted communication using the StartTLS extension,
+and fail if we can't.
+
+.Performing a search with a bound user with StartTLS
+====
+[source,shell]
+----
+LDAPTLS_CACERT=cert_bundle.pem LDAPTLS_REQCERT=hard \
+       ldapsearch -z 1 -x \
+               -H ldap://ldap.example.com:389 \
+               -D "cn=freeradius,dc=example,dc=com" \
+               -w mypassword \
+               -b "dc=example,dc=com" \
+               -ZZ
+----
+====
+
+.Retrieving certificate chains from the server
+****
+If using LDAP over TLS `openssl s_client` can display information about the
+certificates presented by the LDAP server. The information returned
+(particularly the certificate issuer(s)) in useful to determine what
+certificates need to be available to the LDAP client.
+
+The `openssl` invocation is different depending on whether StartTLS or LDAPS
+is used.
+
+.LDAPS - Retrieving the certificate chain of the fictitious ldap.example.com server
+====
+```
+echo -n | openssl s_client -host ldap.example.com -port 636 -prexit -showcerts
+CONNECTED(00000003)
+depth=1 C = OT, ST = Tentacle Cove, O = FreeRADIUS, OU = Services, CN = example.com, emailAddress = support@example.com
+verify return:0
+---
+Certificate chain
+ 0 s:/C=OT/ST=Tentacle Cove/L=Grenoble/O=FreeRADIUS/OU=Services/CN=ldap.example.com/emailAddress=support@example.com
+   i:/C=OT/ST=Tentacle Cove/O=FreeRADIUS/OU=Services/CN=example.com/emailAddress=support@example.com
+-----BEGIN CERTIFICATE-----
+MIIHDjCCBPagAwIBAgIJANAO5znieeLNMA0GCSqGSIb3DQEBCwUAMIGSMQswCQYD
+...
+```
+====
+
+.StartTLS - Retrieving the certificate chain of the fictitious ldap.example.com server
+====
+```
+echo -n | openssl s_client -host ldap.example.com -port 389 -prexit -showcerts -starttls ldap
+CONNECTED(00000003)
+depth=1 C = OT, ST = Tentacle Cove, O = FreeRADIUS, OU = Services, CN = example.com, emailAddress = support@example.com
+verify return:0
+---
+Certificate chain
+ 0 s:/C=OT/ST=Tentacle Cove/L=Grenoble/O=FreeRADIUS/OU=Services/CN=ldap.example.com/emailAddress=support@example.com
+   i:/C=OT/ST=Tentacle Cove/O=FreeRADIUS/OU=Services/CN=example.com/emailAddress=support@example.com
+-----BEGIN CERTIFICATE-----
+MIIHDjCCBPagAwIBAgIJANAO5znieeLNMA0GCSqGSIb3DQEBCwUAMIGSMQswCQYD
+...
+```
+[NOTE]
+.Availability of `-starttls ldap`
+Not all builds of `openssl s_client` support `-starttls ldap`.  As of OpenSSL
+1.1.1 this feature is still only available in the OpenSSL master branch. See
+this GitHub Pull Request for details:
+https://github.com/openssl/openssl/pull/2293.
+====
+****
diff --git a/doc/antora/modules/howto/pages/modules/ldap/ldapsearch/index.adoc b/doc/antora/modules/howto/pages/modules/ldap/ldapsearch/index.adoc
new file mode 100644 (file)
index 0000000..6fdf4ba
--- /dev/null
@@ -0,0 +1,32 @@
+= Mapping and testing with ldapsearch
+
+Every LDAP directory is different.  Each directory has different connection parameters,
+locations for objects, and attributes within those objects.
+
+This section of the LDAP howto will guide you through discovering the values
+needed to configure the LDAP module.
+
+We _strongly_ recommend running these checks in this section on the same machine
+that will be used to host the RADIUS server.  A common cause of connectivity
+issues is that a firewall is preventing network access from the RADIUS server to
+the LDAP server. Running `ldapsearch` on a third machine may not reveal these
+firewall issues.
+
+== xref:modules/ldap/ldapsearch/before_starting.adoc[Before starting]
+
+What questions to ask your LDAP administrator before you begin working with the
+directory.
+
+== xref:modules/ldap/ldapsearch/connection_parameters.adoc[Determining connection parameters]
+
+Discover through trial and error, what connection parameters the LDAP
+directory requires.
+
+== xref:modules/ldap/ldapsearch/locating_objects.adoc[Locating objects]
+
+Discover where users and groups are located within the directory.
+
+== xref:modules/ldap/ldapsearch/translating_to_the_ldap_module.adoc[Using results to configure the LDAP module]
+
+Translate LDAP search arguments and the results from previous sections
+into LDAP module configuration items
diff --git a/doc/antora/modules/howto/pages/modules/ldap/ldapsearch/locating_objects.adoc b/doc/antora/modules/howto/pages/modules/ldap/ldapsearch/locating_objects.adoc
new file mode 100644 (file)
index 0000000..52edb72
--- /dev/null
@@ -0,0 +1,250 @@
+= Locating objects
+
+Once the correct xref:modules/ldap/ldapsearch/connection_parameters.adoc[connection parameters]
+ have been determined, the next step in building your configuration
+is to locate where user and group objects are stored in the directory.
+
+== How to locate user objects
+Users are represented by a fairly limited subset of ObjectClasses.  The
+following filters are usually sufficient to identify users in different
+directory types.
+
+- OpenLDAP - `(|(ObjectClass=organizationalPerson)(ObjectClass=posixAccount)(ObjectClass=Person))`
+- ActiveDirectory - `(|(ObjectClass=User)(ObjectClass=posixAccount))`
+- Novell eDir - `(ObjectClass=User)`
+
+.Performing a search with a user object filter
+====
+```
+ldapsearch -z 10 -x -H ldap://ldap.example.com:389 -b "dc=example,dc=com" "(|(ObjectClass=organizationalPerson)(ObjectClass=PosixAccount)(ObjectClass=Person))"
+
+# extended LDIF
+#
+# LDAPv3
+# base <dc=example,dc=com> with scope subtree
+# filter: (|(ObjectClass=organizationalPerson)(ObjectClass=PosixAccount)(ObjectClass=Person))
+# requesting: ALL
+#
+
+...
+
+# doctopus, octopuses, example.com
+dn: uid=doctopus,ou=octopuses,dc=example,dc=com
+objectClass: person
+objectClass: organizationalPerson
+objectClass: inetOrgPerson
+cn: Doctopus McTentacles
+sn: McTentacles
+uid: dpus
+givenName: Doctopus
+userPassword:: MGN0MHB1NTNzUnVsMw==
+
+# search result
+search: 3
+result: 0 Success
+
+# numResponses: 18
+# numEntries: 17
+```
+====
+
+There's a lot of useful information in this result:
+
+- We know where the user objects are likely to be located, as it's fairly
+obvious from the DN (`...,ou=octopuses,dc=example,dc=com`). _Note: In the
+majority of non-FreeRADIUS themed LDAP directories the user object container
+will be `ou=people`_.
+- The result shows a `uid` attribute.  This will almost always be the correct
+attribute for identifying a user.
+- The result shows a base64 encoded `userPassword` attribute.  This
+means users can be authenticated without rebinding the connection (more
+efficient).
+- There's no `memberOf` attributes. This means user to group mappings
+are likely stored in group objects instead of the user objects themselves.
+
+=== What to record
+
+- `user_object_base_dn`        - The DN higher in the tree than relevant users
+objects.
+- `user_object_class_filter`   - The filter which matches the objectClass(es) of
+user objects.
+- `uid_attribute`              - The attribute used to identify the user
+(usually `uid`, but can vary considerably between instances).
+- `user_password_attribute`    - The attribute used to hold password data (if
+present).
+- `account_disabled_attribute` - Any attributes used to indicate whether an
+account is disabled. To determine if this attribute exists, repeat the user
+search (above) with a filter for a user account known to be disabled e.g.
+`(uid=a-disabled-user)`.
+- `account_enabled_attribute`  - Any attributes used to indicate whether an
+account is enabled.  Should be present in the search results already obtained.
+
+.Locating users in "mature" LDAP directories
+****
+The above result represents an ideal scenario.  In reality, LDAP directories
+often accumulate a lot of cruft.  Users objects may be located in
+multiple container objects, and at multiple levels within a directory.
+
+If you believe this to be the case with your directory, remove the `-z` argument
+limiting the number of result, and pipe the output of `ldapsearch` through a
+command chain _such as_: `grep dn: | sed -e 's/dn: [^,]*,//' | sort | uniq -c`.
+
+This operation will return a list of objects which _contain_ user objects,
+and how many user objects they contain.
+
+.Determining which objects contain users
+====
+```
+ldapsearch -x -H ldap://ldap.example.com:389 -b "dc=example,dc=com" "(|(ObjectClass=organizationalPerson)(ObjectClass=PosixAccount)(ObjectClass=Person))" | grep dn: | sed -e 's/dn: [^,]*,//' | sort | uniq -c`
+2 ou=octopuses,dc=example,dc=com
+10 ou=people,dc=example,dc=com
+1 ou=people,dc=created-by-someone-who-didnt-know-what-dc-meant,dc=example,dc=com
+2 ou=people,ou=remnant-of-an-ancient-edir-instance,dc=example,dc=com
+2 ou=giraffes,dc=example,dc=com
+```
+====
+
+The user object base DN selected for your rlm_ldap configuration must be higher
+in the object tree than the objects containing users.  In the above example the
+user object base DN would be `dc=example,dc=com`.
+
+If an error message is returned similar to `Size limit exceeded (4)`, it means
+that the LDAP server is refusing to provide the complete set of results. If the
+LDAP directory supports result paging, adding a paging request such as `-E
+'pr=100'` may negate the per-search result limit.
+
+For large result sets you should also specify a list of attributes to retrieve
+after the search filter.  Limiting the attributes returned reduces load on the
+LDAP server, and generally results in a quicker response.
+
+```
+ldapsearch ... -E 'pr=100' "(|(ObjectClass=...))" "dn" | ...
+```
+****
+
+== How to locate group objects
+
+Groups are represented by a fairly limited subset of ObjectClasses.  The
+following filters are usually sufficient to identify groups in different
+directory types.
+
+- OpenLDAP - `(objectClass=groupOfNames)`
+- ActiveDirectory - `(objectClass=group)`
+- Novell eDir - `(objectClass=group)`
+
+.Performing a search with a group object filter
+====
+```
+ldapsearch -z 10 -x -H ldap://ldap.example.com:389 -b "dc=example,dc=com" "(objectClass=posixGroup)"
+
+# extended LDIF
+#
+# LDAPv3
+# base <ou=people,dc=example,dc=com> with scope subtree
+# filter: (ObjectClass=posixGroup)
+# requesting: ALL
+#
+
+...
+
+dn: cn=mariner-alert,ou=groups,dc=example,dc=com
+cn: mariner-alert
+description: Heads up to all delicious denizens of the directory
+member: uid=doctopus,ou=octopuses,dc=example,dc=com
+member: uid=rocktopus,ou=octopuses,dc=example,dc=com
+objectClass: groupOfNames
+objectClass: fdGroupMail
+mail: mariner-alert@example.com
+
+# search result
+search: 3
+result: 0 Success
+
+# numResponses: 10
+# numEntries: 9
+```
+====
+
+.Locating groups in "mature" directories
+****
+As with users, groups may be located in multiple areas of the directory.
+
+.Determining which objects contain groups
+====
+```
+ldapsearch -x -H ldap://ldap.example.com:389 -b "dc=example,dc=com" -E 'pr=100' "(ObjectClass=posixGroup)" dn | grep dn: | sed -e 's/dn: [^,]*,//' | sort | uniq -c`
+3 ou=groups,dc=example,dc=com
+1 ou=groups,o=long-defunct-org-that-was-merged-with-example-com,dc=example,dc=com
+3 ou=groups,ou=interns-summer-project,dc=example,dc=com
+2 ou=groups,ou=backup-restored-in-the-wrong-place,dc=example,dc=com
+```
+====
+****
+
+=== Group membership scheme variants
+Groups memberships can be represented in multiple ways.  There are four main
+variants:
+
+1. User objects which reference groups by DN, _usually_ with multiple
+instances of the `memberOf` attribute.
+2. User objects which reference groups by name, again, _usually_
+with multiple instances of the `memberOf` attribute.
+3. Group objects which reference their members by DN, _usually_ with multiple
+instances of the `member` attribute.
+4. Group objects which reference their members by name, _usually_ with
+multiple instances of the `memberUid` attribute.
+
+In order to perform efficient group lookups which group membership scheme the
+LDAP server uses must be determined.
+
+Repeat the search for user objects using a filter that matches a user known
+to be members of one or more groups e.g.
+`(&(objectClass=posixUser)(uid=doctopus))`.
+
+- If the result shows a attribute containing the DN of known group, the LDAP
+server implements _variant 1_.
+
+- If the result shows a attribute containing the name of known group, the LDAP
+server implements _variant 2_.
+
+Repeat the the search for group objects using a filter that matches a group
+the user is known to be a member of e.g. `(&(objectClass=groupOfNames)(cn=mariner-alert))`.
+
+- If the result shows an attribute containing the DN of a user, the ldap server
+implements _variant 3_.
+
+- If the result shows an attribute containing the name of a user, the ldap server
+implements _variant 4_.
+
+=== If the variant can't be determined
+
+- There's a typographical error in the search filter or `ldapsearch` arguments.
+- The bound user doesn't have sufficient permission to view user or group
+objects. Contact your LDAP administrator.
+- The LDAP directory uses a special scheme to represent groups which requires
+querying a special OID, or providing additional search controls (outside of the
+scope of this howto).
+- Group memberships are represented with 'operational' attributes.  Repeat the
+user object search with '+' set as the second positional argument.
+
+.Searching for a user object and returning operational attributes
+```
+ldapsearch -z 10 -x -H ldap://ldap.example.com:389 -b "dc=example,dc=com" "(ObjectClass=posixUser)" +
+```
+
+=== What to record
+
+* `group_object_base_dn`             - The DN higher in the tree than all
+relevant group objects.
+* `group_name_attribute`             - The attribute used to identify the group
+(usually `cn`).
+* _variant 1_
+** `membership_attribute`            - User object attribute containing group
+membership information.
+* _variant 2_
+** `membership_attribute`            - User object attribute containing group
+membership information.
+* _variant 3_
+** `membership_user_dn_filter`       - A filter matching users by DN.
+* _vairant 4_
+** `membership_user_name_filter`     - A filter matching users by user name.
diff --git a/doc/antora/modules/howto/pages/modules/ldap/ldapsearch/translating_to_the_ldap_module.adoc b/doc/antora/modules/howto/pages/modules/ldap/ldapsearch/translating_to_the_ldap_module.adoc
new file mode 100644 (file)
index 0000000..f48d354
--- /dev/null
@@ -0,0 +1,102 @@
+= Translating ldapsearch arguments to LDAP configuration items
+
+[width="100%",cols="30%,20%,50%",options="header",]
+|===
+| Purpose                                     | ldapsearch argument         | `ldap { ... }` config item
+| Limit number of search results              | ```-z```                    | Not supported.
+| Use basic authentication                    | ```-x```                    | Set by default.
+| LDAP Host URI                               | ```-H <uri>```              | ```server = '<uri>'``` +
+                                                                              ```port = (389\|<custom port>)```
+| Base DN                                     | ```-b <dn>```               | ```base_dn = '<dn>'```
+| Bind DN                                     | ```-D <dn>```               | ```identity = '<dn>'```
+| Bind Password                               | ```-w <password>```         | ```password = '<password>'```
+| Enable LDAPS                                | ```-H ldaps://<uri>```      | ```server = 'ldaps://<uri>'``` +
+                                                                              ```port = (636\|<custom port>)```
+| Enable StartTLS                             | ```-ZZ```                   | ```tls { start_tls = yes }```
+| Specify RootCA and intermediaries           | ```LDAPTLS_CACERT=<ca_cert_and_intermediaries.pem>``` | ```tls { ca_file = '<ca_cert_and_intermediaries.pem>' }```
+| Require cert validation to succeed          | ```LDAPTLS_REQCERT=hard```  | ```tls { require_cert = 'demand' }```
+|===
+
+= Translating ldapsearch results to rlm_ldap configuration items
+
+== Users
+
+[width="100%",cols="30%,70%",options="header",]
+|===
+| Purpose                                     | `ldap { user { ... } }` config item
+| Specify where to search for users           | ```base_dn = '<user_object_base_dn>'```
+| Specify how to find a user                  | ```filter = "(&(<user_object_class_filter>)(<uid_attribute>=%{%{Stripped-User-Name}:-%{User-Name}})"```
+| Retrieve a "known good" password            | ```update { &control:Password-With-Header = <user_password_attribute>```
+| Allow accounts to be explicitly disabled    | ```access_attribute = '<account_disabled_attribute>'``` +
+                                                ```access_positive = 'no'```
+| Require accounts to be explicitly enabled   | ```access_attribute = '<account_enabled_attribute>'``` +
+                                                ```access_positive = 'yes'```
+|===
+
+== Groups - Common
+
+[width="100%",cols="30%,70%",options="header",]
+|===
+| Purpose                                     | `ldap { group { ... } }` config item
+| Specify where to search for group           | ```base_dn = '<group_object_base_dn>'```
+| Specify which objects are groups            | ```filter = '<group_object_class_filter>'```
+| Specify which attribute in a group object
+  identifies the group                        | ```name_attribute = '<group_name_attribute>'```
+|===
+
+== Groups - variant 1
+
+User objects reference groups using DNs.
+
+[width="100%",cols="30%,70%",options="header",]
+|===
+| Purpose                                     | `ldap { group { ... } }` config item
+| Specify how to find group objects by DN, when referenced by a user object. | ```membership_attribute = '<group_object_base_dn>'```
+|===
+
+== Groups - variant 2
+
+User objects reference groups using group names.
+
+[width="100%",cols="30%,70%",options="header",]
+|===
+| Purpose                                     | `ldap { group { ... } }` config item
+| Specify how to find group objects by name, when referenced by a user object. | ```membership_attribute = '<group_object_base_dn>'```
+|===
+
+== Groups - variant 3
+
+Group objects reference users using DNs.
+
+[width="100%",cols="30%,70%",options="header",]
+|===
+| Purpose                                     | `ldap { group { ... } }` config item
+| Specify how to find group objects referencing a user by DN. | ```membership_filter = "(<membership_user_dn_filter>=%{control:Ldap-UserDn})"```
+|===
+
+== Groups - variant 4
+
+Group objects reference users using user names.
+
+[width="100%",cols="30%,70%",options="header",]
+|===
+| Purpose                                     | `ldap { group { ... } }` config item
+| Specify how to find group objects referencing a user by name. | ```membership_filter = "(<membership_user_name_filter>=%{%{Stripped-User-Name}:-%{User-Name}})"```
+|===
+
+.Mixing and matching group membership schemes
+****
+Although rare, it is possible to have all four group membership scheme variants
+in a single directory. FreeRADIUS supports this configuration.
+
+For _variant 1_ and _variant 2_ FreeRADIUS will automatically determine if the
+user object attribute contained a DN or group name.
+
+For _variant 3_ and _variant 4_ it's possible to construct a filter which matches
+both on user DN and user name e.g.
+
+[source,config]
+----
+membership_filter = "(|(<membership_user_dn_filter>=%{control:Ldap-UserDn})(<membership_user_name_filter>=%{%{Stripped-User-Name}:-%{User-Name}}))"
+----
+****