managing IPsec packets with iptables

A few weeks back I upgraded my wireless router and added an racoon based IPsec VPN. When I built my first home router / wireless access point a few years ago I did so to get some experience configuring Linux systems and writing firewall rules. I’ve revisited this script a number of times as I’ve added new functionality to the access point and now I’ve added a few rules for IPsec traffic that I figured were worth mentioning here.

First off the script is a “default drop” policy. This means that any packets that don’t match a rule in the firewall ending in a jump to ACCEPT target are dropped. More specifically the default policy for the input, output and forward chains are DROP:

iptables --policy INPUT DROP
iptables --policy OUTPUT DROP
iptables --policy FORWARD DROP

This means that any traffic that doesn’t have an explicit rule that allows it to pass will be dropped. The design goal I had in mind is two write all rules that allow traffic to be as exact as possible so as to not match any unintended traffic. This is as close to mandatory access control for network traffic as I can figure.

Rules for IKE exchange

Hopefully I’ll get a chance to publish the whole script but it’s pretty long (600 lines or so) and needs some cleanup. But the rules I’ve added to allow the racoon server are pretty clean and they’re super useful so here they are:

The first stage of establishing the VPN tunnel is direct negotiation between the client and the access point (the wireless router in this case). The racoon server listens on ports 500 and 4500 and it negotiates directly with the clients racoon server on the same ports. I’m including just the raw rules here but you should wrap these up in functions to keep your scripts clean:

iptables --table filter --append INPUT 
  --in-interface extif                 
  --proto udp                          
  --destination ${PUBLIC_IP}           
  --dport 500                          
  --source  0.0.0.0/                   
  --sport 500                          
  --jump ACCEPT

iptables --table filter --append INPUT 
  --in-interface extif                 
  --proto udp                          
  --destination ${PUBLIC_IP}           
  --dport 4500                         
  --source  0.0.0.0/0                  
  --sport 4500                         
  --jump ACCEPT

iptables --table filter --append OUTPUT 
  --out-interface extif                 
  --proto udp                           
  --destination 0.0.0.0/0               
  --dport 500                           
  --source ${PUBLIC_IP}                 
  --sport 500                           
  --jump ACCEPT

iptables --table filter --append OUTPUT 
  --out-interface extif                 
  --proto udp                           
  --destination 0.0.0.0/0               
  --dport 4500                          
  --source ${PUBLIC_IP}                 
  --sport 4500                          
  --jump ACCEPT
}

Notice that I’ve limited the interface through which the IKE traffic can be accepted and transmitted as well as the IP of the server and the source and destination ports. The loosest part is that of the clients IP which we can’t know in advance. These rules are pretty standard, the interesting ones come next.

Rules for VPN traffic

So now our clients can negotiate security associations with the IKE server. This allows them to get IP addresses on the VPN network but they can’t talk to any other systems on the VPN or the other networks connected to the router (my file server etc).

VPN talking to internal network

To enable this we need FOWARD rules allowing traffic from the VPN network (10.0.2.0/24 that arrives on the interface ‘extif’) to go to the other network connected to the router (10.0.1.0/24 that arrives on the interface ‘wirif’). Typically this would look like:

iptables -A FORWARD         
  --in-interface extif      
  --source 10.0.2.0/24      
  --out-interface wirif     
  --destination 10.0.1.0/24 
  --jump ACCEPT

iptables -A FORWARD         
  --in-interface wirif      
  --source 10.0.1.0/24      
  --out-interface extif     
  --destination 10.0.2.0/24 
  --jump ACCEPT

These rules look pretty tight right? We’ve limited the the networks that can communicate and the interfaces on which the traffic should originate. We can do better though.

I’m particularly wary of the interface on the router that’s connected to the internet. The iptables rules above will allow the traffic we want to allow but it doesn’t require that the traffic from the 10.0.2.0/24 network arrive over the VPN! Theoretically an attacker that isn’t on the VPN could spoof traffic with the VPN IP and exchange packets with the protected 10.0.1.0/24 network. This is bad.

