fixes and new options
[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 fi
158
159
160 if [[ $t == apache2 ]]; then
161 rm -f $se/000-default.conf
162 # note, we exepct ServerRoot of /etc/apache2
163 # apache requires exactly 1 listen directive per port (when no ip is also given),
164 # so we have to parse the config to do it programatically.
165 listen_80=false
166 listen_port=false
167 cd /etc/apache2
168 conf_files=(apache2.conf)
169
170
171 for (( i=0; i < ${#conf_files[@]}; i++ )); do
172 f="${conf_files[i]}"
173 # note: globs are expanded here.
174 conf_files+=( $(sed -rn "s,^\s*Include(Optional)?\s+(\S+).*,\2,p" "$f") )
175 case $(readlink -f "$f") in
176 $vhost_file|$redir_file) continue ;;
177 esac
178 for p in $(sed -rn "s,^\s*listen\s+(\S+).*,\1,Ip" "$f"); do
179 case $p in
180 80) listen_80=true ;;&
181 $port) listen_port=true ;;
182 esac
183 done
184 done
185
186 echo "$0: creating $vhost_file"
187 cat >$vhost_file <<EOF
188 <VirtualHost $vhostip:$port>
189 ServerName $h
190 ServerAlias www.$h
191 DocumentRoot $root
192 <Directory $root>
193 Options -Indexes ${symlinkarg}FollowSymlinks
194 </Directory>
195 EOF
196
197 if [[ $extra_settings ]]; then
198 cat -- $extra_settings >>$vhost_file
199 fi
200
201 # go faster!
202 if [[ -e /etc/apache2/mods-available/http2.load ]]; then
203 # https://httpd.apache.org/docs/2.4/mod/mod_http2.html
204 a2enmod -q http2
205 cat >>$vhost_file <<EOF
206 Protocols h2 http/1.1
207 EOF
208 fi
209
210 if [[ $proxy ]]; then
211 a2enmod -q proxy proxy_http
212 # fyi: trailing slash is important
213 # reference: https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html
214 # retry=0: https://stackoverflow.com/questions/683052/why-am-i-getting-an-apache-proxy-503-error
215 cat >>$vhost_file <<EOF
216 ProxyPass "/" "http://$proxy/" retry=0
217 ProxyPassReverse "/" "http://$proxy/"
218 EOF
219 fi
220
221
222 if $ssl; then
223 a2enmod -q headers
224 https_arg=" https"
225 common_ssl_conf=/etc/apache2/common-ssl.conf
226 cat >>$vhost_file <<EOF
227 SSLCertificateFile $cert_dir/fullchain.pem
228 SSLCertificateKeyFile $cert_dir/privkey.pem
229 Include $common_ssl_conf
230 # From cerbot generated config example, taken 4/2017,
231 # should be rechecked once a year or so.
232 Header always set Strict-Transport-Security "max-age=31536000"
233 SSLUseStapling on
234 Header always set Content-Security-Policy upgrade-insecure-requests
235 EOF
236
237 if (( port == 443 )); then
238 echo "$0: creating $redir_file"
239 cat >$redir_file <<EOF
240 <VirtualHost *:80>
241 ServerName $h
242 ServerAdmin webmaster@localhost
243 DocumentRoot /var/www/html
244
245 ErrorLog \${APACHE_LOG_DIR}/error.log
246 CustomLog \${APACHE_LOG_DIR}/access.log vhost_time_combined
247
248 RewriteEngine on
249 RewriteCond %{SERVER_NAME} =$h
250 RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]
251 </VirtualHost>
252 EOF
253 if ! $listen_80; then
254 cat >>$redir_file <<'EOF'
255 Listen 80
256 EOF
257 fi
258 fi
259
260 # this is a copy of a file certbot, see below.
261 echo "$0: creating $common_ssl_conf"
262 cat >$common_ssl_conf <<'EOF'
263 # This file contains important security parameters. If you modify this file
264 # manually, Certbot will be unable to automatically provide future security
265 # updates. Instead, Certbot will print and log an error message with a path to
266 # the up-to-date file that you will need to refer to when manually updating
267 # this file.
268
269 SSLEngine on
270
271 # Intermediate configuration, tweak to your needs
272 SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
273 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
274 SSLHonorCipherOrder off
275 SSLSessionTickets off
276
277 SSLOptions +StrictRequire
278
279 # Add vhost name to log entries:
280 LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
281 LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
282 EOF
283
284 upstream=https://raw.githubusercontent.com/certbot/certbot/master/certbot-apache/certbot_apache/_internal/tls_configs/current-options-ssl-apache.conf
285 if ! diff -u <(wget -q -O - $upstream) $common_ssl_conf; then
286 cat <<EOF
287 WARNING!!!!!!!!!
288 WARNING!!!!!!!!!
289 WARNING!!!!!!!!!
290 WARNING!!!!!!!!!
291 WARNING!!!!!!!!!
292 upstream ssl settings differ from the snapshot we have taken!!!
293 We diffed with this command:
294 diff -c <(wget -q -O - $upstream) $common_ssl_conf
295 Update this script to take care this warning!!!!!
296 EOF
297 sleep 1
298 fi
299 fi # end if $ssl
300
301 cat >>$vhost_file <<'EOF'
302 ErrorLog ${APACHE_LOG_DIR}/error.log
303 CustomLog ${APACHE_LOG_DIR}/access.log vhost_time_combined
304 </VirtualHost>
305 EOF
306
307 if ! $listen_port; then
308 # reference: https://httpd.apache.org/docs/2.4/mod/mpm_common.html#listen
309 cat >>$vhost_file <<EOF
310 listen ${listenip}${port}${https_arg}
311 EOF
312 fi
313
314
315 a2enmod -q ssl rewrite # rewrite needed for httpredir
316 service apache2 restart
317
318 # I rarely look at how much traffic I get, so let's keep that info
319 # around for longer than the default of 2 weeks.
320 sed -ri --follow-symlinks 's/^(\s*rotate\s).*/\1 365/' /etc/logrotate.d/apache2
321 fi ###### end if apache
322
323 if [[ $t == nginx ]]; then
324 common_ssl_conf=/etc/nginx/common-ssl.conf
325
326 rm -f $se/default
327 cd /etc/nginx
328 [[ -e dh2048.pem ]] || openssl dhparam -out dh2048.pem 2048
329
330 if $ssl; then
331 ssl_arg=ssl
332 if nginx -V |& grep -- '--with-http_v2_module\b' &>/dev/null; then
333 # fun fact: nginx can be configured to do http2 without ssl.
334 ssl_arg+=" http2"
335 fi
336 fi
337
338 cat >$common_ssl_conf <<'EOF'
339 # let's encrypt gives us a bad nginx config, so use this:
340 # https://mozilla.github.io/server-side-tls/ssl-config-generator/
341 # using modern config. last checked 2017/4/22
342 ssl_session_timeout 1d;
343 ssl_session_cache shared:SSL:50m;
344 ssl_session_tickets off;
345
346 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
347 ssl_dhparam /etc/nginx/dh2048.pem;
348
349 # modern configuration. tweak to your needs.
350 ssl_protocols TLSv1.2;
351 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';
352 ssl_prefer_server_ciphers on;
353
354 # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
355 add_header Strict-Transport-Security max-age=15768000;
356
357 # OCSP Stapling ---
358 # fetch OCSP records from URL in ssl_certificate and cache them
359 ssl_stapling on;
360 ssl_stapling_verify on;
361
362 ## verify chain of trust of OCSP response using Root CA and Intermediate certs
363 # ian: commented out, unnecessary for le certs or my nginx ver.
364 #ssl_trusted_certificate $cert_dir/fullchain.pem;;
365
366 # ian: commented out, our local dns is expected to work fine.
367 #resolver <IP DNS resolver>;
368 EOF
369 cat >$vhost_file <<EOF
370 server {
371 server_name $h www.$h;
372 root $root;
373 listen $listenip$port $ssl_arg;
374 EOF
375 if [[ ! $listenip ]]; then
376 cat >>$vhost_file <<EOF
377 listen [::]:$port $ssl_arg;
378 EOF
379 fi
380 cat >>$vhost_file <<EOF
381 location $root {
382 autoindex off;
383 }
384 EOF
385 if $ssl; then
386 cat >>$vhost_file <<EOF
387 ssl_certificate $cert_dir/fullchain.pem;
388 ssl_certificate_key $cert_dir/privkey.pem;
389 include $common_ssl_conf;
390 EOF
391
392 if (( port == 443 )); then
393 cat >$redir_file <<EOF
394 server {
395 server_name $h www.$h;
396 listen 80 $http2_arg;
397 listen [::]:80 $http2_arg;
398 return 301 https://$server_name$request_uri;
399 }
400 EOF
401 fi
402 fi # end if $ssl
403
404 if [[ $extra_settings ]]; then
405 cat $extra_settings >>$vhost_file
406 fi
407
408 if [[ $proxy ]]; then
409 cat >>$vhost_file <<EOF
410 location / {
411 proxy_set_header Host \$host;
412 proxy_set_header X-Real-IP \$remote_addr;
413 proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
414 proxy_set_header X-Forwarded-Ssl on;
415 proxy_set_header X-Forwarded-Port $port;
416 proxy_pass http://$proxy;
417 }
418 EOF
419 fi
420
421 cat >>$vhost_file <<EOF
422 }
423 EOF
424
425
426 service nginx restart
427
428 fi ####### end if nginx
429
430 cat >/etc/apache2/conf-enabled/local-custom.conf <<'EOF'
431 # vhost_combined with %D (request time in microseconds)
432 # this file is just a convenient place to drop it.
433 LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %D" vhost_time_combined
434 SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)
435 EOF