misc bug fixes
[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 if [[ ! -e $f ]] || openssl x509 -checkend 86400 -noout -in $f; then
119 # cerbot needs an existing virtualhost.
120 $0 -p 80 $t $h
121 # when generating an example config, add all relevant security options:
122 # --hsts --staple-ocsp --uir
123 certbot certonly -n --must-staple --email $email --no-self-upgrade \
124 --agree-tos --${t%2} -d $h
125 rm $vhost_file
126 fi
127 fi
128
129
130 if [[ $t == apache2 ]]; then
131 rm -f $se/000-default.conf
132 # note, we exepct ServerRoot of /etc/apache2
133 # apache requires exactly 1 listen directive per port (when no ip is also given),
134 # so we have to parse the config to do it programatically.
135 listen_80=false
136 listen_port=false
137 cd /etc/apache2
138 conf_files=(apache2.conf)
139
140
141 for (( i=0; i < ${#conf_files[@]}; i++ )); do
142 f="${conf_files[i]}"
143 # note: globs are expanded here.
144 conf_files+=( $(sed -rn "s,^\s*Include(Optional)?\s+(\S+).*,\2,p" "$f") )
145 case $(readlink -f "$f") in
146 $vhost_file|$redir_file) continue ;;
147 esac
148 echo "$f"
149 for p in $(sed -rn "s,^\s*listen\s+(\S+).*,\1,Ip" "$f"); do
150 case $p in
151 80) listen_80=true ;;&
152 $port) listen_port=true ;;
153 esac
154 done
155 done
156
157
158 cat >$vhost_file <<EOF
159 <VirtualHost *:$port>
160 ServerName $h
161 ServerAlias www.$h
162 DocumentRoot $root
163 EOF
164
165 if [[ $extra_settings ]]; then
166 cat -- $extra_settings >>$vhost_file
167 fi
168
169 # go faster!
170 if [[ -e /etc/apache2/mods-available/http2.load ]]; then
171 # https://httpd.apache.org/docs/2.4/mod/mod_http2.html
172 a2enmod http2
173 cat >>$vhost_file <<EOF
174 Protocols h2 http/1.1
175 EOF
176 fi
177
178 if [[ $proxy ]]; then
179 a2enmod proxy proxy_http
180 # fyi: trailing slash is important
181 # reference: https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html
182 # retry=0: https://stackoverflow.com/questions/683052/why-am-i-getting-an-apache-proxy-503-error
183 cat >>$vhost_file <<EOF
184 ProxyPass "/" "http://$proxy/" retry=0
185 ProxyPassReverse "/" "http://$proxy/"
186 EOF
187 fi
188
189
190 if $ssl; then
191 a2enmod headers
192 https_arg=" https"
193 common_ssl_conf=/etc/apache2/common-ssl.conf
194 cat >>$vhost_file <<EOF
195 SSLCertificateFile $cert_dir/fullchain.pem
196 SSLCertificateKeyFile $cert_dir/privkey.pem
197 Include $common_ssl_conf
198 # From cerbot generated config example, taken 4/2017,
199 # should be rechecked once a year or so.
200 Header always set Strict-Transport-Security "max-age=31536000"
201 SSLUseStapling on
202 Header always set Content-Security-Policy upgrade-insecure-requests
203 EOF
204
205 echo "$0: creating $redir_file"
206 cat >$redir_file <<EOF
207 <VirtualHost *:80>
208 ServerName $h
209 ServerAdmin webmaster@localhost
210 DocumentRoot /var/www/html
211
212 ErrorLog \${APACHE_LOG_DIR}/error.log
213 CustomLog \${APACHE_LOG_DIR}/access.log vhost_time_combined
214
215 RewriteEngine on
216 RewriteCond %{SERVER_NAME} =$h
217 RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]
218 </VirtualHost>
219 EOF
220 if ! $listen_80; then
221 cat >>$redir_file <<'EOF'
222 Listen 80
223 EOF
224 fi
225
226 # this is a copy of a file certbot, see below.
227 echo "$0: creating $common_ssl_conf"
228 cat >$common_ssl_conf <<'EOF'
229 # Baseline setting to Include for SSL sites
230
231 SSLEngine on
232
233 # Intermediate configuration, tweak to your needs
234 SSLProtocol all -SSLv2 -SSLv3
235 SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
236 SSLHonorCipherOrder on
237 SSLCompression off
238
239 SSLOptions +StrictRequire
240
241 # Add vhost name to log entries:
242 LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
243 LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
244
245 #CustomLog /var/log/apache2/access.log vhost_combined
246 #LogLevel warn
247 #ErrorLog /var/log/apache2/error.log
248
249 # Always ensure Cookies have "Secure" set (JAH 2012/1)
250 #Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4"
251 EOF
252
253 upstream=https://github.com/certbot/certbot/raw/master/certbot-apache/certbot_apache/options-ssl-apache.conf
254 if ! diff -c <(wget -q -O - $upstream) $common_ssl_conf; then
255 cat <<EOF
256 WARNING!!!!!!!!!
257 WARNING!!!!!!!!!
258 WARNING!!!!!!!!!
259 WARNING!!!!!!!!!
260 WARNING!!!!!!!!!
261 upstream ssl settings differ from the snapshot we have taken!!!
262 We diffed with this command:
263 diff -c <(wget -q -O - $upstream) $common_ssl_conf
264 Update this script to take care this warning!!!!!
265 EOF
266 sleep 1
267 fi
268 fi # end if $ssl
269
270 cat >>$vhost_file <<'EOF'
271 ErrorLog ${APACHE_LOG_DIR}/error.log
272 CustomLog ${APACHE_LOG_DIR}/access.log vhost_time_combined
273 </VirtualHost>
274 EOF
275
276 if ! $listen_port; then
277 # reference: https://httpd.apache.org/docs/2.4/mod/mpm_common.html#listen
278 cat >>$vhost_file <<EOF
279 listen ${port}${https_arg}
280 EOF
281 fi
282
283
284 a2enmod ssl rewrite # rewrite needed for httpredir
285 service apache2 restart
286
287 # I rarely look at how much traffic I get, so let's keep that info
288 # around for longer than the default of 2 weeks.
289 sed -ri --follow-symlinks 's/^(\s*rotate\s).*/\1 365/' /etc/logrotate.d/apache2
290 fi ###### end if apache
291
292 if [[ $t == nginx ]]; then
293 common_ssl_conf=/etc/nginx/common-ssl.conf
294
295 rm -f $se/default
296 cd /etc/nginx
297 [[ -e dh2048.pem ]] || openssl dhparam -out dh2048.pem 2048
298
299 if $ssl; then
300 ssl_arg=ssl
301 if nginx -V |& grep -- '--with-http_v2_module\b' &>/dev/null; then
302 # fun fact: nginx can be configured to do http2 without ssl.
303 ssl_arg+=" http2"
304 fi
305 fi
306
307 cat >$common_ssl_conf <<'EOF'
308 # let's encrypt gives us a bad nginx config, so use this:
309 # https://mozilla.github.io/server-side-tls/ssl-config-generator/
310 # using modern config. last checked 2017/4/22
311 ssl_session_timeout 1d;
312 ssl_session_cache shared:SSL:50m;
313 ssl_session_tickets off;
314
315 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
316 ssl_dhparam /etc/nginx/dh2048.pem;
317
318 # modern configuration. tweak to your needs.
319 ssl_protocols TLSv1.2;
320 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';
321 ssl_prefer_server_ciphers on;
322
323 # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
324 add_header Strict-Transport-Security max-age=15768000;
325
326 # OCSP Stapling ---
327 # fetch OCSP records from URL in ssl_certificate and cache them
328 ssl_stapling on;
329 ssl_stapling_verify on;
330
331 ## verify chain of trust of OCSP response using Root CA and Intermediate certs
332 # ian: commented out, unnecessary for le certs or my nginx ver.
333 #ssl_trusted_certificate $cert_dir/fullchain.pem;;
334
335 # ian: commented out, our local dns is expected to work fine.
336 #resolver <IP DNS resolver>;
337 EOF
338 cat >$vhost_file <<EOF
339 server {
340 server_name $h www.$h;
341 root $root;
342 listen $port $ssl_arg;
343 listen [::]:$port $ssl_arg;
344
345 EOF
346 if $ssl; then
347 cat >>$vhost_file <<EOF
348 ssl_certificate $cert_dir/fullchain.pem;
349 ssl_certificate_key $cert_dir/privkey.pem;
350 include $common_ssl_conf;
351 EOF
352
353 cat >$redir_file <<EOF
354 server {
355 server_name $h www.$h;
356 listen 80 $http2_arg;
357 listen [::]:80 $http2_arg;
358 return 301 https://$server_name$request_uri;
359 }
360 EOF
361 fi # end if $ssl
362
363 if [[ $extra_settings ]]; then
364 cat $extra_settings >>$vhost_file
365 fi
366
367 if [[ $proxy ]]; then
368 cat >>$vhost_file <<EOF
369 location / {
370 proxy_set_header Host \$host;
371 proxy_set_header X-Real-IP \$remote_addr;
372 proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
373 proxy_set_header X-Forwarded-Ssl on;
374 proxy_set_header X-Forwarded-Port $port;
375 proxy_pass http://$proxy;
376 }
377 EOF
378 fi
379
380 cat >>$vhost_file <<EOF
381 }
382 EOF
383
384
385 service nginx restart
386
387 fi ####### end if nginx
388
389 cat >/etc/apache2/conf-enabled/local-custom.conf <<'EOF'
390 # vhost_combined with %D (request time in microseconds)
391 # this file is just a convenient place to drop it.
392 LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %D" vhost_time_combined
393 SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)
394 EOF