diff --git a/changelog.d/20230418_091146_rra_DM_38747.md b/changelog.d/20230418_091146_rra_DM_38747.md new file mode 100644 index 000000000..52349f69d --- /dev/null +++ b/changelog.d/20230418_091146_rra_DM_38747.md @@ -0,0 +1,3 @@ +### New features + +- Support Kerberos GSSAPI binds to authenticate to an LDAP server. diff --git a/docs/user-guide/helm.rst b/docs/user-guide/helm.rst index 4542381a3..b3b6b48cd 100644 --- a/docs/user-guide/helm.rst +++ b/docs/user-guide/helm.rst @@ -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://" + +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://" + userDn: "" + +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://" + 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: "" 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``. @@ -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. diff --git a/docs/user-guide/secrets.rst b/docs/user-guide/secrets.rst index d04315956..f436297e7 100644 --- a/docs/user-guide/secrets.rst +++ b/docs/user-guide/secrets.rst @@ -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. diff --git a/scripts/install-base-packages.sh b/scripts/install-base-packages.sh index c5746f43a..15e2d131d 100755 --- a/scripts/install-base-packages.sh +++ b/scripts/install-base-packages.sh @@ -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 diff --git a/scripts/start.sh b/scripts/start.sh index 7e9d9003e..86e777a72 100755 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -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 diff --git a/src/gafaelfawr/config.py b/src/gafaelfawr/config.py index c8d7e67cc..162a6d371 100644 --- a/src/gafaelfawr/config.py +++ b/src/gafaelfawr/config.py @@ -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.""" @@ -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.""" @@ -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, diff --git a/src/gafaelfawr/factory.py b/src/gafaelfawr/factory.py index 04ad83bf0..c90caaa5f 100644 --- a/src/gafaelfawr/factory.py +++ b/src/gafaelfawr/factory.py @@ -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(