Tag: ldap

SSSD LDAP Schema

I lost access to all of my Linux servers at work. And, unlike the normal report where nothing changed but xyz is now failing, I knew exactly what happened. A new access request had been approved about ten minutes previously. Looking at my ID, for some reason adding a new group membership changed account gid number to that new group. Except … that shouldn’t have actually dropped my access. If I needed the group to be my primary ID, I should have been able to use newgrp to switch contexts. Instead, I got prompted for a group password (which, yes, is a thing. No, no one uses it).

The hosts were set up to authenticate to AD using LDAP, and very successfully let me log in (or not, if I mistyped my password). They, however, would only see me as a member of my primary group. Well, today, I finally got a back door with sufficient access to poke around.

Turns out I was right — something was improperly configured so groups were not being read from the directory but rather implied from the gid value. I added the configuration parameter ldap_schema to instruct the server to use member instead of memberUid for memberships. I used rfc2307bis as that’s the value I was familiar with. I expect “AD” could be used as well, but figured we were well beyond AD 2008r2 and didn’t really want to dig farther into the nuanced differences between the two settings.

From https://linux.die.net/man/5/sssd-ldap

ldap_schema (string)

Specifies the Schema Type in use on the target LDAP server. Depending on the selected schema, the default attribute names retrieved from the servers may vary. The way that some attributes are handled may also differ.

Four schema types are currently supported:

  • rfc2307
  • rfc2307bis
  • IPA
  • AD

The main difference between these schema types is how group memberships are recorded in the server. With rfc2307, group members are listed by name in the memberUid attribute. With rfc2307bis and IPA, group members are listed by DN and stored in the member attribute. The AD schema type sets the attributes to correspond with Active Directory 2008r2 values.

 

Determining Active Directory Version

We have a number of applications that authenticate to Active Directory. Invariably, when there are authentication issues, the vendor support person asks “what version of AD is this?” … not an unreasonable question, but also not something the person who supports Application XYZ is apt to know in a larger company. Fortunately, there are a few places within the directory that you can find details about AD versions.

The simplest is the version of Windows the domain controllers are running … although it’s possible domain controllers have been upgraded but the AD functional level has not yet been changed.

ldapsearch -h ad.example.com -D "ldapquery@example.com" -w "P@s54LD@pQu3ry" -p389 -b "ou=domain controllers,dc=example,dc=com" "(&(objectClass=computer))" operatingSystem

CN=dc007,OU=Domain Controllers,dc=example,DC=com
operatingSystem=Windows Server 2019 Datacenter

CN=dc008,OU=Domain Controllers,dc=example,DC=com
operatingSystem=Windows Server 2019 Datacenter

CN=dc020,OU=Domain Controllers,dc=example,DC=com
operatingSystem=Windows Server 2019 Datacenter

CN=dc021,OU=Domain Controllers,dc=example,DC=com
operatingSystem=Windows Server 2019 Datacenter

 

You can also find the objectVersion of the schema:

ldapsearch -h ad.example.com -D "ldapquery@example.com" -w "P@s54LD@pQu3ry" -p389 -b "cn=schema,cn=configuration,dc=example,dc=com" "(&(objectVersion=*))" objectVersion

CN=Schema,CN=Configuration,dc=example,DC=com
objectVersion=88

What does 88 mean? It depends! Either Windows 2019 or 2022

Version Operating System
13 Windows 2000 Server
30 Windows Server 2003 (Before R2)
31 Windows Server 2003 R2
44 Windows Server 2008 (Before R2)
47 Windows Server 2008 R2
56 Windows Server 2012
69 Windows Server 2012 R2
87 Windows Server 2016
88 Windows Server 2019
88 Windows Server 2022

 

Or the functional level of the forest and its partitions:

ldapsearch -H ldap://ad.example.com -D "ldapquery@example.com" -w "P@s54LD@pQu3ry" -b "cn=partitions,cn=configuration,dc=example,dc=com" "(&(MSDS-Behavior-Version=*))" MSDS-Behavior-Version

