✨ Adds MariaDB image with direct LDAP PAM authentication

- Introduced a new Containerfile to build a MariaDB image with direct LDAP PAM support.
- Implemented a custom PAM module (`pam_ldap_direct.so`) built from the PADL source to avoid dependencies on `libpam-ldap` and `nslcd`.
- Updated the entrypoint script to dynamically generate the PAM configuration file (`/etc/pam_ldap.conf`) based on environment variables.
- Added a README.md to document the image usage, configuration, and build process.
- Created a test suite to validate the image build and entrypoint behavior, ensuring proper handling of environment variables and configuration generation.
- Configured health checks and ensured the stock MariaDB entrypoint and lifecycle are preserved.
This commit is contained in:
akritikos committed 2026-10-09 14:13:03 +03:00
1 parent a4fc4e2ae2
commit f5a5204b75
11 files changed
+519 -282

No files matched your search

+55 -24
View File
@@ -1,26 +1,64 @@
#!/usr/bin/env bash
set -euo pipefail
set -f
# Render pam_ldap's config from the environment so the AD bind credentials are
# never baked into the image. pam_ldap performs a search+bind against AD: bind
# as the service account, find the user by ${LDAP_LOGIN_ATTR}, then re-bind as
# that DN with the supplied password to verify it — the same shape as pgpool's
# `ldap` method (see pool_hba.conf).
: "${LDAPURI:?LDAPURI is required (e.g. ldaps://hecate.ad.kritikos.io)}"
: "${LDAP_BASE:?LDAP_BASE is required (e.g. DC=ad,DC=kritikos,DC=io)}"
: "${LDAPBINDDN:?LDAPBINDDN is required (AD service/bind account)}"
: "${LDAPPASSWORD:?LDAPPASSWORD is required}"
: "${LDAP_LOGIN_ATTR:?LDAP_LOGIN_ATTR is required (e.g. sAMAccountName)}"
# demand = validate the AD cert against the trust store; relax to 'allow' only
# for testing, or add your internal CA to the image and keep 'demand'.
: "${LDAP_TLS_REQCERT:?LDAP_TLS_REQCERT is required (demand|allow|never)}"
: "${LDAP_TLS_REQCERT:?LDAP_TLS_REQCERT is required (demand or hard)}"
: "${CERTS_GID:?CERTS_GID is required (GID owning the host TLS key)}"
fail() {
printf 'ldap-entrypoint: %s\n' "$1" >&2
exit 1
}
for setting in LDAPURI LDAP_BASE LDAPBINDDN LDAPPASSWORD LDAP_LOGIN_ATTR LDAP_TLS_REQCERT CERTS_GID; do
[[ ${!setting} != *$'\n'* && ${!setting} != *$'\r'* ]] \
|| fail "$setting must be a single line"
done
case "${LDAP_TLS_REQCERT,,}" in
demand|hard) ;;
*) fail "LDAP_TLS_REQCERT must be demand or hard; LDAP certificate verification cannot be disabled" ;;
esac
[[ $LDAP_LOGIN_ATTR =~ ^[A-Za-z][A-Za-z0-9-]*$ ]] \
|| fail "LDAP_LOGIN_ATTR must be an LDAP attribute name"
[[ $CERTS_GID =~ ^[0-9]+$ ]] || fail "CERTS_GID must be numeric"
tls_mode=''
for uri in $LDAPURI; do
case "$uri" in
ldaps://*) mode=on ;;
ldap://*) mode=start_tls ;;
*) fail "LDAPURI must use ldaps:// or ldap:// with StartTLS" ;;
esac
[[ -z $tls_mode || $tls_mode == "$mode" ]] \
|| fail "LDAPURI cannot mix LDAPS and StartTLS endpoints"
tls_mode=$mode
done
[[ -n $tls_mode ]] || fail "LDAPURI must contain an endpoint"
module=/usr/local/lib/security/pam_ldap_direct.so
helper=/usr/lib/mysql/plugin/auth_pam_tool_dir/auth_pam_tool
ca_file=/etc/ssl/certs/ca-certificates.crt
[[ -r $module ]] || fail "direct LDAP PAM module is missing: $module"
[[ -x $helper && -u $helper ]] || fail "MariaDB's setuid PAM helper is unavailable"
[[ -r $ca_file ]] || fail "system CA bundle is missing: $ca_file"
privileges=$(LC_ALL=C setpriv --dump) \
|| fail "cannot inspect the PAM helper privilege constraints"
no_new_privs=$(printf '%s\n' "$privileges" | sed -n 's/^no_new_privs:[[:space:]]*//p')
case "$no_new_privs" in
0) ;;
1) fail "no-new-privileges prevents MariaDB's PAM helper from reading its root-only configuration" ;;
*) fail "cannot determine the PAM helper privilege constraints" ;;
esac
umask 077
# pam_ldap.so on this image reads /etc/ldap.conf (the PADL default path), and
# libpam-ldap ships a placeholder there (base dc=example,dc=net) that otherwise
# shadows our config and makes every login fail. Render both paths.
for conf in /etc/ldap.conf /etc/pam_ldap.conf; do
cat > "$conf" <<EOF
cat > /etc/pam_ldap.conf <<EOF
uri ${LDAPURI}
base ${LDAP_BASE}
binddn ${LDAPBINDDN}
@@ -29,19 +67,12 @@ scope sub
referrals no
pam_login_attribute ${LDAP_LOGIN_ATTR}
pam_filter objectclass=User
tls_cacertdir /etc/ssl/certs
tls_reqcert ${LDAP_TLS_REQCERT}
ssl ${tls_mode}
tls_cacertfile ${ca_file}
tls_checkpeer yes
EOF
chmod 600 "$conf"
done
chmod 600 /etc/pam_ldap.conf
# Grant mysql read access to the host TLS key (mode 0640, owned by GID
# ${CERTS_GID}). compose exposes that GID via `group_add`, but the stock
# entrypoint drops root -> mysql with gosu, which rebuilds mysql's groups from
# /etc/group and discards the compose-added GID — mysql then can't read ssl_key
# and MariaDB aborts with "Unable to get private key". While still root, put
# mysql in a group with that GID so it survives the privilege drop.
: "${CERTS_GID:?CERTS_GID is required (GID owning the host TLS key)}"
if [ "$(id -u)" = '0' ]; then
if getent group "${CERTS_GID}" >/dev/null; then
certgroup="$(getent group "${CERTS_GID}" | cut -d: -f1)"