[2018.08.07]
I’ve been netsearching a way to make SSL client authentication
(SSLVerifyClient require
) play nicely along LDAP authorization (e.g.
Require ldap-group ...
)… to no avail.
In order for basic (LDAP) authorization to kick in, one MUST configure
the SSL stack to fake basic authentication: SSLOptions +FakeBasicAuth
Unfortunately, once a user has been successfully authenticated using his
SSL certificate (with no password communicated what-so-ever), FakeBasicAuth
will take over with two gotchas:
The “authenticated” username will be set to the SSL certificate’s subject Distinguished Name (DN)
The “authenticated” password will be set to “password” (literally)
Which both don’t make much sense to any existing LDAP directory per-se.
But the FOSS world being such a marvelous environment - this time thanks to OpenLDAP - there is a rather (very!) elegant solution to the problem at hand!
For the sake of the explanation, we’ll assume the SSL client certificates
have a DN that includes a Common Name (CN) that matches the LDAP directory’s
existing users' UID; example given: /C=CH/O=Example Corp./OU=example.org/CN=username
NOTE: from Apache >= 2.5, thanks to the SSLUserName
parameter, any part
of the certificate’s subject or Subject Alternative Name (SAN) may be used;
but we’re not there yet (since I’m among those fossilized Debian/Stable
retarted sysadmins).
Since our problem stems from a dummy (constant) password and a username that
must be massaged into something meaningful to our LDAP directory, let’s
create a virtual view on top of the existing posixAccount
s, with some
nifty rewrite rules, thanks to OpenLDAP relay and rwm overlays;
in /etc/ldap/slapd.apacheSslClients-overlay.conf
, to be included in /etc/ldap/slapd.conf
before the definition of the dc=example,dc=org
database:
## Database: ou=apacheSslClients,dc=example,dc=org (RELAY/RWM)
# REF: https://httpd.apache.org/docs/2.4/mod/mod_auth_basic.html#authbasicfake
# NOTE: The Apache SSL Clients virtual container masquerades the users
# directory (ou=users,dc=example,dc=org) for the purposes of certificate-
# based authentication, by stripping all unnecessary attributes
# and mapping the 'userPassword' to the dummy "password".
# !!! WARNING !!! WARNING !!! WARNING !!! WARNING !!! WARNING !!! WARNING !!!
# Do NOT make this database a sub-directory of the existing users directory!
# We do NOT want the dummy "password" to be used for authentication purposes
# other than for Apache SSL (inherently-authenticated) clients!
# !!! WARNING !!! WARNING !!! WARNING !!! WARNING !!! WARNING !!! WARNING !!!
# Database
database relay
suffix "ou=apacheSslClients,dc=example,dc=org"
# Features
readonly on
lastmod off
# Rewriting (mapping) rules
overlay rwm
rwm-rewriteEngine on
# ... attributes (client<->server)
rwm-map objectclass simpleSecurityObject posixAccount
rwm-map objectclass *
rwm-map attribute uid *
rwm-map attribute userPassword *
rwm-map attribute *
# ... default client->server requests
rwm-rewriteContext default
rwm-rewriteRule "^uid=[^,]*cn=([^,/]*)[^,]*,ou=apacheSslClients,dc=example,dc=org$" "uid=$1,ou=users,dc=example,dc=org" ":@"
rwm-rewriteRule "^(.+,)?ou=apacheSslClients,dc=example,dc=org$" "$1ou=users,dc=example,dc=org" ":@"
rwm-rewriteContext searchFilter
rwm-rewriteRule "^(.*)\\(uid=[^)]*cn=([^/)]*)[^)]*\\)(.*)$" "$1(uid=$2)$3" ":@"
# ... bind client->server requests
rwm-rewriteContext bindDN
rwm-rewriteRule "^uid=[^,]+,ou=apacheSslClients,dc=example,dc=org$" "cn=apacheSslClient,dc=example,dc=org" ":@"
rwm-rewriteRule ".*ou=apacheSslClients,dc=example,dc=org$" "" "#"
# ... all server->client responses
rwm-rewriteContext searchEntryDN
rwm-rewriteRule "^(.+,)?ou=users,dc=example,dc=org$" "$1ou=apacheSslClients,dc=example,dc=org" ":@"
rwm-rewriteContext searchAttrDN alias searchEntryDN
rwm-rewriteContext matchedDN alias searchEntryDN
## ACL
# -> Protect passwords
access to attrs=userPassword
by * none
# -> Simplistic default (allow anonymous search from Apache)
access to *
by * read
The rwm magic here is four-fold:
LDAP posixAccount
entries are remapped to simpleSecurityObject
, by retaining
only their uid
and userPassword
attributes
The LDAP DN received from Apache - with that horridly-formatted SSL “username”
(subject DN) - is massaged by extracting the CN part of that “username” into
the sensible uid=$1,ou=users,dc=example,dc=org
(this solves gotcha N.1)
All bind attempts received within the scope of the virtual database are mapped
to the single cn=apacheSslClient,dc=example,dc=org
entry, which userPassword
is set to “password” (literally)
(this solves gotcha N.2)
The DN returned to Apache is massaged back to uid=$1,ou=ApacheSslClients,dc=example,dc=org
(this will also help authorization; see below)
As mentioned, the cn=apacheSslClient,dc=example,dc=org
dummy Apache user/password
needs to be created; as per following LDIF:
dn: cn=apacheSslClient,dc=example,dc=org
changetype: add
cn: apacheSslClient
objectClass: simpleSecurityObject
userPassword: password
Now that LDAP authentication has been taken care of, let’s deal with LDAP
group-based authorization. Once again, we’ll use a virtual view on top of the
existing groupOfNames
;
in /etc/ldap/slapd.apacheSslGroups-overlay.conf
, to be included in /etc/ldap/slapd.conf
before the definition of the dc=example,dc=org
database:
## Database: ou=apacheSslGroups,dc=example,dc=org (RELAY/RWM)
# NOTE: The Apache SSL Groups virtual container masquerades the groups
# directory (ou=groups,dc=example,dc=org) for the purposes of certificate-
# based authentication, by stripping all unnecessary attributes and
# mapping the group members to the Apache SSL Clients.
# Database
database relay
suffix "ou=apacheSslGroups,dc=example,dc=org"
# Features
readonly on
lastmod off
# Rewriting (mapping) rules
overlay rwm
rwm-rewriteEngine on
# ... attributes (client<->server)
rwm-map objectclass groupOfNames *
rwm-map objectclass *
rwm-map attribute cn *
rwm-map attribute member *
rwm-map attribute *
# ... default client->server requests
rwm-rewriteContext default
rwm-rewriteRule "^(.+,)?ou=apacheSslGroups,dc=example,dc=org$" "$1ou=groups,dc=example,dc=org" ":@"
rwm-rewriteContext compareAttrDN
rwm-rewriteRule "^uid=([^,]*),.*$" "uid=$1,ou=users,dc=example,dc=org" ":@"
# ... all server->client responses
rwm-rewriteContext searchEntryDN
rwm-rewriteRule "^(.+,)?ou=groups,dc=example,dc=org$" "$1ou=apacheSslGroups,dc=example,dc=org" ":@"
rwm-rewriteContext matchedDN alias searchEntryDN
rwm-rewriteContext searchAttrDN
rwm-rewriteRule "^uid=([^,]*),.*$" "uid=$1,ou=apacheSslClients,dc=example,dc=org" ":@"
## ACL
# -> Simplistic default (allow anonymous search from Apache)
access to *
by * read
The rwm magic is still the same, with the addition of compareAttrDN
and
searchAttrDN
massaging, rewriting/mapping the groupOfNames
’s member
s
from/to Apache ou=apacheSslClients,dc=example,dc=org
SSL clients.
NOTE: This magic works only with groupOfNames
, which member
s are DNs,
and will NOT work with posixGroup
s; this is a limitation of rwm.
NOTE(2): Apache >= 2.5 SSLUserName
parameter may allow to get rid of
this view, allowing to match the authenticated SSL client directly to
existing posixGroup
s.
SSL client authentication versus LDAP authorization is therefrom straight-forward:
# Users SSL (certificate-based) authentication
SSLRequireSSL
SSLVerifyClient require
SSLOptions +FakeBasicAuth +StdEnvVars
# (ignored until Apache 2.5)
SSLUserName SSL_CLIENT_S_DN_CN
# Users (dummy) LDAP "authentication"
AuthType Basic
AuthName "Access Restricted - SSL certificate-based authentication"
AuthBasicProvider ldap
AuthLDAPURL ldaps://ldap.example.org/ou=apacheSslClients,dc=example,dc=org?uid?one?(objectClass=simpleSecurityObject)
AuthLDAPGroupAttribute member
AuthLDAPGroupAttributeIsDN On
# Authorization
Require ldap-group cn=somegroup,ou=apacheSslGroups,dc=example,dc=org