January 22, 2006

Running TWiki under SELinux

Somehow I have apparently managed to get it running. No doubt I’ll forget a few things along the way. I’ll say two things up front. First, read my previous entry: make sure the partition TWiki sits on is not mounted with the nosuid option. Second, O’Reilly’s SELinux book turns out to be decent enough. Probably better than a lot of the other documentation you’ll find out there. It should be dated since it’s written around FC2 apparently, but in fact I’ve found it quite applicable where it was important.

Now. I’ve got a bonus hard step for you: I’m putting TWiki in /srv/www/virtual.host.name/twiki. twiki is not under DocumentRoot (DocumentRoot being /srv/www/virtual.host.name/root).

Extract TWiki to the aforementioned directory. I used the Dakar release, BTW. Here are some excerpts from my Apache configuration, for starters:

### TWiki
ScriptAlias /twiki/bin/ /srv/www/virtual.host.name/twiki/bin/
Alias /twiki/pub/ /srv/www/virtual.host.name/twiki/pub/
<Directory /srv/www/virtual.host.name/twiki>
    Options -Indexes
</Directory>
<Directory /srv/www/virtual.host.name/twiki/bin>
    Options +ExecCGI
    SetHandler cgi-script
</Directory>
<Directory ~ "^/srv/www/virtual.host.name/twiki/(data|templates|lib)">
    Deny from all
</Directory>

That isn’t the whole configuration, obviously, but it is the parts that I feel are important to getting TWiki working. (Note that this isn’t using mod_perl yet, but I hope it will be in the near future.) Next we need to… modify your SELinux policy! Yay custom modifications! You’ll need to stop ignoring those e-mails from yum that indicate selinux-policy-targeted has been updated. Make sure you have selinux-policy-targeted-sources installed. cd /etc/selinux/targeted/src/policy. From there, here are the files I created:

[root@host policy]# cat types/local.te
# /srv
type srv_t, file_type, root_dir_type, sysadmfile;
[root@host policy]# cat domains/program/local.te
# Apache access to /srv.
allow { httpd_t httpd_sys_script_t } srv_t:dir { search getattr };
[root@host policy]# cat file_contexts/local.fc
/srv(/.*)?                      system_u:object_r:srv_t

Also, you’ll need to touch file_contexts/program/local.fc. Then make reload and you should get your new policy. God willing this is the policy that will be used at next boot, but I definitely haven’t tested that.

Now that we’ve got that set up, we need to set some contexts up.

