server/ldap_auth.py: simple bind to ldaps://prime.local:636 as sAMAccountName@prime.local, nested-group membership via the LDAP_MATCHING_RULE_IN_CHAIN extensible match, and a selftest() that validates the DC certificate without binding so it can never contribute to a lockout. Verified against the live domain, not just reasoned about: selftest() to prime.local -> ok, "certificate validates" selftest() to 192.168.3.37 -> refused, untrusted (no IP SAN) empty / whitespace password -> empty_input, with Connection nulled out so any call to bind() would have raised missing CA file -> unconfigured, is_config_problem=True Tls.validate -> ssl.CERT_REQUIRED, explicit ca_certs_file Three things here are load-bearing and commented as such at the call site: - The empty-password guard runs BEFORE bind(). An LDAP simple bind with an empty password is an anonymous bind and it SUCCEEDS, so without the guard a blank password authenticates as whatever username was submitted. - No `version=` pin on Tls. An earlier draft of this file pinned PROTOCOL_TLSv1_2, which would have silently downgraded every connection from the TLS 1.3 these DCs actually negotiate. - Retries cover connect failures only. A rejected credential returns immediately, because every failed bind counts against the domain lockout policy and this endpoint must not become a way to lock people out of Windows. The trust anchor is server/certs/prime-ca-chain.pem - PRIME CONTROLS ROOT CA plus ISSUING CA 1, public certificates with no private key, checked in because they are public and long-lived (2051 / 2036). The system trust store is deliberately not used: it currently trusts five other self-signed CAs on this estate. LDAP_CA_FILE overrides the path for a mounted bundle. docker-compose.yml: the `outbound` network is no longer optional. Its comment said to detach it if you were not using the Micron asset picker; doing that now breaks every sign-in, since `internal` has no default gateway and therefore no route to prime.local:636. Not yet verified, and called out rather than assumed: the nested-group case needs a real group with a nested member, and the in-container `openssl s_client -CAfile` check needs the stack. Both are T10.1 done-when boxes still open. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
27 lines
1.6 KiB
Plaintext
27 lines
1.6 KiB
Plaintext
# Pinned to exact versions for reproducible builds — no silent dependency drift
|
|
# on every `docker compose up --build`. To update: bump a version here on purpose,
|
|
# run `pip-audit` against the result, and test. For supply-chain integrity, the
|
|
# next step is a hashed lockfile (`pip-compile --generate-hashes` → install with
|
|
# `pip install --require-hashes`).
|
|
fastapi==0.138.1
|
|
uvicorn[standard]==0.49.0
|
|
gunicorn==26.0.0
|
|
sqlalchemy==2.0.51
|
|
alembic==1.18.5 # database migrations
|
|
psycopg[binary]==3.3.4
|
|
pymssql==2.3.13 # read-only lookups against the Micron asset DB (SQL Server).
|
|
# Chosen over pyodbc because it ships self-contained wheels —
|
|
# pyodbc would also need msodbcsql18 + unixODBC installed in
|
|
# the image. To use pyodbc instead, add it here, install the
|
|
# Microsoft ODBC driver in the Dockerfile, and switch
|
|
# MICRON_DB_URL to mssql+pyodbc://…?driver=ODBC+Driver+18+for+SQL+Server
|
|
pydantic==2.13.4
|
|
python-dotenv==1.2.2
|
|
bcrypt==5.0.0 # password hashing
|
|
PyJWT==2.13.0 # signed session tokens
|
|
ldap3==2.9.1 # D13: LDAPS simple bind against prime.local. Pure Python,
|
|
# so no system libldap/OpenLDAP headers in the image. The
|
|
# trust anchor is server/certs/prime-ca-chain.pem, NOT the
|
|
# system store — see server/ldap_auth.py.
|
|
starlette==1.3.1 # pinned transitive (cookie / CORS handling — security-relevant)
|