-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwg-setup-client
executable file
·426 lines (373 loc) · 13.2 KB
/
wg-setup-client
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
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
#!/bin/bash
# Generate a WireGuard configuration for wg-quick or
# systemd-networkd, if it's the active network manager.
# Meant to easily setup large amounts of clients to a network by just
# providing individual IP addresses
# This is not sophisticated, but meant to customize or just inspire. Have fun.
# created by Eicke Herbertz, 2020
set -euo pipefail
error() {
echo "Error: $*" >&2
exit 1
}
has-systemd() {
command -v systemctl >/dev/null
}
systemd-booted() {
[[ -d /run/systemd/system ]]
}
usage() {
cat <<EOF
Usage: $0 [-fhst] [-d descr] [-e <endpoint>] [-i <name>] [-p pubkey]
[ip-addr] [port (with -s)]
-b,--backend=<backend> Backend to configure (networkd, wg-quick or auto (default))
-d,--descr=<description> Description for .netdev (default: WireGuard VPN)
-e,--endpoint=<endpoint> WireGuard endpoint of the Server
-f,--force overwrite existing files
-h,--help Show this usage text
-i,--iface=<ifname> name of the WireGuard interface to create (default: wg0)
-o,--out-file=<file> File mode: don't configure interface, write configuration to
given file (- for stdout). For networkd files, either .netdev
or .network can be provided and both will be created.
-p,--pubkey=<pubkey> WireGuard public key of the Server
-s,--server Create a server configuration (skip peer setup)
-t,--type=<type> Type of output for file mode, supported values are:
auto (default), wg-quick, networkd, zip, qr-utf8, qr-png
*auto* uses qr-utf8 if out-file is stdout, otherwise tries
to read the file extension and falls back to wg-quick format.
ip-addr ip address for this client
port listening port in --server mode
EOF
exit $1
}
# Adapted from https://stackoverflow.com/a/32690695/2208453
ip2int() {
local a b c d
{ IFS=. read a b c d; } <<< $1
echo $(( ( ( ( ( (a << 8) | b) << 8) | c) << 8) | d))
}
int2ip() {
local ui32=$1 ip n
for n in 0 8 16 24; do
ip=$(((ui32 & 0xff << $n) >> $n))${ip:+.${ip}}
done
echo $ip
}
network() {
local addr="$(ip2int $1)"
local mask="$((0xffffffff << (32 - $2)))"
int2ip $((addr & mask))
}
networkd_network_header() {
local ifname=$1
cat <<EOF
[Match]
Name = ${ifname}
[Network]
EOF
}
networkd_netdev_header() {
local ifname="$1"
local desc="$2"
cat <<EOF
[NetDev]
Name = ${ifname}
Kind = wireguard
Description = ${desc}
[WireGuard]
EOF
}
wg_quick_conf_header() {
local ifname="$1"
local desc="$2"
cat <<EOF
# ${ifname}: ${desc}
[Interface]
EOF
}
command -v wg >/dev/null || error "wg not found. Please install wireguard-tools first!"
opts=d:e:fhi:o:p:st:
lopts=descr:,endpoint:,force,help,iface:,out-file:,pubkey:,server,type:
parsed_opts=$(getopt -o $opts -l $lopts -n "$0" -- "$@")
eval set -- "$parsed_opts"
wg_description="WireGuard VPN"
wg_endpoint=
backend=auto
force=
wg_ifname=wg0
wg_public_key=
file_mode=
out_file=
out_type=auto
create_server=
ip_address=
server_port=
while [[ "$1" != "--" ]]; do
case $1 in
-b|--backend)
backend="$2"
shift 2
;;
-d|--descr)
wg_description="$2"
shift 2
;;
-e|--endpoint)
wg_endpoint="$2"
shift 2
;;
-f|--force)
force=yes
shift
;;
-h|--help)
usage
;;
-i|--iface)
wg_ifname="$2"
shift 2
;;
-o|--out-file)
file_mode=1
out_file="$2"
shift 2
;;
-p|--pubkey)
wg_public_key="$2"
shift 2
;;
-s|--server)
create_server=yes
shift
;;
-t|--type)
out_type="$2"
shift 2
;;
*)
echo "Programming error" >&2
usage 1
;;
esac
done
shift # away the --
# All positional arguments are optional. Client setup accepts one, server setup two values.
[[ $# -ge 1 ]] && ip_address="$1"
[[ $# -ge 2 ]] && server_port="$2"
[[ $# -ge 3 ]] && usage 1
# Fail when client setup is called with a second positional.
[[ ! ${create_server} && -n "${server_port}" ]] && usage 1
# Check that we are running as root or testing (TODO: have an actual test suite)
[[ $UID -eq 0 || -n "${file_mode}" ]] || error "Please run as root!"
# Now read all required parameters that weren't provided as arguments interactively
# The IP address is required for both server and client
if [[ -z "${ip_address}" ]]; then
[[ ${create_server} ]] && \
echo -n "Enter server ip address: " || \
echo -n "Enter ip address for this client: "
read -r ip_address
fi
[[ "${ip_address}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?$ ]] || error "Invalid ip address"
# Then, read the listening port for a server or a server´s public key and endpoint for a client
if [[ ${create_server} ]]; then
if [[ -z "${server_port}" ]]; then
echo -n "Enter server listening port (empty: random port): "
read -r server_port
fi
[[ "${server_port}" =~ ^[0-9]{1,5}$ ]] || error "Invalid port"
else
if [[ -z "${wg_public_key}" ]]; then
echo -n "Server PublicKey: "
read -r wg_public_key
fi
[[ "${wg_public_key}" =~ ^[A-Za-z0-9/+]{43}=$ ]] || error "Invalid PublicKey format!"
if [[ -z "${wg_endpoint}" ]]; then
echo -n "Server Endpoint: "
read -r wg_endpoint
fi
[[ "${wg_endpoint}" =~ ^[A-Za-z0-9.-]+:[0-9]+$ ]] || error "Invalid Endpoint (host:port)!"
fi
# If subnet is provided on ip address use it
if [[ "${ip_address%/*}" != "${ip_address}" ]]; then
ip_netmask="${ip_address#*/}"
ip_address="${ip_address%/*}"
[[ "${ip_netmask}" =~ ^[0-9]{2}$ ]] || error "Invalid subnet length ${ip_netmask}"
else
echo "Warning: No subnet lenght provided, using /24!"
ip_netmask=24
fi
# Calculate network from IP address and subnet length
network_addr=$(network "${ip_address}" "${ip_netmask}")
# Check parsed result (TODO: write tests for the conversion and not check here)
[[ "${network_addr}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] ||
error "Failed to calculate network address (Result: ${network_addr})"
# TODO: make this configurable
wg_allowed_ips="${network_addr}/${ip_netmask}"
# If backend is set to auto, check which one to use
if [[ "$backend" == "auto" ]]; then
if [[ $file_mode ]]; then
backend=none
if [[ "$out_type" == "auto" ]]; then
case "$out_file" in
-)
out_type=qr-utf8 ;;
*.png)
out_type=qr-png ;;
*.zip)
out_type=zip ;;
*.network|*.netdev)
out_type=networkd ;;
*)
out_type=wg-quick ;;
esac
fi
elif has-systemd && systemctl is-enabled systemd-networkd.service >/dev/null; then
backend=networkd
out_type=networkd
wg_basename=${WG_BASENAME:-90-wireguard}
out_file=/etc/systemd/network/${wg_basename}.netdev
else
backend=wg-quick
out_type=wg-quick
out_file=/etc/wireguard/${wg_ifname}.conf
fi
fi
[[ -f "${out_file}" && -z "${force}" ]] &&
error "${out_file} already exists (use -f to overwrite)!"
# Setup the selected backend. The code further below will not use the $backend variable directly,
# but the WG_QUICK_CONF, WG_NETDEV and WG_NETWORK variables defined here.
# It is possible to predefine these variables in the environment and use custom paths.
case "$out_type" in
networkd)
echo "Using systemd-networkd for configuration"
if [[ "$out_file" == "-" ]]; then
error "Can't write systemd-networkd configuration to stdout!"
elif [[ "${out_file%.netdev}" == "${out_file}" ]]; then
wg_netdev=${out_file%.network}.netdev
wg_network=${out_file}
else
wg_netdev=${out_file}
wg_network=${out_file%.netdev}.network
fi
# Check for existing files one more since we mangled filenames.
[[ -f "${wg_netdev}" && -z "${force}" ]] &&
error "${wg_netdev} already exists (use -f to overwrite)!"
[[ -f "${wg_network}" && -z "${force}" ]] &&
error "${wg_network} already exists (use -f to overwrite)!"
;;
wg-quick)
echo "Using wg-quick for configuration"
[[ "$out_file" == "-" ]] && out_file=/dev/stdout
wg_quick_conf=${out_file}
;;
zip|qr-utf8|qr-png)
echo "Creating temporary configuration for ${out_type}"
temp_dir=$(mktemp -d)
temp_file=${out_file##*/}
temp_file=${temp_file%.*}.conf
wg_quick_conf=${temp_dir}/${temp_file}
;;
*)
error "Unknown output type ${out_type}!"
;;
esac
# The networkd configuration is split into a .network and a .netdev file, while wg-quick has only one.
if [[ ${wg_network:-} ]]; then
networkd_network_header "${wg_ifname}" > "${wg_network}"
networkd_netdev_header "${wg_ifname}" "${wg_description}" > "${wg_netdev}"
else
wg_quick_conf_header "${wg_ifname}" "${wg_description}" > "${wg_quick_conf}"
fi
# The configuration keys and format of networkd and wg-quick are identical, only the
# sections have different names. So we can use the same code for both.
wg_conf_target="${wg_quick_conf:-${wg_netdev}}"
# This will write an empty value if no server_port is provided. If we bring the interface
# up successfully, the actual port will be set later in the script.
[[ ${create_server} ]] && echo "ListenPort = ${server_port}" >> "${wg_conf_target}"
# Generate and write the private key. We'll only keep the public key to print out at the end.
private_key="$(wg genkey)"
public_key="$(wg pubkey <<<${private_key})"
echo "PrivateKey = ${private_key}" >> "${wg_conf_target}"
unset private_key
# Only the IP address goes into the network file when using systemd-networkd
echo "Address = ${ip_address}/${ip_netmask}" >> "${wg_quick_conf:-${wg_network}}"
# Add a blank line before peer configuration
echo >> "${wg_conf_target}"
# If configuring a client, write the server peer configuration
[[ ! ${create_server} ]] && cat >> "${wg_conf_target}" <<EOF
[${wg_netdev:+WireGuard}Peer]
PublicKey = ${wg_public_key}
AllowedIPs = ${wg_allowed_ips}
Endpoint = ${wg_endpoint}
PersistentKeepalive = 25
EOF
# Set permissions and ownership of the new files
chmod 0640 "${wg_conf_target}"
[[ -n "${wg_netdev:-}" && -z "${file_mode}" ]] && chgrp systemd-network "${wg_netdev}"
# Select which service to enable and (re)start
if [[ -z "${wg_quick_conf:-}" ]]; then
service="systemd-networkd.service"
else
service="wg-quick@${wg_ifname}.service"
fi
# Enable the service if we have systemd
if [[ ! ${file_mode} ]] && has-systemd; then
systemctl enable "${service}"
fi
if [[ ${file_mode} ]]; then
echo "File mode: Skipping further system configuration."
case "$out_type" in
zip)
zip -j "$out_file" "$wg_quick_conf"
;;
qr-png)
qrencode -t PNG -o "$out_file" -r "$wg_quick_conf"
;;
qr-utf8)
qrencode -t UTF8 -o "$out_file" -r "$wg_quick_conf"
;;
esac
[[ -d "${temp_dir:-}" ]] && rm -rf "$temp_dir"
# Check for wireguard module and try to bring up the interface
elif ! lsmod | grep ^wireguard >/dev/null && ! modprobe wireguard; then
echo "Can't load WireGuard module! Probably the kernel got updated, please reboot!" >&2
if [[ ${create_server} && -z "${server_port}" ]]; then
echo "Could not start server and no port was set!" >&2
echo "You will have to set the ListenPort manually in ${wg_conf_target}" >&2
fi
else
if has-systemd && systemd-booted; then
echo "(Re)starting ${service}"
systemctl restart "${service}"
elif has-systemd; then
echo "systemd not booted, skipping (re)starting ${service}."
else
echo "Installing without systemd: Running wg-quick up ${wg_ifname}"
if ! wg-quick up ${wg_ifname}; then
echo "Couldn't start WireGuard interface! If running in a container, probably CAP_NET_ADMIN is missing." >&2
fi
fi
# If we are setting up a server and had no listening port provided, WireGuard will select a
# random port which we'll extract and write it to the configuration to make it persistent.
if [[ ${create_server} && -z "${server_port}" ]]; then
server_port=$(wg show "${wg_ifname}" | awk '/listening port/{print $3}')
if [[ -z "${server_port}" ]]; then
echo "Warning: Could not determine ListenPort!" >&2
echo "You will have to set the ListenPort manually in ${wg_conf_target}" >&2
else
sed -i "s/ListenPort = /ListenPort = ${server_port}/" "${wg_conf_target}"
fi
fi
fi
if [[ ! ${create_server} ]]; then
echo "============================================================
WireGuard setup successful! Server side add-peer command:
wg-setup add-peer ${HOSTNAME} ${public_key} ${ip_address}"
exit 0
else
echo "============================================================
WireGuard server setup successful!
PublicKey: ${public_key}
ListenPort: ${server_port}"
exit 0
fi