Configure One‑Way OpenLDAP to Active Directory Sync with TLS on Debian (sssd/samba-tool) — Step by step
Redes & Segurança · By Kevin Marcondes · Published on · 11 min read
This tutorial shows how to configure one‑way synchronization from OpenLDAP to Active Directory over TLS on Debian using sssd and samba‑tool. It covers preparing TLS, Kerberos, sssd for identity resolution, a samba‑tool sync script, scheduling via systemd, validation steps and troubleshooting for common errors.
Prerequisites and network scenario
We will synchronize users from OpenLDAP to Active Directory, ensuring secure transport via TLS (LDAPS or StartTLS). You need AD administrator access, a service bind account in OpenLDAP, a trusted CA, resolvable DNS names and synchronized clocks.
Use example domains such as ad.example.com for the AD and ldap.example.org for OpenLDAP. Have a test OU in the AD to validate first. Actions will create and modify AD users; use a staging environment before production.
If TLS/PKI is new to you, review mTLS concepts in the mTLS between services guide (/en/mtls-nginx-smallstep-docker-compose) or read about internal PKI in Docker Compose (search for internal PKI in Compose). For general posture, see the VPS security checklist (/en/vps-linux-security-checklist).
- Updated Debian system and a shell with sudo.
- OpenLDAP reachable via LDAP/LDAPS, base: dc=example,dc=org.
- Active Directory reachable, base: DC=ad,DC=example,DC=com.
- AD admin account and OpenLDAP bind account.
- Root/intermediate CA imported on the Debian host.
- AD DNS resolving SRV and A/AAAA records.
- Time synchronization (NTP).
| Service | Port | Use |
|---|---|---|
| LDAP (StartTLS) | 389/TCP | Directory queries/updates with StartTLS |
| LDAPS | 636/TCP | Directory queries/updates over direct TLS |
| Kerberos | 88/TCP+UDP | Authentication (GSSAPI) on AD |
| DNS | 53/UDP+TCP | Discovery of AD DCs and SRV records |
1) Prepare TLS: CAs, certificates and test StartTLS/LDAPS
Install the root/intermediate CA used by OpenLDAP and AD (or your corporate CA). ldapsearch must validate certificates. Prefer LDAPS (636) when available; StartTLS (389) is also acceptable.
If the CA is not installed, secure LDAP connections will fail. Test OpenLDAP first, then the AD. For public certificates the trust chain works like HTTPS; if needed, see tips on Let's Encrypt (How to Get Free SSL with Let's Encrypt) (/en/get-free-ssl-lets-encrypt).
# 1) Instalar CA corporativa
sudo install -m 0644 /path/to/your-ca.crt /usr/local/share/ca-certificates/your-ca.crt
sudo update-ca-certificates
# 2) Testar OpenLDAP com StartTLS (389)
ldapsearch -H ldap://ldap.example.org -ZZ -x \
-b "dc=example,dc=org" -s base namingContexts
# 3) Testar OpenLDAP com LDAPS (636)
ldapsearch -H ldaps://ldap.example.org -x \
-b "dc=example,dc=org" -s base namingContexts
# 4) Testar AD com GSSAPI + LDAPS (exige Kerberos configurado)
ldapsearch -H ldaps://dc1.ad.example.com -Y GSSAPI \
-b "DC=ad,DC=example,DC=com" -s base defaultNamingContext
2) Install packages on Debian and adjust DNS/Kerberos
Install essential tools: sssd, Samba (samba-tool), LDAP and Kerberos utilities. Configure /etc/krb5.conf for your AD realm. Then obtain a Kerberos ticket with kinit. Do not proceed without a successful kinit.
Ensure /etc/resolv.conf points to the AD DNS servers — sssd uses SRV discovery to locate domain controllers.
sudo apt update && sudo apt install -y \
sssd sssd-tools sssd-ldap samba samba-common-bin \
krb5-user ldap-utils openssl
# Exemplo mínimo de /etc/krb5.conf (ajuste domínios e DCs)
sudo tee /etc/krb5.conf > /dev/null <<'EOF'
[libdefaults]
default_realm = EXAMPLE.COM
dns_canonicalize_hostname = false
rdns = false
dns_lookup_kdc = true
[realms]
EXAMPLE.COM = {
kdc = dc1.ad.example.com
kdc = dc2.ad.example.com
admin_server = dc1.ad.example.com
}
[domain_realm]
.ad.example.com = EXAMPLE.COM
ad.example.com = EXAMPLE.COM
EOF
# Obter TGT (ticket)
kinit [email protected]
klist
3) Configure sssd to resolve AD identities over TLS
sssd provides NSS/PAM integration from the AD, useful to validate user creation. It does not perform synchronization; it only resolves identities and authenticates.
Define the AD domain, require valid certificates and enable caching. Keep /etc/sssd/sssd.conf with permission 600. Update nsswitch to include sss for passwd and group lookups.
- sssd does not synchronize directories; it only queries and caches.
- Use ldap_tls_reqcert=demand to fail on invalid certificates.
- After changes, clear the cache with sss_cache -E.
# Criar /etc/sssd/sssd.conf
sudo install -m 0600 /dev/null /etc/sssd/sssd.conf
sudo tee /etc/sssd/sssd.conf > /dev/null <<'EOF'
[sssd]
services = nss, pam
domains = ad.example.com
[domain/ad.example.com]
id_provider = ad
ad_domain = ad.example.com
krb5_realm = EXAMPLE.COM
cache_credentials = true
ldap_id_mapping = true
access_provider = ad
ad_gpo_access_control = permissive
# TLS
ldap_tls_reqcert = demand
ldap_tls_cacert = /etc/ssl/certs/ca-certificates.crt
# Descoberta DNS
dns_discovery_domain = ad.example.com
EOF
sudo systemctl enable --now sssd
# Incluir sss no NSS (ajuste conforme seu arquivo atual)
sudo sed -i 's/^passwd:.*/passwd:\t\tfiles systemd sss/' /etc/nsswitch.conf
sudo sed -i 's/^group:.*/group:\t\tfiles systemd sss/' /etc/nsswitch.conf
4) Test binds and queries on OpenLDAP and AD with TLS
Validate OpenLDAP access with a simple bind using the service account. For the AD prefer GSSAPI with the Kerberos ticket obtained via kinit. Never expose passwords on the command line in production.
Confirm correct filters and bases. Adjust DNs and names to match your environment.
# OpenLDAP: bind simples com conta de serviço
ldapsearch -H ldaps://ldap.example.org \
-D "cn=sync,ou=svc,dc=example,dc=org" -W \
-b "ou=People,dc=example,dc=org" '(objectClass=inetOrgPerson)' uid mail -LLL -o ldif-wrap=no
# AD: GSSAPI com Kerberos (sem senha em linha)
ldapsearch -H ldaps://dc1.ad.example.com -Y GSSAPI \
-b "DC=ad,DC=example,DC=com" '(sAMAccountName=*)' cn -LLL -o nettimeout=5
5) Map attributes and create users in AD with samba-tool
We'll implement OpenLDAP→AD synchronization. Common mappings: uid → sAMAccountName, givenName/sn → first/last name, mail → mail and uidNumber/gidNumber → RFC2307 attributes in the AD.
Passwords cannot be copied between directories directly. The safest flow is to create the AD user with a temporary password and require a change at first login.
This script will create/update AD accounts; point it to a test OU first. Authenticate to the AD with Kerberos (kinit) and run samba-tool with -k yes.
- uid → sAMAccountName
- givenName/sn → first/last name
- mail → mail
- uidNumber/gidNumber → RFC2307 (if used in AD)
# Salve como /usr/local/sbin/ol2ad-sync.sh e torne executável
sudo install -m 0750 /dev/null /usr/local/sbin/ol2ad-sync.sh
sudo tee /usr/local/sbin/ol2ad-sync.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
OPENLDAP_URI="ldaps://ldap.example.org"
OPENLDAP_BINDDN="cn=sync,ou=svc,dc=example,dc=org"
OPENLDAP_BASE="ou=People,dc=example,dc=org"
FILTER='(objectClass=inetOrgPerson)'
AD_URI="ldaps://dc1.ad.example.com"
AD_BASE="DC=ad,DC=example,DC=com"
AD_UPN_SUFFIX="@ad.example.com"
# Requer 'kinit [email protected]' prévio
ldapsearch -H "$OPENLDAP_URI" -D "$OPENLDAP_BINDDN" -W -LLL -o ldif-wrap=no \
-b "$OPENLDAP_BASE" "$FILTER" uid givenName sn mail uidNumber gidNumber \
| awk 'BEGIN{ FS=": "; RS="" }{
uid=""; given=""; sn=""; mail=""; uidn=""; gidn="";
for(i=1;i<=NF;i++){
if($i ~ /^uid: /){ split($i,a,": "); uid=a[2] }
if($i ~ /^givenName: /){ split($i,a,": "); given=a[2] }
if($i ~ /^sn: /){ split($i,a,": "); sn=a[2] }
if($i ~ /^mail: /){ split($i,a,": "); mail=a[2] }
if($i ~ /^uidNumber: /){ split($i,a,": "); uidn=a[2] }
if($i ~ /^gidNumber: /){ split($i,a,": "); gidn=a[2] }
}
if(uid!=""){ printf "%s;%s;%s;%s;%s;%s\n", uid, given, sn, mail, uidn, gidn }
}' \
| while IFS=';' read -r U G S M UIDN GIDN; do
UPN="${U}${AD_UPN_SUFFIX}"
if samba-tool user show "$U" -k yes -H "$AD_URI" >/dev/null 2>&1; then
# Atualizar atributos simples
[ -n "$M" ] && samba-tool user setattribute "$U" mail "$M" -k yes -H "$AD_URI"
continue
fi
TMPPASS="$(openssl rand -base64 12)"
samba-tool user create "$U" "$TMPPASS" \
--given-name="$G" --surname="$S" \
--user-principal-name="$UPN" \
${UIDN:+--uid-number="$UIDN"} ${GIDN:+--gid-number="$GIDN"} \
--use-username-as-cn \
-k yes -H "$AD_URI"
# Forçar troca de senha no primeiro login
samba-tool user setpassword "$U" --must-change-at-next-login -k yes -H "$AD_URI"
done
EOF
sudo chmod 0750 /usr/local/sbin/ol2ad-sync.sh
6) Schedule periodic sync with a systemd timer
Schedule the script with systemd timers. Choose an interval that fits your needs. Use a controlled target OU in AD to reduce blast radius.
The service runs without an interactive environment; ensure the host obtains a Kerberos TGT automatically (for example via a keytab) or run the timer after kinit through automation.
- Use an AD keytab if you want unattended runs.
- Monitor execution logs and failures.
- Set OnCalendar/OnUnitActiveSec according to your SLA.
# Unidade de serviço
sudo tee /etc/systemd/system/ol2ad-sync.service > /dev/null <<'EOF'
[Unit]
Description=OpenLDAP -> AD one-way sync
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/ol2ad-sync.sh
User=root
Group=root
# Opcional: export KRB5CCNAME para um cache de credencial específico
Environment=KRB5CCNAME=/tmp/krb5cc_sync
[Install]
WantedBy=multi-user.target
EOF
# Timer horário (ajuste conforme necessário)
sudo tee /etc/systemd/system/ol2ad-sync.timer > /dev/null <<'EOF'
[Unit]
Description=Run OpenLDAP -> AD sync hourly
[Timer]
OnBootSec=5m
OnUnitActiveSec=1h
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now ol2ad-sync.timer
# Rodar manualmente para testar
sudo systemctl start ol2ad-sync.service
sudo systemctl status ol2ad-sync.service --no-pager
7) How to confirm it worked and troubleshoot common issues
Validate identities via sssd and confirm objects in the AD. Then verify that mapped attributes were applied. If something fails, check TLS, Kerberos and LDAP filters.
For secure remote admin access to the server, consider a dedicated bastion with 2FA: see SSH bastion (keys + 2FA) (/en/ssh-bastion-keys-2fa-ubuntu-22-04).
Passwords are not migratable between directories without specific mechanisms; create users with temporary passwords and force a change at first login. Plan groups and OUs, restrict the permissions of the account running the script and enable auditing. A buggy script can create many objects — run dry-runs and target a test OU first.
For reverse sync (AD→OpenLDAP), invert the logic: read from AD (GSSAPI) and apply to OpenLDAP with ldapadd/ldapmodify. sssd remains useful only for lookups and caching.
- Check TGT: klist and expiration.
- Clear sssd cache after changes: sss_cache -E.
- Clock drift breaks Kerberos; keep NTP in sync.
- TLS handshake failures: verify the CA and CN/SAN of the server certs.
- samba-tool permission errors: review AD groups/ACLs.
- Missing RFC2307 attributes: enable/populate them in AD or adjust the script.
# Verificações
id usuario_teste
getent passwd usuario_teste
getent group "Domain Users"
# Ver no AD
samba-tool user show usuario_teste -k yes -H ldaps://dc1.ad.example.com | sed -n '1,120p'
# Confirmar no OpenLDAP
ldapsearch -H ldaps://ldap.example.org -D "cn=sync,ou=svc,dc=example,dc=org" -W \
-b "ou=People,dc=example,dc=org" "(uid=usuario_teste)" dn -LLL
8) Final notes on mappings and extending the flow
Adjust mappings to fit your LDAP tree and UPN/sAMAccountName policies. Handle uid/sAMAccountName collisions with clear rules. For extra attributes use samba-tool setattribute incrementally.
If needed, include group sync and disabling/deleting AD accounts when entries are removed from OpenLDAP. Start with reports and dry-runs, then apply changes with safeguards and detailed logging.
Frequently asked questions
Does sssd synchronize between OpenLDAP and Active Directory?
No. sssd provides system identities and authentication (NSS/PAM) and can use AD or LDAP as providers. It does not replicate or synchronize objects between directories. Synchronization requires scripts or specific tools that read from one side and write to the other.
Can I sync OpenLDAP passwords to AD?
Not directly. Hash formats and policies differ and AD restricts how passwords are set. Use user creation with a temporary password and “must change at next logon” or centralize password changes in AD. Avoid exposing passwords on the command line.
Should I use LDAPS (636) or StartTLS (389)?
Both provide encryption. Use LDAPS when available for operational simplicity. StartTLS is useful where port 389 is already exposed. In all cases validate the certificate chain and enforce strict client verification (ldap_tls_reqcert=demand).
Which firewall ports need opening?
LDAP 389/TCP (StartTLS), LDAPS 636/TCP, Kerberos 88/TCP+UDP and DNS 53/UDP+TCP for AD discovery. If using Global Catalog, open 3268/3269. Only allow necessary sources and monitor connection attempts.
What if AD does not have RFC2307 attributes enabled?
You can create users without uidNumber/gidNumber and map only sAMAccountName/UPN. If UNIX IDs are required in AD, enable RFC2307 and populate uidNumber/gidNumber via samba-tool setattribute according to your policy.
Is the reverse path (AD→OpenLDAP) possible?
Yes, but it’s a different flow. Read from AD with GSSAPI and apply changes to OpenLDAP using ldapadd/ldapmodify. Test filters and attribute collisions carefully. sssd remains a client-only resolver, not a sync mechanism.
Need help validating TLS/PKI or hardening the sync script?
If you want help validating attribute mappings, automating keytabs and hardening the process, we can discuss next steps.
Discuss OpenLDAP→AD integrationSee also
- Tutorial: set up mTLS between Traefik and Docker Compose services using smallstep CA and automatic certificates — step by step
- Tutorial: integrate Sentry into your GitHub Actions deploy pipeline — automatic source map upload & issue creation (PHP & JavaScript)
- Configure retention and compression for Docker container logs with journald + logrotate + rsyslog on a low-disk VPS
- Tutorial: Configure GitHub Actions for integration tests with PostgreSQL in ephemeral containers and automatic migration rollback — a practical guide