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