Upcoming Changes
October 24th, ITS will remove the ability to anonymously bind to OpenLDAP and will require a service account to connect. On October 31st 2019, UConn ITS and Security require the use of encrypted connections to OpenLDAP. In support of this, we have provided several programming samples if you need to update your code.
OpenLDAP Configuration
To authenticate to OpenLDAP, a service account is required. A service account can be requested from the Identity and Access Management Team via ServiceIT request. The following information is required to authenticate with a service account:
BindDN: The account username (uid=its-example,ou=accounts,ou=ldap,dc=uconn,dc=edu)
Password: Password provided by the Identity and Access Management Team
LDAP URL: ldaps://ldap.uconn.edu:636 or ldap://ldap.uconn.edu (with STARTTLS)
BaseDN: dc=uconn,dc=edu
Coding / Programmatic Considerations
Below are a number of code examples for various programming languages. You will note that all of them are leveraging port 636 which requires SSL.
This is the preferred and supported method for connecting to our OpenLDAP Infrastructure.
For connecting to OpenLDAP use [ldaps://ldap.uconn.edu]
function LDAPSearch($Filter) { $userName = "uid=MY_SERVICE_ACOUNT,ou=accounts,ou=ldap,dc=uconn,dc=edu" $myPassword = "secret" $EntryPoint = "LDAP://ldap.uconn.edu:636/dc=uconn,dc=edu" $authServices = [System.DirectoryServices.AuthenticationTypes]::SecureSocketsLayer $de = New-Object System.DirectoryServices.DirectoryEntry($EntryPoint,$userName,$myPassword,$authServices) $ds = New-Object system.DirectoryServices.DirectorySearcher($de,$Filter) $result = $ds.FindAll() $ds.Dispose() return $result } $Filter = "(&(objectClass=person)(uconnPublished=TRUE)(!(uconnPersonAffiliation=Student Employee))(|(eduPersonAffiliation=staff)(eduPersonAffiliation=affiliate)(eduPersonAffiliation=faculty)(eduPersonAffiliation=employee)))" # XML File to create $FileName = "people.xml" $xmlsettings = New-Object System.Xml.XmlWriterSettings $xmlsettings.Indent = $true $xmlsettings.IndentChars = " " $FoundSet = LDAPSearch($Filter) $XmlWriter = [System.XML.XmlWriter]::Create($FileName, $xmlsettings) # Write the XML Decleration and set the XSL $xmlWriter.WriteStartDocument() $xmlWriter.WriteProcessingInstruction("xml-stylesheet", "type='text/xsl' href='style.xsl'") $xmlWriter.WriteStartElement("people") foreach ($person in $FoundSet) { # Person $xmlWriter.WriteStartElement("person") $xmlWriter.WriteAttributeString("uid", $person.Properties["uid"]) # Attributes of the person UID $XmlWriter.WriteElementString("title", $person.Properties["title"]) $XmlWriter.WriteElementString("phone", $person.Properties["telephoneNumber"]) $XmlWriter.WriteElementString("email", $person.Properties["mail"]) $XmlWriter.WriteElementString("department", $person.Properties["uconnDepartment"]) $xmlWriter.WriteEndElement() # <-- End <UID> } $xmlWriter.WriteEndElement() $xmlWriter.WriteEndDocument() $xmlWriter.Flush() $xmlWriter.Close()
public SearchResultCollection LdapSearch(string searchFilter) { // This is assuming that you are using an XML config file for storing the variables string ldapAccount = ConfigurationManager.AppSettings["ldapAccount"] ?? throw new ArgumentNullException("LDAP User Account cannot be Null"); string ldapPassword = ConfigurationManager.AppSettings["ldapPassword"] ?? throw new ArgumentNullException("LDAP Account Password cannot be Null"); string ldapDN = ConfigurationManager.AppSettings["ldapDN"] ?? throw new ArgumentNullException("LDAP Server Address cannot be Null"); try { DirectoryEntry directoryEntry = new DirectoryEntry(ldapDN, ldapAccount, ldapPassword, AuthenticationTypes.SecureSocketsLayer); Console.WriteLine("Search String: " + searchFilter); DirectorySearcher searcher = new DirectorySearcher(directoryEntry) { SearchScope = SearchScope.Subtree, Filter = searchFilter }; // Select which LDAP attributes to load searcher.PropertiesToLoad.Add("uid"); searcher.PropertiesToLoad.Add("title"); searcher.PropertiesToLoad.Add("telephoneNumber"); searcher.PropertiesToLoad.Add("mail"); searcher.PropertiesToLoad.Add("uconnDepartment"); var result = searcher.FindAll(); searcher.Dispose(); string debugText = string.Format("Total Entries Found: {0}", result.Count); Console.WriteLine(debugText); return result; } catch (Exception ex) { Console.WriteLine("Unable to make LDAP Connection: \n [" + ex.Message + "]"); } return null; }
import ldap import ldap.modlist as modlist serverURL = "ldaps://ldap.uconn.edu:636" bindDN = "uid=MY_SERVICE_ACOUNT,ou=accounts,ou=ldap,dc=uconn,dc=edu" bindPW = "" baseDN = "dc=uconn,dc=edu" searchScope = ldap.SCOPE_SUBTREE # LDAP attributes that we want to read wantedAttrs = [ "uid", "title", "telephoneNumber", "mail", "uconnDepartment" ] myConnection = ldap.initialize(serverURL) myConnection.protocol_version = ldap.VERSION3 myConnection.simple_bind_s(bindDN, bindPW) try: ldapResultID = myConnection.search(baseDN, searchScope, simpleFilter, wantedAttrs) while 1: resultType, resultData = myConnection.result(ldapResultID, 0) # print resultData if (resultData == []): break else: if resultType == ldap.RES_SEARCH_ENTRY: dn = resultData[0][0] data = resultData[0][1] myuid = str(data.get("uid", [None])[0]).decode(encoding="UTF-8").strip() mytitle = str(data.get("title", [None])[0]).decode(encoding="UTF-8").strip() myphone = str(data.get("telephoneNumber", [None])[0]).decode(encoding="UTF-8").strip() myemail = str(data.get("mail", [None])[0]).decode(encoding="UTF-8").strip() mydepartment = str(data.get("uconnDepartment", [None])[0]).decode(encoding="UTF-8").strip() myConnection.unbind() except ldap.LDAPError, e: print e
// This code goes directly to the 636 SSL port $ldaphost = "ldaps://ldap.uconn.edu"; $ldapUsername = "uid=MY_SERVICE_ACOUNT,ou=accounts,ou=ldap,dc=uconn,dc=edu"; $ldapPassword = ""; $ds = ldap_connect($ldaphost); if(!ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3)) { print "Could not set LDAPv3\r\n"; } else { // now we need to bind to the ldap server $bth = ldap_bind($ds, $ldapUsername, $ldapPassword) or die("\r\nCould not connect to LDAP server\r\n"); }
import java.util.Hashtable; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttribute; import javax.naming.directory.DirContext; import javax.naming.directory.ModificationItem; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; public class LdapExample { public static final String JAVA_HOME = "C:\\Program Files\\Java\\jdk1.8.0_131"; public static void main(String[] args) { final String ldapAdServer = "ldap.uconn.edu:636"; final String ldapSearchBase = "dc=uconn,dc=edu"; final String ldapUsername = "uid=MY_SERVICE_ACCOUNT,ou=accounts,ou=ldap,dc=uconn,dc=edu"; final String ldapPassword = ""; final String ldapAccountToLookup = "(|(uid=zzz190001))"; LdapContext ctx = null; try { Hashtable env = new Hashtable(); env.put(Context.SECURITY_AUTHENTICATION, "simple"); if(ldapUsername != null) { env.put(Context.SECURITY_PRINCIPAL, ldapUsername); } if(ldapPassword != null) { env.put(Context.SECURITY_CREDENTIALS, ldapPassword); } env.put(Context.SECURITY_PROTOCOL, "ssl"); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); if( "ssl".equals(env.get(Context.SECURITY_PROTOCOL)) ) { env.put(Context.PROVIDER_URL, "ldaps://"+ldapAdServer); } else { throw new Exception("SSL not enabled"); } System.out.println("java home: "+System.getProperty("java.home")); System.setProperty("javax.net.ssl.keyStore", JAVA_HOME+"\\jre\\lib\\security\\cacerts"); System.setProperty("javax.net.ssl.trustStore", JAVA_HOME+"\\jre\\lib\\security\\cacerts"); //ensures that objectSID attribute values //will be returned as a byte[] instead of a String env.put("java.naming.ldap.attributes.binary", "objectSID"); // the following is helpful in debugging errors //env.put("com.sun.jndi.ldap.trace.ber", System.err); SearchControls ctls = new SearchControls(); ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); ctls.setReturningAttributes( new String[] { "ds-cfg-listen-port", "objectclass" } ); ctx = new InitialLdapContext( env, null ); LdapExample ldap = new LdapExample(); NamingEnumeration list = ldap.findAccount(ctx, ldapSearchBase, ldapAccountToLookup); listResult( list ); } catch(Exception e) { e.printStackTrace(); } finally { try { if(ctx!=null) { ctx.close(); } } catch(Exception e) { } } System.out.println("LdapExample exit"); }
import ( "log" "github.com/jtblin/go-ldap-client" ) func main() { client := &ldap.LDAPClient{ Base: "dc=uconn,dc=edu", Host: "ldap.example.com", Port: 636, UseSSL: true, BindDN: "uid=MY_SERVICE_ACCOUNT,ou=accounts,ou=ldap,dc=uconn,dc=edu", BindPassword: "", UserFilter: "(uid=%s)", GroupFilter: "", Attributes: []string{"uid", "title", "telephoneNumber", "mail", "uconnDepartment"}, } defer client.Close() err := client.Connect() if err != nil { log.Fatalf("Failed to connect : %+v", err) } // Do something }
Important Note:
The standard OpenLDAP port of [389] will continue to be available. However, you will be required to configure your application or code to leverage STARTTLS. If you do not, the server will terminate the connection.
Operating System Considerations
We have included some optional configuration changes for your convenience.
We have noticed that ldap/ldapi connections sometimes have issue connecting to the secured LDAP.The following is a sample ldap.conf that is located in [/etc/ldap] on Ubuntu or in [/etc/openldap] on RHEL/CentOS
Note below that addition of TLS_REQCERT. If you happen to have a custom certificate for this host you do not need to set this value.
Otherwise TLS_REQCERT needs to be set to never.# # LDAP Defaults # # See ldap.conf(5) for details # This file should be world readable but not world writable. BASE dc=uconn,dc=edu URI ldaps://ldap.uconn.edu SASL_MECH EXTERNAL TLS_CACERTDIR /etc/ssl/certs/ca-certificates.crt TLS_REQCERT never # Turning this off breaks GSSAPI used with krb5 when rdns = false SASL_NOCANON on