#!/bin/sh # # © 2009 David Woodhouse # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # ################ # # This is a replacement for the standard vpnc-script used with vpnc and # openconnect. It sets up VPN routing which doesn't screw over the # _normal_ routing of the box. # # It sets up a new network namespace for the VPN to use, and it runs # a Secure Shell dæmon inside that namespace, with full access to all # routes on the VPN. # # It links the 'real' network namespace of the computer to this new one # by an IPv6 site-local connection -- you can ssh into the 'VPN namespace' # by connecting to the host 'fec0::1'. # # You don't need any IPv6 configuration or connectivity for this; you # only need to have IPv6 support in your kernel. The use of IPv6 is purely # local to your machine. # # This gives you effectively the same service as if your company used a # SSH "bastion host" for access, instead of a VPN. It's just that the # bastion host is a special network namespace in your _own_ machine. # # Since your connection to it is _private_, though, you can run a few # services that a secure bastion host could not -- like a web proxy, # for example. You can also just forward certain points so that, for # example, connections to port 25 on your bastion host are automatically # forwarded inside the VPN to your internal mail server. There should # be a sample xinetd.conf with this script which shows how to do that # using 'netcat' started from xinetd. # # It probably helps if you think of the VPN namespace as if it was a # separate machine. From the network point of view, that's what it is. # It just happens to share the file system (and a lot of other stuff) # with your _real_ computer. # # You can configure various other services to use this for connections into # your VPN, as follows... # # SOCKS # # SSH has a built-in SOCKS server. If you run 'ssh -D 1080 fec0::1', SSH # will listen on port 1080 and will forward connections through the SSH # connection and give you full SOCKS access to the VPN. # # It might make sense to make this script automatically start a SSH # connection with SOCKS enabled, if you want that to be available. # # SSH # # The OpenSSH client is capable of connecting to a SSH server by running # and arbitrary command and using its stdin/stdout, instead of having to # make a direct TCP connection to the server. # # So you can configure it to SSH into the VPN 'namespace' and use the # 'netcat' command for certain connections. You can add something like # this to your ~/.ssh/config: # # Host *.example.internal # ProxyCommand ssh fec0::1 exec nc %h %p # # (This also works if your company has made the mistake of overloading the # public 'company.com' domain for internal purposes, instead of doing the # sensible thing and using a _separate_ domain.) # # MAIL # # Like SSH, most decent mail clients are able to run a command to connect # to their IMAP server instead of being limited to a direct TCP connection. # # Commands you might want to use could look like... # ssh $MAILSRV exec /usr/sbin/dovecot --exec-mail imap # ssh $MAILSRV exec /usr/sbin/wu-imapd # ssh fec0::1 openssl s_client -quiet -connect $MAILSRV:993 -crlf 2>/dev/null # # Where '$MAILSRV' is the name of your mail server, of course. # # Note that the first two assume that you've set up SSH as described above, # so that SSH connections to the mail server work transparently. For the # latter, you probably need to redirect stderr to /dev/null to avoid # spurious output from openssl configuring your mail client (openssl doesn't # seem to take the -quiet option very seriously). # # For mail clients which _cannot_ simply run an external command for their # connection, first file a bug and then see the 'PORT FORWARDING' section # below. # # WEB # # Firefox and most other browsers should understand a 'proxy autoconfig' # file and that can tell it to use SOCKS (see above) for certain domains. # A suitable PAC file might look like this: # # function FindProxyForURL(url, host) # { # if (dnsDomainIs(host, "company.com")) # return "SOCKS5 localhost:1080"; # # return "DIRECT"; # } # # PORT FORWARDING # # You can use SSH to forward certain ports, of course -- but there's another, # simpler option. # # The included example of xinetd configuration will accept connections on # port 25 and 993 of the host fec0::1, and will automatically forward them # using netcat to the appropriate hosts within your VPN. This can be extended # to forward other ports. # # OTHER SERVICES # # Most other services should also be available through SSH, through the # SOCKS proxy, or by port forwarding in some way. If all else fails, you # can just ssh into the vpn namespace (ssh fec0::1) and have a shell with # complete access. # # BREAKING OUT OF THE VPN # # If you ssh _into_ your machine from the VPN side, you'll get a shell in # the VPN namespace. To 'break out' from there, you may want to ssh to # fec0::2 which is the normal machine. # # CONTROLLING ACCESS TO THE VPN # # One serious flaw with the _traditional_ VPN setup is that it allows # _all_ processes and users on the machine to have free access to the # VPN, instead of only the user who is supposed to have access. The # approach implemented here allows you to fix that, by running the # SSHD in the VPN namespace with a separate configuration that allows # only certain users to connect to it. # # (Be aware that using port forwarding or using SSH to run a SOCKS proxy # will negate that benefit, of course) # # David Woodhouse # 2009-06-06 IP="`which ip 2> /dev/null | grep '^/'`" SCRIPTNAME=`basename $0` NETNSNAME=$SCRIPTNAME # XINETDCONF=`dirname $0`/xinetd.netns.conf PS4=" \$\$+ " connect_parent() { export PARENT_NETNS=$$ $IP link set $TUNDEV down if ! $IP link set $TUNDEV netns $$; then echo "Setting network namespace for $TUNDEV failed" echo "Perhaps you don't have network namespace support in your kernel?" exit 1 fi $IP netns delete $NETNSNAME >/dev/null 2>&1 if ! $IP netns add $NETNSNAME; then echo "Creating network namespace $NETNSNAME failed" echo "Perhaps you don't have network namespace support in your kernel?" exit 1 fi $IP link add dev $TUNDEV-vpnssh%d type veth # XXX: Assume vpnssh0 and vpnssh1; ip doesn't tell us! LOCALDEV=$TUNDEV-vpnssh0 export REMOTEDEV=$TUNDEV-vpnssh1 $IP netns exec $NETNSNAME $0 $@ & CHILDPID=$! # XXX: If we do this too soon (before the unshare), we're just # giving it to our _own_ netns. which achieves nothing. # So give it away until we _can't_ give it away any more. while $IP link set $REMOTEDEV netns $CHILDPID 2>/dev/null; do sleep 0.1 done # Give away the real VPN tun device too $IP link set $TUNDEV netns $CHILDPID $IP link set $LOCALDEV up $IP addr add fec0::2/64 dev $LOCALDEV echo "VPN now accessible through 'ssh fec0::1'" if ! grep -q 127.0.0.1 /etc/resolv.conf; then echo "WARNING: Your host needs to be running a local dnsmasq or named" echo "WARNING: and /etc/resolv.conf needs to point to 127.0.0.1" # XXX: We could probably fix that for ourselves... fi } connect() { if [ -z "$PARENT_NETNS" ]; then connect_parent exit 0 fi # This is the child, which remains running in the background # Wait for the tundev to appear in this namespace while ! ip link show $TUNDEV >/dev/null 2>&1 ; do sleep 0.1 done # Set up Legacy IP in the new namespace $IP link set lo up $IP link set $TUNDEV up if [ -n "$INTERNAL_IP4_ADDRESS" ]; then $IP -4 addr add $INTERNAL_IP4_ADDRESS dev $TUNDEV $IP -4 route add default dev $TUNDEV fi if [ -n "$INTERNAL_IP6_ADDRESS" ]; then $IP -6 addr add $INTERNAL_IP6_ADDRESS dev $TUNDEV $IP -6 route add default dev $TUNDEV fi if [ "$INTERNAL_IP4_MTU" != "" ]; then $IP link set $TUNDEV mtu $INTERNAL_IP4_MTU fi # Set up the veth back to the real system $IP link set $REMOTEDEV up $IP -6 addr add fec0::1/64 dev $REMOTEDEV # Run dnsmasq to provide DNS service for this namespace. # The host needs to be running its own local nameserver/dnsmasq and # /etc/resolv.conf should be pointing to 127.0.0.1 already. DNSMASQ_ARGS="--port=53 -k -R" for NS in $INTERNAL_IP4_DNS; do DNSMASQ_ARGS="$DNSMASQ_ARGS -S $NS" done for NS in $INTERNAL_IP6_DNS; do DNSMASQ_ARGS="$DNSMASQ_ARGS -S $NS" done /usr/sbin/dnsmasq $DNSMASQ_ARGS & DNSMASQ_PID=$! # Set up sshd /usr/sbin/sshd -D & SSHD_PID=$! XINETD_PID= if [ "$XINETDCONF" != "" ] && [ -r "$XINETDCONF" ]; then /usr/sbin/xinetd -dontfork -f $XINETDCONF & XINETD_PID=$! fi # Wait for the veth link to be closed... while ip link show $REMOTEDEV >/dev/null 2>&1 ; do sleep 1 done kill -TERM $DNSMASQ_PID kill -TERM $SSHD_PID if [ "$XINETD_PID" != "" ]; then kill -TERM $XINETD_PID fi # Wait a while to avoid tun BUG() if we quit and the netns goes away # before vpnc/openconnect closes its tun fd. sleep 1 } disconnect() { # Kill our end of the veth link, leaving the child script to clean up $IP link del $TUNDEV-vpnssh0 while ! $IP netns delete $NETNSNAME >/dev/null 2>&1 ; do sleep 0.1 done } case $reason in connect) connect ;; disconnect) disconnect ;; esac