forked from ausbin/nsdo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
readme.head
339 lines (261 loc) · 12.3 KB
/
readme.head
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
nsdo
====
This repository holds my system configuration for running particular
applications under OpenVPN, Cisco AnyConnect, or GlobalProtect VPNs on Ubuntu.
Here are my two simultaneous usecases:
1. Run clients for some peer-to-peer protocols through OpenVPN without
affecting other traffic, like browsing Wikipedia, which blocks edits
from my VPN provider
2. `ssh` into a cluster on my university's network via a Palo Alto Networks
GlobalProtect VPN without sending my personal traffic through my
university's network
Putting these two VPNs in their own [Linux network namespaces][1] and
having a command that lets me run an application in a namespace, like
$ nsdo gatech ssh something.gatech.edu
addresses both usecases well, and that's what this repository does.
However, the story does not end with network namespaces thanks to
`/etc/resolv.conf`, which needs a different version for all three
network namespaces involved. `ip netns exec` attempts to work around
this on every execution by making a new mount namespace and
bind-mounting each file in `/etc/netns/NSNAME/` to `/etc/`. So
theoretically, to fix our `resolv.conf` problem, you would write the
VPN-specific DNS configuration to `/etc/netns/NSNAME/resolv.conf`; but
`/etc/resolv.conf` is a symlink to
`/run/systemd/resolve/stub-resolv.conf` on my system. So iproute2 ends
up bind-mounting to that link destination, killing the bind mount
when something `rename()`s that volatile file.
So this repository takes a different approach: create a single mount
namespace corresponding to the network namespace, and inside it, mount
`/var/ns-etc/NSNAME/` with [`overlayfs`][5] on top of `/etc/`. Then,
when we run something in the network namespace, `nsdo` will call
`setns()` for this mount namespace as well as the network namespace.
It mounts `/var/ns-etc/NSNAME/` as the `overlayfs` "upper layer", so
changes made in the namespace actually persist in `/var/ns-etc/NSNAME/`
rather than `/etc/`. It cannot mount `/etc/netns/NSNAME/` because
`overlayfs` [gets upset at the overlap in paths][9].
For convenience, the `nsdo` binary has the [setuid bit][2] set, giving
it root privileges, which allows it to change namespaces, `setuid()` to
the user who ran the command, and then `exec()` the requested command.
Installation
------------
Clone this repository and run:
$ make
$ sudo make install install-anyconnect install-openvpn
To change the default installation directory of `/usr/local`, set
`PREFIX` to something else when you call both `make` and `make install`.
Leave off any of `install-anyconnect` or `install-openvpn` if you don't
want those configurations.
### Cisco AnyConnect
I use [openconnect][6], a free-as-in-freedom client for Cisco AnyConnect
VPNs available in a distribution's repository near you.
The `install-anyconnect` target of the Makefile mentioned above will
create an `[email protected]` systemd unit. If you create a profile
named `gatech.conf` in `/usr/local/share/openconnect/`, you should be
able to `sudo systemctl start openconnect@gatech` and then be on your
way.
A profile (say, `/usr/local/share/openconnect/gatech.conf`) looks like
this:
server=https://anyc.vpn.gatech.edu
pass1=hunter2
pass2=push
--authgroup=gatech-2fa-Duo
--user=aadams80
It's messy, but lines starting with `--` are long options passed
directly to `openconnect` (see `openconnect(8)` for a list of long
options). Anything else must be one of the three keys above (`server`,
`pass1`, `pass2`), which the `openconnect-wrapper` in this repository
processes and handles for you.
If `pass1=...` is missing in the profile (the better choice
security-wise), you'll need to input it with
`systemd-tty-ask-password-agent` as shown in the example below.
`pass2` is also optional. It's the second line of the password sent to
the server; the Georgia Tech VPN interprets `push` as "send me a 2FA
push notification on my phone". After I approve the 2FA request on my
phone, the VPN connects and I'm good to go.
If this setup causes trouble on your machine, please open an issue. I
want to make this robust, but I don't know much about others' VPN
configurations, so I'm making this up as I go.
#### ssh Configuration with ProxyCommand
It's easy to forget the `nsdo gatech` in front of an `ssh` command, so I
added the following to my `~/.ssh/config` (last line is the important
one):
Host pace
User aadams80
HostName coc-ice.pace.gatech.edu
IdentityFile ~/.ssh/id_rsa_pace
IdentitiesOnly yes
ProxyCommand /usr/local/bin/nsdo gatech /usr/bin/nc %h %p
Then I can login with simply
$ ssh pace
#### Finished Product
$ sudo systemctl start openconnect@gatech
$ sudo systemd-tty-ask-password-agent # only needed without pass1 in profile
Password for AnyConnect VPN gatech: *******
[Approve the 2FA request on my phone]
$ nsdo gatech curl https://austinjadams.com/ip
143.215.38.178
$ whois 143.215.38.178
...
OrgName: Georgia Institute of Technology
OrgId: GIT-Z
Address: 756 W Peachtree ST
City: Atlanta
StateProv: GA
PostalCode: 30308
Country: US
### Palo Alto Networks GlobalProtect
The Palo Alto Networks website [asserts that "GlobalProtect™ is more than a
VPN."][11] I agree — it's also a pain in the neck! Thankfully, [openconnect][6]
has support for GlobalProtect VPNs, so the code from the AnyConnect section
above works for GlobalProtect as well.
My configuration file for [the new Georgia Tech GlobalProtect VPN][12]
(`/usr/local/share/openconnect/gatech.conf`) looks like this, pretty similar to
the AnyConnect example above:
server=https://vpn.gatech.edu
pass1=hunter2
pass2=push
--protocol=gp
--user=aadams80
There was one catch, which is the following error `openconnect` was throwing:
2 gateway servers available:
dc-ext-gw.vpn.gatech.edu (dc-ext-gw.vpn.gatech.edu)
ni-ext-gw.vpn.gatech.edu (ni-ext-gw.vpn.gatech.edu)
Please select GlobalProtect gateway.
GATEWAY: [dc-ext-gw.vpn.gatech.edu|ni-ext-gw.vpn.gatech.edu]:***
User input required in non-interactive mode
Changing the `server` line in the configuration above to one of those gateway
hosts fixed the problem:
...
server=https://dc-ext-gw.vpn.gatech.edu
...
Otherwise, the advice in the AnyConnection section applies to GlobalProtect.
### OpenVPN
The `install-openvpn` Makefile target above installs a systemd
[drop-in][7] configuration file for `[email protected]` at
`/usr/local/lib/systemd/system/[email protected]/50-netns.conf`.
If you don't have a `/usr/lib/systemd/system/[email protected]`,
[here][8]'s a link to an upstream copy.
At the bottom of a normal openvpn configuration file in
`/etc/openvpn/client/` (say, `/etc/openvpn/client/foo.conf`), you should
be able to add the following:
# ... (rest of configuration) ...
# script should run `ip`, not openvpn
route-noexec
ifconfig-noexec
up "/usr/local/bin/openvpn-ns"
route-up "/usr/local/bin/openvpn-ns"
script-security 2
Then you should be able to `sudo systemctl start openvpn-client@foo`. If
you encounter problems, please open an issue because I want to
understand others' VPN/OS situations better.
#### Finished Product
$ sudo systemctl start openvpn-client@foo
$ nsdo foo some-graphical-p2p-application &
### Forwarding Ports into a Namespace
By design, applications cannot connect to ports bound in other network
namespaces. So if you have a server running in some other network
namespace with nsdo (e.g., a headless peer-to-peer client), you cannot
connect to it from the default network namespace. For example:
$ nsdo foo nc -l -p 6969 <<<"hi!" &
$ nc -v localhost 6969 <<<"hello"
localhost [127.0.0.1] 6969: Connection refused
$ nsdo foo nc -v localhost 6969 <<<"hello"
hi!
hello
You can work around this using `veth`, a kernel feature [designed][10] to
allow network namespaces to communicate. veth interfaces act just like
any interface but come in pairs — one for each namespace.
#### The veth systemd unit
I added a new systemd unit, `foo-veth.service` in `/etc/systemd/system/`
that looks like this:
[Unit]
Description=veth for foo netns
[Service]
Type=oneshot
RemainAfterExit=yes
# configure our end
ExecStart=/usr/bin/ip link add ns-foo up type veth peer name ns-def netns foo
ExecStart=/usr/bin/ip addr add 10.0.255.1/24 dev ns-foo
# configure vpn end
ExecStart=/usr/bin/ip -netns foo link set dev ns-def up
ExecStart=/usr/bin/ip -netns foo addr add 10.0.255.2/24 dev ns-def
# tear down everything
ExecStop=/usr/bin/ip link del ns-foo
[Install]
I would make this a template unit named `[email protected]` and commit it to
this repository so you can install it with the Makefile, but I am not
sure how best to allocate IP address spaces (e.g., `10.0.255.0/24`)
based off the instance name (e.g., `foo`). Once I created that, though,
I enabled the unit (`--now` will start it right now):
# systemctl daemon-reload
# systemctl enable --now foo-veth
Now, if you run `ip link` both inside and outside the namespace, you can
see the veth interfaces:
$ ip link
...
12: ns-foo@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether ee:05:c1:aa:83:26 brd ff:ff:ff:ff:ff:ff link-netns foo
$ nsdo foo ip link
...
3: ns-def@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether ee:e1:0b:b9:6b:6f brd ff:ff:ff:ff:ff:ff link-netnsid 0
For convenience, I would add the name of the namespace to `/etc/hosts`:
10.0.255.2 foo
Now the example from earlier "works", like this:
$ nsdo foo nc -l -p 6970 <<<"hi!" &
$ nc -v foo 6970 <<<"hello"
hi!
hello
#### Configuring the Server
Now, suppose we have a more realistic situation: we want to run a server
in the namespace that isn't just an instance of netcat, like an HTTP
server. Assuming the server has some systemd unit
`original-server.service`, you can add a drop-in configuration file for
it at `/etc/systemd/system/original-server.service.d/50-netns.conf` as
follows (`/usr/bin/the-original-server --original --args` is the
original command line from `/lib/systemd/system/original-server.service`
or wherever):
[Unit]
[Service]
ExecStart=
ExecStart=/usr/local/bin/nsdo foo /usr/bin/the-original-server --original --args
Then start it up:
# systemctl daemon-reload
# systemctl restart original-server
Now you should be able to access it from the default network namespace.
For example, if it's an HTTP server listening on port 6969:
$ curl http://foo:6969/
hello, world!
#### Port forwarding with iptables
Suppose the server is now peacefully listening on port 6969 in the `foo`
network namespace. If you want other machines on the network to be able
to access that server via port 6969 on the host machine, you can use
iptables:
# iptables -A PREROUTING ! -s 10.0.255.0/24 -p tcp -m tcp --dport 6969 -j DNAT --to-destination 10.0.255.2
# iptables -A POSTROUTING -o ns-foo -j MASQUERADE
# iptables-save
Now, on another machine, we should be able to access the machine
running the server:
$ curl http://originalmachine:6969/
hello, world!
License
-------
[MIT/X11 License][3]
[1]: https://lwn.net/Articles/580893/
[2]: https://en.wikipedia.org/wiki/Setuid
[3]: https://github.com/ausbin/nsdo/blob/master/LICENSE
[4]: https://austinjadams.com/blog/running-select-applications-through-openvpn/
[5]: https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html
[6]: https://www.infradead.org/openconnect/
[7]: https://www.freedesktop.org/software/systemd/man/systemd.unit.html
[8]: https://github.com/OpenVPN/openvpn/blob/452e016cba977cb1c109e74977029b9c0de33de2/distro/systemd/openvpn-client%40.service.in
[9]: https://unix.stackexchange.com/q/578196/62375
[10]: https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=e314dbdc1c0dc6a548ecf0afce28ecfd538ff568
[11]: https://web.archive.org/web/20210804030408/https://www.paloaltonetworks.com/products/globalprotect
[12]: https://gatech.service-now.com/home?id=kb_article_view&sysparm_article=KB0026837
Manpage
-------