- 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.
144 lines
8.1 KiB
Markdown
144 lines
8.1 KiB
Markdown
# MariaDB with direct LDAP PAM authentication
|
|
|
|
This image keeps MariaDB's `auth_pam` plugin and a direct LDAP PAM module. No nslcd or SSSD daemon
|
|
is configured or started. The existing SQL accounts `IDENTIFIED VIA pam USING 'mariadb'` and the
|
|
MaxScale clear-password pass-through configuration remain unchanged.
|
|
|
|
## Why the package name was insufficient
|
|
|
|
In Ubuntu 26.04, [libpam-ldap](https://packages.ubuntu.com/resolute/libpam-ldap) is a virtual package
|
|
provided by [libpam-ldapd](https://packages.ubuntu.com/resolute/libpam-ldapd). That implementation's
|
|
`pam_ldap.so` requires nslcd. An `apt-get install libpam-ldap` request therefore did not install
|
|
the direct module described by the old image comments.
|
|
|
|
The [Containerfile](Containerfile) now builds the direct PADL implementation from
|
|
[commit 656448f091cbeb9efb3ece08e6868e40b8e7b6f8](https://github.com/PADL/pam_ldap/tree/656448f091cbeb9efb3ece08e6868e40b8e7b6f8).
|
|
The downloaded archive must match SHA256
|
|
`1aa622e85621b9018025e3483956d854566a5bc0636a4e8d9d005093f02dd16b`.
|
|
The build and runtime stages share the same MariaDB base, avoiding cross-distribution library copies.
|
|
Only the built module and its license files are copied into the runtime stage.
|
|
|
|
The module is installed at `/usr/local/lib/security/pam_ldap_direct.so` and selected by that
|
|
absolute path in [pam.d/mariadb](pam.d/mariadb). A distro module with the same generic
|
|
`pam_ldap.so` name cannot silently replace it in this PAM stack.
|
|
|
|
This is legacy upstream code, not a claim that the distribution now supports it. Its revision,
|
|
dependencies and authentication behavior need review when updating the image. The build selects
|
|
the source's GNU C89 dialect and supplies its source directory in `PERL5LIB` for the upstream
|
|
version generator; neither change suppresses an authentication or TLS failure.
|
|
|
|
## Runtime configuration
|
|
|
|
The [entrypoint](docker-entrypoint.sh) continues to accept the existing variables:
|
|
|
|
| Variable | Meaning |
|
|
| --- | --- |
|
|
| `LDAPURI` | One or more whitespace-separated LDAP endpoints, all using the same transport |
|
|
| `LDAP_BASE` | User-search base DN |
|
|
| `LDAPBINDDN` | Existing directory search account's bind DN |
|
|
| `LDAPPASSWORD` | Its password, supplied at runtime, never in the image or repository |
|
|
| `LDAP_LOGIN_ATTR` | Login-name attribute, such as `sAMAccountName` |
|
|
| `LDAP_TLS_REQCERT` | `demand` or `hard`; weaker verification is rejected |
|
|
| `CERTS_GID` | The actual host TLS-key group ID, retained in mysql's groups after the stock privilege drop |
|
|
|
|
All values must be single-line, and required credentials have no defaults. The renderer writes
|
|
one root-only `/etc/pam_ldap.conf`, which is the path compiled into the direct module. It no longer
|
|
writes a second `/etc/ldap.conf`.
|
|
|
|
PADL's verification directive is `tls_checkpeer yes`, not `tls_reqcert`. Its CA-file directive
|
|
is `tls_cacertfile`; this image points it to `/etc/ssl/certs/ca-certificates.crt`, installed by
|
|
`ca-certificates`. No private CA file is required for the lab's Let's Encrypt certificates.
|
|
Endpoints must still provide a valid full chain for their hostnames.
|
|
|
|
`ldaps://` endpoints use LDAPS. `ldap://` endpoints explicitly use StartTLS before binding.
|
|
Mixing those transports in one endpoint list is rejected rather than silently selecting an
|
|
incompatible mode or permitting an unencrypted fallback.
|
|
|
|
The MariaDB PAM helper remains setuid root so it can read the bind credential. The build checks
|
|
the MariaDB plugin/helper and direct module dependencies, restricts the helper directory to
|
|
root and mysql, and verifies mysql can execute the helper. Do not enable `no-new-privileges`
|
|
for this backend container: it would prevent that elevation. Startup now reports this
|
|
configuration error explicitly instead of proceeding to generic access-denied failures.
|
|
|
|
The privilege check uses `setpriv --dump` from `util-linux`, which queries the kernel's
|
|
`PR_GET_NO_NEW_PRIVS` interface. It does not depend on a `NoNewPrivs` line in process status:
|
|
that field only appeared in Linux 4.10, whereas the kernel query exists since 3.5. This matters on
|
|
older Synology kernels. A confirmed flag value of 1 is still rejected; an unavailable or malformed
|
|
query fails explicitly rather than silently assuming privilege elevation will work.
|
|
|
|
The stock MariaDB entrypoint, certificate group handling, data directory, replication settings
|
|
and database healthcheck remain in use. This change does not create accounts, change passwords,
|
|
reseed databases, or alter database ownership.
|
|
|
|
## Build and rollout
|
|
|
|
The default engine version is explicitly `13.0.2`, matching the running primary and replica
|
|
observed during this incident. The old custom tag `11.8` actually contained MariaDB 13.0.2;
|
|
do not deploy an actual 11.8 binary against the restored 13.0 data.
|
|
|
|
From this image directory, the owner can build a distinctly named image:
|
|
|
|
```sh
|
|
docker build -f Containerfile -t containers.kritikos.io/system/mariadb-extended:13.0.2-pam-direct .
|
|
```
|
|
|
|
Publish and pin the resulting image digest through the normal registry workflow. The assistant
|
|
does not build, push or deploy images to the lab.
|
|
|
|
Deploy to zagreus first, retaining its data mount, server ID, read-only setting, replication
|
|
configuration, full-chain certificate and actual `CERTS_GID`. Check a direct AD-authenticated
|
|
MariaDB connection to zagreus before changing Plutus. Then update only Plutus's MariaDB container;
|
|
that briefly disconnects primary clients, but requires no NAS reboot and no PostgreSQL restart.
|
|
|
|
Verify on each updated backend:
|
|
|
|
```sh
|
|
docker exec mariadb mariadbd --version
|
|
docker exec mariadb cat /etc/pam.d/mariadb
|
|
docker exec mariadb ldd /usr/local/lib/security/pam_ldap_direct.so
|
|
```
|
|
|
|
The PAM service must name the explicit direct module, its dependencies must resolve, and the
|
|
engine must remain 13.0.2. Do not print `/etc/pam_ldap.conf`; it contains the directory bind password.
|
|
|
|
Then, from a client, use a real directory user's password with verified TLS:
|
|
|
|
```sh
|
|
mariadb --no-defaults --protocol=TCP --host=zagreus.ad.kritikos.io --port=3306 --user=kritikos --password --ssl --ssl-verify-server-cert --database=grimmory --execute='SELECT CURRENT_USER(), CURRENT_ROLE(), @@server_id;'
|
|
```
|
|
|
|
Repeat against plutus and finally `mariadb.kritikos.io` through MaxScale. A root or monitoring
|
|
account using native-password authentication does not validate PAM. Check replica status and
|
|
ordinary database health as well; this image does not change the existing replication channel.
|
|
|
|
## Validation scope
|
|
|
|
The pinned source was compiled and dynamically loaded locally under WSL with available PAM and
|
|
OpenLDAP headers. The compiled module exported `pam_sm_authenticate` and `pam_sm_acct_mgmt`,
|
|
linked directly to `libldap`, and detected both `ldap_initialize` and StartTLS support.
|
|
That was a local build, not execution inside the target Ubuntu image or against AD.
|
|
|
|
[tests/test_pam_image.py](tests/test_pam_image.py) checks the pinned image recipe and runs a
|
|
rendered entrypoint in temporary directories with stubbed identity/group/stock-entrypoint commands.
|
|
It exercises TLS modes, configuration generation, credential redaction, missing components,
|
|
privilege constraints and argument forwarding without contacting hosts:
|
|
|
|
```sh
|
|
python3 -m unittest discover -s tests -v
|
|
```
|
|
|
|
Entrypoint execution tests need a POSIX environment. The assistant has not built or deployed the
|
|
Docker image. On 2026-10-03, the owner verified the `kritikos` PAM/AD login directly against both
|
|
backends over verified TLS, with the expected aggregate role and primary/replica server IDs.
|
|
|
|
The later successful read through MaxScale required a separate site configuration correction on
|
|
zagreus: `bind_address = *`, providing both IPv4 and IPv6 listeners. Its IPv4-only listener did not
|
|
cover the IPv6 addresses advertised by its hostname. This was a backend listener change, not an
|
|
LDAP password, grant or TLS-verification change.
|
|
|
|
A later controlled test wrote one row through MaxScale to Plutus (server ID 1, read-only OFF)
|
|
and read the same row directly on zagreus (server ID 2, read-only ON), retaining the recorded
|
|
writer server ID 1. That verifies primary write routing and replication of a new row. It does not
|
|
establish failover behavior or crash durability. Cleanup of the disposable probe table was not
|
|
shown in those results.
|