Tuesday, April 06, 2010
Client certificates in Apache
I've been spending an inordinate amount of time playing around with Apache, starting with
mod_lua
, which lead me
to reconfigure both Apache 2.0.52 (which came installed by default) and
Apache 2.3.5 (compiled from source, because mod_lua
is only
available for Apache 2.3) so they could run at the same time. This lead to
using IPv6 because I
have almost two dozen “sites” running locally (and as I've found, it's
just as easy to use IPv6 addresses as it is IP addresses, although the DNS PTR
records get
a little silly).
This in turn lead to installing more secure sites locally, because I can (using TinyCA makes it trivial actually), and this lead to a revamp of my secure site (note: the link takes you to an unsecure page—the actual secure site uses a certificate signed by my “certificate authority” which means you'll get a warning which can be avoided by installing the certificate from the unsecure site). And from there, I learned a bit more about authenticating with client certificates. Specifically, isolating certain pages to just individual users.
So, to configure client side certificates, you need to create a client certificate (easy with TinyCA as it's an option when signing a request) and install it in the browser. You then need to install the certificate authority certificate so that Apache can use it to authenticate against the client certificate (um … yeah). In the Apache configuration file, just add:
SSLCACertificateFile /path/to/ca.crt
Then add the appropriate mod_ssl
options to the secure site (client-side authentication only works with
secure connections). For example, here's my configuration:
<VirtualHost 66.252.224.242:443> ServerName secure.conman.org DocumentRoot /home/spc/web/sites/secure.conman.org/s-htdocs # ... <Directory /home/spc/web/sites/secure.conman.org/s-htdocs/library> SSLRequireSSL SSLRequire %{SSL_CLIENT_S_DN_O} eq "Conman Laboratories" \ and %{SSL_CLIENT_S_DN_OU} eq Clients" SSLVerifyClient require SSLVerifyDepth 5 </Directory> </VirtualHost>
And in order to protect a single file with more stringent controls (and here for example, is my bookmarks file):
<VirtualHost 66.252.224.242:443> # ... <Location /library/bookmarks.html> SSLRequireSSL SSLRequire %{SSL_CLIENT_S_DN_O} eq "Conman Laboratories" \ and %{SSL_CLIENT_S_DN_CN} eq "Sean Conner" SSLVerifyClient require SSLVerifyDepth 5 </Location> </VirtualHost>
The <Files>
directive in Apache didn't work—I
suspect because the <Directory>
directive is processed
first and it allows anybody from the unit “Clients” access and thus any
<Files>
directives are ignored, whereas
<Location>
directives are processed before
<Directory>
directives, and thus anyone not me
is denied access to my bookmarks.
Now, I just need to figure out what to do about some recent updates to Apache, since I have some “old/existing clients” to support (namely, Firefox 2 on my Mac, which I can't upgrade because I'm stuck at 10.3.9 on the system, because the DVD player is borked … )
IF IT AIN'T BROKE DON'T FIX IT!!!!!!!!!
Sigh.
I can fix the client certificate
issue if I install the latest Apache
2.2, which has the SSLInsecureRenegotiation
option, but that requires OpenSSL 0.9.8m or higher (and all this
crap because of a small
bug in OpenSSL). So, before mucking with my primary server, I decide to
test this all out on my home computer (running the same distribution of
Linux as my server).
Well, I notice that OpenSSL just came out with verion 1.0.0, so I decide
to snag that version. Download, config
(what? No
configure
still?), make
and make
install
, watch it go into the wrong location (XXXXXX I wanted it in /usr/local/lib/
no /usr/local/openssl/lib
!), rerun config
with other options and get it where I want it.
Okay.
And hey, while I'm here, might as well download the latest OpenSSH and get that
working. I nuke the existing OpenSSH installtion (yum remove
openssh
) since I won't need it, and start the configure
,
make
and make install
, but the
configure
script bitches about the version of zlib
installed
(XXXX! I know RedHat is conservative about using the
latest and greatest, but come on! It's been five years since
version 1.2.3 came out! Sheesh!) so before I can continue, I must do the
download, configure
, make
and make
install
dance for zlib
. Once that is out of
the way …
checking OpenSSL header version... 1000000f (OpenSSL 1.0.0 29 Mar 2010) checking OpenSSL library version... 90701f (OpenSSL 0.9.7a Feb 19 2003) checking whether OpenSSL's headers match the library... no configure: error: Your OpenSSL headers do not match your library. Check config.log for details. If you are sure your installation is consistent, you can disable the check by running "./configure --without-openssl-header-check". Also see contrib/findssl.sh for help identifying header/library mismatches.
Oh XXXXXX XXXX …
IT'S IN /usr/local/lib
YOU USELESS
SCRIPT!
But alas, no amount of options or environment variables work. And no,
while I might be willing to debug mod_lua
, I am not about to debug a 31,000
line shell script. Might as well reinstall the OpenSSH package …
[root]lucy:~>yum install openssh Setting up Install Process Setting up repositories Segmentation fault (core dumped)
Um … what?
[root]lucy:~>yum install openssh Setting up Install Process Setting up repositories Segmentation fault (core dumped)
What the XXXX?
Oh please oh please oh please don't tell me that yum
just
assumes you have OpenSSH installed …
Okay, where is this program dying?
[root]lucy:/tmp>gdb /usr/bin/yum core.3783 GNU gdb Red Hat Linux (6.3.0.0-1.132.EL4rh) Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-redhat-linux-gnu"..."/usr/bin/yum": not in executable format: File format not recognized Core was generated by /usr/bin/python /usr/bin/yum search zlib'. Program terminated with signal 11, Segmentation fault. #0 0x007ff3a3 in ?? () (gdb)
Oh … it's Python.
Um ‥ wait a second …
It's … Python! It's a script!
WHAT THE XXXX?
What did I do to cause the Python interpreter to crash?
Aaaaaaaaaaaaaaaaaaaaaaaaaah!
Okay, I managed to find some RPMs of OpenSSH to install. That didn't fix
yum
.
Okay, don't panic.
Obviously, it's something I've done that caused this.
The only things I've done is to install up libraries in
/usr/local/lib
.
Okay, keep any programs from loading up anything from
/usr/local/lib
. That's easy enough—I justed edited
/etc/ld.so.conf
to remove that directory, and ran
ldconfig
. Try it again.
Okay, yum
works!
And through a process of elimination, I found the
culprit—zlib
! Apparently, the version of Python I have
doesn't like zlib 1.2.4
.
Sheesh!
Okay, yes, I bring ths upon myself for not running the latest and greatest. I don't update continously because that way lies madness—things just breaking (in fact, the last thing I did upgrade, which was OpenSSL on my webserver the other day, broke functionality I was using, which prompted this whole mess in the first place!). At least I was able to back out the changes I made, but I have to keep this in mind:
IF IT AIN'T BROKE DON'T FIX IT!!!!!
Write Apache modules quickly in Lua
I really like mod_lua
, even in its alpha state. In less
than five minutes I had a webpage that would display a different quote each
time it was referenced. I was able to modify the Lua based qotd, changing:
QUOTESFILE = "/home/spc/quotes/quotes.txt" quotes = {} do local eoln = "\r\n" local f = io.open(QUOTESFILE,"r") local s = "" for line in f:lines() do if line == "" then -- each quote is separated by a blank link if #s < 512 then table.insert(quotes,s) end s = "" else s = s .. line .. eoln end end f:close() end math.randomseek(os.time()) function main(socket) socket:write(quotes[math.random(#quotes)]) end
to
QUOTESFILE = "/home/spc/quotes/quotes.txt" quotes = {} do local eoln = "\r\n" local f = io.open(QUOTESFILE,"r") local s = "" for line in f:lines() do if line == "" then -- each quote is separated by a blank link if #s < 512 then table.insert(quotes,s) end s = "" else s = s .. line .. eoln end end f:close() end math.randomseek(os.time()) function handler(r) r.content_type = "text/plain" r:puts(quotes[math.random(#quotes)]) end
(you can see, it didn't take much), and adding
LuaMapHandler /quote.html /home/spc/web/lua/lib/quote.lua
to the site configuration (what you don't see is the only other line you
need, LuaRoot
), reload Apache and I now have webpage backed by
Lua.
And from there, it isn't much to add some HTML to the output, but it should be clear that adding Apache modules in Lua isn't that hard.
What did take me by surprise is that there's no real way to do the heavy
initialization just once. That bit of reading in the quotes file? It's
actually done for every request—mod_lua
just compiles the
code and keeps the compiled version cached and for each request, runs the
compiled code. It'd be nice if there was a way to do some persistent
initialization once (a feature I use in the current mod_litbook
),
but as written, mod_lua
doesn't have support for that.
I also haven't see any action on my bug report—not a good sign.
I'm wondering if I might not have to pick up the ball
mod_lua
and run with it …