dn: CN=Partitions,CN=Configuration,DC=example,DC=com
msDS-Behavior-Version: 7

dn: CN=EXAMPLE,CN=Partitions,CN=Configuration,DC=example,DC=com
msDS-Behavior-Version: 7

What does 7 mean? Well, that depends too. It’s either Windows 2016 or 2019!

msDS-Behavior-Version Forest
Domain Domain Controller
0 2000 2000 Mixed / Native 2000
1 2003 Interim 2003 Interim N/A
2 2003 2003 2003
3 2008 2008 2008
4 2008 R2 2008 R2 2008 R2
5 2012 2012 2012
6 2012 R2 2012 R2 2012 R2
7 2016 2016 2016
7 2019 2019 2019

 

Dynamically determining AD Page Size

Question — is it possible to dynamically determine the maximum page size when communicating with AD via LDAP? Since the page size (1) changed between versions and (2) can be user-customized … a guess is sub-optimal.

Answer — yes. If only the default query policy is used, search at
CN=Default Query Policy,CN=Query-Policies,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,*domain naming context* (e.g.
CN=Default Query Policy,CN=Query-Policies,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=example,DC=com) with a filter like “(&(cn=*))”

Return the ldapAdminLimits attribute. Parse MaxPageSize out of the attribute:

lDAPAdminLimits (13): MaxValRange=1500; MaxReceiveBuffer=10485760; MaxDatagramRecv=4096; MaxPoolThreads=4; MaxResultSetSize=262144; MaxTempTableSize=10000; MaxQueryDuration=120; **MaxPageSize=1000**; MaxNotificationPerConn=5; MaxActiveQueries=20; MaxConnIdleTime=900; InitRecvTimeout=120; MaxConnections=5000;

To find all of the query policies, search at CN=Query-Policies,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,*domain naming context* for (&(objectClass=queryPolicy)) … either research a lot about query policies and figure out how to determine which applies to your connection or take the lowest value and know you’re safe.

LDAP Authentication: Python Flask

This is a quick python script showing how the flask-ldap3-login module can be used to authenticate and gather user attribute values

from flask_ldap3_login import LDAP3LoginManager
from ldap3 import Tls
import ssl

config = dict()

config['LDAP_HOST'] = 'ad.example.com'

# Use SSL unless you are debugging a problem. Clear text port is 389 and tls_ctx needs to be removed from add_server call
config['LDAP_USE_SSL'] = True
config['LDAP_PORT'] = 636

# Base DN
config['LDAP_BASE_DN'] = 'dc=example,dc=com'

# User Base DN, prepended to Base DN
config['LDAP_USER_DN'] = 'ou=UserDN'

# Groups Base DN, prepended to Base DN
config['LDAP_GROUP_DN'] = 'ou=SecurityGroupDN'

# Server will be manually added to establish SSL
config['LDAP_ADD_SERVER'] = False

# Domain component of userprincipal name
config['LDAP_BIND_DIRECT_SUFFIX'] = '@example.com'

# Search scope needs to be subtree
config['LDAP_USER_SEARCH_SCOPE'] = "SUBTREE"

# Attributes to return
config['LDAP_GET_USER_ATTRIBUTES'] = ("mail", "givenName", "sn")

# Setup a LDAP3 Login Manager.
ldap_manager = LDAP3LoginManager()

# Init the mamager with the config since we aren't using an app
ldap_manager.init_config(config)

# TLS settings to establish trust without validating CA issuance chain. 
# Can use CERT_REQUIRED and ca_certs_file with path to cacerts that includes issuing chain
tls_ctx = Tls(
    validate=ssl.CERT_NONE,
    version=ssl.PROTOCOL_TLSv1,
    valid_names=[
        'ad.example.com',
    ]
)

ldap_manager.add_server(
    config.get('LDAP_HOST'),
    config.get('LDAP_PORT'),
    config.get('LDAP_USE_SSL'),
    tls_ctx=tls_ctx
)

