LDAP provider

Introduction

ℹ️
This feature is currently in “Beta”. The configuration is subject to change.

LDAP Provider enables applications that use LDAP for user authentication to take advantage of modern authentication (e.g., OIDC) without rewriting the application.

A deployment pattern known as a facade is typically used to update and modernize LDAP-based applications. When an application uses this pattern, it utilizes an orchestrator acting as an identity-aware proxy (Proxy app) and an orchestrator acting as an LDAP server (LDAP Provider) and sandwiches the application between the orchestrators.

During implementation, the LDAP configuration for the application is updated to point to the orchestrator, acting as the LDAP server instead of the currently configured LDAP server (e.g., Active Directory). This configuration change is the only update required on the application server, no application code changes are needed.

Using this pattern, end-user requests flow through the orchestrator configured as an app proxy, so they can be authenticated using your preferred IDPs, support resilience, and be authorized based on the configured policy.

Once the user is authenticated, the orchestrator uses the configured service extensions to log into the application behind the scenes, so the end-user never sees the native application login page. The application typically uses the credentials received and authenticates them via an LDAP Bind request. When the orchestrator receives this Bind request, it authenticates the user based on the type of Bind request (e.g., Simple, SASL) received and how the orchestrator has been configured.

For instance, the orchestrator acting as an identity-aware proxy can:

  1. Create a one-time user.
  2. Pass this one-time user to the application being protected.
  3. The application will send a Bind request using these credentials.
  4. The orchestrator acting as an LDAP server authenticates the request using the data stored about the one-time user (e.g., hashed one-time password).

Once authenticated, the application typically sends a Search request to the LDAP Provider asking for the attributes of the authenticated user (e.g., employee ID, email, name). Upon receiving the response, the application finishes performing any required actions to show the user the requested page.

This deployment pattern supports many applications requiring LDAP and can easily be fine-tuned for your environment. Additionally, while we mention that this can be deployed on two separate orchestrators, one acting as an identity-aware proxy and one acting as an LDAP server, these can be the same orchestrator, or groups of orchestrators to support Highly Available (HA) deployments.

LDAP Provider Harness

ℹ️
The LDAP provider is different from the LDAP connector which acts as a client to query external LDAP sources.

Supported Features

ℹ️
Only LDAP v3 is supported.

LDAP Operations

Bind

The Bind operation is used to authenticate the LDAP connection. Currently, the following authentication methods are supported:

See the RFC for more information.

Simple

Simple authentication consists of sending the LDAP server the fully qualified DN of the client and the client’s clear-text password (RFC 2251 and RFC 2829). Given that the password is not protected and can be easily read from the network, it is recommended that the connection use TLS.

See the RFC for more information.

SASL

The Simple Authentication and Security Layer (SASL) enables LDAP to support pluggable authentication. This allows LDAP clients and servers to negotiate standard or non-standard custom mechanisms for authentication. In addition, SASL mechanisms can support negotiating SASL-layer encryption/integrity verification. When negotiated, this layer can help reduce the risk of compromised connection.

While the LDAP Provider permits SASL binds to be performed on a TLS-protected connection, it does not allow the use of SASL-layer encryption/integrity verification mechanisms on such a connection.

Once a SASL-layer encryption/integrity verification mechanism is used on a connection, the client MUST NOT send an additional bind request. If an additional Bind request is received after the SASL layer is used, an “Unwilling To Perform (53)” response will be returned.

See the RFC for more information.

The following SASL Mechanisms are currently supported:

GSS-SPNEGO

GSS-SPNEGO is a pseudo-security mechanism that enables GSS-API peers to determine in-band whether their credentials support a common set of one or more GSS-API mechanisms. This is useful for applications that share multiple mechanisms with the LDAP Provider. At this time, only NTLM is supported.

See the RFC for more information.

NTLM

NTLM is the NT LAN Manager Authentication protocol that is typically used in Windows environments for authentication between clients and servers, and optionally, can provide session security when requested by the client. Within LDAP, session security is provided via a SASL-layer encryption/integrity verification mechanism.

Supported NTLM Versions:

  • v2

See the specification for more information

Unbind

Unbind signals to close the session’s connection.

See the RFC for more information.

Search

The Search operation is used to request a server to return a set of entries matching the search criterion. If the request is for the Root DSE (an empty DN), the LDAP Provider will return the entries based on the attributes requested. The following attributes are supported out of the box, and their values will be based on the Orchestrator configuration:

  • supportedLDAPVersion: The value will always be 3 (only LDAP v3 is supported).
  • supportedSASLMechanisms: The values will be based on which SASL Mechanisms are enabled.

