aboutsummaryrefslogtreecommitdiffgithub
path: root/content/blog/running-select-applications-through-anyconnect.md
blob: 79330fb56d702fcfba4cec5adb27891e109c1997 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
+++
date = "2016-08-18T12:00:00-04:00"
draft = false
title = "Running Select Applications through a Cisco AnyConnect VPN"
description = "How I configured my GNU/Linux system to run only certain applications through an AnyConnect VPN"
+++

## This is outdated now. Please see the [README of the `nsdo` GitHub repository][9] instead

---
---
---

In an [earlier article specific to OpenVPN][1], I wrote:

> To isolate VPN applications from applications running 'bare,' I use
> the following method, which involves [nsdo][2]. It has the handy
> effect of putting each instance of the openvpn client in its own
> network namespace, allowing you to run a bunch of VPNs at the same
> time, each available only to programs you choose.

This article covers the same idea,  except for Cisco AnyConnect VPNs
using [OpenConnect][3], a libre implementation of AnyConnect. (I haven't
tried the official nonfree client, but I'm assuming it's not worth the
trouble, for this purpose at least.)

First, I use the same `netns@.service` [systemd service][4] I wrote in
[the OpenVPN article][1] by putting it in `/etc/systemd/system/`:

    [Unit]
    Description=network namespace %I

    [Service]
    Type=oneshot
    ExecStart=/bin/ip netns add %I
    ExecStop=/bin/ip netns del %I
    RemainAfterExit=yes

By putting network namespace creation/deletion in a separate unit
(rather than a script launched by OpenConnect/OpenVPN at connection
time), the VPN uses the same network namespace across restarts. For
example, if I have a browser running in a VPN namespace and restart my
VPN client, my browser doesn't stay in an outdated netns after my VPN
client starts up, creates a new netns, and changes the netns to which
`/var/run/netns/X` points.

I also created an `openconnect@.service`, which depends on
`netns@.service`:

    [Unit]
    Description=OpenConnect VPN Connection: Profile %I
    Requires=network.target netns@%i.service
    After=network.target netns@%i.service
    
    [Service]
    Type=simple
    WorkingDirectory=/usr/local/etc/openconnect/
    ExecStart=/usr/local/etc/openconnect/openconnect-wrapper %I

    [Install]
    WantedBy=multi-user.target

Notice that it executes `openconnect-wrapper`, a wrapper shell script,
which I (arbitrarily) chose to put in `/usr/local/etc/openconnect/`.
Here it is:

    #!/bin/bash
    set -e
    
    [[ -z $1 ]] && {
        printf "$0: usage: $0 <preset>\n" >&2
        exit 1
    }
    preset="$1"
    
    [[ ! -f $preset.conf ]] && {
        printf "$0: error: '$(pwd)/$preset.conf' does not exist!\n" >&2
        exit 2
    }
    
    # Expect the hostname to be the first line in the file, immediately
    # preceded by `# '
    host="$(head -n 1 "$preset.conf" | cut -b 3-)"
    pass="$(cat "$preset.pass" || systemd-ask-password "Password for AnyConnect VPN $preset:")"
    
    # vpnc-script-netns expects this to be set
    export NETNS="$preset"
    exec openconnect --config "$preset.conf" --script ./vpnc-script-netns --passwd-on-stdin --non-inter "$host" <<<"$pass"

I wrote the script to allow me to have presets for different servers. It
reads the hostname from the first line of the config file (after `#` and
a space) and calls [`systemd-ask-password`][5] to ask for the password
so that I don't have to write my password in plaintext anywhere. (When
you start the service, you can enter the password by running
[`systemd-tty-ask-password-agent`][6] as root.)

Here's my configuration file for Georgia Tech's VPN, `gatech.conf` (the wrapper script
reads the comment at the top to determine the hostname):

    # https://anyc.vpn.gatech.edu
    authgroup=gatech
    user=aadams80

The wrapper script also tells OpenConnect to configure the network
interfaces using `vpnc-script-netns`, another shell script I hacked
together:

    #!/bin/bash
    set -e
    
    [[ -z $NETNS ]] && {
        printf "$0"': $NETNS is not set! Please set it to the name of the desired namespace\n' >&2
        exit 1
    }
    
    case $reason in
        connect)
            ip netns exec "$NETNS" ip link set dev lo up
            ip link set dev "$TUNDEV" up netns "$NETNS" mtu "$INTERNAL_IP4_MTU"
            # Setup only IPv4 for now
            ip netns exec "$NETNS" ip addr add "$INTERNAL_IP4_ADDRESS" dev "$TUNDEV"
            ip netns exec "$NETNS" ip route add default dev "$TUNDEV"
    
            # Put DNS servers in /etc/netns/$NETNS/resolv.conf
            mkdir -p "/etc/netns/$NETNS"
            {
                printf '# Generated by vpnc-script-netns\n'
                for dns_server in $INTERNAL_IP4_DNS; do
                    printf 'nameserver %s\n' "$dns_server"
                done
            } >"/etc/netns/$NETNS/resolv.conf"
        ;;
    esac

Currently, it handles only IPv4 because my university's VPN
doesn't appear to support IPv6 yet.

I guess that's it. With a config file named `gatech.conf` in the same
directory as `vpnc-script-netns` and `openconnect-wrapper`, I start the
VPN with:

    # systemctl start openconnect@gatech
    # systemd-tty-password-agent
    Password for AnyConnect VPN gatech: ***********************

and then run applications in it like:

    $ nsdo gatech firefox

Or, using [iproute2][8] instead of [nsdo][2]:

    # ip netns exec gatech sudo -u $USER firefox

Or using `nsenter` from [util-linux][7]:

    # nsenter -n/var/run/netns/gatech sudo -u $USER firefox

Preventing Inactivity Timeouts
------------------------------

After a period of inactivity, the Georgia Tech VPN will close my
connection. To prevent this, I just leave an instance of `ping` running
in the namespace:

    $ nsdo gatech ping -i 60 gatech.edu &

But that could easily be a systemd unit, so I'll try to update this post
later with one.

[1]: {{< relref "running-select-applications-through-openvpn.md" >}}
[2]: https://code.austinjadams.com/nsdo
[3]: http://www.infradead.org/openconnect/
[4]: http://www.freedesktop.org/software/systemd/man/systemd.service.html
[5]: https://www.freedesktop.org/software/systemd/man/systemd-ask-password.html
[6]: https://www.freedesktop.org/software/systemd/man/systemd-tty-ask-password-agent.html
[7]: https://git.kernel.org/cgit/utils/util-linux/util-linux.git
[8]: https://wiki.linuxfoundation.org/networking/iproute2
[9]: https://github.com/ausbin/nsdo#readme