Apereo CAS - Securing Actuator Endpoints with LDAP

Posted by Misagh Moayyed on December 23, 2020 · 6 mins read ·

CAS, being a Spring-Boot application at heart, includes several actuator endpoints to help you monitor and manage the server when it’s pushed to production. Such actuator endpoints can be secured in a variety of ways the most common of which would be username/password via basic authentication. In this blog post, we will examine the security configuration of actuator endpoints secured whose access is controlled via LDAP.

Our starting position is as follows:

CAS Configuration

Actuator endpoints that are provided by CAS are available in a separate extension module that must be included in the build configuration of the CAS WAR overlay. Once included, we can begin by enabling the status actuator endpoint and exposing it over HTTP. We will also mark the endpoint’s access rule as AUTHENTICATED to disable anonymous access

management.endpoint.status.enabled=true
management.endpoints.web.exposure.include=status

cas.monitor.endpoints.endpoint.status.access=AUTHENTICATED

At this point, if you attempt to access the status endpoint,

curl https://sso.example.org/cas/actuator/status | jq 

…you should see the following output:

{
  "status": 401,
  "error": "Unauthorized",
  "message": "Unauthorized",
  "path": "/cas/actuator/status"
}

LDAP Configuration

The AUTHENTICATED access rule forces CAS, via Spring Security, to require authenticated requests via basic authentication. Therefor, we need to teach CAS about credential sources such as LDAP that can validate credentials and report back the authentication result status, thereby allowing or refusing access:

cas.monitor.endpoints.ldap.ldap-url=ldap://localhost:1389
cas.monitor.endpoints.ldap.base-dn=ou=people,dc=example,dc=org
cas.monitor.endpoints.ldap.search-filter=uid={user}
cas.monitor.endpoints.ldap.bind-dn=cn=Directory Manager,dc=example,dc=org
cas.monitor.endpoints.ldap.bind-credential=Password

This time, if you attempt to access the status endpoint,

curl -u ldapuser:ldappassword https://sso.example.org/cas/actuator/status | jq 

…you should see the following output:

{
  "status": 200,
  "description": "OK",
  "health": "UP",
  "host": "misaghmoayyed",
  "server": "https://sso.example.org",
  "version": "6.3.0"
}

User Attributes and RBAC

Of course, not all valid accounts in LDAP should always be allowed access to an endpoint. What is often more desirable is the ability to limit access to a selection of authorized users based on predefined roles. For example, let’s say that all valid users in LDAP who carry an sn attribute with a value of admin should be authorized for access, and all others should be denied. So let’s teach CAS about this setup:

cas.monitor.endpoints.ldap.ldap-authz.base-dn=ou=people,dc=example,dc=org
cas.monitor.endpoints.ldap.ldap-authz.search-filter=uid={user}
cas.monitor.endpoints.ldap.ldap-authz.role-attribute=sn
cas.monitor.endpoints.ldap.ldap-authz.role-prefix=ROLE_

# Access requirement that user roles must pass
spring.security.user.roles=ROLE_ADMIN

User Groups and RBAC

Similarly, user roles can additionally be decided based on user’s groups and memberships. Once the user is found in LDAP, an additional query will begin to look for user groups and memberships. When memberships are fetched from LDAP, the final authenticated profile is examines against pre-defined roles to determine access requirements:

cas.monitor.endpoints.ldap.ldap-authz.group-base-dn=ou=Groups,dc=example,dc=org
cas.monitor.endpoints.ldap.ldap-authz.group-filter=uniquemember={user}
cas.monitor.endpoints.ldap.ldap-authz.group-attribute=businessCategory
cas.monitor.endpoints.ldap.ldap-authz.group-prefix=GRP_

spring.security.user.roles=GRP_HR_ADMIN

The {user} parameter will be replaced with the user DN at runtime to locate group membership. For each membership entry the value of the businessCategory is used to build the role, along with the prefix GRP_. For example, cn=casuser,ou=People,dc=example,dc=org is a member of the group cn=HR Managers,ou=Groups,dc=example,dc=org, as specified by the uniquemember attribute. The businessCategory for this membership is set to HR_ADMIN, which permits CAS to build the final role as GRP_HR_ADMIN and allows the endpoint request to pass authorization and gain access.

Bonus

If you attempt to access the endpoint via the browser, you would be by default greeted with a login form and asked for username/password. To disable this behavior, make sure the following setting is defined:

cas.monitor.endpoints.form-login-enabled=false

So…

I hope this review was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. Please feel free to engage and contribute as best as you can.

Happy Coding,

Misagh Moayyed