LDAP and Active Directory

The LDAP and Active Directory Connectors use enterprise LDAP and Active Directory (respectively) directory service to provide authentication and attributes used for:

  • migrating user profile information from legacy to cloud identity systems,
  • adding HTTP headers consumed by on-premises applications, or
  • maintaining state for user sessions as they move across identity systems and applications.

Configuration options

The following values can be provided to the LDAP connector via the Maverics configuration file.

URL

The url(s) of the LDAP server that Maverics connects with. Both a single URL and a list of URLs are supported. When multiples URLs are provided, a round-robin load balancing scheme will be used to distribute traffic.

Service Account Username

The serviceAccountUsername used to connect to the LDAP server for query operations.

Service Account Password

The serviceAccountPassword used to connect to the LDAP server for query operations.

Base DN

The baseDN specifies the location in which to perform the LDAP search.

Username Search Key

The usernameSearchKey to filter on during query and bind operations.

Attribute delimiter

The attributeDelimiter used to separate multi-valued attributes. This is an optional field and is only necessary if an attribute is multi-valued. If no value is provided, a default of “,” will be used for the delimiter.

Enable Authentication

Set enableAuthentication to true in order to use LDAP as an IDP.

connectors:
  - type: ldap
    enableAuthentication: true
    # ...

Authentication Search Scope

The authenticationSearchScope config is an optional configuration which must be used with serviceAccountUsername, serviceAccountPassword, usernameSearchKey and enableAuthentication to support authenticating users contained in nested
organizational units inside the specified baseDN. If you need to authenticate with users which exist in nested organizational units then authenticationSearchScope must be set to support their authentication.

connectors:
  - type: ldap
    enableAuthentication: true
    usernameSearchKey: uid
    baseDN: ou=Engineering,ou=People,dc=example,dc=com
    authenticationSearchScope: singleLevel
    serviceAccountUsername: cn=exampleUsername,dc=example,dc=com
    serviceAccountPassword: <examplePassword>
    # ...

The following configurations are supported:

Base Object

baseObject is the scope which constrains the search to the baseDN.

Single Level

singleLevel is the scope which constrains the search to the immediate subordinates of the entry named by baseDN.

Whole Subtree

wholeSubtree is the scope which constrains the search to the baseDN and all of its subordinates.

Login URL

When using the LDAP connector as an IDP, the loginURL setting (optional) can be used to set a custom endpoint for posting the user’s credentials. If unset, the form is will be submitted to a default location of /.ldap-login. This endpoint is hosted by the Orchestrator so the domain specified should resolve to a domain the Orchestrator hosts, or be set to just a path. In either case, it must not conflict with any paths to protected applications.

ℹ️

When specifying a loginURL, use absolute (full) URL including scheme, hostname, and path to ensure status requests are routed correctly. Always specify custom loginURL value if the routePattern includes a hostname.

For example, if the routePattern is defined as follows:

routePatterns:
  - api.example.com

then the loginURL value might be:

loginURL: https://api.example.com/login

connectors:
  - type: ldap
    enableAuthentication: true
    loginURL: "https://example.com/ldap-login"
    # ...

Custom Login HTML

When using the LDAP connector as an IDP, customLoginHTML (optional) can present a custom page to prompt the user for authentication. It should contain the file system location of an HTML page. If the value is unset a default login page will be used.

connectors:
  - type: ldap
    enableAuthentication: true
    loginURL: "https://example.com/ldap-login"
    # Using a static HTML page.
    customLoginHTML: /var/www/html/ldap-login.html
    # ...

By defining customLoginHTML, the Orchestrator can deliver a custom HTML page stored in the filesystem, as shown above. The Orchestrator uses Go Templates to provide relevant arguments to be rendered.

This page will need to POST the username and password content to the login URL, which will be delivered in the LoginURL template value. The originally requested page can be found in the RedirectURL template value, and should be posted to the login URL along with the username and password.

<html>
    <body>
        <form action={{.LoginURL}} method=POST>
            <label for="username">Username</label>
            <input type="text" id="username" name="username" />
            <br>
            <label for="password">Password</label>
            <input type="password" id="password" name="password" />
            <br>
            <input type="hidden" name="redirectURL" value="{{.RedirectURL}}" />
            <br>
            <input type="submit" name="submit" value="Submit" />
        </form>
    </body>