Policy Matching

So we need to add to these rules policy that will require the packets come from, and go to the VPN network through the IPsec tunnel. I’d never even heard of the iptables policy module till I was searching for the answer to this problem. Here’s what I came up with:

iptables --append FORWARD    
  --match policy             
  --dir in                   
  --pol ipsec                
  --mode tunnel              
  --tunnel-dst ${PUBLIC_IP}  
  --tunnel-src 0.0.0.0/0     
  --in-interface extif       
  --source 10.0.2.0/24       
  --out-interface wirif      
  --destination 10.0.1.0/24  
  --jump ACCEPT

iptables --append FORWARD    
  --match policy             
  --dir out                  
  --pol ipsec                
  --mode tunnel              
  --tunnel-dst 0.0.0.0/0     
  --tunnel-src ${PUBLIC_IP}  
  --in-interface wirif       
  --source 10.0.1.0/24       
  --out-interface extif      
  --destination 10.0.2.0/24  
  --jump ACCEPT

This allows the two networks to communicate, but this time it specifies that the packets must travel through the IPsec tunnel as we’d expect.

So now our VPN users can connect to the racoon server on the firewall and can talk to hosts behind the firewall. What’s left? Well my router / firewall also happens to have a dnsmasq server that offers clients DHCP leases and DNS service. The DNS service is particularly useful for those connecting to my storage server so they don’t have to memorize IP addresses.

Offering DNS services to VPN users

VPN users should be able to use the DNS service too (they don’t need DHCP since they get their IPs from racoon). The rules to allow this are very similar to the FORWARD rules above:

iptables --append INPUT     
  --match policy            
  --pol ipsec               
  --dir in                  
  --mode tunnel             
  --tunnel-dst ${PUBLIC_IP} 
  --tunnel-src 0.0.0.0/0    
  --protocol udp            
  --in-interface extif      
  --source 10.0.2.0/24      
  --source-port 1024:65535  
  --destination 10.0.1.1/24 
  --destination-port 53     
  --jump ACCEPT

iptables --append OUTPUT        
  --match policy                
  --pol ipsec                   
  --dir out                     
  --mode tunnel                 
  --tunnel-dst 0.0.0.0/0        
  --tunnel-src ${PUBLIC_IP}     
  --protocol udp                
  --source $10.0.1.1/24         
  --source-port 53              
  --out-interface extif         
  --destination 10.0.2.0/24     
  --destination-port 1024:65535 
  --jump ACCEPT

These rules are pretty self explanatory once you understand the syntax. There’s lots of info in these rules too, we identify the direction of the packets through the ipsec tunnel, the endpoints of the tunnel and the standard IP stuff: source address and port, destination and port as well as the interfaces.

That’s it. 8 rules are all that’s required to allow VPN users to connect, speak to hosts on my internal network and to talk to the DNS server. Using the policy match these rules are pretty exact … if there’s anyway I can make them better let me know in the comments.

Racoon IPsec VPN on Debian Lenny

I’ve been wanting to set up a “pure” IPsec VPN using racoon for a while now. Part just for fun, part because I can. I spent a weekend on it once a while back, didn’t make much progress, got sick of trying to figure out cryptic racoon debug output and then gave up (more pressing stuff, you know how it is).

Anyways I ran into a situation where I NEED a VPN now. I manage a few systems that are facing the internet so they’re constantly under attack (mostly bots trying to brute force ssh all the time). Remote administration over ssh is pretty much all I need but I’d like to be able to keep a closer eye on the hardware through the “Integrated Lights-Out” system (they’re HP Proliant servers). These I don’t want facing the internet. Similarly I don’t want the configuration interfaces for my switch facing the public either.