RootDSE requests can be made on unauthenticated connections since clients typically use the response to determine which authentication methods are mutually supported.

If the client requires specific Root DSE attributes in the response, additional values can be set via the rootDSE.attributes configuration. Any values set here will be included in Root DSE Search requests.

For other Search requests, the connection MUST be authenticated. When received, the processing is deferred to the Search Service Extension to consolidate and filter data sources and return the results to the client.

See the specification for more information

Extended - StartTLS

StartTLS upgrades the TCP connection to TLS using the provided tls config. LDAP clients may leverage StartTLS to secure their connection if the LDAP provider has been configured with ldap:// vs. ldaps://.

See the specification for more information

Configuration

enabled (Required) - Whether or not the LDAP Provider is enabled. It MUST be set to true for the LDAP Provider to start.

uri (Required) - The address that the LDAP Provider listens on. It MUST begin with either ldap:// or ldaps://. If using ldaps then the configuration must define and use tls. Default ports will be used if unspecified in uri: ldap:// defaulting to 389 or ldaps:// defaulting to 636. Examples below:

  • ldaps://:636 - The LDAP Provider will listen on all interfaces at port 636 using TLS.
  • ldap://127.0.0.1:389 The LDAP Provider will listen on the loopback interface at port 389 and will not use TLS.
  • ldap://0.0.0.0 The LDAP Provider will listen on all interfaces at the default port 389 and will not use TLS.

readDeadline (Optional. Defaults to 5m) - The max period of time that established connections will stay connected without receiving data. If this time is elapsed, the connection will be closed.

writeDeadline (Optional. Defaults to 5m) - The max period of time that established connections will stay connected without sending data. If this time is elapsed, the connection will be closed.

tls (Optional) - The name of the TLS configuration specified at the root of the Orchestrator config.

Root DSE

rootDSE.attributes (Optional) - The custom attributes that should be returned by the LDAP Provider when a Root DSE Search is received.

Search

search.searchSE (Optional) - The service extension that is invoked when a non-Root DSE search is received. While this is optional, for most applications, this service extension will be required.

⚠️
As the author of a service extension you are responsible for its behavior.

Authentication

authentication.allowAnonymous (Optional. Defaults to false) - Whether the LDAP Provider allows anonymous authentication when receiving an LDAP Search.

Simple Authentication

authentication.methods.simple.enabled (Optional. Defaults to false) - Whether the LDAP Provider allows Bind requests using the Simple authentication choice.

authentication.methods.simple.authenticateSE (Optional. Required when Simple Authentication is enabled) - The required service extension that is invoked when a Bind request is received using simple authentication.

⚠️
As the author of a service extension you are responsible for its behavior.

SASL

authentication.methods.sasl.enabled (Optional. Defaults to false) - Whether the LDAP Provider supports Bind requests using a SASL Mechanism. If disabled, all SASL mechanisms will be disabled, regardless of whether they are enabled or not.

GSS-SPNEGO

authentication.methods.sasl.mechanisms.gssspnego.enabled (Optional. Defaults to false) - Whether the LDAP Provider supports the GSS-SPNEGO Mechanism. Currently, when enabled, NTLM MUST also be enabled. If disabled, NTLM will also be disabled.

authentication.methods.sasl.mechanisms.gssspnego.ntlm.enabled (Optional. Defaults to false) - Whether the LDAP Provider supports negotiating NTLM as part of the GSS-SPNEGO mechanism.

authentication.methods.sasl.mechanisms.gssspnego.ntlm.disableRequiring128bitEncryption (Optional. Defaults to false) - Whether the LDAP Provider disables requiring 128-bit encryption for NTLM.

authentication.methods.sasl.mechanisms.gssspnego.ntlm.netbiosDomainName (Optional) - Sets the NetBIOS domain name used by NTLM.

authentication.methods.sasl.mechanisms.gssspnego.ntlm.netbiosMachineName (Optional) - Sets the NetBIOS machine name used by NTLM. This is typically the name of the server that the LDAP Provider is hosted on.

authentication.methods.sasl.mechanisms.gssspnego.ntlm.dnsDomainName (Optional) - Sets the FQDN of the server’s domain.

authentication.methods.sasl.mechanisms.gssspnego.ntlm.dnsForestName (Optional) - Sets the FQDN of the server’s forest. The DnsForestName is empty on machines that are not domain joined.

