Switch LDAP avatar lookups to LDAPS (#95)
Lint / PHP (phpcs PSR-12) (push) Successful in 18s
Lint / JS (eslint) (push) Successful in 7s
Lint / PHP requirements (version + extensions) (push) Successful in 28s
Lint / Notify on failure (push) Skipped
Security / PHP Security (semgrep) (push) Successful in 1m56s
Lint / Deploy (push) Successful in 3s
Lint / PHP (phpcs PSR-12) (push) Successful in 18s
Lint / JS (eslint) (push) Successful in 7s
Lint / PHP requirements (version + extensions) (push) Successful in 28s
Lint / Notify on failure (push) Skipped
Security / PHP Security (semgrep) (push) Successful in 1m56s
Lint / Deploy (push) Successful in 3s
api/user_avatar.php connected via ldap://$ldapHost:$ldapPort — never ldaps://, and there was no ldap_start_tls() call anywhere in the codebase. LDAP_BIND_PW was sent over the wire unencrypted on every avatar fetch. Switched to ldaps://, and changed LDAP_HOST/LDAP_PORT's defaults to ldap.lotusguild.org:6360 (lldap's LDAPS listener) instead of the bare IP on port 3890 (plaintext). PHP's ldap extension verifies the server cert's hostname by default, so a bare IP won't validate against the LDAPS cert (issued for *.lotusguild.org) — LDAP_HOST has to be a hostname the cert covers. This is deliberately not configurable back to plaintext ldap://. Infra change (pve-infra, separate repo/commit): added a Pi-hole split-horizon override so ldap.lotusguild.org resolves internally to the real LDAP server's LAN IP — its existing public DNS record points elsewhere (an unrelated host), and there was no internal-only DNS entry for it before this. Verified against the real lldap server (pct 147, LDAPS on 6360, a live Let's Encrypt *.lotusguild.org cert): confirmed the Pi-hole override resolves correctly from hosts using it as their resolver, then ran the exact ldap_connect/ldap_bind sequence via `php -r` directly on the production tinker_tickets host (10.10.10.45) with a deliberately wrong bind password — got "Invalid credentials" (a real LDAP protocol response), not a transport/TLS error, proving the full connect + TLS handshake + hostname verification + bind path works end-to-end in the actual deployment environment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117oBw2jN4kALYeS8HPq4zV
This commit is contained in:
+7
-3
@@ -74,10 +74,14 @@ TRUSTED_PROXIES=
|
|||||||
; Timezone (default: America/New_York)
|
; Timezone (default: America/New_York)
|
||||||
TIMEZONE=America/New_York
|
TIMEZONE=America/New_York
|
||||||
|
|
||||||
; LDAP / lldap (for user avatar lookups)
|
; LDAP / lldap (for user avatar lookups). Connects over LDAPS — 6360 is
|
||||||
|
; lldap's default LDAPS port, NOT its plaintext port (3890), since
|
||||||
|
; LDAP_BIND_PW below would otherwise go over the wire unencrypted.
|
||||||
|
; LDAP_HOST must be a hostname matching the LDAPS cert (TLS hostname
|
||||||
|
; verification is not skipped), not a bare IP.
|
||||||
LDAP_ENABLED=true
|
LDAP_ENABLED=true
|
||||||
LDAP_HOST=10.10.10.39
|
LDAP_HOST=ldap.lotusguild.org
|
||||||
LDAP_PORT=3890
|
LDAP_PORT=6360
|
||||||
LDAP_BIND_DN="uid=tinker-tickets,ou=people,dc=example,dc=com"
|
LDAP_BIND_DN="uid=tinker-tickets,ou=people,dc=example,dc=com"
|
||||||
LDAP_BIND_PW=
|
LDAP_BIND_PW=
|
||||||
LDAP_BASE_DN="dc=example,dc=com"
|
LDAP_BASE_DN="dc=example,dc=com"
|
||||||
|
|||||||
+4
-1
@@ -113,7 +113,10 @@ $avatarData = null;
|
|||||||
$ldapQueryOk = false; // true only if the LDAP lookup completed without error
|
$ldapQueryOk = false; // true only if the LDAP lookup completed without error
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$ldap = @ldap_connect("ldap://$ldapHost:$ldapPort");
|
// LDAPS, not plain ldap:// — LDAP_BIND_PW is sent during ldap_bind() below,
|
||||||
|
// and lldap's plaintext port (3890) would put it on the wire unencrypted.
|
||||||
|
// lldap's LDAPS listener defaults to port 6360 (see config.php).
|
||||||
|
$ldap = @ldap_connect("ldaps://$ldapHost:$ldapPort");
|
||||||
if (!$ldap) {
|
if (!$ldap) {
|
||||||
throw new RuntimeException("ldap_connect failed");
|
throw new RuntimeException("ldap_connect failed");
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-3
@@ -154,9 +154,19 @@ $GLOBALS['config'] = [
|
|||||||
'TIMEZONE' => $envVars['TIMEZONE'] ?? 'America/New_York',
|
'TIMEZONE' => $envVars['TIMEZONE'] ?? 'America/New_York',
|
||||||
'TIMEZONE_OFFSET' => null, // Will be calculated below
|
'TIMEZONE_OFFSET' => null, // Will be calculated below
|
||||||
|
|
||||||
// LDAP / lldap settings (for user avatar lookups)
|
// LDAP / lldap settings (for user avatar lookups). Connects over LDAPS
|
||||||
'LDAP_HOST' => $envVars['LDAP_HOST'] ?? '10.10.10.39',
|
// (see api/user_avatar.php) — lldap's default LDAPS port is 6360, not
|
||||||
'LDAP_PORT' => (int)($envVars['LDAP_PORT'] ?? 3890),
|
// its plaintext port 3890. The bind password must never go over the
|
||||||
|
// wire unencrypted, so this is not configurable back to a plaintext
|
||||||
|
// ldap:// connection.
|
||||||
|
//
|
||||||
|
// LDAP_HOST must be a hostname matching the LDAPS cert's *.lotusguild.org
|
||||||
|
// CN/SAN, not a bare IP — PHP's ldap extension verifies the cert's
|
||||||
|
// hostname by default and a mismatch fails the connection. Pi-hole has a
|
||||||
|
// split-horizon override so ldap.lotusguild.org resolves internally to
|
||||||
|
// the real LDAP server IP (its public DNS record points elsewhere).
|
||||||
|
'LDAP_HOST' => $envVars['LDAP_HOST'] ?? 'ldap.lotusguild.org',
|
||||||
|
'LDAP_PORT' => (int)($envVars['LDAP_PORT'] ?? 6360),
|
||||||
'LDAP_BIND_DN' => $envVars['LDAP_BIND_DN'] ?? 'uid=tinker-tickets,ou=people,dc=example,dc=com',
|
'LDAP_BIND_DN' => $envVars['LDAP_BIND_DN'] ?? 'uid=tinker-tickets,ou=people,dc=example,dc=com',
|
||||||
'LDAP_BIND_PW' => $envVars['LDAP_BIND_PW'] ?? '',
|
'LDAP_BIND_PW' => $envVars['LDAP_BIND_PW'] ?? '',
|
||||||
'LDAP_BASE_DN' => $envVars['LDAP_BASE_DN'] ?? 'dc=example,dc=com',
|
'LDAP_BASE_DN' => $envVars['LDAP_BASE_DN'] ?? 'dc=example,dc=com',
|
||||||
|
|||||||
Reference in New Issue
Block a user