LDAP TLS

验证:未记录
适用版本
未记录
验证范围
尚未登记验证记录。

本文缩写(英文全称与中文含义)
缩写 英文全称/名称 中文含义
AD Active Directory 微软活动目录
API Application Programming Interface 应用程序编程接口
CA Certificate Authority 证书颁发机构
DN Distinguished Name 目录条目的唯一名称
DS Domain Services / Directory Server AD DS 中指域服务;389 DS 等产品中指目录服务器
EKU Extended Key Usage 证书扩展密钥用途
JNDI Java Naming and Directory Interface Java 命名与目录接口
LDAP Lightweight Directory Access Protocol 轻量级目录访问协议
LDAPS LDAP over TLS 通过 TLS 保护的 LDAP
NSS Network Security Services / Name Service Switch TLS 证书库语境指前者;Linux 名称解析语境指后者
SAN Subject Alternative Name 证书主题备用名称,用于声明域名或 IP
TLS Transport Layer Security 传输层安全协议
URL Uniform Resource Locator 统一资源定位符,通常指访问地址
https://www.openldap.org/doc/admin26/tls.html
https://www.port389.org/docs/389ds/howto/howto-ssl.html
https://www.freeipa.org/page/V4/Automatic_Certificate_Request_Generation
https://learn.microsoft.com/en-us/entra/identity/domain-services/tutorial-configure-ldaps

1. Important Points#

Production LDAP should not send credentials over cleartext network.

preferred:
    LDAPS on 636
    or StartTLS on 389 with strict certificate verification

avoid:
    ldap:// over untrusted network
    anonymous bind in production
    insecure certificate verification
    expired self-signed certs nobody monitors

LDAPS vs StartTLS:

Mode Port Meaning Notes
LDAPS 636 TLS from connection start operationally simple
StartTLS 389 plaintext connection upgraded to TLS standards-friendly, must enforce upgrade
Plain LDAP 389 no TLS dev/local only

2. Server Configuration#

certificate layout#

/etc/ldap/tls/
    ldap.example.com.crt
    ldap.example.com.key
    ca.crt

permissions:
    private key readable only by LDAP service user
    cert and CA readable by LDAP service
    cert SAN contains ldap.example.com

OpenLDAP example#

Use cn=config to point slapd to certificate files:

dn: cn=config
changetype: modify
replace: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/ldap/tls/ca.crt
-
replace: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ldap/tls/ldap.example.com.crt
-
replace: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ldap/tls/ldap.example.com.key

Apply:

ldapmodify -H ldapi:/// -Y EXTERNAL -f tls.ldif

sudo systemctl restart slapd

Enable LDAPS listener in service config according to distro packaging. Common listener set:

ldap:/// ldapi:/// ldaps:///

389 Directory Server example#

389 DS manages TLS through NSS database and dsconf tooling. Exact commands vary by version and deployment method.

Checklist:

import CA
import server certificate and private key
enable TLS listener
require secure binds where appropriate
restart instance
verify with ldapsearch over ldaps

Active Directory / managed directory#

AD DS LDAPS usually requires a certificate on domain controllers with proper Server Authentication usage and SAN matching the DC name. Managed directory products often require uploading a certificate chain and enabling secure LDAP from the service console/API.

Checklist:

certificate:
    trusted by clients
    has correct EKU for server auth
    SAN matches LDAP hostname
    private key installed on domain controller/service

network:
    allow 636 only from trusted clients
    do not expose LDAPS publicly

3. Client Configuration / Verify#

Verify LDAPS:

openssl s_client -connect ldap.example.com:636 -servername ldap.example.com </dev/null

Verify RootDSE:

LDAPTLS_CACERT=/etc/ssl/company-ca.pem ldapsearch -H ldaps://ldap.example.com:636 -x -b "" -s base namingContexts supportedLDAPVersion

Verify bind:

LDAPTLS_CACERT=/etc/ssl/company-ca.pem ldapwhoami -H ldaps://ldap.example.com:636 -x -D "uid=svc-keycloak,ou=service-accounts,dc=example,dc=com" -W

Expected:

certificate verify ok
hostname matches certificate SAN
ldapsearch returns namingContexts
ldapwhoami returns service account DN

Keycloak connection:

Connection URL:
    ldaps://ldap.example.com:636

Bind DN:
    uid=svc-keycloak,ou=service-accounts,dc=example,dc=com

