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