# Validate credentials
response = ldap_manager.authenticate_direct_credentials('e0012345', 'P@s5w0rdG03sH3re')
print(response.status)
print(response.user_info)

LDAP Authentication and Authorization: PHP

Blah

<?php
    error_reporting(0);
    #=== FUNCTION ==================================================================
    #      NAME: ldapAuthenticationAndAuthorizationWithAttributes
    #      PARAMETERS:
    #                    $strLDAPHost                   String  LDAP Server URI
    #                    $strUIDAttr                    String  Schema attribute for user ID search
    #                    $strSystemUser                 String  System credential username
    #                    $strSystemPassword             String  System credential password
    #                    $strUserBaseDN                 String  User search LDAP base DN
    #                    $strLogonUserID                String  Input user ID
    #                    $strLogonUserPassword          String  Input user password
    #					 $arrayAttrsToReturn			String	Attributes to be returned
    #                    $strGroupBaseDN                String  (optional) Group search LDAP base DN
    #                    $strGroupNamingAttribute       String  (optional) Schema attribute for group search
    #                    $strMembershipAttr             String  (optional) Schema attribute for group membership
    #                    $strAuthGroup                  String  (optional) Group name
    #     DESCRIPTION: Verify authentication and authorization against AD server.a
    #
    #     RETURNS: array(BindReturnCode, Authorized, array(returnValues))
    #                        BindReturnCode:    -1 indicates LDAP connection failure, -2 indicates system account auth failed, -3 indicates user auth not attempted, >=0 is IANA-registered resultCode values (https://www.iana.org/assignments/ldap-parameters/ldap-parameters.xml#ldap-parameters-6)
    #							NOTE: 0 is successful authentication in IANA-registered resultCode
    #                        Authorized:        0 authorization not attempted, -1 is not a member of the located group, 1 is member of the located group
    #						arrayUserAttributeValues	Array with values of $arrayAttrsToReturn
    #
    #     USAGE: $arrayUserAuthorized = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", $strInputUserName, $strInputUserPassword, array('givenName', 'sn'), "ou=securitygroups,dc=example,dc=com","cn", "member", "LJRTestGroup")
    #===============================================================================
    function ldapAuthenticationAndAuthorizationWithAttributes($strLDAPHost,$strUIDAttr, $strSystemUser, $strSystemPassword, $strUserBaseDN, $strLogonUserID, $strLogonUserPassword, $arrayAttrsToReturn, $strGroupBaseDN=null, $strGroupNamingAttribute=null, $strMembershipAttr=null, $strAuthGroup=null){
        $arrayAuthResults = array();
        $arrayUserAttributeValues = array();
        // Validate password is not null, otherwise directory servers implementing unauthenticated bind (https://tools.ietf.org/html/rfc4513#section-5.1.2) will return 0 on auth attempts with null password
        if( strlen($strLogonUserPassword) < 1){
            $arrayAuthResults['BindReturnCode'] = -3;
            $arrayAuthResults['Authorized'] = -1;
        }
        else{
            // Connect to the LDAP directory for system ID queries
            $systemDS = ldap_connect($strLDAPHost);
            ldap_set_option($systemDS, LDAP_OPT_PROTOCOL_VERSION, 3);

            if ($systemDS) {
                // Bind with the system ID and find $strLogonUserID FQDN
                $systemBind = ldap_bind($systemDS, $strSystemUser, $strSystemPassword);

                if(ldap_errno($systemDS) == 0){
                    $strLDAPFilter="(&($strUIDAttr=$strLogonUserID))";
                    $result=ldap_search($systemDS,$strUserBaseDN,$strLDAPFilter, $arrayAttrsToReturn);

                    $entry = ldap_first_entry($systemDS, $result);

                    $strFoundUserFQDN= ldap_get_dn($systemDS, $entry);

                    if($strFoundUserFQDN){
                        $userDS = ldap_connect($strLDAPHost);
                        ldap_set_option($userDS, LDAP_OPT_PROTOCOL_VERSION, 3);

                        $userBind = ldap_bind($userDS, $strFoundUserFQDN, $strLogonUserPassword);
                        $arrayAuthResults['BindReturnCode'] = ldap_errno($userDS);

                        ldap_close($userDS);

                        if($arrayAuthResults['BindReturnCode'] == 0){
                        	$objFoundUser = ldap_get_entries($systemDS, $result);
							for($arrayAttrsToReturn as $strAttributeName){
								$arrayUserAttributeValues[$strAttributeName] = $objFoundUser[0][$strAttributeName];

							}
							$arrayAuthResults['AttributeValues'] = $arrayUserAttributeValues;
                            //////////////////////////////////////////////////////////////////////////////////////
                            // If an auth group has been supplied, verify authorization
                            //////////////////////////////////////////////////////////////////////////////////////
                            if($strAuthGroup){
								// Escapes in DN need to be double-escaped or bad search filter error is encountered
                                $strGroupQuery = "(&($strGroupNamingAttribute=$strAuthGroup)($strMembershipAttr=" . str_replace("\\","\\\\", $strFoundUserFQDN) . "))";

                                $groupResult = ldap_search($systemDS,$strGroupBaseDN, $strGroupQuery);
                                $authorisedState = ldap_count_entries($systemDS ,$groupResult);

                                // If a group matching the filter is found, the user is authorised
                                if($authorisedState == 1){
                                    $arrayAuthResults['Authorized'] = 1;
                                }
                                // Otherwise the user is not a member of the group and is not authorised
                                else{
                                    $arrayAuthResults['Authorized'] = -1;
                                }
                            }
                            else{
                                $arrayAuthResults['Authorized'] = 0;
                            }
                            //////////////////////////////////////////////////////////////////////////////////////
                            ldap_close($systemDS);
                        }
                        // If the bind failed, the user has not logged in successfully so they cannot be authorized
                        else{
                            $arrayAuthResults['Authorized'] = -1;

                            ldap_close($systemDS);
                            ldap_close($userDS);
                        }
                    }
                    // User not found in directory
                    else{
                        $arrayAuthResults['BindReturnCode'] = 32;
                        $arrayAuthResults['Authorized'] = -1;
                    }
                }
                // system bind failed
                else{
                    $arrayAuthResults['BindReturnCode'] = -2;
                    $arrayAuthResults['Authorized'] = -1;
                    ldap_close($systemDS);
                }
            }
            // ldap connection failed
            else{
                $arrayAuthResults['BindReturnCode'] = -1;
                $arrayAuthResults['Authorized'] = -1;
            }
        }
        return $arrayAuthResults;
    }

    print "User password not supplied:\n";
    $arrayNullPassword = array();
    $arrayNullPassword = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", '');
    var_dump($arrayNullPassword);

    print "Bad password:\n";
    $arrayBadPassword = array();
    $arrayBadPassword = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", 'N0tTh3P@s5w0rd',"ou=SecurityGroups,dc=example,dc=com","cn", "member");
    var_dump($arrayBadPassword);

    print "\nInvalid user:\n";
    $arrayUserNotInDirectory = array();
    $arrayUserNotInDirectory = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "xe0012345", 'xDoesN0tM@tt3r');
    var_dump($arrayUserNotInDirectory);

    print "\nGood password without authorization:\n";
    $arrayUserAuthenticated = array();
    $arrayUserAuthenticated = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", 'Us3rP@s5w0rdG035H3re|Us3rP@s5w0rdG035H3re');
    var_dump($arrayUserAuthenticated);

    print "\nGood password with authorized user:\n";
    $arrayUserAuthorized = array();
    $arrayUserAuthorized = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", 'Us3rP@s5w0rdG035H3re|Us3rP@s5w0rdG035H3re',"ou=SecurityGroups,dc=example,dc=com","cn", "member", "cfyP_Unix_UnixUsers");
    var_dump($arrayUserAuthorized);

    print "\nGood password with unauthorized user:\n";
    $arrayUserNotAuthorized = array();
    $arrayUserNotAuthorized = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", 'Us3rP@s5w0rdG035H3re|Us3rP@s5w0rdG035H3re',"ou=SecurityGroups,dc=example,dc=com","cn", "member", "WIN AM Team West");
    var_dump($arrayUserNotAuthorized);

    print "\nBad system account:\n";
    $arrayBadSystemCred = array();
    $arrayBadSystemCred = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "xSy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", 'Us3rP@s5w0rdG035H3re|Us3rP@s5w0rdG035H3re');
    var_dump($arrayBadSystemCred);

