diff options
author | Andrew Cady <d@jerkface.net> | 2021-10-09 05:16:40 -0400 |
---|---|---|
committer | Andrew Cady <d@jerkface.net> | 2021-10-09 05:19:13 -0400 |
commit | 001803289d137b61b84778a518744be47bf3e70b (patch) | |
tree | 8f64a5fb68d8febc1c7350f847b5f3bcfec9600f /cryptonomic-vpn | |
parent | 00dfe043a4f6a58a81e3e00738f30a97819b2176 (diff) |
rename executable to cryptonomic-vpn
Diffstat (limited to 'cryptonomic-vpn')
-rwxr-xr-x | cryptonomic-vpn | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/cryptonomic-vpn b/cryptonomic-vpn new file mode 100755 index 0000000..5e95558 --- /dev/null +++ b/cryptonomic-vpn | |||
@@ -0,0 +1,375 @@ | |||
1 | #!/bin/bash | ||
2 | set -e | ||
3 | [ "$UID" = 0 ] || exec sudo -- "$0" "$@" | ||
4 | |||
5 | help() | ||
6 | { | ||
7 | prefixU="Usage: $0 " | ||
8 | prefix0=" $0 " | ||
9 | prefix1=${prefix0//?/ } | ||
10 | cat <<EOF | ||
11 | |||
12 | Synopsis: | ||
13 | |||
14 | $prefix0 <hostname> | ||
15 | |||
16 | $prefix0 --remote-ip <IP address> \\ | ||
17 | $prefix1 --remote-name <hostname> | ||
18 | |||
19 | |||
20 | Usage: | ||
21 | |||
22 | $prefix0 --remote-ip <IP address> \\ | ||
23 | $prefix1 --remote-name <hostname> \\ | ||
24 | $prefix1 [--remote-key-type rsa] \\ | ||
25 | $prefix1 [--local-key <filename>] | ||
26 | |||
27 | |||
28 | Options: | ||
29 | |||
30 | --remote-name | ||
31 | |||
32 | Specify the name that we give to the remote side. | ||
33 | |||
34 | Remote name is mandatory, except that if a single non-option argument is | ||
35 | given, then it is treated as if it were the argument to --remote-name. | ||
36 | |||
37 | --remote-ip | ||
38 | |||
39 | The IP specifies an IP to connect to. | ||
40 | |||
41 | The name specifies a name we give to the remote side. | ||
42 | |||
43 | If not specified, the hostname specified with the --remote-name option | ||
44 | will be resolved to obtain the IP. | ||
45 | |||
46 | --remote-key-type | ||
47 | |||
48 | The remote key will be fetched via the SSH protocol on port 22 on the | ||
49 | remote IP. This option specifies which server key type to request from | ||
50 | the server. | ||
51 | |||
52 | If specified, it must be 'rsa'. | ||
53 | |||
54 | --local-key | ||
55 | |||
56 | This specifies the location of the private key file. | ||
57 | |||
58 | The filename is considered relative to /etc/ssh if it has no slashes. | ||
59 | |||
60 | The default is 'ssh_host_rsa_key'. | ||
61 | |||
62 | The key must be an RSA key. | ||
63 | |||
64 | EOF | ||
65 | } | ||
66 | |||
67 | |||
68 | # Default command line argument values. | ||
69 | NO_ACT=y | ||
70 | REMOTE_KEY_TYPE=rsa | ||
71 | LOCAL_KEY=ssh_host_rsa_key | ||
72 | |||
73 | # Hard-coded private key source and destinations. | ||
74 | LOCAL_KEY_DEST_BASENAME=ssh_host_rsa_key | ||
75 | LOCAL_PRIVATE_KEY_DEST=/etc/swanctl/private/$LOCAL_KEY_DEST_BASENAME | ||
76 | LOCAL_PUBLIC_KEY_DEST=/etc/swanctl/pubkey/$LOCAL_KEY_DEST_BASENAME.pub | ||
77 | |||
78 | die() { printf 'Error: %s\n' "$*" >&2; exit 1; } | ||
79 | warn() { printf 'Warning: %s\n' "$*" >&2; } | ||
80 | |||
81 | parse_options() | ||
82 | { | ||
83 | OPTS=$(getopt \ | ||
84 | --options 'hynf' \ | ||
85 | --longoptions 'help,yes-act,no-act,remote-ip:,remote-name:,remote-key-type:,local-key:' \ | ||
86 | -n connect-vpn \ | ||
87 | -- "$@") | ||
88 | eval set -- "$OPTS" | ||
89 | unset OPTS | ||
90 | |||
91 | while true | ||
92 | do | ||
93 | case "$1" in | ||
94 | -h | --help ) help; exit;; | ||
95 | -y | --yes-act ) NO_ACT=;; | ||
96 | -n | --no-act ) NO_ACT=y;; | ||
97 | --remote-ip ) shift; REMOTE_IP=$1;; | ||
98 | --remote-name ) shift; REMOTE_NAME=$1;; | ||
99 | --remote-key-type ) shift; REMOTE_KEY_TYPE=$1;; | ||
100 | --local-key ) shift; LOCAL_KEY=$1;; | ||
101 | -- ) shift; break;; | ||
102 | esac | ||
103 | shift | ||
104 | done | ||
105 | |||
106 | case "$# $1" in | ||
107 | 1\ *.*.*.cryptonomic.net) CRYPTONOMIC_DOMAIN=$1; shift ;; | ||
108 | 1\ *) REMOTE_NAME=$1; shift ;; | ||
109 | esac | ||
110 | |||
111 | if [ "$CRYPTONOMIC_DOMAIN" ] | ||
112 | then | ||
113 | REMOTE_NAME=${CRYPTONOMIC_DOMAIN%%.*} | ||
114 | [ "$REMOTE_IP" ] || REMOTE_IP=$(resolve_domain_name "$REMOTE_NAME") | ||
115 | |||
116 | elif [ $# != 0 ] | ||
117 | then | ||
118 | help | ||
119 | exit 1 | ||
120 | fi | ||
121 | |||
122 | # The validation functions modify the values to normalize them. | ||
123 | validate_remote_ip || die 'invalid remote ip' | ||
124 | validate_remote_name || die "invalid remote name '$REMOTE_NAME'" | ||
125 | validate_remote_key_type || die 'invalid remote key type' | ||
126 | validate_local_key || die 'invalid local key' | ||
127 | } | ||
128 | |||
129 | resolve_domain_name() | ||
130 | { | ||
131 | normalize_address "$@" | ||
132 | } | ||
133 | |||
134 | normalize_address() | ||
135 | { | ||
136 | local IP | ||
137 | [ "$1" ] || return | ||
138 | IP=$(getent ahosts "$1") | ||
139 | IP=${IP%% *} | ||
140 | [ "$IP" ] || return | ||
141 | echo "$IP" | ||
142 | } | ||
143 | |||
144 | validate_remote_ip() | ||
145 | { | ||
146 | REMOTE_IP=$(normalize_address "${REMOTE_IP:-$REMOTE_NAME}") | ||
147 | } | ||
148 | |||
149 | validate_remote_name() | ||
150 | { | ||
151 | domain_name_regexp='^[[:alpha:]][-.[:alnum:]]*[[:alnum:]]*$' | ||
152 | [[ $REMOTE_NAME =~ $domain_name_regexp ]] | ||
153 | } | ||
154 | |||
155 | validate_remote_key_type() | ||
156 | { | ||
157 | [ "$REMOTE_KEY_TYPE" = rsa ] | ||
158 | } | ||
159 | |||
160 | validate_local_key() | ||
161 | { | ||
162 | # TODO: check that it is RSA | ||
163 | case "$LOCAL_KEY" in | ||
164 | */*) ;; | ||
165 | *) LOCAL_KEY=/etc/ssh/$LOCAL_KEY ;; | ||
166 | esac | ||
167 | [ -f "$LOCAL_KEY" -a -r "$LOCAL_KEY" ] || die "local key ($LOCAL_KEY)" | ||
168 | } | ||
169 | |||
170 | main() | ||
171 | { | ||
172 | parse_options "$@" | ||
173 | if [ "$NO_ACT" ] | ||
174 | then | ||
175 | exec 2>&1 | ||
176 | keycopy | ||
177 | test_new_config | ||
178 | else | ||
179 | die unimplemented | ||
180 | fi | ||
181 | exit | ||
182 | } | ||
183 | |||
184 | match_and_drop_first_word() | ||
185 | { | ||
186 | expect=$1 | ||
187 | while read word rest | ||
188 | do | ||
189 | if [ "$word" = "$expect" ] | ||
190 | then | ||
191 | printf '%s\n' "$rest" | ||
192 | return | ||
193 | fi | ||
194 | done | ||
195 | false | ||
196 | } | ||
197 | |||
198 | keyscan() | ||
199 | { | ||
200 | if [ -e keyscan.cache ] | ||
201 | then | ||
202 | cat keyscan.cache | ||
203 | else | ||
204 | semi_quietly ssh-keyscan -t "${REMOTE_KEY_TYPE}" "$1" | ||
205 | fi | ||
206 | } | ||
207 | |||
208 | write_successfully() | ||
209 | { | ||
210 | local f=$(mktemp) || return | ||
211 | local out="$1" | ||
212 | [ "$2" = -- ] || return | ||
213 | shift 2 | ||
214 | if "$@" > "$f" | ||
215 | then | ||
216 | if [ "$NO_ACT" ] | ||
217 | then | ||
218 | ( | ||
219 | exec >&2 | ||
220 | echo "Write $out:" | ||
221 | case "$(file --mime-encoding "$f")" in | ||
222 | *': binary') xxd "$f" ;; | ||
223 | *) cat "$f" ;; | ||
224 | esac | sed 's/^/ /' | ||
225 | echo | ||
226 | ) | ||
227 | rm -f "$f" | ||
228 | else | ||
229 | mv "$f" "$out" | ||
230 | fi | ||
231 | else | ||
232 | rm -f "$f" | ||
233 | return 1 | ||
234 | fi | ||
235 | } | ||
236 | |||
237 | semi_quietly() | ||
238 | { | ||
239 | local t=$(mktemp) | ||
240 | if "$@" 2>"$t" | ||
241 | then | ||
242 | rm -f "$t" | ||
243 | else | ||
244 | cat "$t" >&2 | ||
245 | fi | ||
246 | } | ||
247 | |||
248 | openssl() | ||
249 | { | ||
250 | semi_quietly command openssl "$@" | ||
251 | } | ||
252 | |||
253 | write_public_key() | ||
254 | { | ||
255 | openssl rsa -in "$1" -outform DER | ||
256 | } | ||
257 | write_private_key() | ||
258 | { | ||
259 | openssl rsa -in "$1" -outform DER -pubout | ||
260 | } | ||
261 | |||
262 | write_remote_key() | ||
263 | { | ||
264 | case "$REMOTE_KEY_TYPE" in | ||
265 | rsa) ssh-keygen -e -f "$1" -m PEM | openssl rsa -RSAPublicKey_in -outform DER ;; | ||
266 | *) echo "Unsupported key type." >&2; exit 1 ;; | ||
267 | esac | ||
268 | } | ||
269 | |||
270 | keycopy() | ||
271 | { | ||
272 | private_key_tmp=$(mktemp) || return | ||
273 | cp "$LOCAL_KEY" "$private_key_tmp" | ||
274 | ssh-keygen -N '' -p -m PEM -f "$private_key_tmp" >/dev/null 2>&1 | ||
275 | trap 'rm -f "$private_key_tmp"' EXIT | ||
276 | |||
277 | write_successfully "$LOCAL_PRIVATE_KEY_DEST" -- write_private_key "$private_key_tmp" | ||
278 | write_successfully "$LOCAL_PUBLIC_KEY_DEST" -- write_public_key "$private_key_tmp" | ||
279 | |||
280 | trap - EXIT | ||
281 | rm -f "$private_key_tmp" | ||
282 | |||
283 | trap 'rm -f "$t"' EXIT | ||
284 | t=$(mktemp) | ||
285 | keyscan "$REMOTE_IP" | match_and_drop_first_word "$REMOTE_IP" > "$t" | ||
286 | write_successfully /etc/swanctl/pubkey/"$REMOTE_NAME".pub -- write_remote_key "$t" | ||
287 | trap - EXIT | ||
288 | rm -f "$t" | ||
289 | } | ||
290 | |||
291 | nocomments() | ||
292 | { | ||
293 | sed 's/#.*//; /^ *$/d' | ||
294 | } | ||
295 | |||
296 | config() | ||
297 | { | ||
298 | local conn="$1" remote_addrs="$2" local_key="$3" | ||
299 | local public_key_file="$4" private_key_file="$5" | ||
300 | local remote_ts=0::0/0 vips=:: | ||
301 | id=$(key_to_suffix "$local_key") || return | ||
302 | sed -e 's/^ //' <<END | ||
303 | connections { | ||
304 | ${conn} { | ||
305 | remote_addrs = ${remote_addrs} | ||
306 | vips = ${vips} | ||
307 | local { | ||
308 | pubkeys = ${public_key_file} | ||
309 | id = ${id} | ||
310 | } | ||
311 | remote { | ||
312 | id = "${remote_addrs}" | ||
313 | pubkeys = ${conn}.pub | ||
314 | } | ||
315 | children { | ||
316 | child { | ||
317 | remote_ts = ${remote_ts} | ||
318 | dpd_action = restart | ||
319 | } | ||
320 | } | ||
321 | } | ||
322 | } | ||
323 | secrets { | ||
324 | private { | ||
325 | file = ${private_key_file} | ||
326 | } | ||
327 | } | ||
328 | END | ||
329 | } | ||
330 | |||
331 | get_my_mac() | ||
332 | { | ||
333 | iface=$(ip -oneline route get "$1" | sed -ne 's/.* dev \([^ ]*\) .*/\1/p') | ||
334 | [ "$iface" ] || return | ||
335 | my_mac=$(ip -oneline -6 addr show dev "$iface" | sed -ne 's/.* inet6 fe80::\([^/]*\)\/.*/\1/p') | ||
336 | [ "$my_mac" ] | ||
337 | } | ||
338 | |||
339 | key_to_suffix() | ||
340 | { | ||
341 | local keytype=1 hashtype=2 | ||
342 | ssh-keygen -r . -f "$1" | sed -E -ne 's/^. IN SSHFP '"$keytype $hashtype"' .{48}(.{4})(.{4})(.{4})(.{4})$/\1:\2:\3:\4/p' | ||
343 | } | ||
344 | |||
345 | NO_ACT() | ||
346 | { | ||
347 | [ "$NO_ACT" ] || "$@" | ||
348 | } | ||
349 | |||
350 | write_config() | ||
351 | { | ||
352 | write_successfully /etc/swanctl/conf.d/"$REMOTE_NAME".conf -- \ | ||
353 | config \ | ||
354 | "$REMOTE_NAME" \ | ||
355 | "$REMOTE_IP" \ | ||
356 | "$LOCAL_KEY" \ | ||
357 | "$LOCAL_PUBLIC_KEY_DEST" \ | ||
358 | "$LOCAL_PRIVATE_KEY_DEST" | ||
359 | } | ||
360 | |||
361 | test_new_config() | ||
362 | { | ||
363 | NO_ACT ipsec stop | ||
364 | |||
365 | write_config | ||
366 | |||
367 | NO_ACT ipsec start | ||
368 | NO_ACT sleep 2 | ||
369 | NO_ACT swanctl -c | ||
370 | NO_ACT ipsec listpubkeys | ||
371 | NO_ACT ipsec up "${REMOTE_NAME}" | ||
372 | } | ||
373 | |||
374 | main "$@" | ||
375 | |||