authentication.methods.sasl.mechanisms.gssspnego.ntlm.dnsMachineName (Optional) - Sets the FQDN of the server.

authentication.methods.sasl.mechanisms.gssspnego.ntlm.getHashedCredentialsSE (Required) - The service extension that is invoked during the verification of the NTLM Authenticate message. It is responsible for retrieving the NT and LM hashes for the provided user.

⚠️
As the author of a service extension you are responsible for its behavior.

Configuration Examples

LDAP Provider with TLS

tls:
  ldapTLS:
    certFile: /etc/maverics/certs/ldap.crt
    keyFile: /etc/maverics/certs/ldap.key
  maverics:
    certFile: /etc/maverics/certs/maverics.crt
    keyFile: /etc/maverics/certs/maverics.key

http:
  address: :443
  tls: maverics

ldapProvider:
  enabled: true
  uri: ldaps://ldap.example.com:636
  tls: ldapTLS

Add support for Simple Authentication

tls:
  ldapTLS:
    certFile: /etc/maverics/certs/ldap.crt
    keyFile: /etc/maverics/certs/ldap.key
  maverics:
    certFile: /etc/maverics/certs/maverics.crt
    keyFile: /etc/maverics/certs/maverics.key

http:
  address: :443
  tls: maverics

ldapProvider:
  enabled: true
  uri: ldaps://ldap.example.com:636
  tls: ldapTLS
  authentication:
    methods:
      simple:
        enabled: true
        authenticateSE:
          funcName: Authenticate
          file: /etc/maverics/extensions/simple.go

Add support for non-Root DSE Search Requests

tls:
  ldapTLS:
    certFile: /etc/maverics/certs/ldap.crt
    keyFile: /etc/maverics/certs/ldap.key
  maverics:
    certFile: /etc/maverics/certs/maverics.crt
    keyFile: /etc/maverics/certs/maverics.key

http:
  address: :443
  tls: maverics

ldapProvider:
  enabled: true
  uri: ldaps://ldap.example.com:636
  tls: ldapTLS
  search:
    searchSE:
      funcName: Search
      file: /etc/maverics/extensions/search.go

Add support for custom attributes to be returned upon a Root DSE request

tls:
  ldapTLS:
    certFile: /etc/maverics/certs/ldap.crt
    keyFile: /etc/maverics/certs/ldap.key
  maverics:
    certFile: /etc/maverics/certs/maverics.crt
    keyFile: /etc/maverics/certs/maverics.key

http:
  address: :443
  tls: maverics

ldapProvider:
  enabled: true
  uri: ldaps://ldap.example.com:636
  tls: ldapTLS
  rootDSE:
    attributes:
      subschemaSubentry:
        - "CN=Aggregate,CN=Schema,CN=Configuration,DC=acme,DC=local"
      namingContexts:
        - "DC=acme,DC=local"
        - "CN=Configuration,DC=acme,DC=local"
        - "CN=Schema,CN=Configuration,DC=acme,DC=local"
        - "DC=ForestDnsZones,DC=acme,DC=local"
        - "DC=DomainDnsZones,DC=acme,DC=local"
      defaultNamingContext:
        - "DC=acme,DC=local"
      schemaNamingContext:
        - "CN=Schema,CN=Configuration,DC=acme,DC=local"
      configurationNamingContext:
        - "CN=Configuration,DC=acme,DC=local"
      rootDomainNamingContext:
        - "DC=acme,DC=local"

Add support for SASL / GSS-SPNEGO / NTLM Authentication

tls:
  ldapTLS:
    certFile: /etc/maverics/certs/ldap.crt
    keyFile: /etc/maverics/certs/ldap.key
  maverics:
    certFile: /etc/maverics/certs/maverics.crt
    keyFile: /etc/maverics/certs/maverics.key

http:
  address: :443
  tls: maverics

ldapProvider:
  enabled: true
  uri: ldaps://ldap.example.com:636
  tls: ldapTLS
  authentication:
    sasl:
      enabled: true
      mechanisms:
        gssspnego:
          enabled: true
          ntlm:
            enabled: true
            disableRequiring128bitEncryption: false
            netbiosDomainName: ACME
            netbiosMachineName: MAVERICS01
            dnsDomainName: acme.org
            dnsForestName: acme.org
            dnsMachineName: MAVERICS01.acme.org
            getHashedCredentialsSE:
              funcName: NTLMGetHashedCredentials
              file: /etc/maverics/extensions/gss_spnego_ntlm.go