?>

LDAP Authentication: PHP and Active Directory

This is a very brief function that authenticates a user against Active Directory. Because you can authenticate using a fully qualified DN, sAMAccountName, or userPrincipalName … there’s no need to use a system credential or search for the user provided you’ve got a single domain in your forest (i.e. you know what to prepend to the sAMAccountName or postpend to userPrincipalName).

If you need to perform authorization as well as authentication, you’ll need the user’s FQDN so use the generic LDAP authentication and authorization function.

<?php
    error_reporting(0);
    #=== FUNCTION ==================================================================
    #      NAME: activeDirectoryLDAPAuthentication
    #      PARAMETERS: 
    #                    $strLDAPHost                   String  LDAP Server URI
    #                    $strLogonUserID                String  Input user ID
    #                    $strLogonUserPassword          String  Input user password
    #     DESCRIPTION: Verify authentication againt Active Directory server.
    #     
    #     RETURNS: int BindReturnCode:    -2 indicates LDAP connection failure, -3 indicates user auth not attempted, >=0 is IANA-registered resultCode values (https://www.iana.org/assignments/ldap-parameters/ldap-parameters.xml#ldap-parameters-6)
    #							NOTE: 0 is successful authentication in IANA-registered resultCode
    #
    #     USAGE: $iBindResult = activeDirectoryLDAPAuthentication("ldaps://ad.example.com", $strInputUserName, $strInputUserPassword)
    #===============================================================================
    function activeDirectoryLDAPAuthentication($strLDAPHost, $strLogonUserID, $strLogonUserPassword){
        $iBindReturnCode = null;
        // Validate password is not null, otherwise directory servers implementing unauthenticated bind (https://tools.ietf.org/html/rfc4513#section-5.1.2) will return 0 on auth attempts with null password
        if( strlen($strLogonUserPassword) < 1){
            $iBindReturnCode = -1;
        }
        else{
            $userDS = ldap_connect($strLDAPHost);
            if($userDS){
                ldap_set_option($userDS, LDAP_OPT_PROTOCOL_VERSION, 3);

                $userBind = ldap_bind($userDS, $strLogonUserID . '@example.com', $strLogonUserPassword);
                $iBindReturnCode = ldap_errno($userDS);
                ldap_close($userDS);
            }
            // ldap connection failed
            else{
                $iBindReturnCode = -2;              
            }        
        }
        return $iBindReturnCode;
    }

    $iBadUser = activeDirectoryLDAPAuthentication("ldaps://ad.example.com", "xe0012345", 'N0tTh3P@s5w0rd');
    print "\nInvalid user: $iBadUser\n";

    $iUserAuthenticated = activeDirectoryLDAPAuthentication("ldaps://ad.example.com", "e012345", 'Go0dP@s5w0rdH3r3');
    print "\nGood password: $iUserAuthenticated\n";

    $iBadPassword = activeDirectoryLDAPAuthentication("ldaps://ad.example.com", "e0012345", 'N0tTh3P@s5w0rd');
    print "\nBad password: $iBadPassword\n";

    $iBadHost = activeDirectoryLDAPAuthentication("ldaps://abc.example.com", "e0012345", 'N0tTh3P@s5w0rd');
    print "\nBad host: $iBadHost\n";

