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"
}