chcon -t srv_t /srv
chcon -R -t httpd_sys_content_t /srv/www
chcon -R -t httpd_sys_script_rw_t /srv/www/virtual.host.name/twiki
chcon -t httpd_sys_script_exec_t /srv/www/virtual.host.name/twiki/bin/*
chcon -t httpd_sys_script_rw_t /srv/www/virtual.host.name/twiki/bin/logos
chown -R apache /srv/www/virtual.host.name/twiki
chmod -R u+w /srv/www/virtual.host.name/twiki

For installation purposes, TWiki needs everything writable. (Of course, I’m quite sure it doesn’t, but I’m kind of following install directions here.) I’m also assuming that when you extracted TWiki everything in the bin directory got set executable; if it didn’t, chmod u+x bin/* now. (In particular a few scripts like viewauth didn’t get set executable, now that I think about it.) Now hit /twiki/bin/configure in a web browser. Configure TWiki. When done, follow whatever instructions it gives you, then:

chcon -R -t httpd_sys_content_ro_t /srv/www/virtual.host.name/twiki
chcon -R -t httpd_sys_content_rw_t /srv/www/virtual.host.name/twiki/{data,pub}
chcon -t httpd_sys_script_exec_t /srv/www/virtual.host.name/twiki/bin/*
chcon -t httpd_sys_script_ro_t /srv/www/virtual.host.name/twiki/bin/logos
chown -R root /srv/www/virtual.host.name/twiki
chmod -R og-w /srv/www/virtual.host.name/twiki
chown -R apache /srv/www/virtual.host.name/twiki/{data,pub}

I chowned everything back to root just because that’s who owns everything else in /srv/www/virtual.host.name. If you have some other user, feel free to use it. What’s important is that the apache user can write to the data and pub directories in TWiki’s directory.

A final note: use of httpd_sys_content_t above is probably pure whimsy. It could (and quite possibly should) be replaced with httpd_sys_script_ro_t I’m thinking. I’m just not positive. Read the documents referenced at the end of my last entry for more information on what they do, paying special attention to the value of httpd_unified on your system (getsebool httpd_unified).

January 20, 2006

SELinux, Apache, and CGI

Quick tip: I was getting errors such as

audit(1137791734.344:3): avc: denied { execute_no_trans } for pid=5346
comm="httpd" name="configure" dev=dm-2 ino=49385
scontext=user_u:system_r:httpd_t
tcontext=user_u:object_r:httpd_sys_script_exec_t tclass=file

audit(1137795025.582:4): avc: denied { execute_no_trans } for pid=5462
comm="httpd" name="configure" dev=dm-2 ino=49385
scontext=user_u:system_r:httpd_t
tcontext=system_u:object_r:httpd_sys_content_t tclass=file

in dmesg. I had some CGI scripts labeled first with httpd_sys_script_exec_t, then httpd_sys_content_t; I hoped one of these types would work, but neither did. I kept getting denied for execute_no_trans. But why was I doing execute_no_trans (execute without domain (type) transition) when I think I should have been attempting plain ol’ execute?

Enter nosuid. The partition was mounted nosuid. nosuid prevents SELinux domain transitions. (Thanks to Aleksandar Milivojevic for posting this bug to (I think it was) the Fedora SELinux list where Google found it, and of course to Colin Walters for the answer.) I removed nosuid from the partition and now my CGI scripts work.

For more documentation on all the Apache-related SELinux features, see httpd_selinux(8) on your Fedora system, and also Understanding and Customizing the Apache HTTP SELinux Policy.

January 18, 2006

Squid and sites that can’t be cached

I had a particular web application today that wasn’t working through the default RH Squid install, plus transparent proxying. Symptom:

  • Open browser (IE), open the given URL, get login page
  • Login
  • Close all browser windows (not logging out of the web application)
  • Open new browser window, open the given URL, get 500 server error

First I tried removing the objects from the cache. On this RH 8.0 box, squidclient was called just client:

[root@gateway root]# /usr/sbin/client -m PURGE -h 127.0.0.1 http://offending/url
HTTP/1.0 200 OK
Server: Squid/2.4.STABLE7
Mime-Version: 1.0
Date: Wed, 18 Jan 2006 23:53:13 GMT
Content-Length: 0
X-Cache: MISS from gateway
Proxy-Connection: close

[root@gateway root]#

This made the 500 go away, until someone else logged in. Then everyone (except the logged in person, I think) would get 500 errors again. So then I tried forcing Squid to go direct for the site in question. This didn’t work either.

Finally, I read the FAQ on not caching anything and used the no_cache directive with the ACL I created when trying always_direct. Then I had to make sure to clean out (using the above method) any cached pages from that site. Then I apparently had to do a service squid restart. Finally, the site seems to work normally.

Setting up SMTP authentication (and SSL) on Postfix

I’ve got a CentOS 4 system running Postfix. I need to add SMTP authentication. Fun part: I have to authenticate off of an Active Directory server.

There may be multiple ways to go about this:

  • Authenticate via LDAP
  • Authenticate via Kerberos
  • Authenticate via pam_smb_auth

Of course, I have reasons why (I think) all of these are unacceptable:

  • I don’t have a user/password to bind to the LDAP server with, and I have read in multiple places that AD doesn’t support anonymous binds. Plus the server doesn’t have an SSL certificate, apparently, and thus won’t talk LDAP-over-SSL to me (amusingly, the ldaps port accepts the connection and immediately disconnects).
  • Can’t do Kerberos because I have no host principle, and I don’t have the Administrator password for the domain so I can create one. Did I mention this has to be done by morning?
  • No pam_smb_auth because I can’t join the domain; see above, no Administrator password. Now, technically, this might not be a problem, I’m really not sure. (For example, smbclient can “authenticate” me just fine, even tough the machine I’m coming from isn’t joined to the domain.) How does pam_smb_auth work, anyway? I have the feeling it does NTLM, even when talking to an AD server.

SASL actually has an NTLM method itself, which I really didn’t look in to. No, instead I found… the IMAP (rimap) method. /etc/sysconfig/saslauthd:

# Directory in which to place saslauthd's listening socket, pid file, and so
# on.  This directory must already exist.
SOCKETDIR=/var/run/saslauthd
# Mechanism to use when checking passwords.  Run "saslauthd -v" to get a list
# of which mechanism your installation was compiled to use.
MECH=rimap
# Additional flags to pass to saslauthd on the command line.  See saslauthd(8)
# for the list of accepted flags.
FLAGS="-O my.imap.server.ip"

chkconfig saslauthd on; service saslauthd start. You can use testsaslauthd to make sure it authenticates you. Next, make /usr/lib/sasl2/smtpd.conf (comes with the Postfix RPM) look something like:

pwcheck_method: saslauthd
mech_list: plain login

The mech_list is actually the only option I had to add. You can only use methods that take the password in plaintext with saslauthd; hence plain and login.

Now hold on: we’re passing passwords in plaintext? That sounds bad. Let us at least use (offer) SSL (or TLS) on SMTP.

Create a certificate for your mail server. As root, cd && /usr/share/ssl/misc/CA -newca and answer the questions. This creates /root/demoCA. Try something like openssl req -newreq -nodes -out newreq.pem -keyout newkey.pem -days 730, and answer some more questions. Then try /usr/share/ssl/misc/CA -sign. This should create newcert.pem. install -o 0 -g 0 -m 750 /etc/postfix/tls && install -o 0 -g 0 -m 640 new{key,cert}.pem /etc/postfix/tls/ to install these files. You can delete newreq.pem as far as I know. (Note: I think there should be some slightly less arcane commands/scripts to create keys. For example, the suite that comes with OpenVPN is nice, though it might do a thing or two that is specific to OpenVPN. Does someone know of a nicer front end for running your own CA? Needs to be command line, since a lot of boxes don’t have—and don’t want—web.)

Finally, we can tell Postfix about TLS and SASL authentication. Here’s the end of my /etc/postfix/main.cf:

smtpd_tls_key_file = /etc/postfix/tls/host-key.pem
smtpd_tls_cert_file = /etc/postfix/tls/host-cert.pem
smtpd_tls_CAfile = /etc/postfix/tls/ca-cert.pem
smtpd_use_tls = yes
smtpd_sasl_auth_enable = yes
#smtpd_tls_auth_only = yes

Note that I’ve commented out smtpd_tls_auth_only there. If you uncomment that line, smtpd will only announce SMTP authentication if a connection using TLS. Note I say announce: I found out that smtpd still happily accepts AUTH LOGIN. Anyway, if you can, I’d turn this on. Really, I’d go through the TLS patch documentation (/usr/share/doc/postfix-*/TLS/, or on the web I’m sure) and read about how to make a separate SMTP port that requires SSL. Then make all your clients do that.