?>


Identifying System-Only AD Attributes

This information is specific to Active Directory. MSDN has documentation for each schema attribute — e.g. CN — which documents if the attribute is “system only” or not.

For an automated process, search at the base cn=schema,cn=configuration,dc=example,dc=com with the filter (&(ldapDisplayName=AttributeName))and return the value of systemOnly. E.G. this shows that operatingSystemServicePack is user writable.

***Searching...
ldap_search_s(ld, "cn=schema,cn=configuration,dc=example,dc=com", 2, "(&(ldapDisplayName=operatingSystemServicePack))", attrList,  0, &msg)
Getting 1 entries:
Dn: CN=Operating-System-Service-Pack,CN=Schema,CN=Configuration,dc=example,dc=com
systemOnly: FALSE; 

You can also list all of the system-only attributes by using the filter (&(systemOnly=TRUE)) and returning ldapDisplayName

***Searching...
ldap_search_s(ld, "cn=schema,cn=configuration,dc=example,dc=com", 2, "(&(systemOnly=TRUE))", attrList,  0, &msg)
Getting 189 entries:
Dn: CN=OM-Object-Class,CN=Schema,CN=Configuration,dc=example,dc=com
lDAPDisplayName: oMObjectClass; 

Dn: CN=Canonical-Name,CN=Schema,CN=Configuration,dc=example,dc=com
lDAPDisplayName: canonicalName; 

