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