Skip to content

Commit

Permalink
Support Kerberos GSSAPI binds to LDAP
Browse files Browse the repository at this point in the history
Install the required libraries and the k5start program in the
container and, if a keytab and krb5.conf file are present in the
container, run the application under k5start. (This will be handled
by the Helm chart for invocations other than the default uvicorn
one.) If ldap.useKerberos is set in the configuration and there is
no userDn set, use GSSAPI as the authentication method.

Add documentation for the secret and Helm configuration. Reorganize
the LDAP configuration documentation a bit to use subheadings.
  • Loading branch information
rra committed Apr 18, 2023
1 parent aede116 commit 00e9c76
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 20 deletions.
3 changes: 3 additions & 0 deletions changelog.d/20230418_091146_rra_DM_38747.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### New features

- Support Kerberos GSSAPI binds to authenticate to an LDAP server.
76 changes: 65 additions & 11 deletions docs/user-guide/helm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -211,29 +211,83 @@ There are some additional options under ``config.oidc`` that you may want to set
The claim of the OpenID Connect ID token from which to take the username.
The default is ``uid``.

.. _ldap-groups:
.. _ldap:

LDAP groups
===========
LDAP
====

When using either CILogon or generic OpenID Connect as an authentication provider, you can choose to obtain group information from an LDAP server rather than an ``isMemberOf`` attribute inside the token.
The preferred way for Gafaelfawr to get metadata about users (full name, email address, group membership, UID and GID, etc.) when using CILogon or OpenID Connect is from an LDAP server.
If the GitHub authentication provider is used, this information instead comes from GitHub and LDAP is not supported.

To do this, add the following configuration:
If LDAP is enabled, group membership is always taken from LDAP (see :ref:`ldap-groups`) instead of the ID token from the upstream authentication provider.
Other information about the user may also be retrieved from LDAP if configured (see :ref:`ldap-user`).

LDAP authentication
-------------------

.. note::

This section describes how the Gafaelfawr service itself authenticates to the LDAP server.
Users are never authenticated using LDAP.
User authentication always uses OpenID Connect or GitHub.

Gafaelfawr supports anonymous binds, simple binds (username and password), or Kerberos GSSAPI binds.

To use anonymous binds (the default), just specify the URL of the LDAP server with no additional bind configuration.

.. code-block:: yaml
config:
ldap:
url: "ldaps://<ldap-server>"
To use simple binds, also specify the DN of the user to bind as.
If this is set, ``ldap-password`` must be set in the Gafaelfawr Vault secret to the password to use with the simple bind.

.. code-block:: yaml
config:
ldap:
url: "ldaps://<ldap-server>"
userDn: "<bind-dn-of-user>"
To use Kerberos GSSAPI binds, provide a ``krb5.conf`` file that contains the necessary information to connect to your Kerberos server.
Normally at least ``default_realm`` should be set.
Including a full copy of your standard ``/etc/krb5.conf`` file should work.
If this is set, ``ldap-keytab`` must be set in the Gafaelfawr Vault secret to the contents of a Kerberos keytab file to use for authentication to the LDAP server.

.. code-block:: yaml
config:
ldap:
url: "ldaps://<ldap-server>"
kerberosConfig: |
[libdefaults]
default_realm = EXAMPLE.ORG
[realms]
EXAMPLE.ORG = {
kdc = kerberos.example.org
kdc = kerberos-1.example.org
kdc = kerberos-2.example.org
default_domain = example.org
}
.. _ldap-groups:

LDAP groups
-----------

To obtain user group information from LDAP, add the following configuration:

.. code-block:: yaml
config:
ldap:
groupBaseDn: "<base-dn-for-search>"
You may need to set the following additional options under ``config.ldap`` depending on your LDAP schema:

``config.ldap.userDn``
The DN of the user to bind as.
Gafaelfawr currently only supports simple binds.
If this is set, ``ldap-password`` must be set in the Gafaelfawr Vault secret to the password to use with the simple bind.

``config.ldap.groupObjectClass``
The object class from which group information should be looked up.
Default: ``posixGroup``.
Expand All @@ -254,7 +308,7 @@ The name of each group will be taken from the ``cn`` attribute and the GID will
.. _ldap-user:

LDAP user information
=====================
---------------------

By default, Gafaelfawr takes the user's name, email, and numeric UID from the upstream provider via the ``name``, ``mail``, and ``uidNumber`` claims in the ID token.
If LDAP is used for group information, this data, plus the primary GID, can instead be obtained from LDAP.
Expand Down
12 changes: 9 additions & 3 deletions docs/user-guide/secrets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,16 @@ The Phalanx installer expects a Vault secret named ``gafaelfawr`` in the relevan
Only used if ForgeRock Identity Management support is enabled.
See :ref:`forgerock` for more information.