Complete configuration

tls:
  ldapTLS:
    certFile: /etc/maverics/certs/ldap.crt
    keyFile: /etc/maverics/certs/ldap.key
  maverics:
    certFile: /etc/maverics/certs/maverics.crt
    keyFile: /etc/maverics/certs/maverics.key

http:
  address: :443
  tls: maverics

ldapProvider:
  enabled: true
  uri: ldaps://ldap.example.com:636
  readDeadline: 5m
  writeDeadline: 5m
  tls: ldapTLS
  rootDSE:
    attributes:
      subschemaSubentry:
        - "CN=Aggregate,CN=Schema,CN=Configuration,DC=acme,DC=local"
      namingContexts:
        - "DC=acme,DC=local"
        - "CN=Configuration,DC=acme,DC=local"
        - "CN=Schema,CN=Configuration,DC=acme,DC=local"
        - "DC=ForestDnsZones,DC=acme,DC=local"
        - "DC=DomainDnsZones,DC=acme,DC=local"
      defaultNamingContext:
        - "DC=acme,DC=local"
      schemaNamingContext:
        - "CN=Schema,CN=Configuration,DC=acme,DC=local"
      configurationNamingContext:
        - "CN=Configuration,DC=acme,DC=local"
      rootDomainNamingContext:
        - "DC=acme,DC=local"
  search:
    searchSE:
      funcName: Search
      file: /etc/maverics/extensions/search.go
  authentication:
    allowAnonymous: false
    methods:
      simple:
        enabled: true
        authenticateSE:
          funcName: Authenticate
          file: /etc/maverics/extensions/simple.go
      sasl:
        enabled: true
        mechanisms:
          gssspnego:
            enabled: true
            ntlm:
              enabled: true
              disableRequiring128bitEncryption: false
              netbiosDomainName: ACME
              netbiosMachineName: MAVERICS01
              dnsDomainName: acme.org
              dnsForestName: acme.org
              dnsMachineName: MAVERICS01.acme.org
              getHashedCredentialsSE:
                funcName: NTLMGetHashedCredentials
                file: /etc/maverics/extensions/gss_spnego_ntlm.go

Service Extension Examples

⚠️
These examples are for illustrative purposes only, and do not reflect what production versions should look like. Production versions will depend on the application being protected and its environment.

/etc/maverics/extensions/search.go

import (
	"strings"

	"github.com/strata-io/go-ntlm"
	"github.com/strata-io/service-extension/orchestrator"
)

// This is an example of an Search Service Extension and shows a simple way to verify 
// the LDAP connection between the application and the Orchestrator. It is configured
// to return hard-coded attributes for a test user, regardless of the Search request
// received.
//
// A production version will be dependant on the application and environment; however,
// typically, attributes originate from the end-users Session and would NOT be
// hardcoded. For example, if the policy requires a user to be authenticated 
// using Entra ID, then the attributes would likely come from Entra ID, and stored
// on the cache via the Orchestrator servicing as the proxy for the application.
func Search(api orchestrator.Orchestrator, dn string, filter string, reqAttrs []string) (map[string]map[string]interface{}, error) {
	api.Logger().Debug(
		"service", "LDAP Provider",
		"extension", "Search",
		"msg", "processing request",
		"dn", dn,
		"filter", filter,
		"attributes", strings.Join(reqAttrs, ","),
	)

	result := make(map[string]map[string]interface{})

	// User attributes would typically originate from the users Session and would NOT
	// be hardcoded. For example, if the policy requires a user to be authenticated 
	// using Entra ID, then the attributes would likely come from Entra ID.
	userAttrs := make(map[string]interface{})
	userAttrs["Name"] = "Test User"
	userAttrs["mail"] = "[email protected]"
	userAttrs["givenName"] = "Test"
	userAttrs["sn"] = "User"

	result["CN=Test,CN=Domain Users,CN=Users,DC=acme,DC=local"] = userAttrs

	api.Logger().Debug(
		"service", "LDAP Provider",
		"extension", "Search",
		"msg", "request processed",
		"dn", dn,
		"filter", filter,
		"attributes", strings.Join(reqAttrs, ","),
	)

	return result, nil
}

/etc/maverics/extensions/simple.go

import (
	"strings"

	"github.com/strata-io/go-ntlm"
	"github.com/strata-io/service-extension/orchestrator"
)

