add option, improve error handling
[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 -n CONFIG_NAME default is client
40 -o SERVER_CONFIG_NAME Default is CONFIG_NAME
41 -s SCRIPT_PATH Use custom up/down script at SCRIPT_PATH. copied to same path
42 on client, if client is not localhost.
43
44 Generate a client cert and config and install it on locally or on
45 CLIENT_HOST if given. Uses default config options, and expects be able
46 to ssh to VPN_SERVER_HOST and CLIENT_HOST as root, or if CLIENT_HOST is
47 localhost, just to sudo this script as root.
48
49
50
51
52 Note: Uses GNU getopt options parsing style
53 EOF
54 exit ${1:-0}
55 }
56
57 # to get the common name
58 # cn=$(s openssl x509 -noout -nameopt multiline -subject \
59 # -in /etc/openvpn/client/mail.crt | \
60 # sed -rn 's/^\s*commonName\s*=\s*(.*)/\1/p')
61
62
63 ####### begin command line parsing and checking ##############
64
65 shell="bash -c"
66 name=client
67 custom_script=false
68 script=/etc/openvpn/update-resolv-conf
69 client_host=$CLIENT_HOST
70
71 temp=$(getopt -l help hb:c:n:o:s: "$@") || usage 1
72 eval set -- "$temp"
73 while true; do
74 case $1 in
75 -b) common_name="$2"; shift 2 ;;
76 -c) client_host=$2; shell="ssh root@$client_host"; shift 2 ;;
77 -n) name="$2"; shift 2 ;;
78 -o) server_name="$2"; shift 2 ;;
79 -s) custom_script=true; script="$2"; shift 2 ;;
80 -h|--help) usage ;;
81 --) shift; break ;;
82 *) echo "$0: Internal error! unexpected args: $*" ; exit 1 ;;
83 esac
84 done
85
86 if [[ ! $server_name ]]; then
87 server_name="$name"
88 fi
89
90 if [[ ! $common_name ]]; then
91 if [[ $client_host ]]; then
92 common_name=$client_host
93 else
94 common_name=$HOSTNAME
95 fi
96 fi
97
98 host=$1
99 [[ $host ]] || usage 1
100
101 ####### end command line parsing and checking ##############
102
103 # bash or else we get motd spam. note sleep 2, sleep 1 failed.
104 $shell '[[ -e /etc/openvpn ]] || apt install openvpn'
105 if ! ssh root@$host bash -s -- $server_name $common_name < client-cert-helper \
106 | $shell 'id -u | grep -xF 0 || s=sudo; $s tar xzv -C /etc/openvpn/client'; then
107 echo ssh root@$host cat /tmp/vpn-mk-client-cert.log:
108 ssh root@$host cat /tmp/vpn-mk-client-cert.log
109 echo EOF for root@$host:/tmp/vpn-mk-client-cert.log
110 exit 1
111 fi
112
113 port=$(echo '/^port/ {print $2}' | ssh root@$host awk -f - /etc/openvpn/server/$name.conf | tail -n1)
114
115
116 f=/etc/openvpn/client/$name.crt
117 if ! $shell "test -s $f"; then
118 # if common name is not unique, you get empty file. and if we didn't silence
119 # build-key, you'd see an error "TXT_DB error number 2"
120 echo "$0: error: $f is empty or otherwise bad. is this common name unique?"
121 exit 1
122 fi
123
124 $shell "dd of=/etc/openvpn/client/$name.conf" <<EOF
125 # From example config, from debian stretch to buster
126 client
127 dev tun
128 proto udp
129 remote $host $port
130 resolv-retry infinite
131 nobind
132 persist-key
133 persist-tun
134 ca ca-$name.crt
135 cert $name.crt
136 key $name.key
137 # disabled for better performance
138 #comp-lzo
139 verb 3
140
141 # matching server config
142 cipher AES-256-CBC
143
144 # example config has the commented line, but this other thing looks stronger,
145 # and I've seen it in a vpn provider I trust
146 # ns-cert-type server
147 remote-cert-tls server
148
149 # more resilient when running as nonroot
150 persist-key
151
152 # See comments in server side configuration.
153 # The minimum of the client & server config is what is used by openvpn.
154 reneg-sec 432000
155
156 tls-auth ta-$name.key 1
157 EOF
158
159 if [[ $script ]]; then
160 $shell "tee -a /etc/openvpn/client/$name.conf" <<EOF
161 # This script will update local dns
162 # to what the server sends, if it sends dns.
163 script-security 2
164 up "$script"
165 down "$script"
166 EOF
167
168 if [[ $client_host ]] && $custom_script; then
169 $shell "dd of=$script" <$script
170 $shell "chmod +x $script"
171 fi
172 fi
173
174 $shell 'cd /etc/openvpn; for f in client/*; do ln -sf $f .; done'