This is a short and sweet tutorial on how to deploy CAS via the WAR Overlay method.
This tutorial specifically requires and focuses on:
6.4.x
Overlays are a strategy to combat repetitive code and/or resources. Rather than downloading the CAS codebase and building it from source, overlays allow you to download a pre-built vanilla CAS web application server provided by the project itself, override/insert specific behavior into it and then merge it all back together to produce the final (web application) artifact. You can find a lot more about how overlays work here.
Please note that a CAS WAR Overlay can also be generated on demand using the CAS Initializr.
The concept of the WAR Overlay is NOT a CAS invention. It’s specifically an Apache Maven feature and of course, there are techniques and plugins available to apply the same concept to Gradle-based builds as well. For this tutorial, the Gradle overlay we will be working with is available here. Be sure to check out the appropriate branch, that is 6.4
.
Once you have forked and cloned the repository locally, you’re ready to begin.
master
branch of the repository applies to CAS 6.5.x
deployments. That may not necessarily remain true when you start your own deployment. So examine the branches and make sure you checkout
the one matching your intended CAS version.
Similar to Grey’s, a Gradle WAR overlay is composed of several facets the most important of which are the build.gradle
and gradle.properties
file. These are build-descriptor files whose job is to teach Gradle how to obtain, build, configure (and in certain cases deploy) CAS artifacts.
The CAS Gradle Overlay is composed of several sections. The ones you need to worry about are the following.
In gradle.properties
file, project settings and versions are specified:
cas.version=6.4.0
springBootVersion=2.5.4
appServer=-tomcat
gradleVersion=7.2
tomcatVersion=9
tomcatFullVersion=9.0.52
group=org.apereo.cas
sourceCompatibility=11
targetCompatibility=11
The gradle.properties
file describes what versions of CAS, Spring Boot, and Java are required for the deployment. You are in practice mostly concerned with the cas.version
setting and as new (maintenance) releases come out, it would be sufficient to simply update that version and re-run the build.
This might be a good time to review the CAS project’s Release Policy as well as Maintenance Policy.
The next piece describes the dependencies of the overlay build. These are the set of components almost always provided by the CAS project that will be packaged up and put into the final web application artifact. At a minimum, you need to have the cas-server-webapp-${appServer}
module available because that is the web application into which you intend to inject your settings and customizations if any. Also, note that the module declarations are typically configured to download the CAS version instructed by the property cas.version
.
Here is an example:
dependencies {
if (project.hasProperty("external")) {
implementation "org.apereo.cas:cas-server-webapp:${casServerVersion}"
} else {
implementation "org.apereo.cas:cas-server-webapp${project.appServer}:${casServerVersion}"
}
// Other dependencies may be listed here...
}
Including a CAS module/dependency in the build.gradle
simply advertises to CAS your intention of turning on a new feature or a variation of a current behavior. Do NOT include something in your build just because it looks and sounds cool. Remember that the point of an overlay is to only keep track of things you actually need and care about, and no more.
Now that you have a basic understanding of the build descriptor, it’s time to actually run the build. A Gradle build is often executed by passing specific goals/commands to Gradle itself, aka gradlew
. So for instance in the terminal and once inside the project directory you could execute things like:
cd cas-overlay-template
./gradlew clean
The WAR Overlay project provides you with an embedded Gradle wrapper whose job is to first determine whether you have Gradle installed. If not, it will download and configure one for you based on the project’s needs. The gradlew tasks
command describes the set of available operations you may carry out with the build script.
README
file to keep to date.
As an example, here’s what I see if I were to run the build command:
./gradlew clean copyCasConfiguration build
...
Starting a Gradle Daemon (subsequent builds will be faster)
Configuration on demand is an incubating feature.
BUILD SUCCESSFUL in 14s
2 actionable tasks: 2 executed
...
You can see that the build attempts to download, clean, compile and package all artifacts, and finally, it produces a build/libs/cas.war
which you can then use for actual deployments.
I am going to skip over the configuration of /etc/cas/config
and all that it deals with. If you need the reference, you may always use this guide to study various aspects of CAS configuration.
Suffice it to say that, quite simply, CAS deployment expects the main configuration file to be found under /etc/cas/config/cas.properties
. This is a key-value store that is able to dictate and alter the behavior of the running CAS software.
As an example, you might encounter something like:
cas.server.name=https://cas.example.org:8443
cas.server.prefix=${cas.server.name}/cas
logging.config=file:/etc/cas/config/log4j2.xml
…which at a minimum, identifies the CAS server’s URL and prefix and instructs the running server to locate the logging configuration at file:/etc/cas/config/log4j2.xml
. The overlay by default ships with a log4j2.xml
that you can use to customize logging locations, levels, etc. Note that the presence of all that is contained inside /etc/cas/config/
is optional. CAS will continue to fall back onto defaults if the directory and the files within are not found.
It is VERY IMPORTANT that you contain and commit the entire overlay directory (save the obvious exclusions such as the build
directory) into some sort of source control system, such as git
. Treat your deployment just like any other project with tags, releases, and functional baselines.
We need to first establish a primary mode of validating credentials by sticking with LDAP authentication. The strategy here, as indicated by the CAS documentation, is to declare the intention/module in the build script:
implementation "org.apereo.cas:cas-server-support-ldap:${casServerVersion}"
…and then configure the relevant cas.authn.ldap[x]
settings for the directory server in use. Most commonly, that would translate into the following settings:
cas.authn.ldap[0].type=AUTHENTICATED
cas.authn.ldap[0].ldap-url=ldaps://ldap1.example.org
cas.authn.ldap[0].base-dn=dc=example,dc=org
cas.authn.ldap[0].search-filter=cn={user}
cas.authn.ldap[0].bind-dn=cn=Directory Manager,dc=example,dc=org
cas.authn.ldap[0].bind-credential=...
To resolve and fetch the needed attributes which will be used later by CAS for release, the simplest way would be to let LDAP authentication retrieve the attributes directly from the directory server. The following setting allows us to do just that:
cas.authn.ldap[0].principal-attribute-list=memberOf,cn,givenName,mail
Client applications that wish to use the CAS server for authentication must be registered with the server apriori. CAS provides a number of facilities to keep track of the registration records and you may choose any that fits your needs best. In more technical terms, CAS deals with service management using two specific components: Individual implementations that support a form of a database are referred to as Service Registry components and they are many. There is also a parent component that sits on top of the configured service registry as more of an orchestrator that provides a generic facade and entry point for the rest of CAS without entangling all other operations and subsystems with the specifics and particulars of a storage technology.
In this tutorial, we are going to try to configure CAS with the JSON service registry.
First, ensure you have declared the appropriate module/intention in the build:
implementation "org.apereo.cas:cas-server-support-json-service-registry:${casServerVersion}"
Next, you must teach CAS how to look up JSON files to read and write registration records. This is done in the cas.properties
file:
cas.service-registry.core.init-from-json=false
cas.service-registry.json.location=file:/etc/cas/services
…where a sample ApplicationName-1001.json
would then be placed inside /etc/cas/services
:
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "https://app.example.org",
"name" : "ApplicationName",
"id" : 1001,
"evaluationOrder" : 10
}
A robust CAS deployment requires the presence and configuration of an internal database that is responsible for keeping track of tickets issued by CAS. CAS itself comes by default with a memory-based node-specific cache that is often more than sufficient for smaller deployments or certain variations of a clustered deployment. Just like the service management facility, large variety of databases and storage options are supported by CAS under the facade of a Ticket Registry.
In this tutorial, we are going to configure CAS to use a Hazelcast Ticket Registry with the assumption that our deployment is going to be deployed in an AWS-sponsored environment. Hazelcast Ticket Registry is often a decent choice when deploying CAS in a cluster and can take advantage of AWS’s native support for Hazelcast in order to read node metadata properly and locate other CAS nodes in the same cluster in order to present a common, global and shared ticket registry. This is an ideal choice that requires very little manual work and/or troubleshooting, comparing to using options such as Multicast or manually noting down the address and location of each CAS server in the cluster.
First, ensure you have declared the appropriate module/intention in the build:
implementation "org.apereo.cas:cas-server-support-hazelcast-ticket-registry:${casServerVersion}"
Next, the AWS-specific configuration of Hazelcast would go into our cas.properties
:
cas.ticket.registry.hazelcast.cluster.discovery.enabled=true
cas.ticket.registry.hazelcast.cluster.discovery.aws.access-key=...
cas.ticket.registry.hazelcast.cluster.discovery.aws.secret-key=...
cas.ticket.registry.hazelcast.cluster.discovery.aws.region=us-east-1
cas.ticket.registry.hazelcast.cluster.discovery.aws.security-group-name=...
# cas.ticket.registry.hazelcast.cluster.discovery.aws.tag-key=
# cas.ticket.registry.hazelcast.cluster.discovery.aws.tag-value=
That should do it.
Of course, if you are working on a more modest CAS deployment in an environment that is more or less owned by you and you prefer more explicit control over CAS node registrations in your cluster, the following settings would be more ideal:
# cas.ticket.registry.hazelcast.cluster.instance-name=localhost
# cas.ticket.registry.hazelcast.cluster.port=5701
# cas.ticket.registry.hazelcast.cluster.port-auto-increment=true
cas.ticket.registry.hazelcast.cluster.members=123.321.123.321,223.621.123.521,...
As a rather common use case, the majority of CAS deployments that intend to turn on multifactor authentication support tend to do so via Duo Security. We will be going through the same exercise here where we let CAS trigger Duo Security for users who belong to the mfa-eligible
group, indicated by the memberOf
attribute on the LDAP user account.
First, ensure you have declared the appropriate module/intention in the build:
implementation "org.apereo.cas:cas-server-support-duo:${casServerVersion}"
Then, put specific Duo Security settings in cas.properties
. Things such as the secret key, integration key, etc which should be provided by your Duo Security subscription:
cas.authn.mfa.duo[0].duo-secret-key=
cas.authn.mfa.duo[0].duo-integration-key=
cas.authn.mfa.duo[0].duo-api-host=
# cas.authn.mfa.duo[0].duo-application-key=
At this point, we have enabled Duo Security and we just need to find a way to instruct CAS to route the authentication flow over to Duo Security in the appropriate condition. Our task here is to build a special condition that activates multifactor authentication if any of the values assigned to the attribute memberOf
contain the value mfa-eligible
. This condition is placed in the cas.properties
file:
cas.authn.mfa.triggers.principal.global-principal-attribute-name-triggers=memberOf
cas.authn.mfa.triggers.principal.global-principal-attribute-value-regex=mfa-eligible
If the above condition holds true and CAS is to route to a multifactor authentication flow, that would obviously be one supported and provided by Duo Security since that’s the only provider that is currently configured to CAS.
Many CAS deployments rely on the /status
endpoint for monitoring the health and activity of the CAS deployment. This endpoint is typically secured via an IP address, allowing external monitoring tools and load balancers to reach the endpoint and parse the output. In this quick exercise, we are going to accomplish that task, allowing the status
endpoint to be available over HTTP to localhost
.
First, ensure you have declared the appropriate module/intention in the build:
implementation "org.apereo.cas:cas-server-support-monitor:${casServerVersion}"
To enable and expose the status
endpoint, the following settings should come in handy:
management.endpoints.web.base-path=/actuator
management.endpoints.web.exposure.include=status
management.endpoint.status.enabled=true
cas.monitor.endpoints.endpoint.status.access=IP_ADDRESS
cas.monitor.endpoints.endpoint.status.required-ip-addresses=127.0.0.1
Remember that the default path for endpoints exposed over the web is at /actuator
, such as /actuator/status
.
The build/libs
directory contains the results of the overlay process. Since I have not actually customized and overlaid anything yet, all configuration files simply match their default and are packaged as such. As an example, let’s grab the default message bundle and change the text associated with screen.welcome.instructions
.
build
directory. The changesets will be cleaned out and set back to defaults every time you do a build. Follow the overlay process to avoid surprises.
First, I will need to move the file to my project directory so that during the overlay process Gradle can use that instead of what is provided by default.
Here we go:
./gradlew getResource -PresourceName=messages.properties
Then I’ll leave everything in that file alone, except the line I want to change.
...
screen.welcome.instructions=Speak friend and enter.
...
Then I’ll package things up as usual.
./gradlew clean build
If I explode
the built web application again and look at build/cas/WEB-INF/classes/messages.properties
after the build, I should see that the overlay process has picked and overlaid onto the default my version of the file.
In order to modify the CAS HTML views, each file first needs to be brought over into the overlay. You can use the ./gradlew listTemplateViews
command to see what HTML views are available for customizations. Once chosen, simply use ./gradlew getResource -PresourceName=footer.html
to bring the view into your overlay. Once you have the footer.html
brought into the overlay, you can simply modify the file at src/main/resources/templates/fragments/footer.html
, and then repackage and run the build as usual.
You have a number of options when it comes to deploying the final cas.war
file. The easiest approach would be to simply use the ./gradlew run
command and have the overlay be deployed inside an embedded container. By default, the CAS web application expects to run on the secure port 8443
which requires that you create a keystore file at /etc/cas/
named thekeystore
.
Using the embedded Apache Tomcat container provided by CAS automatically is the recommended approach in almost all cases (The embedded bit; not the Apache Tomcat bit) as the container configuration is entirely automated by CAS and its version is guaranteed to be compatible with the running CAS deployment. Furthermore, updates and maintenance of the servlet container are handled at the CAS project level where you as the adopter are only tasked with making sure your deployment is running the latest available release to take advantage of such updates.
If you wish to run CAS via the embedded Apache Tomcat container behind a proxy or load balancer and have that entity terminate SSL, you will need to open up a communication channel between the proxy and CAS such that (as an example):
The above task list translates to the following properties expected to be found in your cas.properties
:
server.port=8080
server.ssl.enabled=false
cas.server.tomcat.http.enabled=false
cas.server.tomcat.http-proxy.enabled=true
cas.server.tomcat.http-proxy.secure=true
cas.server.tomcat.http-proxy.scheme=https
The overlay embraces the Jib Gradle Plugin to provide easy-to-use out-of-the-box tooling for building CAS docker images. Jib is an open-source Java containerizer from Google that handles all the steps of packaging CAS into a container image. It does not require you to write a Dockerfile
and it is directly integrated into the overlay.
Building a CAS docker image via jib is as simple as:
./gradlew build jibDockerBuild
If you prefer a more traditional approach, there is always:
./gradlew build
docker-compose build
You may also build Docker images using the Spring Boot Gradle plugin.
If you have questions about the contents and the topic of this blog post, or if you need additional guidance and support, feel free to send us a note and ask about consulting and support services.
It’s important that you start off simple and make changes one step at a time. Once you have a functional environment, you can gradually and slowly add customizations to move files around.
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.
Monday-Friday
9am-6pm, Central European Time
7am-1pm, U.S. Eastern Time
Monday-Friday
9am-6pm, Central European Time