d495754fac80df39b1f1fb7f4e7593cd0e9bb436
[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
22 usage() {
23 cat <<EOF
24 usage: ${0##*/} VPN_SERVER_HOST
25
26 -b COMMON_NAME By default, use $HOSTNAME or $CLIENT_HOST. If the cert
27 already exists on the server, with the CLIENT_NAME
28 name, we use the existing one. See comment below if we
29 ever want to check existing common names. They must be
30 unique per server, so you can use $(uuidgen) if
31 needed. You used to be able to create multiple with the
32 same name, but not connect at the same time, but now,
33 the generator keeps track, so you can't generate.
34 -c CLIENT_HOST default is localhost. Else we ssh to root@CLIENT_HOST
35 -n CONFIG_NAME default is client
36 -s SCRIPT_PATH Use custom up/down script at PATH, copied to same path
37 on client.
38
39 Generate a client cert and config and install it on locally or on
40 CLIENT_HOST if given. Uses default config options, and expects be able
41 to ssh to VPN_SERVER_HOST and CLIENT_HOST as root, or if CLIENT_HOST is
42 localhost, just to sudo this script as root.
43
44
45
46
47 Note: Uses GNU getopt options parsing style
48 EOF
49 exit ${1:-0}
50 }
51
52 # to get the common name
53 # cn=$(s openssl x509 -noout -nameopt multiline -subject \
54 # -in /etc/openvpn/client/mail.crt | \
55 # sed -rn 's/^\s*commonName\s*=\s*(.*)/\1/p')
56
57
58 ####### begin command line parsing and checking ##############
59
60 shell="bash -c"
61 name=client
62 custom_script=false
63 script=/etc/openvpn/update-resolv-conf
64
65 temp=$(getopt -l help hb:c:n:s: "$@") || usage 1
66 eval set -- "$temp"
67 while true; do
68 case $1 in
69 -b) common_name="$2"; shift 2 ;;
70 -c) client_host=$2; shell="ssh root@$client_host"; shift 2 ;;
71 -n) name="$2"; shift 2 ;;
72 -s) custom_script=true; script="$2"; shift 2 ;;
73 -h|--help) usage ;;
74 --) shift; break ;;
75 *) echo "$0: Internal error! unexpected args: $*" ; exit 1 ;;
76 esac
77 done
78
79 if [[ ! $common_name ]]; then
80 if [[ $client_host ]]; then
81 common_name=$client_host
82 else
83 common_name=$HOSTNAME
84 fi
85 fi
86
87 host=$1
88 [[ $host ]] || usage 1
89
90 ####### end command line parsing and checking ##############
91
92
93 # bash or else we get motd spam. note sleep 2, sleep 1 failed.
94 ssh root@$host bash <<EOF | $shell 'id -u | grep -xF 0 || s=sudo; $s tar xzv -C /etc/openvpn/client'
95 set -eE -o pipefail
96
97 server_dir=/etc/openvpn
98 if [[ -e /etc/openvpn/server ]]; then
99 server_dir=/etc/openvpn/server
100 fi
101
102 exists=true
103 for x in /etc/openvpn/easy-rsa/keys/{$name.{crt,key},ca.crt}; do
104 if [[ ! -e \$x ]]; then
105 exists=false
106 break
107 fi
108 done
109
110 if ! \$exists; then
111 cd /etc/openvpn/easy-rsa
112 source vars >/dev/null
113
114 { echo -e '\n\n\n\n\n'$common_name'\n\n\n\n\n'; sleep 2; echo -e 'y\ny\n'; } | ./build-key $name &>/dev/null
115 fi
116
117 d=\$(mktemp -d)
118 cp /etc/openvpn/easy-rsa/keys/ca.crt \$d/$name-ca.crt
119 cp /etc/openvpn/easy-rsa/keys/$name.{crt,key} \$d
120
121 cp \$server_dir/ta.key \$d/$name-ta.key
122
123 tar cz -C \$d .
124 rm -rf \$d
125 EOF
126
127 f=/etc/openvpn/client/$name.crt
128 if ! $shell "test -s $f"; then
129 # if common name is not unique, you get empty file. and if we didn't silence
130 # build-key, you'd see an error "TXT_DB error number 2"
131 echo "$0: error: $f is empty or otherwise bad. is this common name unique?"
132 exit 1
133 fi
134
135 $shell "dd of=/etc/openvpn/client/$name.conf" <<EOF
136 # From example config, from debian stretch as of 1-2017
137 client
138 dev tun
139 proto udp
140 remote $host 1194
141 resolv-retry infinite
142 nobind
143 persist-key
144 persist-tun
145 ca $name-ca.crt
146 cert $name.crt
147 key $name.key
148 # disabled for better performance
149 #comp-lzo
150 verb 3
151
152 # This script will update local dns
153 # to what the server sends, if it sends dns.
154 script-security 2
155 up $script
156 down $script
157
158 # matching server config
159 cipher AES-256-CBC
160
161
162 # example config has the commented line, but this other thing looks stronger,
163 # and I've seen it in a vpn provider I trust
164 # ns-cert-type server
165 remote-cert-tls server
166
167 # more resilient when running as nonroot
168 persist-key
169
170 # See comments in server side configuration.
171 # The minimum of the client & server config is what is used by openvpn.
172 reneg-sec 432000
173
174 tls-auth $name-ta.key 1
175 EOF
176
177 if [[ $client_host ]] && $custom_script; then
178 $shell "dd of=$script" <$script
179 $shell "chmod +x $script"
180 fi
181
182 $shell 'cd /etc/openvpn; for f in client/*; do ln -sf $f .; done'