``ldap-keytab`` (optional)
The Kerberos keytab used for Kerberos GSSAPI binds to an LDAP server.
This should be the file contents of a keytab file encoded in base64 without line wrapping, using a command such as ``base64 -w 0 < keytab-file``.
Only used if LDAP lookups are enabled and a Kerberos configuration is provided.
See :ref:`ldap` for more information.

``ldap-password`` (optional)
The password used for simple binds to the LDAP server used as a source of data about users.
Only used if LDAP lookups are enabled.
See :ref:`ldap-groups` for more information.
The password used for simple binds to an LDAP server.
Only used if LDAP lookups are enabled and simple binds are configured.
See :ref:`ldap` for more information.

``oidc-client-secret`` (optional)
The secret for an OpenID Connect authentication provider.
Expand Down
12 changes: 7 additions & 5 deletions scripts/install-base-packages.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ apt-get update
# Install security updates:
apt-get -y upgrade

# git is required by setuptools-scm. libpq-dev is required by psycopg2.
# libldap2-dev and libsasl2-dev are required by bonsai.
apt-get -y install --no-install-recommends git libpq-dev libldap2-dev \
libldap-common libsasl2-modules \
libsasl2-dev
# git is required by setuptools-scm. libpq-dev is required by psycopg2. The
# other packages are required by bonsai for LDAP binds or to manage the
# Kerberos ticket cache. (krb5-user is not strictly needed, but it's useful
# for debugging.)
apt-get -y install --no-install-recommends git krb5-user kstart \
libldap2-dev libldap-common libsasl2-dev libsasl2-modules \
libsasl2-modules-gssapi-mit libpq-dev

# Delete cached files we don't need anymore:
apt-get clean
Expand Down
11 changes: 10 additions & 1 deletion scripts/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,14 @@

set -eu

# Always initialize the database if needed. This shouldn't require LDAP access
# and thus isn't run with Kerberos tickets.
gafaelfawr init
uvicorn --factory gafaelfawr.main:create_app --host 0.0.0.0 --port 8080

# Start the server under k5start if Kerberos is configured.
cmd="uvicorn --factory gafaelfawr.main:create_app --host 0.0.0.0 --port 8080"
if [ -f "/etc/krb5.conf" ] && [ -f "/etc/krb5.keytab" ]; then
exec k5start -aqUFf /etc/krb5.keytab -- $cmd
else
exec $cmd
fi
17 changes: 17 additions & 0 deletions src/gafaelfawr/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ class LDAPSettings(CamelCaseModel):
user_dn: Optional[str] = None
"""Simple bind user DN for the LDAP server."""

use_kerberos: bool = False
"""Whether to use Kerberos GSSAPI binds.
If both this and ``user_dn`` are set, simple binds take precedence. This
allows triggering all of the other Kerberos handling while still using
simple binds instead of GSSAPI binds, to make testing easier.
"""

password_file: Optional[Path] = None
"""File containing simple bind password for the LDAP server."""

Expand Down Expand Up @@ -560,6 +568,14 @@ class LDAPConfig:
password: Optional[str]
"""Password for simple bind authentication to the LDAP server."""

use_kerberos: bool
"""Whether to use Kerberos GSSAPI binds.
If both this and ``user_dn`` are set, simple binds take precedence. This
allows triggering all of the other Kerberos handling while still using
simple binds instead of GSSAPI binds, to make testing easier.
"""

group_base_dn: str
"""Base DN to use when executing LDAP search for group membership."""

Expand Down Expand Up @@ -892,6 +908,7 @@ def from_file(cls, path: Path) -> Self:
url=settings.ldap.url,
user_dn=settings.ldap.user_dn,
password=ldap_password,
use_kerberos=settings.ldap.use_kerberos,
group_base_dn=settings.ldap.group_base_dn,
group_object_class=settings.ldap.group_object_class,
group_member_attr=settings.ldap.group_member_attr,
Expand Down
2 changes: 2 additions & 0 deletions src/gafaelfawr/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ async def from_config(cls, config: Config) -> Self:
user=config.ldap.user_dn,
password=config.ldap.password,
)
elif config.ldap.use_kerberos:
client.set_credentials("GSSAPI")
ldap_pool = AIOConnectionPool(client)

return cls(
Expand Down

0 comments on commit 00e9c76

Please sign in to comment.