So what I needed was a management network that I could connect to through a VPN gateway remotely (typically known as a “roadwarrior” setup). The ALIX system I’ve been blogging about in the recent past is what I’m using as the gateway / server. This post is a quick run down of the contortions I went through to get this working and why I didn’t get it working just how I want it 😦

Requirements

  • racoon only, no L2TP
  • rsasig authentication
  • as little hard coded network configuration as possible on the client

I thought the above was pretty ambitious. Configuring racoon is pretty complicated but after pouring over the man page for racoon.conf I found the mode_cfg section which specifies the network configuration for the server to send out to authenticated clients. A little more digging turned up a few examples, particurlarly useful were the netbsd howto and the howto forge racoon roadwarrior configuration.

Both of these give a working example of using the hybrid_rsa authentication with mode_cfg. This isn’t exactly what I wanted but it solves 2 of my 3 requirements above so it’s a great start. Next was adding my own server and client certificates to the configuration and making sure that both the certificate and the remote identifier were being verified. I didn’t want to keep having to type in a password when connecting to the VPN so I moved on to getting rsasig authentication working. Naturally at this point all hell broke loose.

It took me forever to figure it out, but it looks like the ipsec-tools version that ships with Lenny (0.7.1) doesn’t play nice with rsasig authentication and mode_cfg. The client and server are able to negotiate phase 1 without any troubles when the client never requests the configuration data from the server. I tried all sorts of configuration combinations hoping to find something that worked with no luck. Eventually I ran across an old and unanswered post to the ipsec-tools users mailing list from a few years back describing the same problem I’m having with the 0.7.0 version of racoon. Probably safe to assume that this behavior is what I’m running into on the version 0.7.1.

At this point my options were to upgrade to a later version and hope the bug was fixed or use hybrid auth. Guess which one I chose … hybrid auth ain’t so bad 🙂 Yeah typing in a password is a PITA but both client and server can still be configured to check each others certs and asn1dns identifiers in phase 1 so very little (if any) security is compromised. 2 out of 3 requirements isn’t bad. None of the desired functionality was lost but I do have to supply a password each time I connect to the VPN. Meh.

Configurations

Since the articles on howto forge and netbsd.org are so good I won’t bore you with a full description of my racoon configuration since it’s very similar. I will include them here for completeness and cover the parts where they differ.

The server.racoon.conf is very similar except for the credential verification for the client and some details in the mode_cfg. I’m using the split_network directive to send routing information to the client so we don’t have to hardcode any routes. The scripts on the client side had to be changed to accommodate this but it wasn’t that hard (I’ll get to this in a second). Also notice that I’m using all class C networks (/24 in CIDR) so no routes need to be specified on systems plugged into the management network directly.

In my client.racoon.conf the only difference is in the verification of the servers credentials (certs). I started out testing using certificates generated from my own root CA. When this was deployed I got certificates from cacert.org which is a great service.

The significant changes on the client side were in the phase one up and down networking scripts. I really didn’t like the scripts from the howto forge article (some of it didn’t seem necessary). They were a great starting place though but since I’m using the split_network I had to be able to properly handle dynamically creating and removing these routes. The final scripts can be found here: client.phaseone-up.sh and client.phaseone-down.sh

If you look closely you’ll notice that in the phaseone-down script I flush all SAs when taking down the VPN. Obviously this isn’t what we want: we want only to delete the relevant SAs. Unfortunately version 0.7.1 of the ipsec-tools package doesn’t play well with the way Linux manages SAs so the deleteall setkey function doesn’t work right. Flush is all we’re left with unless we’re gona parse the SADB output for the SPIs we need. This bug was reported on the ipsec-tools devel mailing list with a patch and it seemed well received so it’s likely in a later release. I’ll put off writing fancy bash script and just upgrade racoon soon.

Hope this is useful to everyone out there setting up a roadwarrior friendly racoon VPN. Leave me a comment if this was useful or if any of it’s unclear.