Dn: CN=Managed-Objects,CN=Schema,CN=Configuration,dc=example,dc=com
lDAPDisplayName: managedObjects; 

Dn: CN=MAPI-ID,CN=Schema,CN=Configuration,dc=example,dc=com
lDAPDisplayName: mAPIID; 

Dn: CN=Mastered-By,CN=Schema,CN=Configuration,dc=example,dc=com
lDAPDisplayName: masteredBy; 

Dn: CN=Top,CN=Schema,CN=Configuration,dc=example,dc=com
lDAPDisplayName: top; 

Dn: CN=NTDS-DSA-RO,CN=Schema,CN=Configuration,dc=example,dc=com
lDAPDisplayName: nTDSDSARO; 

Dn: CN=Application-Process,CN=Schema,CN=Configuration,dc=example,dc=com
lDAPDisplayName: applicationProcess; 
...

 

Preventing Unauthenticated Binds in Active Directory

There is finally a Windows server-side solution to prevent “unauthenticated bind” requests (detailed in LDAP RFC 4513 section 5.1.2 with a note regarding the subsequent security considerations in section 6.3.1) from allowing unauthorized users to successfully authenticate.

It has always been possible to handle in code (i.e. verify that username and password are both non-null prior to communicating with the directory server) and is my personal preference as a developer cannot predict how individual directory services will be configured.

But for the third-party apps that don’t prevent unauthenticated binds, a setting to disallow unauthenticated bind operations to Active Directory was added in Windows 2019 — in your Configuration partition, open the properties of CN=Directory Service, CN=Windows NT, CN=Services, CN=Configuration — find the msDS-Other-Settings attribute, and add a new entry DenyUnauthenticatedBind=1

Using LDP To Browse Active Directory

One of the RSAT tools, ldp.exe, can be quite useful if you are trying to interact with Active Directory via LDAP but don’t know anything about the domain.

From “Connection”, chose “Connect”. Most domain controllers have A records registered for the domain name, so you can connect to the domain name.

Active Directory generally prohibits anonymous read, so you’ll need to bind to the directory. From “Connection”, chose “Bind”. If your computer is logged into the domain, you can select “Bind as currently logged on user”. If not, select “Bind with credentials” — in addition to the fully qualified DN of an account, AD allows you to bind with both userPrincipalName and sAMAccountName. userPrincipalName is userid@<domain.name> and sAMAccountName is domain\userid.

Now that you’ve logged into the domain, you can select “View” and “Tree”. If you leave the BaseDN blank, LDP will find the root of the directory partition.


Voila, you’ll see your domain. You can click around, or right-click the root of the domain and select “Search”. Look for something generic like “(&(objectClass=person))” to find user accounts. You’ll be able to see what attributes are used for what data.

Additionally, at the top of the window, you’ll see the hostname of the domain controller you are using and the root base DN for the domain.

LDAP Auth With Tokens

