LDAP Authentication and Encryption

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