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