I’ve encountered a few web applications (including more than a handful of out-of-the-box, “I paid money for that”, applications) that perform LDAP authentication/authorization every single time the user changes pages. Or reloads the page. Or, seemingly, looks at the site. OK, not the later, but still. When the load balancer probe hits the service every second and your application’s connection count is an order of magnitude over the probe’s count … that’s not a good sign!

On the handful of sites I’ve developed at work, I have used cookies to “save” the authentication and authorization info. It works, but only if the user is accepting cookies. Unfortunately, the IT types who typically use my sites tend to have privacy concerns. And the technical knowledge to maintain their privacy. Which … I get, I block a lot of cookies too. So I’ve begun moving to a token-based scheme. Microsoft’s magic cloudy AD via Microsoft Graph is one approach. But that has external dependencies — lose Internet connectivity, and your app becomes unusable. I needed another option.

There are projects on GitHub to authenticate a user via LDAP and obtain a token to “save” that access has been granted. Clone the project, make an app.py that connects to your LDAP directory, and you’re ready.

from flask_ldap_auth import login_required, token
from flask import Flask
import sys

app = Flask(__name__)
app.config['SECRET_KEY'] = 'somethingsecret'
app.config['LDAP_AUTH_SERVER'] = 'ldap://ldap.forumsys.com'
app.config['LDAP_TOP_DN'] = 'dc=example,dc=com'
app.register_blueprint(token, url_prefix='/auth')

@app.route('/')
@login_required
def hello():
return 'Hello, world'

if __name__ == '__main__':
app.run()

The authentication process is two step — first obtain a token from the URL http://127.0.0.1:5000/auth/request-token. Assuming valid credentials are supplied, the URL returns JSON containing the token. Depending on how you are using the token, you may need to base64 encode it (the httpie example on the GitHub page handles this for you, but the example below includes the explicit encoding step).

You then use the token when accessing subsequent pages, for instance http://127.0.0.1:5000/

import requests
import base64

API_ENDPOINT = "http://127.0.0.1:5000/auth/request-token"
SITE_URL = "http://127.0.0.1:5000/"

tupleAuthValues = ("userIDToTest", "P@s5W0Rd2T35t")

tokenResponse = requests.post(url = API_ENDPOINT, auth=tupleAuthValues)

if(tokenResponse.status_code is 200):
jsonResponse = tokenResponse.json()
strToken = jsonResponse['token']
print("The token is %s" % strToken)

strB64Token = base64.b64encode(strToken)
print("The base64 encoded token is %s" % strB64Token)

strHeaders = {'Authorization': 'Basic {}'.format(strB64Token)}

responseSiteAccess = requests.get(SITE_URL, headers=strHeaders)
print(responseSiteAccess.content)
else:
print("Error requesting token: %s" % tokenResponse.status_code)

Run and you get a token which provides access to the base URL.

[lisa@linux02 flask-ldap]# python authtest.py
The token is eyJhbGciOiJIUzI1NiIsImV4cCI6MTUzODE0NzU4NiwiaWF0IjoxNTM4MTQzOTg2fQ.eyJ1c2VybmFtZSI6ImdhdXNzIn0.FCJrECBlG1B6HQJKwt89XL3QrbLVjsGyc-NPbbxsS_U:
The base64 encoded token is ZXlKaGJHY2lPaUpJVXpJMU5pSXNJbVY0Y0NJNk1UVXpPREUwTnpVNE5pd2lhV0YwSWpveE5UTTRNVFF6T1RnMmZRLmV5SjFjMlZ5Ym1GdFpTSTZJbWRoZFhOekluMC5GQ0pyRUNCbEcxQjZIUUpLd3Q4OVhMM1FyYkxWanNHeWMtTlBiYnhzU19VOg==
Hello, world

A cool discovery I made during my testing — a test LDAP server that is publicly accessible. I’ve got dev servers at work, I’ve got an OpenLDAP instance on Docker … but none of that helps anyone else who wants to play around with LDAP auth. So if you don’t want to bother populating directory data within your own test OpenLDAP … some nice folks provide a quick LDAP auth source.