LDAP
Authenticate requests against membership information that is stored in a Lightweight Directory Access Protocol (LDAP) server.If you import or export resources across workspaces, your policies might not apply. For more information, see Import and export policies.
About LDAP
The Lightweight Directory Access Protocol (LDAP) is an open protocol that you can use to store and retrieve hierarchically structured data over a network. Many enterprises use LDAP to centrally store and secure organizational information. In particular, LDAP is often used for membership directories. You might set up LDAP to store information such as the following:
- User details like name and email
- Group membership details for each user
- Permissions for each group
You can deploy an LDAP server to your Kubernetes cluster. Then, use a Gloo external auth policy to authenticate users and control access based on their group membership details in the LDAP server.
Want to learn more about LDAP? Try out this LDAP tutorial by Digital Ocean.
How does LDAP work with Gloo?
Upon receiving an authentication request that uses LDAP, Gloo performs the following steps.
- Gloo looks for a Basic Authentication header on the request to extract the username (
uid
) and credentials (userPassword
). - If the header is not present, a
401
response is returned. - If the header is present, Gloo extracts the username from the basic auth header. Then, it builds the distinguished name (DN) for the user entry by substituting the username from the header for the
%s
placeholder in the LDAPuserDnTemplate
setting.To prevent injection attacks, Gloo removes special characters from the username before continuing to the next bind step. - Gloo performs a BIND operation with the LDAP server, using the DN for the user entry from the previous step.
- If the bind operation fails, a
401
response is returned. This response means that the user could not be found or the credentials are incorrect. - Gloo issues a search operation for the user entry (with a
base
scope). In the user entry, Gloo looks for an attribute with a name equal tomembershipAttributeName
. - Gloo checks if one of the values for the attribute matches one of the
allowedGroups
in the policy. If so, Gloo completes the request. Otherwise, Gloo returns a403
response. This response means that although the user can be authenticated, the user does not have the appropriate permissions to complete the request.
Before you begin
- Complete the multicluster getting started guide to set up the following testing environment.
- Three clusters along with environment variables for the clusters and their Kubernetes contexts.
- The Gloo Platform CLI,
meshctl
, along with other CLI tools such askubectl
andistioctl
. - The Gloo management server in the management cluster, and the Gloo agents in the workload clusters.
- Istio installed in the workload clusters.
- A simple Gloo workspace setup.
- Install Bookinfo and other sample apps.
- Make sure that the external auth service is installed and running. If not, install the external auth service.
kubectl get pods --context ${REMOTE_CONTEXT1} -A -l app=ext-auth-service
- Make sure that you have the
ldapsearch
CLI tool on your local machine. This tool is part of the default developer tools in macOS. You can check if you have the tool by running the following command to print the usage details. To install LDAP and Helper utilities, see this Digital Ocean guide.ldapsearch --help
Deploy an LDAP server
Before you can create an LDAP external auth policy, you must have an LDAP server. The following example configures a simple set of users and groups, and deploys the LDAP server to your cluster.
-
Download the sample LDAP setup script.
-
Review what the sample LDAP setup script does. For in-depth information, see About the LDAP setup script. In short, the script sets up LDAP users and groups, as well as creates the following Kubernetes resources:
- A
configmap
with the LDAP server setup configuration. - A
deployment
to run OpenLDAP. - A
service
to provide access to the deployment.
- A
-
Make the downloaded script executable.
chmod +x setup-ldap.sh
-
Set your Kubernetes context to the cluster that you want to deploy the LDAP server to and create the external auth policy in.
kubectl config use-context ${REMOTE_CONTEXT1}
-
Run the LDAP setup script. The script accepts an optional string argument to specify the namespace to create the resources in. Otherwise, you can omit the argument to create the resources in the
default
namespace../setup-ldap.sh
Example output:
No namespace provided, using default namespace Creating configmap with LDAP server bootstrap config... configmap/ldap created Creating LDAP service and deployment... deployment.apps/ldap created service/ldap created
-
Enable port-forwarding on the deployment so that you can test the LDAP server.
kubectl port-forward deployment/ldap 8088:389
-
In a new tab in your terminal, search for the distinguished names (DNs) of all entries in the
solo
andio
domain components (DCs). For more information about this command, see the LDAP docs.ldapsearch -H ldap://localhost:8088 -D "cn=admin,dc=solo,dc=io" -w "solopwd" -b "dc=solo,dc=io" -LLL dn
Example output:
dn: dc=solo,dc=io dn: cn=admin,dc=solo,dc=io dn: ou=people,dc=solo,dc=io dn: uid=marco,ou=people,dc=solo,dc=io dn: uid=rick,ou=people,dc=solo,dc=io dn: uid=scottc,ou=people,dc=solo,dc=io dn: ou=groups,dc=solo,dc=io dn: cn=developers,ou=groups,dc=solo,dc=io dn: cn=sales,ou=groups,dc=solo,dc=io dn: cn=managers,ou=groups,dc=solo,dc=io
Good job, now you have an LDAP server running! Continue to Configure an external auth policy with LDAP.
About the LDAP setup script
The LDAP setup script sets up a basic LDAP server with a few different users and groups. This setup includes the Kubernetes resources to create in the cluster.
The root of the LDAP directory hierarchy is the dc=solo,dc=io
entry, which has two child entries for users and groups.
ou=groups,dc=solo,dc=io
is the parent entry for user groups in the organization. It has three groups:
- cn=
developers
,ou=groups,dc=solo,dc=io - cn=
sales
,ou=groups,dc=solo,dc=io - cn=
managers
,ou=groups,dc=solo,dc=io
03_people.ldif: |
# Create a parent 'people' entry
dn: ou=people,dc=solo,dc=io
objectClass: organizationalUnit
ou: people
description: All solo.io people
# Add 'marco'
dn: uid=marco,ou=people,dc=solo,dc=io
objectClass: inetOrgPerson
cn: Marco Schwarz
sn: Schwarz
uid: marco
userPassword: marcopwd
mail: marco.schwarz@solo.io
# Add 'rick'
dn: uid=rick,ou=people,dc=solo,dc=io
objectClass: inetOrgPerson
cn: Rick Duke
sn: Duke
uid: rick
userPassword: rickpwd
mail: rick.duke@solo.io
# Add 'scottc'
dn: uid=scottc,ou=people,dc=solo,dc=io
objectClass: inetOrgPerson
cn: Scott Crawley
sn: Crawley
uid: scottc
userPassword: scottcpwd
mail: scott.crawley@solo.io
ou=people,dc=solo,dc=io
is the parent entry for people in the organization. It has the following entries:
- uid=
marco
,ou=people,dc=solo,dc=io - uid=
rick
,ou=people,dc=solo,dc=io - uid=
scott
,ou=people,dc=solo,dc=io
04_groups.ldif: |+
# Create top level 'group' entry
dn: ou=groups,dc=solo,dc=io
objectClass: organizationalUnit
ou: groups
description: Generic parent entry for groups
# Create the 'developers' entry under 'groups'
dn: cn=developers,ou=groups,dc=solo,dc=io
objectClass: groupOfNames
cn: developers
description: Developers group
member: uid=marco,ou=people,dc=solo,dc=io
member: uid=rick,ou=people,dc=solo,dc=io
member: uid=scottc,ou=people,dc=solo,dc=io
# Create the 'sales' entry under 'groups'
dn: cn=sales,ou=groups,dc=solo,dc=io
objectClass: groupOfNames
cn: sales
description: Sales group
member: uid=scottc,ou=people,dc=solo,dc=io
# Create the 'managers' entry under 'groups'
dn: cn=managers,ou=groups,dc=solo,dc=io
objectClass: groupOfNames
cn: managers
description: Managers group
member: uid=rick,ou=people,dc=solo,dc=io
The user credentials and memberships are summarized in the following table.
Username | Password | Member of developers | Member of sales | Member of managers |
---|---|---|---|---|
marco | marcopwd | ✅ | ❌ | ❌ |
rick | rickpwd | ✅ | ❌ | ✅ |
scott | scottpwd | ✅ | ✅ | ❌ |
Configure an external auth policy with LDAP
Create the external auth policy with LDAP.
-
Create an external auth server to use for your policy. The following example refers directly to the default Gloo Mesh external auth service, but you can also use a virtual destination instead. For more information, see External auth server setup.
kubectl --context ${REMOTE_CONTEXT1} apply -f - <<EOF apiVersion: admin.gloo.solo.io/v2 kind: ExtAuthServer metadata: name: ext-auth-server namespace: bookinfo spec: destinationServer: port: number: 8083 ref: cluster: $REMOTE_CLUSTER1 name: ext-auth-service namespace: gloo-mesh-addons EOF
-
Create an external auth policy that uses the LDAP server.
When you create the policy with a destination selector, only Kubernetes services can be specified in the
applyToDestination
section. Gloo virtual destinations or Gloo external services are not supported.kubectl --context ${REMOTE_CONTEXT1} apply -f - <<EOF apiVersion: security.policy.gloo.solo.io/v2 kind: ExtAuthPolicy metadata: name: ratings-ldap namespace: bookinfo spec: applyToDestinations: - selector: labels: app: ratings config: server: name: ext-auth-server namespace: bookinfo cluster: $REMOTE_CLUSTER1 glooAuth: configs: - ldap: address: "ldap://ldap.default.svc.cluster.local:389" userDnTemplate: "uid=%s,ou=people,dc=solo,dc=io" membershipAttributeName: memberOf allowedGroups: - "cn=managers,ou=groups,dc=solo,dc=io" EOF
Review the following table to understand this configuration. For more information, see the API reference.
Setting | Description |
---|---|
applyToDestinations |
Configure which destinations to apply the policy to, by using labels. Destinations can be a Kubernetes service, VirtualDestination, or ExternalService. If you do not specify any destinations or routes, the policy applies to all destinations in the workspace by default. If you do not specify any destinations but you do specify a route, the policy applies to the route but to no destinations. |
server |
The external auth server to use for the policy. |
ldap |
Configure the LDAP server details. |
address |
The address of the LDAP server that Gloo queries when a request matches the policy. This example uses the Kubernetes DNS name and port of the LDAP service that you deployed in the default namespace. |
userDnTemplate |
The template string for Gloo to build the DNs of the user entry to authenticate and authorize. The string must have a single occurrence of the %s placeholder. This placeholder is used to substitute the value from the request header that you want to look for, such as the user ID (uid ) in this example. In this example, the template matches the format of the user entry DNs in the config map. |
membershipAttributeName |
A case-insensitive name of the attribute with the names of the groups that a user entry is a member of. If not set, the default value memberOf is used. In the example, the config map sets memberOf automatically for each user entry that is in a group. |
allowedGroups |
The DNs of the user groups that are allowed to access services that are protected by this policy. In this example, only members of "cn=managers,ou=groups,dc=solo,dc=io" group can get successful responses. |
Verify the external auth LDAP policy
To test the LDAP policy, make a series of requests as different users. The following table is based off the users that you created in your LDAP config map. The username and password are encoded to base 64 in the format username:password
so that you can pass them in a basic auth header.
Username | Password | Basic auth header | Notes |
---|---|---|---|
marco | marcopwd | Authorization: Basic bWFyY286bWFyY29wd2Q= | Member of developers group |
rick | rickpwd | Authorization: Basic cmljazpyaWNrcHdk | Member of developers and managers groups |
john | doe | Authorization: Basic am9objpkb2U= | Unknown user, not a member of any group |
-
Send a request to the
ratings
app without any auth header. Now, the request is blocked with a401 Unauthorized
response.Create a temporary curl pod in the
bookinfo
namespace, so that you can test the app setup. You can also use this method in Kubernetes 1.23 or later, but an ephemeral container might be simpler, as shown in the other tab.- Create the curl pod.
kubectl run -it -n bookinfo --context $REMOTE_CONTEXT1 curl \ --image=curlimages/curl:7.73.0 --rm -- sh
- Send a request to the ratings app.
curl -v http://ratings:9080/ratings/1
Use the
kubernetes debug
command to create an ephemeral curl container in the deployment. This way, the curl container inherits any permissions from the app that you want to test. If you don't run Kubernetes 1.23 or later, you can deploy a separate curl pod or manually add the curl container as shown in the other tab.kubectl --context ${REMOTE_CONTEXT1} -n bookinfo debug -i pods/$(kubectl get pod --context ${REMOTE_CONTEXT1} -l app=reviews -A -o jsonpath='{.items[0].metadata.name}') --image=curlimages/curl -- curl -v http://ratings:9080/ratings/1
If the output has an error about
EphemeralContainers
, see Ephemeral containers don’t work when testing Bookinfo. - Create the curl pod.
-
Repeat the previous step with the credentials of the unknown member,
john
. The request is blocked with the same401 Unauthorized
response.Create a temporary curl pod in the
bookinfo
namespace, so that you can test the app setup. You can also use this method in Kubernetes 1.23 or later, but an ephemeral container might be simpler, as shown in the other tab.From the curl pod, send a request to the ratings app.
curl -v -H "Authorization: Basic am9objpkb2U=" http://ratings:9080/ratings/1
Use the
kubernetes debug
command to create an ephemeral curl container in the deployment. This way, the curl container inherits any permissions from the app that you want to test. If you don't run Kubernetes 1.23 or later, you can deploy a separate curl pod or manually add the curl container as shown in the other tab.kubectl --context ${REMOTE_CONTEXT1} -n bookinfo debug -i pods/$(kubectl get pod --context ${REMOTE_CONTEXT1} -l app=reviews -A -o jsonpath='{.items[0].metadata.name}') --image=curlimages/curl -- curl -v -H "Authorization: Basic am9objpkb2U=" http://ratings:9080/ratings/1
-
Try a request with the credentials of a known member,
marco
. This user is a member of thedevelopers
group, but the LDAP policy only grants permission to members of themanagers
group. The request is blocked, but this time with a403 Forbidden
response to indicate the lack of permissions.Create a temporary curl pod in the
bookinfo
namespace, so that you can test the app setup. You can also use this method in Kubernetes 1.23 or later, but an ephemeral container might be simpler, as shown in the other tab.From the curl pod, send a request to the ratings app.
curl -v -H "Authorization: Basic bWFyY286bWFyY29wd2Q=" http://ratings:9080/ratings/1
Use the
kubernetes debug
command to create an ephemeral curl container in the deployment. This way, the curl container inherits any permissions from the app that you want to test. If you don't run Kubernetes 1.23 or later, you can deploy a separate curl pod or manually add the curl container as shown in the other tab.kubectl --context ${REMOTE_CONTEXT1} -n bookinfo debug -i pods/$(kubectl get pod --context ${REMOTE_CONTEXT1} -l app=reviews -A -o jsonpath='{.items[0].metadata.name}') --image=curlimages/curl -- curl -v -H "Authorization: Basic bWFyY286bWFyY29wd2Q=" http://ratings:9080/ratings/1
-
Finally, send a request with the credentials of
rick
, who is a member of themanagers
group and meets the LDAP policy requirements.Create a temporary curl pod in the
bookinfo
namespace, so that you can test the app setup. You can also use this method in Kubernetes 1.23 or later, but an ephemeral container might be simpler, as shown in the other tab.- From the curl pod, send a request to the ratings app.
curl -v -H "Authorization: Basic cmljazpyaWNrcHdk" http://ratings:9080/ratings/1
- Exit the temporary pod. The pod deletes itself.
exit
Use the
kubernetes debug
command to create an ephemeral curl container in the deployment. This way, the curl container inherits any permissions from the app that you want to test. If you don't run Kubernetes 1.23 or later, you can deploy a separate curl pod or manually add the curl container as shown in the other tab.kubectl --context ${REMOTE_CONTEXT1} -n bookinfo debug -i pods/$(kubectl get pod --context ${REMOTE_CONTEXT1} -l app=reviews -A -o jsonpath='{.items[0].metadata.name}') --image=curlimages/curl -- curl -v -H "Authorization: Basic cmljazpyaWNrcHdk" http://ratings:9080/ratings/1
The request succeeds!
HTTP/1.1 200 OK ... {"id":1,"ratings":{"Reviewer1":5,"Reviewer2":4}}
- From the curl pod, send a request to the ratings app.
Cleanup
- Remove the LDAP server that you created.
kubectl delete --context ${REMOTE_CONTEXT1} configmap ldap kubectl delete --context ${REMOTE_CONTEXT1} deployment ldap kubectl delete --context ${REMOTE_CONTEXT1} service ldap
- Remove the Gloo resources that you created.
kubectl --context $REMOTE_CONTEXT1 -n bookinfo delete ExtAuthServer ext-auth-server kubectl --context $REMOTE_CONTEXT1 -n bookinfo delete ExtAuthPolicy ratings-ldap