do deploy hooks on initial cert
[basic-https-conf] / web-conf
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 [[ $EUID == 0 ]] || exec sudo -E "$BASH_SOURCE" "$@"
17
18 set -eE -o pipefail
19 trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
20
21 readonly this_file="$(readlink -f -- "${BASH_SOURCE[0]}")"
22 readonly this_dir="${this_file%/*}"
23
24 shopt -s nullglob # used in apache config file expansion
25
26 usage() {
27 cat <<EOF
28 Usage: ${0##*/} [OPTIONS] [EXTRA_SETTINGS_FILE] apache2|nginx DOMAIN
29 apache/nginx config & let's encrypt
30
31 If using tls then it expects certbot to be installed and in PATH. Also,
32 certbot cronjob should be taken care of outside this script. In the
33 debian package, it installs a systemd timer. If a script exists (I
34 expect it only on my , Ian Kelling's sytem) we install a systemd timer
35 to on failure. You can see the relevant script in my git repo
36 distro-setup, and log-quiet.
37
38
39 /a/bin/distro-setup/certbot-renew-hook
40
41
42
43 EXTRA_SETTINGS_FILE can be - for stdin
44 -a IPv4_ADDR IP address to listen on. Default all addresses.
45 ipv6 address support could be added to this script.
46 -e EMAIL Contact address for let's encrypt. Default is
47 root@\$(hostname --fqdn')
48 which is root@$(hostname --fqdn) on this host.
49 -f [ADDR:]PORT Enable proxy to [ADDR:]PORT. ADDR default is 127.0.0.1
50 -i Insecure, no ssl.
51 -p PORT Main port to listen on, default 443. 80 implies -i.
52 -r DIR DocumentRoot
53 -s Allow symlinks from the doucmentroot
54 -h|--help Print help and exit
55
56 Note: Uses GNU getopt options parsing style
57 EOF
58 exit $1
59 }
60
61 ##### begin command line parsing ########
62
63 symlinkarg=-
64 ssl=true
65 extra_settings=
66 port=443
67 temp=$(getopt -l help a:e:if:p:r:sh "$@") || usage 1
68 vhostip='*'
69 eval set -- "$temp"
70 while true; do
71 case $1 in
72 -a)
73 listenip="$2:"
74 vhostip="$2"
75 shift 2 ;;
76 -e) email="$2"; shift 2 ;;
77 -f) proxy="$2"; shift 2 ;;
78 -i) ssl=false; shift ;;
79 -p) port="$2"; shift 2 ;;
80 -r) root="$2"; shift 2 ;;
81 -s) symlinkarg=+; shift ;;
82 --) shift; break ;;
83 -h|--help) usage ;;
84 *) echo "$0: Internal error!" ; exit 1 ;;
85 esac
86 done
87
88 # t = type, h = host
89 if (( ${#@} == 3 )); then
90 read -r extra_settings t h <<<"${@}"
91 else
92 read -r t h <<<"${@}"
93 fi
94
95 case $t in
96 apache2|nginx) : ;;
97 *) echo "$0: error: expected apache2 or nginx arg"; usage 1 ;;
98 esac
99
100 if [[ ! $h ]]; then
101 echo "$0: error: expected domain and type arg"
102 usage 1
103 fi
104
105 if [[ ! $root ]]; then
106 root=/var/www/$h/html
107 fi
108
109 if [[ $proxy ]]; then
110 [[ $proxy == *:* ]] || proxy=127.0.0.1:$proxy
111 fi
112
113 if [[ ! $email ]]; then
114 email=root@$(hostname --fqdn)
115 fi
116
117
118 ##### end command line parsing ########
119
120 se=/etc/$t/sites-enabled
121 cert_dir=/etc/letsencrypt/live/$h
122
123 mkdir -p $root
124 case $port in
125 80|443)
126 vhost_file=$se/$h.conf
127 ;;
128 *)
129 vhost_file=$se/$h-$port.conf
130 ;;
131 esac
132 redir_file=$se/$h-redir.conf
133
134 if [[ $port == 80 ]]; then
135 ssl=false
136 # remove any thats hanging around
137 rm -f $redir_file
138 fi
139
140
141 if $ssl; then
142
143 $this_dir/certbot-setup $t
144
145 f=$cert_dir/fullchain.pem
146 threedays=259200 # in seconds
147 if [[ ! -e $f ]] || ! openssl x509 -checkend $threedays -noout -in $f >/dev/null; then
148 # cerbot needs an existing virtualhost.
149 $0 -p 80 $t $h
150 # when generating an example config, add all relevant security options:
151 # --hsts --staple-ocsp --uir --must-staple
152 certbot certonly -n --email $email --no-self-upgrade \
153 --agree-tos --${t%2} -d $h
154 # cleanup the call to ourselves a short bit ago
155 rm $se/$h.conf
156 fi
157 # these scripts only run on renew, that is kinda dumb.
158 export RENEWED_LINEAGE=/etc/letsencrypt/live/$h
159 for script in /etc/letsencrypt/renewal-hooks/deploy/*; do
160 if [[ -x $script ]]; then
161 "$script"
162 fi
163 done
164 fi
165
166
167 if [[ $t == apache2 ]]; then
168 rm -f $se/000-default.conf
169 # note, we exepct ServerRoot of /etc/apache2
170 # apache requires exactly 1 listen directive per port (when no ip is also given),
171 # so we have to parse the config to do it programatically.
172 listen_80=false
173 listen_port=false
174 cd /etc/apache2
175 conf_files=(apache2.conf)
176
177
178 for (( i=0; i < ${#conf_files[@]}; i++ )); do
179 f="${conf_files[i]}"
180 # note: globs are expanded here.
181 conf_files+=( $(sed -rn "s,^\s*Include(Optional)?\s+(\S+).*,\2,p" "$f") )
182 case $(readlink -f "$f") in
183 $vhost_file|$redir_file) continue ;;
184 esac
185 for p in $(sed -rn "s,^\s*listen\s+(\S+).*,\1,Ip" "$f"); do
186 case $p in
187 80) listen_80=true ;;&
188 $port) listen_port=true ;;
189 esac
190 done
191 done
192
193 echo "$0: creating $vhost_file"
194 cat >$vhost_file <<EOF
195 <VirtualHost $vhostip:$port>
196 ServerName $h
197 ServerAlias www.$h
198 DocumentRoot $root
199 <Directory $root>
200 Options -Indexes ${symlinkarg}FollowSymlinks
201 </Directory>
202 EOF
203
204 if [[ $extra_settings ]]; then
205 cat -- $extra_settings >>$vhost_file
206 fi
207
208 # go faster!
209 if [[ -e /etc/apache2/mods-available/http2.load ]]; then
210 # https://httpd.apache.org/docs/2.4/mod/mod_http2.html
211 a2enmod -q http2
212 cat >>$vhost_file <<EOF
213 Protocols h2 http/1.1
214 EOF
215 fi
216
217 if [[ $proxy ]]; then
218 a2enmod -q proxy proxy_http
219 # fyi: trailing slash is important
220 # reference: https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html
221 # retry=0: https://stackoverflow.com/questions/683052/why-am-i-getting-an-apache-proxy-503-error
222 cat >>$vhost_file <<EOF
223 ProxyPass "/" "http://$proxy/" retry=0
224 ProxyPassReverse "/" "http://$proxy/"
225 EOF
226 fi
227
228
229 if $ssl; then
230 a2enmod -q headers
231 https_arg=" https"
232 common_ssl_conf=/etc/apache2/common-ssl.conf
233 cat >>$vhost_file <<EOF
234 SSLCertificateFile $cert_dir/fullchain.pem
235 SSLCertificateKeyFile $cert_dir/privkey.pem
236 Include $common_ssl_conf
237 # From cerbot generated config example, taken 4/2017,
238 # should be rechecked once a year or so.
239 Header always set Strict-Transport-Security "max-age=31536000"
240 SSLUseStapling on
241 Header always set Content-Security-Policy upgrade-insecure-requests
242 EOF
243
244 if (( port == 443 )); then
245 echo "$0: creating $redir_file"
246 cat >$redir_file <<EOF
247 <VirtualHost *:80>
248 ServerName $h
249 ServerAdmin webmaster@localhost
250 DocumentRoot /var/www/html
251
252 ErrorLog \${APACHE_LOG_DIR}/error.log
253 CustomLog \${APACHE_LOG_DIR}/access.log vhost_time_combined
254
255 RewriteEngine on
256 RewriteCond %{SERVER_NAME} =$h
257 RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]
258 </VirtualHost>
259 EOF
260 if ! $listen_80; then
261 cat >>$redir_file <<'EOF'
262 Listen 80
263 EOF
264 fi
265 fi
266
267 # this is a copy of a file certbot, see below.
268 echo "$0: creating $common_ssl_conf"
269 cat >$common_ssl_conf <<'EOF'
270 # This file contains important security parameters. If you modify this file
271 # manually, Certbot will be unable to automatically provide future security
272 # updates. Instead, Certbot will print and log an error message with a path to
273 # the up-to-date file that you will need to refer to when manually updating
274 # this file.
275
276 SSLEngine on
277
278 # Intermediate configuration, tweak to your needs
279 SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
280 SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
281 SSLHonorCipherOrder off
282 SSLSessionTickets off
283
284 SSLOptions +StrictRequire
285
286 # Add vhost name to log entries:
287 LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
288 LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
289 EOF
290
291 upstream=https://raw.githubusercontent.com/certbot/certbot/master/certbot-apache/certbot_apache/_internal/tls_configs/current-options-ssl-apache.conf
292 if ! diff -u <(wget -q -O - $upstream) $common_ssl_conf; then
293 cat <<EOF
294 WARNING!!!!!!!!!
295 WARNING!!!!!!!!!
296 WARNING!!!!!!!!!
297 WARNING!!!!!!!!!
298 WARNING!!!!!!!!!
299 upstream ssl settings differ from the snapshot we have taken!!!
300 We diffed with this command:
301 diff -c <(wget -q -O - $upstream) $common_ssl_conf
302 Update this script to take care this warning!!!!!
303 EOF
304 sleep 1
305 fi
306 fi # end if $ssl
307
308 cat >>$vhost_file <<'EOF'
309 ErrorLog ${APACHE_LOG_DIR}/error.log
310 CustomLog ${APACHE_LOG_DIR}/access.log vhost_time_combined
311 </VirtualHost>
312 EOF
313
314 if ! $listen_port; then
315 # reference: https://httpd.apache.org/docs/2.4/mod/mpm_common.html#listen
316 cat >>$vhost_file <<EOF
317 listen ${listenip}${port}${https_arg}
318 EOF
319 fi
320
321
322 a2enmod -q ssl rewrite # rewrite needed for httpredir
323 service apache2 restart
324
325 # I rarely look at how much traffic I get, so let's keep that info
326 # around for longer than the default of 2 weeks.
327 sed -ri --follow-symlinks 's/^(\s*rotate\s).*/\1 365/' /etc/logrotate.d/apache2
328 fi ###### end if apache
329
330 if [[ $t == nginx ]]; then
331 common_ssl_conf=/etc/nginx/common-ssl.conf
332
333 rm -f $se/default
334 cd /etc/nginx
335 [[ -e dh2048.pem ]] || openssl dhparam -out dh2048.pem 2048
336
337 if $ssl; then
338 ssl_arg=ssl
339 if nginx -V |& grep -- '--with-http_v2_module\b' &>/dev/null; then
340 # fun fact: nginx can be configured to do http2 without ssl.
341 ssl_arg+=" http2"
342 fi
343 fi
344
345 cat >$common_ssl_conf <<'EOF'
346 # let's encrypt gives us a bad nginx config, so use this:
347 # https://mozilla.github.io/server-side-tls/ssl-config-generator/
348 # using modern config. last checked 2017/4/22
349 ssl_session_timeout 1d;
350 ssl_session_cache shared:SSL:50m;
351 ssl_session_tickets off;
352
353 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
354 ssl_dhparam /etc/nginx/dh2048.pem;
355
356 # modern configuration. tweak to your needs.
357 ssl_protocols TLSv1.2;
358 ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
359 ssl_prefer_server_ciphers on;
360
361 # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
362 add_header Strict-Transport-Security max-age=15768000;
363
364 # OCSP Stapling ---
365 # fetch OCSP records from URL in ssl_certificate and cache them
366 ssl_stapling on;
367 ssl_stapling_verify on;
368
369 ## verify chain of trust of OCSP response using Root CA and Intermediate certs
370 # ian: commented out, unnecessary for le certs or my nginx ver.
371 #ssl_trusted_certificate $cert_dir/fullchain.pem;;
372
373 # ian: commented out, our local dns is expected to work fine.
374 #resolver <IP DNS resolver>;
375 EOF
376 cat >$vhost_file <<EOF
377 server {
378 server_name $h www.$h;
379 root $root;
380 listen $listenip$port $ssl_arg;
381 EOF
382 if [[ ! $listenip ]]; then
383 cat >>$vhost_file <<EOF
384 listen [::]:$port $ssl_arg;
385 EOF
386 fi
387 cat >>$vhost_file <<EOF
388 location $root {
389 autoindex off;
390 }
391 EOF
392 if $ssl; then
393 cat >>$vhost_file <<EOF
394 ssl_certificate $cert_dir/fullchain.pem;
395 ssl_certificate_key $cert_dir/privkey.pem;
396 include $common_ssl_conf;
397 EOF
398
399 if (( port == 443 )); then
400 cat >$redir_file <<EOF
401 server {
402 server_name $h www.$h;
403 listen 80 $http2_arg;
404 listen [::]:80 $http2_arg;
405 return 301 https://$server_name$request_uri;
406 }
407 EOF
408 fi
409 fi # end if $ssl
410
411 if [[ $extra_settings ]]; then
412 cat $extra_settings >>$vhost_file
413 fi
414
415 if [[ $proxy ]]; then
416 cat >>$vhost_file <<EOF
417 location / {
418 proxy_set_header Host \$host;
419 proxy_set_header X-Real-IP \$remote_addr;
420 proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
421 proxy_set_header X-Forwarded-Ssl on;
422 proxy_set_header X-Forwarded-Port $port;
423 proxy_pass http://$proxy;
424 }
425 EOF
426 fi
427
428 cat >>$vhost_file <<EOF
429 }
430 EOF
431
432
433 service nginx restart
434
435 fi ####### end if nginx
436
437 cat >/etc/apache2/conf-enabled/local-custom.conf <<'EOF'
438 # vhost_combined with %D (request time in microseconds)
439 # this file is just a convenient place to drop it.
440 LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %D" vhost_time_combined
441 SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)
442 EOF