Users DN:
    ou=people,dc=example,dc=com

Trust:
    import LDAP CA into Keycloak runtime trust store or container trust store

4. Java#

JNDI LDAPS example:

import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

public class LdapCheck {
  public static void main(String[] args) throws Exception {
    Hashtable<String, String> env = new Hashtable<>();
    env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.PROVIDER_URL, "ldaps://ldap.example.com:636");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "uid=svc-keycloak,ou=service-accounts,dc=example,dc=com");
    env.put(Context.SECURITY_CREDENTIALS, System.getenv("LDAP_BIND_PASSWORD"));

    DirContext ctx = new InitialDirContext(env);
    System.out.println(ctx.getNameInNamespace());
    ctx.close();
  }
}

Custom truststore:

keytool -importcert -alias company-root-ca -file company-root-ca.crt -keystore truststore.p12 -storetype PKCS12

java -Djavax.net.ssl.trustStore=truststore.p12 -Djavax.net.ssl.trustStorePassword=changeit LdapCheck

Do not disable endpoint identification / certificate validation in production.

5. Python#

Python with ldap3:

import os
from ldap3 import Server, Connection, Tls
import ssl

tls = Tls(
    ca_certs_file="/etc/ssl/company-ca.pem",
    validate=ssl.CERT_REQUIRED,
    version=ssl.PROTOCOL_TLS_CLIENT,
)

server = Server("ldap.example.com", port=636, use_ssl=True, tls=tls)
conn = Connection(
    server,
    user="uid=svc-keycloak,ou=service-accounts,dc=example,dc=com",
    password=os.environ["LDAP_BIND_PASSWORD"],
    auto_bind=True,
)

conn.search(
    search_base="ou=people,dc=example,dc=com",
    search_filter="(uid=alice)",
    attributes=["uid", "cn", "mail"],
)

print(conn.entries)
conn.unbind()

Do not use no-verify TLS settings in production.

6. Go#

Go with go-ldap:

package main

import (
	"crypto/tls"
	"crypto/x509"
	"os"

	ldap "github.com/go-ldap/ldap/v3"
)

func main() {
	ca, err := os.ReadFile("/etc/ssl/company-ca.pem")
	if err != nil {
		panic(err)
	}
	pool := x509.NewCertPool()
	pool.AppendCertsFromPEM(ca)

	conn, err := ldap.DialURL(
		"ldaps://ldap.example.com:636",
		ldap.DialWithTLSConfig(&tls.Config{
			ServerName: "ldap.example.com",
			MinVersion: tls.VersionTLS12,
			RootCAs:    pool,
		}),
	)
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	err = conn.Bind(
		"uid=svc-keycloak,ou=service-accounts,dc=example,dc=com",
		os.Getenv("LDAP_BIND_PASSWORD"),
	)
	if err != nil {
		panic(err)
	}
}

Do not set InsecureSkipVerify: true in production.

7. Node.js#

Node.js with ldapts:

import { readFileSync } from "node:fs";
import { Client } from "ldapts";

const client = new Client({
  url: "ldaps://ldap.example.com:636",
  tlsOptions: {
    ca: [readFileSync("/etc/ssl/company-ca.pem")],
    servername: "ldap.example.com",
    minVersion: "TLSv1.2",
  },
});

await client.bind(
  "uid=svc-keycloak,ou=service-accounts,dc=example,dc=com",
  process.env.LDAP_BIND_PASSWORD,
);

const result = await client.search("ou=people,dc=example,dc=com", {
  scope: "sub",
  filter: "(uid=alice)",
  attributes: ["uid", "cn", "mail"],
});

console.log(result.searchEntries);
await client.unbind();

Do not use NODE_TLS_REJECT_UNAUTHORIZED=0 or rejectUnauthorized: false in production.

8. Rotation Checklist#

before rotation:
    issue certificate with correct SAN
    distribute new CA/intermediate to clients
    update Keycloak trust
    update app trust stores
    test ldapsearch against staging

during rotation:
    replace server certificate
    restart/reload LDAP service if required
    verify ldaps with openssl and ldapsearch
    verify Keycloak User Federation sync
    monitor failed bind/search rate

after rotation:
    remove old cert after overlap window
    update certificate inventory
    confirm expiry alert sees new date

搜索文档

输入关键词,搜索所有文档。

↑ ↓ 选择 · Enter 打开 · Esc 关闭