document alternate redirect scheme
[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
247 # note, alternatively:
248 cat >/dev/null <<'EOF'
249 #https://webmasters.stackexchange.com/questions/124635/apache-redirect-http-to-https-without-preventing-http
250 <If "%{req:Upgrade-Insecure-Requests} == '1'">
251 Redirect permanent "/" "https://mydomain.ltd/"
252 </If>
253 # or, with generic rewrite, we use this on gnu.org
254 RewriteEngine on
255 RewriteCond %{HTTP:Upgrade-Insecure-Requests} "^1$"
256 RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=307]
257 EOF
258
259 cat >$redir_file <<EOF
260 <VirtualHost *:80>
261 ServerName $h
262 ServerAdmin webmaster@localhost
263 DocumentRoot /var/www/html
264
265 ErrorLog \${APACHE_LOG_DIR}/error.log
266 CustomLog \${APACHE_LOG_DIR}/access.log vhost_time_combined
267
268 RewriteEngine on
269 RewriteCond %{SERVER_NAME} =$h
270 RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]
271 </VirtualHost>
272 EOF
273 if ! $listen_80; then
274 cat >>$redir_file <<'EOF'
275 Listen 80
276 EOF
277 fi
278 fi
279
280 # this is a copy of a file certbot, see below.
281 echo "$0: creating $common_ssl_conf"
282 cat >$common_ssl_conf <<'EOF'
283 # This file contains important security parameters. If you modify this file
284 # manually, Certbot will be unable to automatically provide future security
285 # updates. Instead, Certbot will print and log an error message with a path to
286 # the up-to-date file that you will need to refer to when manually updating
287 # this file. Contents are based on https://ssl-config.mozilla.org
288
289 SSLEngine on
290
291 # Intermediate configuration, tweak to your needs
292 SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
293 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
294 SSLHonorCipherOrder off
295 SSLSessionTickets off
296
297 SSLOptions +StrictRequire
298
299 # Add vhost name to log entries:
300 LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
301 LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
302 EOF
303
304 upstream=https://raw.githubusercontent.com/certbot/certbot/master/certbot-apache/certbot_apache/_internal/tls_configs/current-options-ssl-apache.conf
305 if ! diff -u <(wget -q -O - $upstream) $common_ssl_conf; then
306 cat <<EOF
307 WARNING!!!!!!!!!
308 WARNING!!!!!!!!!
309 WARNING!!!!!!!!!
310 WARNING!!!!!!!!!
311 WARNING!!!!!!!!!
312 upstream ssl settings differ from the snapshot we have taken!!!
313 We diffed with this command:
314 diff -c <(wget -q -O - $upstream) $common_ssl_conf
315 Update this script to take care this warning!!!!!
316 EOF
317 sleep 1
318 fi
319 fi # end if $ssl
320
321 cat >>$vhost_file <<'EOF'
322 ErrorLog ${APACHE_LOG_DIR}/error.log
323 CustomLog ${APACHE_LOG_DIR}/access.log vhost_time_combined
324 </VirtualHost>
325 EOF
326
327 if ! $listen_port; then
328 # reference: https://httpd.apache.org/docs/2.4/mod/mpm_common.html#listen
329 cat >>$vhost_file <<EOF
330 listen ${listenip}${port}${https_arg}
331 EOF
332 fi
333
334
335 a2enmod -q ssl rewrite # rewrite needed for httpredir
336 service apache2 restart
337
338 # I rarely look at how much traffic I get, so let's keep that info
339 # around for longer than the default of 2 weeks.
340 sed -ri --follow-symlinks 's/^(\s*rotate\s).*/\1 365/' /etc/logrotate.d/apache2
341 fi ###### end if apache
342
343 if [[ $t == nginx ]]; then
344 common_ssl_conf=/etc/nginx/common-ssl.conf
345
346 rm -f $se/default
347 cd /etc/nginx
348 [[ -e dh2048.pem ]] || openssl dhparam -out dh2048.pem 2048
349
350 if $ssl; then
351 ssl_arg=ssl
352 if nginx -V |& grep -- '--with-http_v2_module\b' &>/dev/null; then
353 # fun fact: nginx can be configured to do http2 without ssl.
354 ssl_arg+=" http2"
355 fi
356 fi
357
358 cat >$common_ssl_conf <<'EOF'
359 # let's encrypt gives us a bad nginx config, so use this:
360 # https://mozilla.github.io/server-side-tls/ssl-config-generator/
361 # using modern config. last checked 2017/4/22
362 ssl_session_timeout 1d;
363 ssl_session_cache shared:SSL:50m;
364 ssl_session_tickets off;
365
366 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
367 ssl_dhparam /etc/nginx/dh2048.pem;
368
369 # modern configuration. tweak to your needs.
370 ssl_protocols TLSv1.2;
371 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';
372 ssl_prefer_server_ciphers on;
373
374 # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
375 add_header Strict-Transport-Security max-age=15768000;
376
377 # OCSP Stapling ---
378 # fetch OCSP records from URL in ssl_certificate and cache them
379 ssl_stapling on;
380 ssl_stapling_verify on;
381
382 ## verify chain of trust of OCSP response using Root CA and Intermediate certs
383 # ian: commented out, unnecessary for le certs or my nginx ver.
384 #ssl_trusted_certificate $cert_dir/fullchain.pem;;
385
386 # ian: commented out, our local dns is expected to work fine.
387 #resolver <IP DNS resolver>;
388 EOF
389 cat >$vhost_file <<EOF
390 server {
391 server_name $h www.$h;
392 root $root;
393 listen $listenip$port $ssl_arg;
394 EOF
395 if [[ ! $listenip ]]; then
396 cat >>$vhost_file <<EOF
397 listen [::]:$port $ssl_arg;
398 EOF
399 fi
400 cat >>$vhost_file <<EOF
401 location $root {
402 autoindex off;
403 }
404 EOF
405 if $ssl; then
406 cat >>$vhost_file <<EOF
407 ssl_certificate $cert_dir/fullchain.pem;
408 ssl_certificate_key $cert_dir/privkey.pem;
409 include $common_ssl_conf;
410 EOF
411
412 if (( port == 443 )); then
413 cat >$redir_file <<EOF
414 server {
415 server_name $h www.$h;
416 listen 80 $http2_arg;
417 listen [::]:80 $http2_arg;
418 return 301 https://$server_name$request_uri;
419 }
420 EOF
421 fi
422 fi # end if $ssl
423
424 if [[ $extra_settings ]]; then
425 cat $extra_settings >>$vhost_file
426 fi
427
428 if [[ $proxy ]]; then
429 cat >>$vhost_file <<EOF
430 location / {
431 proxy_set_header Host \$host;
432 proxy_set_header X-Real-IP \$remote_addr;
433 proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
434 proxy_set_header X-Forwarded-Ssl on;
435 proxy_set_header X-Forwarded-Port $port;
436 proxy_pass http://$proxy;
437 }
438 EOF
439 fi
440
441 cat >>$vhost_file <<EOF
442 }
443 EOF
444
445
446 service nginx restart
447
448 fi ####### end if nginx
449
450 cat >/etc/apache2/conf-enabled/local-custom.conf <<'EOF'
451 # vhost_combined with %D (request time in microseconds)
452 # this file is just a convenient place to drop it.
453 LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %D" vhost_time_combined
454 SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)
455 EOF