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

When using the LDAP connector as an IDP, customLogin (optional) can present a custom page to prompt the user for authentication.

By defining customLogin, the Orchestrator delivers a custom HTML page stored in the filesystem. The Orchestrator uses Go Templates to provide the necessary data that will be rendered.

If templateFile is unset a default login page will be used.

Template File

templateFile defines the file system location of the custom HTML template to be rendered.

Localization

localization is an optional setting which enables language-specific rendering of the template file based on the user’s Accept-Language header.

The incoming request’s Accept-Language header must contain BCP 47 compatible language tags. These tags will be matched to a corresponding JSON file containing the localized values.

Localizations

localizations define the list of translations and their corresponding ordering. The order of the localizations is important and must be set in order of preference. When no match is found the default localization will be rendered.

Missing localization values are resolved by the template using the empty string. It is important to note that missing localization fields may negatively impact the
user experience.

Language

The language field must be set to the BCP 47 language tag and is used when matching against the user’s Accept-Language header.

File The file field must be set to the path of the JSON file containing the localized values.

Default The default field may only be set to true for a single localization. It denotes which language to fall back to if there is no match to the user tags.

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
    customLogin: 
      templateFile: /etc/maverics/templates/login.html
      localization:
        localizations:
          - language: en
            file: /etc/maverics/locales/login/en.json
            default: true
          - language: fr
            file: /etc/maverics/locales/login/fr.json

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.

Standard Custom Login Page

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

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>

Localized Custom Login Page

connectors:
 - type: ldap
   enableAuthentication: true
   loginURL: "https://example.com/ldap-login"
    # Using a localized HTML page.
   customLogin:
     templateFile: /etc/maverics/templates/login.html
     localization:
       localizations:
         - language: en
           file: /etc/maverics/locales/login/en.json
           default: true
         - language: fr
           file: /etc/maverics/locales/login/fr.json

The templated values to be localized MUST start with the prefix .L10N.: i.e. .L10N.<value>.

Example localized template file:

<!DOCTYPE html>
<html lang="{{.L10n.language}}">
<head><title>{{.L10n.title}}</title></head>
<script type="text/javascript">
    let errorMessage = "{{.Error}}"
    window.onload = function () {
        if (errorMessage !== "") {
            document.getElementById('loginFailed').style.display = 'block';
        }
    };
</script>
<body>
<form action="{{.LoginURL}}" method="POST">
    <label for="username">{{.L10n.username}}</label>
    <input type="text" id="username" name="username" value="{{.Username}}"/><br/>
    <label for="password">{{.L10n.password}}</label>
    <input type="password" id="password" name="password"/><br/>
    <input type="hidden" name="redirectURL" value="{{.RedirectURL}}"/>
    <input type="submit" name="submit" value="{{.L10n.submit}}"/>
</form>
<br>
<div id="loginFailed" style="display: none">
    <div style="color: red">{{.L10n.loginFailedMessage}}: {{.Error}}</div>
</div>
</body>
</html>

Example of a JSON localization file:

{
  "language": "en",
  "title": "LDAP Login",
  "username": "Username",
  "password": "Password",
  "submit": "Submit",
  "loginFailedMessage": "Login failed"
}