Arbitrary Authentication with an nginx Reverse Proxy
Mon Sep 22 18:33:37 EDT 2014
- Setting up nginx in Front of a Domino Server
- Adding Load Balancing to the nginx Setup
- Arbitrary Authentication with an nginx Reverse Proxy
- Domino and SSL: Come with Me If You Want to Live
I had intended that this next part of my nginx thread would cover GeoIP, but that will have to wait: a comment by Tinus Riyanto on my previous post sent my thoughts aflame. Specifically, the question was whether or not you can use nginx for authentication and then pass that value along to Domino, and the answer is yes. One of the aforementioned WebSphere connector headers is $WSRU
- Domino will accept the value of this header as the authenticated username, no password required (it will also tack the pseudo-group "-WebPreAuthenticated-" onto the names list for identification).
Basic Use
So one way to do this would be to hard-code in a value - you could disallow Anonymous access but treat all traffic from nginx as "approved" by giving it some other username, like:
proxy_set_header $WSRU "CN=Web User/O=SomeOrg";
Which would get you something, I suppose, but not much. What you'd really want would be to base this on some external variable, such as the user that nginx currently thinks is accessing it. An extremely naive way to do that would be to just set the line like this:
proxy_set_header $WSRU $remote_user;
Because nginx doesn't actually do any authentication by default, what this will do will be to authenticate with Domino as whatever name the user just happens to toss in the HTTP Basic authentication. So... never do that. However, nginx can do authentication, with the most straightforward mechanism being similar to Apache's method. There's a tutorial here on a basic setup:
http://www.howtoforge.com/basic-http-authentication-with-nginx
With such a config, you could make a password file where the usernames match something understandable to Domino and the password is whatever you want, and then use the $remote_user
name to pass it along. You could expand this to use a different back-end, such as LDAP, and no doubt the options continue from there.
Programmatic Use
What had me most interested is the possibility of replacing the DSAPI login filter I wrote years ago, which is still in use and always feels rickety. The way that authentication works is that I set a cookie containing a BASE64-encoded and XOR-key-encrypted version of the username on the XPages side and then the C code looks for that and, if present, sets that as the user for the HTTP request. This is exactly the sort of thing this header could be used for.
One of the common nginx modules (and one which is included in the nginx-extras
package on Ubuntu) adds the ability to embed Lua code into nginx. If you're not familiar with it, Lua is a programming language primarily used for this sort of embedding. It's particularly common in games, and anyone who played WoW will recognize its error messages from misbehaved addons. But it fits just as well here: I want to run a small bit of code in the context of the nginx request. I won't post all of the code yet because I'm not confident it's particularly efficient, but the modification to the nginx site config document is enlightening.
First, I set up a directory for holding Lua scripts - normally, it shouldn't go in /etc, but I was in a hurry. This goes at the top of the nginx site doc:
lua_package_path "/etc/nginx/lua/?.lua;;";
Once I did that, I used a function from a script I wrote to set an nginx variable on the request to the decoded version of the username in the location /
block:
set_by_lua $lua_user ' local auth = require "raidomatic.auth" return getRaidomaticAuthName() ';
Once you have that, you can use the variable just like any of the build-in ones. And thus:
proxy_set_header $WSRU $lua_user;
With that set, I've implemented my DSAPI login on the nginx site and I'm free to remove it from Domino. As a side benefit, I now have the username available for SSO when I want to include other app servers behind nginx as well (it works if you pass it LDAP-style comma-delimited names, to make integration easier).
Another Potential Use: OAuth
While doing this, I thought of another perfect use for this kind of thing: REST API access. When writing a REST API, you don't generally want to use session authentication - you could, by having users POST to ?login and then using that cookie, but that's ungainly and not in-line with the rest of the world. You could also use Basic authentication, which works just fine - Domino seems to let you use Basic auth even when you've also enabled session auth, so it's okay. But the real way is to use OAuth. Along this line, Tim Tripcony had written oauth4domino.
In his implementation, you get a new session variable - sessionFromAuthToken
- that represents the user matching the active OAuth token. Using this reverse-proxy header instead, you could inspect the request for an authentication token, access a local-only URL on the Domino server to convert the token to a username (say, a view URL where the form just displays the user and expiration date), and then pass that username (if valid and non-expired) along to Domino.
With such a setup, you wouldn't need sessionFromAuthToken
anymore: the normal session
variable would be the active user and the app will act the same way no matter how the user was authenticated. Moreover, this would apply to non-XSP artifacts as well and should work with reader/author fields... and can work all the way back to R6.
Now, I haven't actually done any of this, but the point is one could.
So add this onto the pile of reasons why you should put a proxy server (nginx or otherwise) in front of Domino. The improvements to server and app structure you can make continue to surprise me.
Nathan T. Freeman - Mon Sep 22 19:21:15 EDT 2014
"...access a local-only URL on the Domino server to convert the token to a username..." Why not just an in-memory map of tokens to strings that you access with an HTTPService? The OpenNTF API already has an excellent sample implementation of this.
Jesse Gallagher - Mon Sep 22 19:26:19 EDT 2014
Yeah, that's a better way to do it - it would be crucial on both sides (Domino and Lua) to make it as efficient as possible, since it'll run for every HTTP request. Being at the HttpService level would likely be the fastest way to return data from the Domino side. A cache on the nginx side would take care of the rest, making it so that the majority of requests don't need to check against Domino at all.
Michael Bourak - Tue Sep 23 10:40:00 EDT 2014
Very Interesting... the oAuth stuff is very promissing. What may miss is the ability to make this "virtual" person member of certain "virtual groups" (I suspect we can do it in DSAPI)
Jesse Gallagher - Tue Sep 23 10:47:04 EDT 2014
True - you can't specify groups in the header param (I forget if you can in DSAPI). However, you can match the user based on globbing - if you build usernames like "cn=Joe Schmoe,ou=SomeExtSource,o=External", then you could have ACL entries like "*/OU=SomeExtSource/O=External" or "*/External".
Michael Bourak - Tue Sep 23 12:15:35 EDT 2014
Agree... the only "missing" element with using */External etc... is that you can not use those in reader fields. We'd have to map this to a role for exemple, and put that role in reader field then.
PS : I think that with DSAPI you can list the groups which the user belongs to (and populate this list with any value you want). Not sure though
John Detterline - Mon Dec 18 15:30:57 EST 2017
Greetings! I'm late to the game, but we're working on a method to provide authentication for some Notes apps that are being converted to XPages. I finally managed to get the LDAP auth module for NGINX to work, but I'm at a loss for how to use LUA to get a Notes name to pass to Domino. Any advice you can give me would be appreciated!