</html>

Health Check

healthCheck defines an optional health check for the connector. This option is required when using the connector in an IDP-continuity scenario. For more info on how to define the health check, please see the docs.

Examples

Loading Attributes From LDAP (LDAP as an attribute provider)

connectors:
  - name: ldap-example
    type: ldap
    url:
      - "ldap://node1.ldap.com"
      - "ldap://node2.ldap.com"
    baseDN: ou=People,o=Example,c=US
    serviceAccountUsername: uid=admin,ou=Admins,o=Example,c=US
    serviceAccountPassword: <examplePassword>
    usernameSearchKey: uid
    attributeDelimiter: ^

Sample Active Directory Connector Configuration

connectors:
  - name: ad-example
    type: activedirectory 
    url:
      - "ldap://node1.ldap.com"
      - "ldap://node2.ldap.com"
    baseDN: ou=People,o=Example,c=US
    serviceAccountUsername: uid=admin,ou=Admins,o=Example,c=US
    serviceAccountPassword: password
    usernameSearchKey: uid
    attributeDelimiter: ^

Authentication with LDAP Connector (LDAP as an IDP)

When LDAP is used as an IDP, the Orchestrator presents a form for the user to enter their username and password. To use Active Directory as an IDP, replace the connector type with activedirectory

tls:
  maverics:
    certFile: certs/maverics.sonarsystems.co.crt
    keyFile: certs/maverics.sonarsystems.co.key

http:
  address: :443
  tls: maverics

apps:
  - name: Sonar
    type: proxy
    routePatterns:
      - /
    tls: sonar-app
    upstream: https://app.sonarsystems.com:8443
    policies:
      - location: /
        authentication:
          idps:
            - ldap
        authorization:
          allowAll: true

connectors:
  - name: ldap
    type: ldap
    url: "ldap://ldap.example.com:389"
    serviceAccountPassword: <examplePassword>
    serviceAccountUsername: cn=exampleUsername,dc=example,dc=com
    baseDN: ou=People,dc=example,dc=com
    usernameSearchKey: uid
    enableAuthentication: true
    authenticationSearchScope: wholeSubtree
    loginURL: https://example.com/ldap-login
    customLoginHTML: /var/www/html/ldap-login.html

Using LDAP attributes in a service extension

Attributes for an LDAP connector are only loaded if used in a policy evaluation or set in headers directly.

For example, in the configuration example below, ldap.groupMembership is used in an authorization policy.

apps:
  - name: exampleApp
    type: proxy
    routePatterns:
      - /
      
    # ...
    
    policies:
      - location: /
        authentication:
          idps:
            - azure
        authorization:
          rules:
            - and:
              - equals: [ "{{azure.authenticated}}", "true" ]
              - contains: [ "{{ldap.groupMembership}}", "cn=Joe Smith,ou=East,dc=MyDomain" ]

    headers:
      - createHeaderSE:
          funcName: CreateHeader
          file: "/etc/maverics/extensions/createHeader.go"

The ldap.groupMembership attribute can then be referenced in the createHeader.go service extension.

/etc/maverics/extensions/createHeader.go

package main

import (
	"net/http"
	"strings"

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

// CreateHeader adds a prefix to each group claim.
func CreateHeader(
	api orchestrator.Orchestrator,
	_ http.ResponseWriter,
	_ *http.Request,
) (http.Header, error) {
	logger := api.Logger()
	logger.Debug("se", "building custom header")
	
	sess, err := api.Session()
	if err != nil {
		logger.Error("se", "unable to retrieve session", "error", err.Error())
		return nil, err
	}
	
	groupclaims, err := sess.GetString("ldap.groupMembership")
	if err != nil {
            logger.Error("se", "unable to retrieve string", "error", err.Error())
            return nil, err
	}
	
	splitHeaders := strings.Split(groupclaims, "^")

	var outHeaders []string
	for _, h := range splitHeaders {
		outHeaders = append(outHeaders, "myPrefix-"+h)
	}
	headers := make(http.Header)
	headers["GROUPS"] = []string{strings.Join(outHeaders, "^")}
	return headers, nil
}

If ldap.groupMembership were not used in a policy or header, it would be empty when referenced in the service extension.