update config even if we already have cert
[vpn-setup] / vpn-mk-client-cert
1 #!/bin/bash
2 # Copyright (C) 2016 Ian Kelling
3
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7
8 # http://www.apache.org/licenses/LICENSE-2.0
9
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 set -eE -o pipefail
17 trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
18
19 [[ $EUID == 0 ]] || exec sudo -E "$BASH_SOURCE" "$@"
20
21 readonly this_file="$(readlink -f -- "${BASH_SOURCE[0]}")"; cd "${this_file%/*}"
22
23
24 usage() {
25 cat <<'EOF'
26 usage: ${0##*/} VPN_SERVER_HOST
27
28 -b COMMON_NAME By default, use $CLIENT_HOST or if it is not given,
29 $HOSTNAME. If the cert already exists on the server,
30 with the CLIENT_NAME name, we use the existing one. See
31 comment below if we ever want to check existing common
32 names. They must be unique per server, so you can use
33 $(uuidgen) if needed. You used to be able to create
34 multiple with the same name, but not connect at the
35 same time, but now, the generator keeps track, so you
36 can't generate.
37
38 -c CLIENT_HOST Default is localhost. Else we ssh to root@CLIENT_HOST.
39 -f Force. Proceed even if cert already exists.
40 -n CONFIG_NAME default is client
41 -o SERVER_CONFIG_NAME Default is CONFIG_NAME
42 -s SCRIPT_PATH Use custom up/down script at SCRIPT_PATH. If client host is
43 not localhost, the script is copied to it. The default
44 script used to be /etc/openvpn/update-resolv-conf, but now
45 that systemd-resolved is becoming popular, there is no default.
46
47 Generate a client cert and config and install it on locally or on
48 CLIENT_HOST if given. Uses default config options, and expects be able
49 to ssh to VPN_SERVER_HOST and CLIENT_HOST as root, or if CLIENT_HOST is
50 localhost, just to sudo this script as root.
51
52
53
54
55 Note: Uses GNU getopt options parsing style
56 EOF
57 exit ${1:-0}
58 }
59
60 # to get the common name
61 # cn=$(s openssl x509 -noout -nameopt multiline -subject \
62 # -in /etc/openvpn/client/mail.crt | \
63 # sed -rn 's/^\s*commonName\s*=\s*(.*)/\1/p')
64
65
66 ####### begin command line parsing and checking ##############
67
68 shell="bash -c"
69 name=client
70 client_host=$CLIENT_HOST
71 force=false
72
73 temp=$(getopt -l help hb:c:fn:o:s: "$@") || usage 1
74 eval set -- "$temp"
75 while true; do
76 case $1 in
77 -b) common_name="$2"; shift 2 ;;
78 -c) client_host=$2; shell="ssh root@$client_host"; shift 2 ;;
79 -f) force=true; shift ;;
80 -n) name="$2"; shift 2 ;;
81 -o) server_name="$2"; shift 2 ;;
82 -s) script="$2"; shift 2 ;;
83 -h|--help) usage ;;
84 --) shift; break ;;
85 *) echo "$0: Internal error! unexpected args: $*" ; exit 1 ;;
86 esac
87 done
88
89 if [[ ! $server_name ]]; then
90 server_name="$name"
91 fi
92
93 if [[ ! $common_name ]]; then
94 if [[ $client_host ]]; then
95 common_name=$client_host
96 else
97 common_name=$HOSTNAME
98 fi
99 fi
100
101 host=$1
102 [[ $host ]] || usage 1
103
104 ####### end command line parsing and checking ##############
105
106
107 f=/etc/openvpn/client/$name.crt
108
109 $shell "dd of=/etc/openvpn/client/$name.conf" <<EOF
110 # From example config, from debian stretch to buster
111 client
112 dev tun
113 proto udp
114 remote $host $port
115 resolv-retry infinite
116 nobind
117 persist-key
118 # persist-tun was here, but if the vpn goes down this makes
119 # the whole thing get stuck if the vpn is our default route
120 # unless we set a special route out just for the vpn.
121 # todo: investigate.
122 ca ca-$name.crt
123 cert $name.crt
124 key $name.key
125 # disabled for better performance
126 #comp-lzo
127 verb 3
128
129 # matching server config
130 cipher AES-256-CBC
131
132 # example config has the commented line, but this other thing looks stronger,
133 # and I've seen it in a vpn provider I trust
134 # ns-cert-type server
135 remote-cert-tls server
136
137 # more resilient when running as nonroot
138 persist-key
139
140 # See comments in server side configuration.
141 # The minimum of the client & server config is what is used by openvpn.
142 reneg-sec 432000
143
144 tls-auth ta-$name.key 1
145 EOF
146
147 if [[ $script ]]; then
148 $shell "tee -a /etc/openvpn/client/$name.conf" <<EOF
149 script-security 2
150 up "$script"
151 down "$script"
152 EOF
153
154 if [[ $client_host && $script ]]; then
155 $shell "dd of=$script" <$script
156 $shell "chmod +x $script"
157 fi
158 fi
159
160 $shell 'cd /etc/openvpn; for f in client/*; do ln -sf $f .; done'
161
162
163 cert_to_test=$f
164 if [[ $client_host ]]; then
165 cert_to_test=$(mktemp)
166 ssh root@$client_host cat $f 2>/dev/null >$cert_to_test ||:
167 fi
168 if ! $force && openssl x509 -checkend $(( 60 * 60 * 24 * 30 )) -noout -in $cert_to_test &>/dev/null; then
169 echo "$0: cert already exists. exiting early"
170 exit 0
171 fi
172
173
174 # bash or else we get motd spam. note sleep 2, sleep 1 failed.
175 $shell '[[ -e /etc/openvpn ]] || apt install openvpn'
176 if ! ssh root@$host bash -s -- $server_name $common_name < client-cert-helper \
177 | $shell 'id -u | grep -xF 0 || s=sudo; $s tar xzv -C /etc/openvpn/client'; then
178 echo ssh root@$host cat /tmp/vpn-mk-client-cert.log:
179 ssh root@$host cat /tmp/vpn-mk-client-cert.log
180 echo EOF for root@$host:/tmp/vpn-mk-client-cert.log
181 exit 1
182 fi
183
184 port=$(echo '/^port/ {print $2}' | ssh root@$host awk -f - /etc/openvpn/server/$name.conf | tail -n1)
185
186
187 if ! $shell "test -s $f"; then
188 # if common name is not unique, you get empty file. and if we didn't silence
189 # build-key, you'd see an error "TXT_DB error number 2"
190 echo "$0: error: $f is empty or otherwise bad. is this common name unique?"
191 exit 1
192 fi