Disclaimer: this project is a Proof of Concept which connects the WebSEAL DSC client interface with a Redis backend. I don’t recommend doing this in a live environment because it may render your ISAM unstable.

Introduction

In traditional IBM Security Access Manager deployments, one would have one or multiple Distributed Session Cache (DSC) servers, but only 1 is ever truly active while the others remain hot-standby. In an elastic environment, this becomes a nuisance because you’d still need 2 or more separate DSC appliances to support distributed sessions.

However, more modern technologies exist, one of which is Redis. Redis is basically a large in-memory data structure store which can scale horizontally, unlike the DSC.

Using Redis, the limit of 4 DSC servers is removed and thus allows the ISAM environment to scale accordingly.

You could choose another backend as well, I took Redis since it’s readily available and is a nice example of the concept.

The idea originated from a discussion which I had with IBM during the IBM MasterSkills University in Munich 2019, which promptly triggered me to reimplement this on a Redis using Python (links below).

Prerequisites

IBM Security Access Manager

My setup is a simple IBM Security Access Manager V9.0.7 Virtual Appliance which has a multi-node cluster running on it. It has its own policy server and hosts several reverse proxies. The fact that the DSC is active, doesn’t really mean anything, we’ll point the reverse proxies to our own interface anyway.

Redis

My Redis is an out-of-the-box application installed on a CentOS 7, following is the installation procedure:

yum install epel-release yum-utils -y;
yum install http://rpms.remirepo.net/enterprise/remi-release-7.rpm -y;
yum-config-manager --enable remi -y;
yum install redis -y;

Next you’ll need to configure it to listen on a non-localhost socket:

vi /etc/redis.conf;
# add the bind address for your VM
bind 192.168.254.194

Save the file and start Redis, simply execute:

systemctl start redis;
systemctl enable redis;

Open the firewall as well when you’re on a VM:

firewall-cmd --permanent --add-port=6379/tcp;
firewall-cmd --reload;

The project

The actual HTTP interface (shim) is provided via Python, you can find it on GitHub (here: https://github.com/dreezey/dsc-on-redis), I used Python 3.7 with the following additional modules:

pip install redis flask requests

I just use the built-in web server from PyCharm when you run the project, which binds to 0.0.0.0 (all interfaces) on port 5000, so make sure that port is open as well. A docker image is also provided which makes this even easier.

The protocol

WebSEAL uses SOAP calls via HTTP to communicate with the DSC, here’s how I decoded the protocol:

  • I have made the DSC on ISAM available to external clients and made sure it was not using TLS.
  • Next up, I set up packet tracing for the external DSC port on the appliance and started generating traffic on the reverse proxy, after some time, you’ll capture most of the operations and their responses.
  • Then I used Wireshark to inspect the packet capture and look into each request / response from the vanilla DSC, and started implementing each function they way I assume it works.

I have documented the protocol roughly, which is available in the GitHub repository as well, but here are some details:

  • Join Replica Set -> “joinReplicaSet”
    Makes the DSC client join a replica-set.
  • Ping -> “ping”
    Pings the DSC whether it’s still alive.
  • Get Updates -> “getUpdates”
    Not entirely sure what this does, but if you reply immediately it will DoS the service :) so it takes the “response-by” parameter into consideration.
  • Get Realm Name -> “getRealmName”
    Again, very generic operation and static response
  • Replica Shutdown -> “replicaShutdown”
    Called when the reverse proxy is being shut down.
  • Create Session -> “createSession”
    Self-explanatory, creates a session
  • Idle Timeout -> “idleTimeout”
    Not entirely sure what it’s supposed to do, but I make it set the credential attribute “com.tivoli.am.eb.is-inactive” for the given session to true, this is later then used in “getSession” as well.
  • Get Session -> “getSession”
    Attempt to fetch a session by session ID, when the “com.tivoli.am.eb.is-inactive” is set to True, it returns an empty session and clears it from Redis as well, otherwise it returns all associated credential attributes.
  • Terminate Session -> “terminateSession”
    Self-explanatory, it gets called when you perform “pksmlogout” or sometimes WebSEAL triggers it, not entirely sure either.

I assume there’s one more which would be something like “updateSession” when the authenticated session performs a step-up, but I must investigate this more in detail.

Configuration

Configuration is simple, make sure your Python application is running and go to the reverse proxy of choice, configure it to support DSC:

[session]
dsess-enabled = yes
standard-junction-replica-set = default

[replica-sets]
replica-set = default

[dsess]
dsess-sess-id-pool-size = 125
dsess-cluster-name = dsess

[dsess-cluster]
server = 9,http://172.20.12.217:5000/DSess/services/DSess
response-by = 60
handle-pool-size = 10
handle-idle-timeout = 30
timeout = 30
max-wait-time = 0
ssl-keyfile =
ssl-keyfile-stash =
ssl-keyfile-label =
load-balance = yes

Save and deploy the changes and restart the reverse proxy.

