OpenLdap – LSC – Active Directory sync and login pass-through
Explain:
- Active Directory server in backend, store all user data, password…
- OpenLDAP install on Ubuntu server, frontend, is a read-only LDAP service to provide users data to other server (web, app…) by using LSC to sync data, this server also use to authentication user by pass-through request to Active Directory server by using saslauthd service. OpenLDAP act as Single Sign On service.
Example user name Jenkins login process:
Install and config OpenLDAP
Install:
apt-get install slapd ldap-utils libsasl2-modules-gssapi-mit
Note: libsasl2-modules-gssapi-mit may not need
Admin username and password: admin / demopassword
(note that admin
is default username of Admin in OpenLDAP)
Edit file /etc/ldap/ldap.conf
, add:
BASE dc=your_ad_domain,dc=local
URI ldap://ldap-server.your_ad_domain.local ldap://ldap-server.your_ad_domain.local:666
ldap-server is the hostname of server openldap installed, to change hostname in Ubuntu:
Edit hostname nano /etc/hostname
content ldap-server.your_ad_domain.local
Edit host file nano /etc/hosts
content 127.0.0.1 ldap-server.your_ad_domain.local
update value, run dpkg-reconfigure slapd
select No
enter DNS domain name: your_ad_domain.local
enter Organization name: CompanyName
enter admin password:demopassword
Database backend to use: MDB
slapt is purged: Yes
move old database: Yes
Allow LDAPv2 protocol? No
Test Ldap is running: ldapsearch -x
View Ldap server status: /etc/init.d/slapd status
Install LSC (tool support sync data between ldap server)
Files & Example: http://tools.lsc-project.org/projects/lsc/repository
$ apt-get install default-jre
$ nano /etc/apt/sources.list
add content:
deb http://lsc-project.org/debian lsc main
deb-src http://lsc-project.org/debian lsc main
run apt-get update
Import reponsitory public key: wget -O - http://ltb-project.org/wiki/lib/RPM-GPG-KEY-LTB-project | sudo apt-key add -
Install: apt-get install lsc
Configuration:
$ mkdir /etc/lsc/ad2openldap
$ nano /etc/lsc/ad2openldap/lsc.xml
LSC will sync data source from Active Directory to OpenLDAP, content of lsc.xml
:
Note:
ad-server.publicdomain.xyz
is a public domain point to IP address of AD server to get data.
<?xml version="1.0" ?>
<lsc xmlns="http://lsc-project.org/XSD/lsc-core-2.1.xsd" revision="0">
<connections>
<ldapConnection>
<name>ldap-src-conn</name>
<url>ldap://ad-server.publicdomain.xyz:389/DC=your_ad_domain,DC=local</url>
<username>user_to_login@your_ad_domain.local</username>
<password>password_to_login_ad</password>
<authentication>SIMPLE</authentication>
<referral>IGNORE</referral>
<derefAliases>NEVER</derefAliases>
<version>VERSION_3</version>
<pageSize>-1</pageSize>
<factory>com.sun.jndi.ldap.LdapCtxFactory</factory>
<tlsActivated>false</tlsActivated>
</ldapConnection>
<ldapConnection>
<name>ldap-dst-conn</name>
<url>ldap://localhost:389/dc=your_ad_domain,dc=local</url>
<username>cn=admin,dc=your_ad_domain,dc=local</username>
<password>demopassword</password>
<authentication>SIMPLE</authentication>
<referral>IGNORE</referral>
<derefAliases>NEVER</derefAliases>
<version>VERSION_3</version>
<pageSize>-1</pageSize>
<factory>com.sun.jndi.ldap.LdapCtxFactory</factory>
<tlsActivated>false</tlsActivated>
</ldapConnection>
</connections>
<tasks>
<task>
<name>Sync_1_Users</name>
<bean>org.lsc.beans.SimpleBean</bean>
<ldapSourceService>
<name>ad-source-service</name>
<connection reference="ldap-src-conn" />
<baseDn>DC=your_ad_domain,DC=local</baseDn>
<pivotAttributes>
<string>samAccountName</string>
</pivotAttributes>
<fetchedAttributes>
<string>description</string>
<string>cn</string>
<string>sn</string>
<string>givenName</string>
<string>samAccountName</string>
<string>userPrincipalName</string>
<string>title</string>
<string>physicalDeliveryOfficeName</string>
<string>telephoneNumber</string>
<string>facsimileTelephoneNumber</string>
<string>department</string>
<string>company</string>
<string>mail</string>
<string>mobile</string>
<string>jpegPhoto</string>
</fetchedAttributes>
<!-- <getAllFilter>(objectClass=user)</getAllFilter> -->
<getAllFilter>(&(objectCategory=person)(objectClass=user)(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))</getAllFilter>
<!-- <getOneFilter>(&(objectClass=user)(samAccountName={samAccountName})(mail=*))</getOneFilter> -->
<getOneFilter>(&(objectCategory=person)(objectClass=user)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(samAccountName={samAccountName}))</getOneFilter>
<!-- <cleanFilter>(&(objectClass=user)(samAccountName={uid})(mail=*))</cleanFilter> -->
<cleanFilter>(&(objectCategory=person)(objectClass=user)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(samAccountName={uid}))</cleanFilter>
</ldapSourceService>
<ldapDestinationService>
<name>opends-dst-service</name>
<connection reference="ldap-dst-conn" />
<baseDn>ou=Users,dc=your_ad_domain,dc=local</baseDn>
<pivotAttributes>
<string>uid</string>
</pivotAttributes>
<fetchedAttributes>
<string>description</string>
<string>cn</string>
<string>sn</string>
<string>userPassword</string>
<string>objectClass</string>
<string>uid</string>
<string>title</string>
<string>physicalDeliveryOfficeName</string>
<string>telephoneNumber</string>
<string>telexNumber</string>
<string>ou</string>
<string>o</string>
<string>mail</string>
<string>mobile</string>
<string>jpegPhoto</string>
</fetchedAttributes>
<getAllFilter>(objectClass=inetorgperson)</getAllFilter>
<getOneFilter>(&(objectClass=inetorgperson)(uid={samAccountName}))</getOneFilter>
</ldapDestinationService>
<propertiesBasedSyncOptions>
<mainIdentifier>"uid=" +
srcBean.getDatasetFirstValueById("samAccountName") +
",ou=Users,dc=your_ad_domain,dc=local"</mainIdentifier>
<defaultDelimiter>;</defaultDelimiter>
<defaultPolicy>FORCE</defaultPolicy>
<dataset>
<name>description</name>
<policy>FORCE</policy>
<forceValues>
<string>js:(srcBean.getDatasetFirstValueById("sn") != null ? srcBean.getDatasetFirstValueById("sn").toUpperCase() : null )</string>
</forceValues>
</dataset>
<dataset>
<name>userPassword</name>
<policy>KEEP</policy>
<createValues>
<string>js:"{SASL}" +
srcBean.getDatasetFirstValueById("samAccountName") + "@your_ad_domain.local"</string>
</createValues>
</dataset>
<dataset>
<name>sn</name>
<!-- <policy>FORCE</policy> -->
<policy>KEEP</policy>
<createValues>
<string>js:srcBean.getDatasetFirstValueById("samAccountName")</string>
</createValues>
</dataset>
<dataset>
<name>description</name>
<policy>FORCE</policy>
<forceValues>
<string>js:(srcBean.getDatasetFirstValueById("sn") != null ? srcBean.getDatasetFirstValueById("sn").toUpperCase() : null )</string>
</forceValues>
</dataset>
<dataset>
<name>uid</name>
<policy>KEEP</policy>
<createValues>
<string>js:srcBean.getDatasetFirstValueById("samAcccountName")</string>
</createValues>
</dataset>
<dataset>
<name>objectClass</name>
<policy>KEEP</policy>
<createValues>
<string>"inetOrgPerson"</string>
</createValues>
</dataset>
<dataset>
<name>telexNumber</name>
<policy>FORCE</policy>
<forceValues>
<string>js:srcBean.getDatasetFirstValueById("facsimileTelephoneNumber")</string>
</forceValues>
</dataset>
<dataset>
<name>o</name>
<policy>FORCE</policy>
<forceValues>
<string>js:srcBean.getDatasetFirstValueById("company")</string>
</forceValues>
</dataset>
<dataset>
<name>ou</name>
<policy>FORCE</policy>
<forceValues>
<string>js:srcBean.getDatasetFirstValueById("department")</string>
</forceValues>
</dataset>
</propertiesBasedSyncOptions>
</task>
<task>
<name>Sync_2_Groups</name>
<bean>org.lsc.beans.SimpleBean</bean>
<ldapSourceService>
<name>group-source-service</name>
<connection reference="ldap-src-conn" />
<baseDn>DC=your_ad_domain,DC=local</baseDn>
<pivotAttributes>
<string>cn</string>
</pivotAttributes>
<fetchedAttributes>
<string>cn</string>
<string>description</string>
<string>member</string>
<!-- <string>objectClass</string> -->
</fetchedAttributes>
<getAllFilter><![CDATA[(objectClass=group)]]></getAllFilter>
<getOneFilter><![CDATA[(&(objectClass=group)(cn={cn}))]]></getOneFilter>
<cleanFilter><![CDATA[(&(objectClass=group)(cn={cn}))]]></cleanFilter>
<!-- <serverType>ActiveDirectory</serverType> -->
</ldapSourceService>
<ldapDestinationService>
<name>group-dst-service</name>
<connection reference="ldap-dst-conn" />
<baseDn>ou=Groups,dc=your_ad_domain,dc=local</baseDn>
<pivotAttributes>
<string>cn</string>
</pivotAttributes>
<fetchedAttributes>
<string>cn</string>
<string>description</string>
<string>uniqueMember</string>
<string>objectClass</string>
<!-- <string>gidNumber</string> -->
</fetchedAttributes>
<getAllFilter><![CDATA[(objectClass=groupOfUniqueNames)]]></getAllFilter>
<getOneFilter><![CDATA[(&(objectClass=groupOfUniqueNames)(cn={cn}))]]></getOneFilter>
</ldapDestinationService>
<propertiesBasedSyncOptions>
<mainIdentifier>js:"cn=" + srcBean.getDatasetFirstValueById("cn") + ",OU=Groups,dc=your_ad_domain,dc=local"</mainIdentifier>
<defaultDelimiter>;</defaultDelimiter>
<defaultPolicy>FORCE</defaultPolicy>
<conditions>
<create>true</create>
<update>true</update>
<delete>true</delete>
<changeId>true</changeId>
</conditions>
<dataset>
<name>uniqueMember</name>
<policy>FORCE</policy>
<forceValues>
<string>
<![CDATA[rjs:
var membersSrcDn = srcBean.getDatasetValuesById("member");
var membersDstDn = new java.util.ArrayList();;
for (var i=0; i < membersSrcDn.size(); i++) {
var memberSrcDn = membersSrcDn.get(i);
var sAMAccountName = "";
try {
sAMAccountName = srcLdap.attribute(memberSrcDn, "samAccountName").get(0);
} catch(e) {
continue;
}
var destDn = ldap.search("ou=Users", "(uid=" + sAMAccountName + ")");
if (destDn.size() == 0 || destDn.size() > 1) {
continue;
}
var destMemberDn = destDn.get(0) + "," + ldap.getContextDn();
membersDstDn.add(destMemberDn);
}
if (membersDstDn.size() == 0) {
membersDstDn.add("uid=empty_member,dc=your_ad_domain,dc=local");
}
membersDstDn
]]>
</string>
</forceValues>
</dataset>
<dataset>
<name>objectClass</name>
<policy>FORCE</policy>
<forceValues>
<string>"groupOfUniqueNames"</string>
<string>"top"</string>
</forceValues>
<delimiter>$</delimiter>
</dataset>
</propertiesBasedSyncOptions>
</task>
</tasks>
</lsc>
You need a logback configuration in this directory: run cp /etc/lsc/logback.xml /etc/lsc/ad2openldap/
To test is working: /usr/bin/lsc -f /etc/lsc/ad2openldap -s all -c all -n
To real sync, remove -n
Create a crontab run sync every 6 hours: run crontab -e
, add 0 */6 * * * /usr/bin/lsc -f /etc/lsc/ad2openldap -s all -c all
, save cron.
Use Active Directory to authentication user for LDAP (OpenLDAP pass-through)
Concept: User login, OpenLdap check user password field (userPassword), if it has format:
{SASL}[email protected]
OpenLdap with authentication user via saslauthd service, saslauthd server with connect to AD and return the result.
$ apt install sasl2-bin
# edit saslauthd
$ nano /etc/default/saslauthd
add content:
# Should saslauthd run automatically on startup? (default: no)
START=yes
# Example: MECHANISMS="pam"
MECHANISMS="ldap"
OpenLDAP service account name: openldap
– view in file /etc/default/slapd
Add OpenLDAP service account to the sasl group: adduser openldap sasl
Create file /etc/saslauthd.conf
, content
ldap_servers: ldap://ad-server.publicdomain.xyz/
ldap_search_base: DC=your_ad_domain,DC=local
ldap_filter: (sAMAccountName=%u)
ldap_bind_dn: cn=user_to_login_ad,ou=SomeOU,dc=your_ad_domain,dc=local
ldap_password: password_to_login_ad
Restart service to update setting: root@ldap-server:/# service saslauthd restart
To test authentication:
$ testsaslauthd -u some_ad_user_ex -p user_password
0: OK "Success."
Create file /usr/lib/sasl2/slapd.conf
, edit file nano /usr/lib/sasl2/slapd.conf
, add content
pwcheck_method: saslauthd
saslauthd_path: /run/saslauthd/mux
OpenLdap with check password field, if format {SASL}xxx…
it with go to Active Directory to authentication user.
Fix Error:
Jan 21 02:25:58 ldap-server.your_ad_domain.local slapd[20263]: SASL [conn=1001] Failure: cannot connect to saslauthd server: Permission denied
To resolve:
apt install apparmor-utils
aa-complain usr.sbin.slapd
Setting /etc/apparmor.d/usr.sbin.slapd
to complain mode.
$ /etc/init.d/slapd restart
$ service saslauthd restart
Done!!!!
Origin post at https://archive.camratus.com/2017/01/24/openldap-lsc-active-directory-sync-and-login-pass-through