// This is an example Simple Authentication Service Extension that shows a quick way 
// to test a Bind request using the Simple Authentication method between the 
// application and the Orchestrator. In this example, only a user with the username 
// of `bob` and the password of `th3Bu!ld3R` to be authenticated.
//
// A production version of this Service Extension would typically retrieve the 
// password hash of the user from either `api.Cache` or `api.Session` and would
// compute the hash of the password received and compare it with the one stored. The 
// user is typically a short-lived / one-time user created by an Orchestrator serving
// as the identity-aware proxy, after the end-user has been authenticated by
// the configured IdP.
func Authenticate(api orchestrator.Orchestrator, username string, password string) (bool, error) {
	api.Logger().Debug(
		"service", "LDAP Provider",
		"extension", "Authenticate",
		"msg", "authenticating user",
		"username", username,
	)

	if strings.EqualFold(username, "bob") && password == "th3Bu!ld3R" {
		api.Logger().Debug(
			"service", "LDAP Provider",
			"extension", "Authenticate",
			"msg", "user authenticated",
			"username", username,
		)
		return true, nil
	}

	api.Logger().Debug(
		"service", "LDAP Provider",
		"extension", "Authenticate",
		"msg", "user not authenticated",
		"username", username,
	)
	return false, nil
}

/etc/maverics/extensions/gss_spnego_ntlm.go

import (
	"fmt"

	"github.com/strata-io/go-ntlm"
	"github.com/strata-io/service-extension/orchestrator"
)

var (
	// THIS IS FOR TESTING PURPOSES ONLY. Do not use this pattern in a production 
	// deployment.
	testUsers = map[string][]byte{
		fmt.Sprintf("%s:%s", ntlm.ToUnicode("acme"), ntlm.ToUnicode("user123")): ntlm.ToUnicode("abc123"),
	}
)

// This is an example of an NTLM NTLMGetHashedCredentials Service Extension and shows 
// a simple way to test users while verifying the LDAP connection between the 
// application and the Orchestrator.
//
// A production version of this Service Extension would typically retrieve the
// NT and LM hashes of the user from either `api.Cache` or `api.Session`, which would
// typically be computed by an Orchestrator serving as the application proxy. The 
// user is typically a short-lived / one-time user created after the end-user has 
// been authenticated and authorized.
func NTLMGetHashedCredentials(
	api orchestrator.Orchestrator,
	user []byte,
	domain []byte,
) (ntHash []byte, lmHash []byte, err error) {
	api.Logger().Debug(
		"service", "LDAP Provider",
		"extension", "NTLMGetHashedCredentials",
		"msg", "retrieving NT and LM hashes for user",
		"domain", fmt.Sprintf("%q", user[:]),
		"user", fmt.Sprintf("%q", user[:]),
	)

	key := fmt.Sprintf("%s:%s", string(domain), string(user))
	pass, ok := testUsers[key]
	if !ok {
		api.Logger().Debug(
			"service", "LDAP Provider",
			"extension", "NTLMGetHashedCredentials",
			"msg", "invalid user",
			"domain", fmt.Sprintf("%q", domain[:]),
			"user", fmt.Sprintf("%q", user[:]),
		)
		return nil, nil, fmt.Errorf("invalid user: %s", key)
	}
	ntHash = ntlm.NTOWFv2(pass, user, domain)
	lmHash = ntlm.LMOWFv2(pass, user, domain)

	api.Logger().Debug(
		"service", "LDAP Provider",
		"extension", "NTLMGetHashedCredentials",
		"msg", "NT and LM hashes found for user",
		"domain", fmt.Sprintf("%q", domain[:]),
		"user", fmt.Sprintf("%q", user[:]),
	)

	return ntHash, lmHash, err
}

Telemetry

Coming soon.

Glossary

  • SASL - Simple Authentication and Security Layer.
  • GSS - Generic Security Service API.
  • SPNEGO - Simple and Protected GSSAPI Negotiation Mechanism.
  • NTLM - New Technology LAN Manager.
  • OID - Object Identifier.

Additional Resources

  1. LDAP Protocol - RFC
  2. LDAP Authentication Methods and Security Mechanisms - RFC
  3. Simple Authentication and Security Layer (SASL) - RFC
  4. The Simple and Protected Generic Security Service Application Program Interface (GSS-API) Negotiation Mechanism - RFC
  5. NT LAN Manager (NTLM) Authentication Protocol