You may want to set “load-balance” to “yes” or use another load balancer to the shims, there’s no more master concept so all hosts can process the request accordingly (this should increase performance as well).

The shim

Startup

When you have “debug_mode” enabled in the project, you’ll notice the first set of log messages coming in, most importantly the WebSEAL will join the replica-set, I have persisted this in Redis as well, although it doesn’t seem necessary, you can verify it in Redis using:

redis-cli -h 192.168.254.194 llen default:replicas
#(integer) 1

And to get each instance separately:

redis-cli -h 192.168.254.194 lindex default:replicas 0
#"dsc2-webseald-testisam"

Where “default” would be the name of your replica set that has been defined in the Reverse Proxy.

Ping

While active, the WebSEAL will send pings. I just reply to these statically because I don’t really see a way to correlate the WebSEAL (except for Client IP) -> ideally, there’d be a cleanup action after a WebSEAL has not sent a ping for a certain amount of time, but that’s for another time.

Session handling

Create Session

Next up, point your browser the reverse proxy which has been configured to make use of the shim. If you’ve previously done this, you’ll notice the webserver registering an additional request, this is the “getSession” function. Because the session probably doesn’t exist in Redis, you’re prompted for login. If something were to be wrong with the response from the shim, WebSEAL would show “Service Unavailable” message.

When you perform a login, the shim will consume the “Session ID” and each credential attribute, e.g.:

  • com.tivoli.am.eb.credential
  • com.tivoli.am.eb.auth-type

And will store it separately in Redis using the following concatenation:

Session ID + ‘:’ + attribute name

The value which is stored is simply the XML data of the attribute.

Beside adding all the credential attributes in Redis, it will also update the “Replica Set” sessions with the new Session ID.

Idle Session

In order to speed up troubleshooting, I have set my “inactive-timeout” to “10”, which means that after 10 seconds of inactivity, WebSEAL will notify the DSC that a session has become inactive. This message is intercepted by the shim as well, and will do one specific action:

  1. It takes the session id
  2. It will collect the credential attribute “com.tivoli.am.eb.is-inactive” and set its value to “true

Presumably, this credential attribute is then used in the “Get Session” operation to verify whether the session has been marked as idle.

Get Session

At some point, you’ll re-request the page and WebSEAL may request the information from the DSC again. In my case, the session has been idle for more then 10 seconds, and I see a “getSession” request coming along.

In my implementation, I first verify whether the session is found in the replica set. If the Session ID is found in the replica set, I verify the credential attribute “com.tivoli.am.eb.is-inactive” (which was set in the “idle session” operation) and will act accordingly:

  • If the value is set to “True”, I will perform a clean-up of the session, which is detailed in “Delete Session”.
  • If the value is set to “False”, I return all associated credential attributes.

Delete Session

In case the retrieved session was marked as inactive, I clean up all credential attributes, and will also remove the Session ID from the replica set.

I’ve also seen WebSEAL send a “terminateSession” request to the endpoint, which essentially does the same, but I couldn’t determine when WebSEAL triggers this action.

Shutdown

When the WebSEAL is stopped, it sends a “replicaShutdown” and I remove it from the replica set in Redis.

Security Considerations

Authentication

When externalizing the DSC service, it’s important to configure some form of authentication, WebSEAL supports either Basic Authentication or Mutual TLS Authentication. For my proof of concept, I haven’t implemented either.

Tamper Protection

There’s no tamper protection either when stored in Redis, as such, anyone with access to the Redis can essentially modify the data to spoof any user in the security domain. A “simple” solution could be that the ISAM Reverse Proxies modify the of the session from XML elements to a JWT.

Conclusion

In conclusion, I’ve had a lot of fun reverse engineering the protocol and implementing it in Python to interface the WebSEAL with a Redis. This deployment strategy makes IBM Security Access Manager Docker/OpenShift deployments more sensible in the fact that they can now scale accordingly, whereas otherwise you’d be limited to only 1 active DSC server.

In retrospect, it would’ve been nice if the protocol would in fact be described, I’ve made some gross assumptions on some points and I’m not entirely sure whether these are correct, e.g.: does “idleTimeout” also modify the credential attribute “com.tivoli.am.eb.is-inactive” in vanilla DSC, are the values in “currentKey” and “oldKey” always “iuhew9873hkediuyer987”, … ?

I haven’t touched the Redis Cluster (or multiple Redis servers) either, this may be interesting as well, I assume that key routing based on the Session ID contains enough entropy for distribution. It would also be interesting to see a performance comparison between the two and verify where a Redis becomes more efficient than the vanilla DSC.

If you’re interested in more IBM Security Access Manager on Docker / OpenShift, I suggest catching up on Jon Harry’s work: https://github.com/jonpharry/isamdocker. It’d be interesting to see this implementation paired with an OpenShift deployment.

If you like the stuff I did there, consider liking the article and following us for future content. If you have any questions be sure to let me know as well!

Happy hacking!

Dries Eestermans

Security Operations

A security enthusiast dedicated to access management. Loves hacking around in products and developing new ideas.