You can test SSL with something like openssl s_client -starttls smtp -connect localhost:25. You can then test SMTP by having a conversation something like:

220 your.host.name ESMTP Postfix
EHLO foo.com
250-your.host.name
250-PIPELINING
250-SIZE 140000000
250-VRFY
250-ETRN
250-STARTTLS
250-AUTH LOGIN PLAIN
250 8BITMIME
AUTH LOGIN
334 VXNlcm5hbWU6
aGFoYQ==
334 UGFzc3dvcmQ6
c3Vja2Vy
235 Authentication successful

Those two crazy strings I typed after AUTH LOGIN are just base64 encoded. You can make them with Perl:

[root@host ~]# perl -MMIME::Base64 -e 'print encode_base64("username")'
dXNlcm5hbWU=

By the way:

>>> decodestring("VXNlcm5hbWU6")
'Username:'
>>> decodestring("UGFzc3dvcmQ6")
'Password:'

All done. One thing I do not like is that authentication fails too quickly: there is no barrier to someone trying a whole bunch of passwords very quickly via SMTP authentication, as near as I can tell. One solution for this might be using something like pam_imap and pointing saslauthd at PAM.

I did refer to http://www.metaconsultancy.com/whitepapers/smtp.htm a bit, as well as the aforementioned TLS docs installed on the system. I also looked at an install of this I did a while back.

