How to Proxy Pass REMOTE_USER: write your own apache module
Monday, March 22nd, 2010The problem:
I was using mod_auth_kerb to authenticate and ProxyPass to pass off the request to another server. I’m trying to support Kerberos Authentication but split the infrastructure into a proxy/app tiering using ProxyPass because I needed the ProxyPassReverseCookieDomain directive. Problem is I need to pass the user that had been authenticated along with the ProxyPass (ie. the value of REMOTE_USER) and found no configs to let me do that with mod_auth_kerb and ProxyPass.
What I tried:
I found a bunch of pages that referenced using a lookahead (LA-U:REMOTE_USER) to get the value of REMOTE_USER. Take that value and set an environment variable. Then use the env var to set a header, say, X-Forwarded-User. This didn’t seem quite right since this was being implemented at the rewrite stage (pre authentication, hence the lookahead’s subrequest) and spawned the overhead of another subrequest to get the initial value. I tried all kinds of permutations of some rewrite configs that looked something like this:
RewriteCond %{LA-U:REMOTE_USER} (.+)
RewriteRule .* – [E=RU:%1]
RequestHeader set X_REMOTE_USER %{RU}ehttp://n2.nabble.com/SSO-with-SSPI-and-SSL-LA-U-REMOTE-USER-always-null-td4086748.html et al.
In spite of the “not quite right” of the subrequest to env var to header I always got a value of (null) back from the lookahead. So this never even worked in combination with mod_auth_kerb (I’ve been told it does with basic auth or with mod_auth_kerb + RewriteRule [P]). Further it seemed inefficient to do all this subrequest to env to header stuff. I figured the most efficient thing to do (relative to processing the requests) would be to write a simple apache module that was in the module chain after auth but before proxy. Turns out that it didn’t take too long to do either.
The Solution:
I started with a tutorial at threebit.net where I just wanted to compile an apache module and insert it into the module chain. This worked like a champ (Thanks Kevin!) and I was logging to my error_log via stderr in no time at all. After reading though some apache code I figured out that r->user was the variable that mod_auth_kerb was populating the authenticated user to and that the ap_hook_handler method was inserting this module into the chain after proxypass. This location in the module chain was a problem because when I turn on proxypass the request was being proxied before the module was being executed. After a bit more docs and code reading I found ap_hook_fixups, which is in a stage between the auth and proxy modules. So that diff would look something like this:
< ap_hook_handler(mod_tut1_method_handler, NULL, NULL, APR_HOOK_LAST);
> ap_hook_fixups(mod_tut1_method_handler, NULL, NULL, APR_HOOK_LAST);
Finally, the method_hander’s code was changed from the stderr functionality to these two lines to use the r->user variable:
apr_table_set(r->headers_in, “X-Forwarded-User”, r->user);
apr_table_unset(r->headers_in, “Authorization”);
This sets the X-Forwarded-User header with the user the proxy has authenticated and strips out the Authorization header to be sure that your not passing any basic auth information (passwords in clear text!) from server to server.
I don’t have a complete set of code anywhere for you to download at this point, though, hopefully there’s enough here that all you’d have to do is swap a few pieces of code out, compile it (I had to update the automake stuff on the tutorial cuz it’s kinda old) and install it according to the tutorial’s directions.
Words of Warning:
1. Secure your app!
If you open your app up to accept X-Forwarded-User and trust that header as a source of an already authenticated user you must make sure that the only host that can pass that header to your app is your proxy! It would not be hard to install this custom module elsewhere (or use the lookahead stuff), slap basic auth on it and pass the header to your app completely ignoring your authoritative authentication infrastructure.
2. This will be applied to every request on your proxy.
There is nothing in this module that will only apply this to a specific vhost or anything. Every request that your proxy processes will get your custom header.
Future?
A nice addition to this would to let you configure the header name in your vhost config (ProxyUserHeader “X-Custom-Header-Name”) or even to submit a patch to mod_proxy so it’s not a separate module but built into mod_proxy (ProxyPassUserHeader “X-Custom-Header-Name”). Seems intriguing to do a bit more with it.