Some key points are emphasized in this article in order to save your time to get linux ldap client in cache working with windows AD (active directory) LDAP server. The first thing to do is to get successful TLS connection to windows AD. Raw tcp case is beyond of this article, there is no problem with it, it is trivial. Windows ldap server uses port 636 for tls and this port can be used to get ldap certificate. As we will see later there is reason for this. linux ldap client uses STARTTLS special ldap extension to switch plain tcp to TLS only. Hence, connection should use port 389. After TLS connection, openldap client lib checks host domain name used in ldap Init() call against some fields of certificate of ldap server. That is why it is worth to get certificate using openssl tool in order to be sure that this check will succed. Unfortunately openldap client lib has pure diagnostic. You can not distinguish case of failure TLS handshake and failure of hostname check described generally above. Moreover openldap client lib doesn't report the details of TLS handshake failure by calling SSL_get_error() openssl api after SSL_connect(). For all cases one is reported one failure whether it is TLS handshake error or hostname check error. Openldap client lib has an option to switch off hostname check but cache callout wrapper ldap.so doesn't support it. Certificate supplied to openldap client might be ldap certificate (in this case certificate is verified on identity by itself) or AD CA certificate. By the way, ldap certificate is signed by this CA certificate. How to get CA certificate from domain controller. Go to Administrative Tools, find License Authority. One runs it, it has certsrv text in title and one is possible to export CA certificate in pem format. Following picture gives clue how to do it. ![certsrv](/sites/default/files/inline/images/domain_ca_cert.png) Next is required to get certificate from ldap to clarify what host name in certificate. Certificate Subject is important. openldap makes check of host connected to against domain name built from ldap certificate Subject. (For details see openldap lib source ./libraries/libldap/tls.c function ldap_pvt_tls_check_hostname() ) Run command for ip and port 636 of AD ldap. openssl s_client -connect 192.168.10.61:636 ```text ... Subject: CN = w2k16dc.test16.org Issuer: DC=org, DC=test16, CN=test16-W2K16DC-CA ... ``` You can copy and paste certificate pem text into file from output of this command. openssl x509 -in ldap.test16.org -noout -text ```text Signature Algorithm: sha256WithRSAEncryption Issuer: DC = org, DC = test16, CN = test16-W2K16DC-CA Validity Not Before: Apr 4 09:54:15 2017 GMT Not After : Apr 4 09:54:15 2018 GMT Subject: CN = w2k16dc.test16.org Subject Public Key Info: ``` Certificate pem part is copied in w2k16dc.pem. CA certificate is w2k16dc.ca.pem file. In given case it is simple - host name w2k16dc.test.org is used. Subject might be in form DC=org,DC=test16,CN=w2k16dc which means the same w2k16dc.test16.org. It makes sense set ip in /etc/hosts to make dns resolving local. ```conf 192.168.10.61 w2k16dc.test16.org ``` Check dns is resolved to right ip ```sh ping w2k16dc.test16.org ``` Now it is possible to check initial TLS connection and hostname check to AD ldap from linux ex.ldap.lxtoad.MAC ```COS #include %syLDAP check(host,certfile,username,password) set LD=##Class(%SYS.LDAP).Init(host,389) if LD=0 { s err=##Class(%SYS.LDAP).GetLastError() s msg=##Class(%SYS.LDAP).Err2String(err) w err_" "_msg,! quit 0 } set retCert=##Class(%SYS.LDAP).SetOption(LD,$$$LDAPOPTXTLSCACERTFILE,certfile) if retCert'=$$$LDAPSUCCESS { W "SetOption() CACERTFILE",! s err=##Class(%SYS.LDAP).GetError(LD) s msg=##Class(%SYS.LDAP).Err2String(err) w err_" "_msg,! quit 0 } set retTLS=##Class(%SYS.LDAP).StartTLSs(LD) if retTLS'=$$$LDAPSUCCESS { W "StartTLS()",! s err=##Class(%SYS.LDAP).GetError(LD) s msg=##Class(%SYS.LDAP).Err2String(err) w err_" "_msg,! quit 0 } set retBind=##Class(%SYS.LDAP).SimpleBinds(LD,username,password) if retBind'=$$$LDAPSUCCESS { W "SimpleBinds()",! s err=##Class(%SYS.LDAP).GetError(LD) s msg=##Class(%SYS.LDAP).Err2String(err) w err_" "_msg,! quit 0 } set n=$L(host,".") set base="" for i=2:1:n set base=base_",DC="_$P(host,".",i) set base="CN=Users"_base W "base="_base,! do work(base) set retUnBind=##Class(%SYS.LDAP).UnBinds(LD) if retUnBind'=$$$LDAPSUCCESS { W "SimpleBinds()",! s err=##Class(%SYS.LDAP).GetLastError() s msg=##Class(%SYS.LDAP).Err2String(err) w err_" "_msg,! quit 0 } quit work(base) set filter="(objectClass=*)" set ret4=##Class(%SYS.LDAP).SearchExts(LD,base, $$$LDAPSCOPESUBTREE, filter, "",, ,,,, .Res ) if ret4'=$$$LDAPSUCCESS { s err=##Class(%SYS.LDAP).GetLastError() s msg=##Class(%SYS.LDAP).Err2String(err) w "SearchExts failure "_err_" "_msg,! } set ret6=##Class(%SYS.LDAP).CountEntries(LD,Res) if ret6'=-1 { W "entries ",ret6,! set e=##Class(%SYS.LDAP).FirstEntry(LD,Res) if e>0 { set dn=##class(%SYS.LDAP).GetDN(LD,e) W dn,! D attrs(LD,dn,e) for { set e=##Class(%SYS.LDAP).NextEntry(LD,e) if e<1 quit set dn=##class(%SYS.LDAP).GetDN(LD,e) W dn,! D attrs(LD,dn,e) } } } else { s err=##Class(%SYS.LDAP).GetLastError() s msg=##Class(%SYS.LDAP).Err2String(err) w "CountEntries failure "_err_" "_msg,! } set ret5=##Class(%SYS.LDAP).MsgFree(Res) if ret5'=$$$LDAPSUCCESS { s err=##Class(%SYS.LDAP).GetLastError() s msg=##Class(%SYS.LDAP).Err2String(err) w "MsgFree "_err_" "_msg,! } quit attrs(LD,dn,e) s AttrName=##Class(%SYS.LDAP).FirstAttribute(LD,e,.Ptr) while AttrName'="" { s AttrValue=##Class(%SYS.LDAP).GetValues(LD,e,AttrName) W AttrName_"="_AttrValue,! set Users(dn,AttrName)=AttrValue s AttrName=##Class(%SYS.LDAP).NextAttribute(LD,e,.Ptr) } quit ``` And now run it ```COS set host="w2k16dc.test16.org" set certfile="/isc/ex/cos/ldap/w2k16dc.ca.pem" ; don't forget to check linux file permission cache is able to read set username="YourUserName" set password="YourPassword" d check^ex.ldap.lxtoad(host,certfile,username,password) ``` If something goes wrong ldap api step is reported - fix issue of step in case of problem. ```COS USER>d check^ex.ldap.lxtoad(host,certfile,username,password) StartTLS() 20481 Unsupported LDAP operation for platform. ``` Error will be the same for different cases. In my case problem is that host name is not in /etc/hosts so it is likely dns name resolving fails. If all goes right output is something like ```COS base=CN=Users,DC=test16,DC=org entries 30 CN=Users,DC=test16,DC=org ``` LDAP certificate w2k16dc.pem also should work. What to use is up to you. I note that CA certificate 5-10 years, LDAP certificate 1 year. I recommend this test before any other settings, because as more high level code is used then less clear what fails. As practice shows people lose much time to get it working tens hours or many days and might finally fail. These notes and this test might minimize time to localize and fix problem and get base of LDAP to work right. Next is configuring LDAP authentication. The main problem here is to set config fields to right value. I cite my example and this show idea how to set right values for your case. Idea of settings ```text LDAP server is a Windows Active Directory server on LDAP host names w2k16dc.test16.org LDAP username to use for searches CN=domadm,CN=Users,DC=test16,DC=org LDAP Base DN to use for searches CN=Users,DC=test16,DC=org LDAP Unique search attribute CN Use TLS/SSL encryption for LDAP sessions ``` The same in term of ^SECURITY utility ```text %SYS>D ^SECURITY 1) User setup 2) Role setup 3) Service setup 4) Resource setup 5) Application setup 6) Auditing setup 7) Domain setup 8) SSL configuration setup 9) Mobile phone service provider setup 10) OpenAM Identity Services setup 11) Encryption key setup 12) System parameter setup 13) X509 User setup 14) Exit Option? 12 1) Edit system options 2) Edit authentication options 3) Edit LDAP options 4) Display system options 5) Export All Security settings 6) Import All Security settings 7) Exit Option? 3 1) Edit LDAP options 2) Test LDAP authentication 3) Exit Option? 1 Is the LDAP server a Windows Active Directory server? Yes => Yes LDAP host names? => w2k16dc.test16.org LDAP DN user to use for searches? => CN=domadm,CN=Users,DC=test16,DC=org LDAP username password? ***** => Please enter the password again? LDAP Base DN to use for searches? => CN=Users,DC=test16,DC=org LDAP Unique search attribute? CN => Use TLS/SSL encryption for LDAP sessions? Yes => Yes TLS/SSL certificate file? => /isc/ex/cos/ldap/w2k16dc.ca.pem User attribute to retrieve comment attribute? description => User attribute to retrieve full name from? displayName => Use LDAP Groups for Roles/Routine/Namespace? No => No User attribute to retrieve default namespace? intersystems-Namespace => User attribute to retrieve default routine? intersystems-Routine => User attribute to retrieve roles? intersystems-Roles => LDAP attribute to retrieve for each user? LDAP Client Timeout? 10 => LDAP Server Timeout? 5 => Confirm changes to LDAP parameters? Yes => Yes System LDAP parameters updated ``` LDAP setting might be configured in SMP (system manamegment portal) and I don't expect problem translate setting for that case. Now time to test authentication. Here I used domadm acccount which is domain administrator. It is too strong. Later you can change user which has only ldap search right but for launch ldap authentication it is ok. Create some one more user for example user1 in Active Directory Users and Computers tool. ```text 1) Edit LDAP options 2) Test LDAP authentication 3) Exit Option? 2 Username to authenticate? user1 Password? Starting background process to perform LDAP authentication Waiting for LDAP authentication to start Starting LDAP authentication test Initializing LDAP connection to w2k16dc.test16.org Starting TLS Certificate: /mnt/d/isc/ex/cos/ldap/w2k16dc.ca.pem TLS Started Searching Root DSE Server supports LDAP versions: 3;2; Attempting to retrieve Base DN from defaultNamingContext Retrieved LDAP Base DN: DC=test16,DC=org Configured Base DN: CN=Users,DC=test16,DC=org Configured Base DN might be incorrect Now authenticating search user: CN=domadm,CN=Users,DC=test16,DC=org Search user CN=domadm,CN=Users,DC=test16,DC=org authenticated Searching for user user1 Username nz found DN: CN=user1,CN=Users,DC=test16,DC=org Retrieving nested groups for user user1 Found 2 nested groups for user user in .062082 seconds Authenticating user nz User user1 authenticated Retrieving user nz attributes Full name: user1 Comment: Decoding error description, please update config attribute Namespace: Decoding error intersystems-Namespace, please update config attribute Routine: Decoding error intersystems-Routine, please update config attribute Using roles from Attribute intersystems-Roles Roles: Decoding error intersystems-Roles, please update config attribute Roles on this instance: Number of groups: 3 Number of nested groups: 2 Groups: CN=Schema Admins,CN=Users,DC=test16,DC=org CN=Remote Desktop Users,CN=Builtin,DC=test16,DC=org CN=Users,CN=Builtin,DC=test16,DC=org Nested Groups: CN=Schema Admins,CN=Users,DC=test16,DC=org CN=Denied RODC Password Replication Group,CN=Users,DC=test16,DC=org WARNING: Parameter "Role required to connect to this system" is not set WARNING: Any LDAP user may be able to connect to this system User user1 will be able to connect to this system Testing authentication settings Not using proxy server Authentication settings test ok Testing authentication performance Authenticated user user1 40 times in 12.43578 seconds - 3.217/sec Test completed ``` The same in programmatic style do TEST^%SYS.LDAP("user1",password). Also you might do similar test in system management portal (Adminitrator->Security->SecurityLDAP/Kerberos configurations). Already there is ldap extention schema for cache (this is next subject soon ) so your output might be shorter. Final step is adding cache ldap schema extension to AD. Be accurate here because being imported schema can not be deleted. Here is cite of ldief file create_isc_schema.ldf ```ldief # intersystems schema for attribute Namespace Routine Roles # Nick.Zhokhov@intersystems.com #https://technet.microsoft.com/en-us/library/cc773309 AD Schema # https://msdn.microsoft.com/en-us/library/cc223177.aspx syntax # schemaIDGUID is not required will be generated automatically at import # attributeSecurityGUID the same. # 1.3.6.1.4.1.1466.115.121.1.5 syntax is not accepted by AD # one is used 2.5.5.12 String(Unicode) oMSyntax 64 # https://msdn.microsoft.com/en-us/library/cc223177.aspx # namespace dn: CN=intersystems-Namespace,CN=Schema,CN=Configuration,DC=X changetype: ntdsSchemaAdd objectClass: top objectClass: attributeSchema cn: iscNamespace attributeID: 1.2.840.113556.1.8000.2448.2.1 #attributeSyntax: 1.3.6.1.4.1.1466.115.121.1.5 attributeSyntax: 2.5.5.12 isSingleValued: TRUE adminDisplayName: intersystems-Namespace adminDescription: intersystems-Namespace oMSyntax: 64 searchFlags: 1 lDAPDisplayName: intersystems-Namespace systemOnly: FALSE #schemaIDGUID:: IdIDCQyMSUK960Rg3LgiXQ== # routine dn: CN=intersystems-Routine,CN=Schema,CN=Configuration,DC=X changetype: ntdsSchemaAdd objectClass: top objectClass: attributeSchema cn: iscRoutine attributeID: 1.2.840.113556.1.8000.2448.2.2 #attributeSyntax: 1.3.6.1.4.1.1466.115.121.1.5 attributeSyntax: 2.5.5.12 isSingleValued: TRUE adminDisplayName: intersystems-Routine adminDescription: intersystems-Routine oMSyntax: 64 searchFlags: 1 lDAPDisplayName: intersystems-Routine systemOnly: FALSE #schemaIDGUID:: P2yKMWNXTgOsEuNMjet90g== # roles # routine dn: CN=intersystems-Roles,CN=Schema,CN=Configuration,DC=X changetype: ntdsschemaadd objectClass: top objectClass: attributeSchema cn: iscRoles attributeID: 1.2.840.113556.1.8000.2448.2.3 #attributeSyntax: 1.3.6.1.4.1.1466.115.121.1.5 attributeSyntax: 2.5.5.12 isSingleValued: FALSE adminDisplayName: intersystems-Roles adminDescription: intersystems-Roles oMSyntax: 64 searchFlags: 1 lDAPDisplayName: intersystems-Roles systemOnly: FALSE #schemaIDGUID:: ksTBxxhAQoCOiBHByViOvg== dn: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 - # Classes dn: CN=IntersystemsAccount,CN=Schema,CN=Configuration,DC=X changetype: ntdsSchemaAdd objectClass: top objectClass: classSchema cn: IntersystemsAccount governsID: 1.2.840.113556.1.8000.2448.1.1 mayContain: intersystems-Namespace mayContain: intersystems-Routine mayContain: intersystems-Roles rDNAttID: cn adminDisplayName: IntersystemsAccount adminDescription: IntersystemsAccount objectClassCategory: 3 lDAPDisplayName: IntersystemsAccount name: IntersystemsAccount systemOnly: FALSE #schemaIDGUID:: bEqSlFeMT7C5vPFyUXnqpQ== dn: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 - dn: CN=User,CN=Schema,CN=Configuration,DC=X changetype: ntdsschemamodify add: auxiliaryClass auxiliaryClass: IntersystemsAccount - dn: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 - ``` Schema might to be loaded in AD by command ```cmd ldifde -i -f create_isc_schema.ldf -c "CN=Schema,CN=Configuration,DC=X" #schemaNamingContext ``` ADSI Edit which is a standard tool in windows might be used to populate the value of attributes intersystems-*. From cache it might be done using %SYS.LDAP class. Final step is to probe cache authentication and authorization. Set system level ldap authentication and authorization to enable and set it for some service for example terminal. Check login is ok and namespace is that pointed in namespace AD LDAP attribute. One appeared cache/iris versions that might not use a modification of LDAP schema since the different mechanisms used for storing cache/iris related information.