BTW, if you want to activate SSL on an AD server for LDAP (and maybe IMAP automatically?), http://support.microsoft.com/default.aspx?scid=kb;en-us;321051 looks useful.

January 14, 2006

I’m dumb (unit testing network code in Python)

Now, before you have a chance to jump in and say you’ve always suspected it, but you’ve been afraid to bring it up, and how glad you are that it was me not you that brought it up, let me tell you why I’m dumb.

Part of a Python application I’m writing needs to be able to sniff packets. Furthermore, I’m doing it directly with Linux’s PF_PACKET instead of libpcap (for reasons irrelevant here; if I remember I’ll cover this at the bottom). To unit test the sniffer component, I attempt to pass some UDP datagrams on the loopback interface and see if the sniffer picks them up.

So in my first stab in doing this, I went ahead and did it in threads. It took me a reasonably long time, even though I had a handle on the way (or at least a way) to do it. The result: testing a sniffer with threads. (P.S.: easy syntax highlighting with enscript: enscript --color -Epython -Whtml threadutils.py -o - | tidy -asxhtml -clean -o threads.xhtml. Supposedly other languages can be done this way too; for example, Perl.) I abstracted out the CooperativeThread class because I’ve created that class several times now, but I don’t think doing so made more code; quite the opposite, since I was able to use it for both the TrafficGenerator and SnifferThread classes.

But at around 180 lines (not counting comments and imports), compared to test a module that’s around 40 lines, it seemed way too big. I had been looking at Twisted earlier, looking at its reactors and its event-driven, generally single-threaded model. It wasn’t the first time I was exposed to the “why do multi-threading, just use select(2)” kind of philosophy, but I’ve always ignored it because I was sure threads were easier.

Finally, my point: I am dumb. No way in hell is it easier to use threads. Proof: the version that uses select(2) takes nearly a third as many lines of code (around 65 lines), was (in my estimation) faster to program, and runs about eight times as fast. No doubt it’ll be easier to debug, too.

The code in the single-threaded version is a little uglier, I think; I haven’t found a nice way to factor that select loop out. Still, I think it’s readable to anyone who’s familiar with sockets and select. I could probably use Twisted, reduce lines of code, and make it easier to understand… to anyone that is familiar with Twisted, which probably isn’t as big of a pool of people as the ones that can read code that just uses select directly. Plus, I’m not terribly fond of the idea of requiring the Twisted framework just for testing. If I end up using it elsewhere, then maybe I’ll go back and use it in this test.


A note on libpcap: it provides no way to determine whether a packet is being sent or received. There are platform- or interface-specific ways to tell, I suppose. On Ethernet, if the source MAC is your own, then I’d say it’s probably being sent (though I’m sure there is some case, maybe involving… bridging? that I’m not thinking of). On Linux, a few different interface types such as PPP use “cooked” mode, where the packet libpcap gives you is preceded by a faux header that includes information about whether a packet is being sent or received. In cooked mode, you get this faux header instead of the link header. (According to code comments, PPP doesn’t always pass a link header, or a correct link header, to the PF_PACKET socket.)

Now, how does it get this information for PPP? Why, the Linux PF_PACKET interface (the method by which libpcap reads packets on Linux) includes some result information that gives the “direction” of the packet (inbound or outbound). However, libpcap only returns this information when it is using cooked PF_PACKET sockets, so you only get this information for protocols like PPP (and only on Linux; other platforms might have a similar quirk I suppose). So if you want the inbound/outbound information for every interface, you basically need to skip use of libpcap and just use the (apparently) Linux-specific interface directly. The good news is, it’s not hard, and it’s even supported in Python.