The container runs only gunicorn, with no web server in front of it
inside the pod. WhiteNoise serves the collectstatic output
(STATIC_ROOT, configurable) with gzip compression. It uses the
non-hashed storage because the vendor CSS references source maps that
are not shipped. All 20 static files on the landing page are served.
Adds gunicorn and whitenoise to the requirements.
They are served by the first middleware, so probes, which use the pod
IP as Host, are not rejected by ALLOWED_HOSTS or redirected to HTTPS.
/healthz only shows the process is serving, so an LDAP outage never
restarts pods. /readyz returns 503 unless the database answers and an
LDAP server accepts the admin bind.
Sessions, password reset tokens and pending registrations live in the
database, so running more than one replica needs a shared one. With
DATABASE_HOST set the app uses PostgreSQL (psycopg 3 binary wheels, no
system libraries) with persistent, health-checked connections.
Otherwise SQLite is used, at a path configurable with SQLITE_PATH so a
single replica can keep it on a volume.
docker-compose.dev.yml gains a PostgreSQL 18 service on
localhost:5433. The full test suite passes on SQLite and PostgreSQL.
- Session and CSRF cookies are secure-only unless DEBUG is on.
- TRUST_X_FORWARDED_PROTO honours X-Forwarded-Proto from a TLS
terminating ingress. Without it, Django 4+ fails the CSRF origin
check on every form POST.
- CSRF_TRUSTED_ORIGINS, SECURE_SSL_REDIRECT and SECURE_HSTS_SECONDS are
configurable and off by default. HSTS for subdomains and preload are
left to ungleich, since they affect all of *.ungleich.ch.
None of the calls to the OTP server set a timeout, so a hanging OTP
server blocked a web worker indefinitely. Each call now times out after
OTP_TIMEOUT seconds (default 10).
The OTP server variables were read straight from the environment in
views.py. They are now OTP_* settings next to everything else, under
the same environment variable names, so existing deployments are
unaffected.
Remove LDAP_SERVER and LDAP_USERS_DN, which nothing used.
env.sample moves to the repository root (where .env is read from) and
lists every variable settings.py reads, including defaults and which
values are secrets.
Containers should log to stdout. The RotatingFileHandler wrote to
logs/debug.log, which is lost with the pod and crashed startup when
the directory was missing.
All logging now goes to a console handler. LOG_LEVEL sets the root
level (default INFO), DJANGO_LOG_LEVEL sets django.* (default INFO),
and LDAP3_LOG=1 turns on ldap3 protocol logging. ENABLE_DEBUG_LOG and
MODULES_TO_LOG are gone.
EMAIL_HOST, EMAIL_PORT and EMAIL_USE_TLS are deprecated in 6.1 in favour
of the MAILERS setting. The same environment variables now feed the
default SMTP mailer, so existing deployments keep working. Also adds
optional EMAIL_HOST_USER, EMAIL_HOST_PASSWORD and EMAIL_TIMEOUT
(default 10s; previously no timeout).
LDAPSERVER was documented as a space separated list, but the whole
string went into a single ldap3.Server. Logins worked (python-ldap
accepts lists), while registration and every write would have failed
with more than one URI.
settings.LDAP_SERVERS is now the split list, used through one ldap3
ServerPool per process: requests go to the first reachable server, a
server that is down is skipped for 60s, connections time out after 5s,
and with all servers down the pool gives up after two passes instead
of retrying forever.
python-ldap is a C extension that needs the OpenLDAP headers to build
(and needed a workaround in the Dockerfile). ldap3, which the app
already used for all writes, is pure Python.
dal.backends.LdapBackend keeps the django-auth-ldap behaviour: search
the uid in every LDAPSEARCH base as the admin DN, require exactly one
match, log in with the user's DN, and mirror givenName, sn and mail into
the local Django user. Empty passwords are rejected (LDAP treats them
as anonymous logins, which succeed).
LDAP_USE_TLS now applies StartTLS to every connection, including the
admin connection used for writes, not just logins.
Remove USE_L10N; localized formatting is always on from 5.0. Replace
User.objects.make_random_password() (removed in 5.1) with a
secrets-based generator for the create API's initial password, using
the same unambiguous alphabet and 16 characters instead of 10.
Step 1 of the upgrade (Python 3.9). Dependencies are bumped to their
newest versions compatible with Django 3.2.
DEFAULT_AUTO_FIELD is set to AutoField, which matches the existing
tables, so no migration is needed and the W042 warnings go away.
The highest uidNumber was cached in dal/ldap_max_uid_file. That breaks
with more than one replica and is lost on every container restart. The
uniqueness check behind it searched base '' (the root DSE), which
OpenLDAP never matches, so nothing caught a stale file. The error path
also referenced a setting that does not exist.
Now a paged search over ENTIRE_SEARCH_BASE finds the highest uidNumber
on each activation. Numbering is unchanged: the next number is above
both LDAP_DEFAULT_START_UID and every existing uidNumber.
LDAP_MAX_UID_FILE_PATH is no longer used.