diff --git a/Dockerfile b/Dockerfile index ccd2200..50f9ae5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,7 @@ RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ slapd \ ldap-utils \ + libsasl2-modules-gssapi-mit \ python3-jinja2 \ python3-ldap3 && \ rm -rf /var/lib/apt/lists/* diff --git a/README.md b/README.md index 04341cc..b13730f 100644 --- a/README.md +++ b/README.md @@ -210,3 +210,61 @@ ldapsearch -x -H ldap://localhost \ ldapwhoami -x -H ldap://localhost \ -D "cn=readonly,ou=service-accounts,dc=koszewscy,dc=waw,dc=pl" -W ``` + +## Kerberos SASL/GSSAPI + +Gate with `KERBEROS_ENABLE=1`. When enabled, slapd is configured at first-run bootstrap with SASL GSSAPI and two authz-regexp rules that map Kerberos principals to LDAP DNs. + +### Environment variables + +| Variable | Default | Description | +|---|---|---| +| `KERBEROS_ENABLE` | `0` | Set to `1` to enable | +| `KRB5_REALM` | — | Kerberos realm (uppercase, e.g. `EXAMPLE.ORG`) | +| `KRB5_SASL_HOST` | — | Hostname matching the `ldap/@REALM` service principal | +| `KRB5_KTNAME` | `/etc/ldap/ldap.keytab` | Path to the keytab inside the container | + +### Principal-to-DN mapping + +| Kerberos principal | LDAP DN | +|---|---| +| `*/admin@REALM` | `cn=admin,` | +| `username@REALM` | `uid=username,ou=users,` | + +### Setup steps + +1. In the Kerberos container, create the service principal and extract a keytab: + ```bash + kadmin.local -q "addprinc -randkey ldap/ldap.example.org@REALM" + kadmin.local -q "ktadd -k /tmp/ldap.keytab ldap/ldap.example.org@REALM" + ``` +2. Copy the keytab to the OpenLDAP host: + ```bash + container cp kerberos:/tmp/ldap.keytab ~/app-data/openldap/ldap.keytab + ``` +3. Mount it into the OpenLDAP container at `KRB5_KTNAME` (default `/etc/ldap/ldap.keytab`) and set the Kerberos env vars in `openldap.env`. +4. On first start, bootstrap applies the SASL configuration automatically. For an already-initialised instance apply it manually: + ```bash + ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<'EOF' + dn: cn=config + changetype: modify + replace: olcSaslHost + olcSaslHost: ldap.example.org + - + replace: olcSaslRealm + olcSaslRealm: EXAMPLE.ORG + - + replace: olcAuthzRegexp + olcAuthzRegexp: {0}uid=([^/]+)/admin,cn=example.org,cn=gssapi,cn=auth cn=admin,dc=example,dc=org + olcAuthzRegexp: {1}uid=([^,]+),cn=example.org,cn=gssapi,cn=auth uid=$1,ou=users,dc=example,dc=org + EOF + ``` + +### Test authentication + +```bash +kinit username@REALM +ldapwhoami -Y GSSAPI -H ldap://ldap.example.org +``` + +Expected: `dn:uid=username,ou=users,dc=example,dc=org` diff --git a/bootstrap/init.py b/bootstrap/init.py index 2c7dc8d..7de357c 100644 --- a/bootstrap/init.py +++ b/bootstrap/init.py @@ -17,6 +17,9 @@ SLAPD_D = Path("/etc/ldap/slapd.d") base_dn = os.environ.get("LDAP_BASE_DN") or "dc=example,dc=org" password = os.environ.get("LDAP_PASSWORD") or "changeit" tls_enabled = os.environ.get("TLS_ENABLED") == "1" +kerberos_enabled = os.environ.get("KERBEROS_ENABLE") == "1" +krb5_realm = os.environ.get("KRB5_REALM", "") +krb5_sasl_host = os.environ.get("KRB5_SASL_HOST", "") admin_dn = f"cn=admin,{base_dn}" @@ -116,6 +119,9 @@ def main(): apply_ldif(LDIF_DIR / "config-acl.ldif", env, base_dn=base_dn, admin_dn=admin_dn) if tls_enabled: apply_ldif(LDIF_DIR / "config-tls.ldif", env) + if kerberos_enabled: + apply_ldif(LDIF_DIR / "config-sasl.ldif", env, + base_dn=base_dn, krb5_realm=krb5_realm, sasl_host=krb5_sasl_host) print("cn=config updated.") diff --git a/bootstrap/ldif/config-sasl.ldif b/bootstrap/ldif/config-sasl.ldif new file mode 100644 index 0000000..c60c2dd --- /dev/null +++ b/bootstrap/ldif/config-sasl.ldif @@ -0,0 +1,11 @@ +dn: cn=config +changetype: modify +replace: olcSaslHost +olcSaslHost: {{ sasl_host }} +- +replace: olcSaslRealm +olcSaslRealm: {{ krb5_realm }} +- +replace: olcAuthzRegexp +olcAuthzRegexp: {0}uid=([^/]+)/admin,cn={{ krb5_realm | lower }},cn=gssapi,cn=auth cn=admin,{{ base_dn }} +olcAuthzRegexp: {1}uid=([^,]+),cn={{ krb5_realm | lower }},cn=gssapi,cn=auth uid=$1,ou=users,{{ base_dn }} diff --git a/entrypoint.sh b/entrypoint.sh index 14eeec5..dcf2a7f 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -31,6 +31,15 @@ else echo "TLS : disabled" fi +kerberos_enabled="0" +if [ "${KERBEROS_ENABLE:-0}" = "1" ]; then + kerberos_enabled="1" + export KRB5_KTNAME="${KRB5_KTNAME:-/etc/ldap/ldap.keytab}" + echo "Kerberos : enabled (keytab: $KRB5_KTNAME)" +else + echo "Kerberos : disabled" +fi + echo "Ensuring slapd runtime directory..." mkdir -p /var/run/slapd chown openldap:openldap /var/run/slapd @@ -59,6 +68,7 @@ EOF LDAP_BASE_DN="$base_dn" \ LDAP_PASSWORD="$password" \ TLS_ENABLED="$tls_enabled" \ + KERBEROS_ENABLE="$kerberos_enabled" \ python3 -u /bootstrap/init.py else echo "Already initialised - skipping bootstrap." diff --git a/env.example b/env.example index 97cc934..1909acb 100644 --- a/env.example +++ b/env.example @@ -3,3 +3,9 @@ LDAP_BASE_DN=dc=example,dc=org LDAP_ORG=Example Organization LDAP_PASSWORD=changeit LDAP_ADMIN_PASSWORD=changeit + +# Kerberos SASL/GSSAPI (optional) +KERBEROS_ENABLE=0 +KRB5_REALM=EXAMPLE.ORG +KRB5_SASL_HOST=ldap.example.org +KRB5_KTNAME=/etc/ldap/ldap.keytab