slightly better docs
[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:i:f: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 if (( ${#@} == 3 )); then
71 read -r extra_settings t h <<<"${@}"
72 else
73 read -r t h <<<"${@}"
74 fi
75
76 case $t in
77 apache2|nginx) : ;;
78 *) echo "$0: error: expected apache2 or nginx arg"; usage 1 ;;
79 esac
80
81 if [[ ! $h ]]; then
82 echo "$0: error: expected domain and type arg"
83 usage 1
84 fi
85
86 if [[ ! $root ]]; then
87 root=/var/www/$h/html
88 fi
89
90 if [[ $proxy ]]; then
91 [[ $proxy == *:* ]] || proxy=127.0.0.1:$proxy
92 fi
93
94 if [[ ! $email ]]; then
95 email=root@$(hostname -A|awk '{print $1}')
96 fi
97
98
99 ##### end command line parsing ########
100
101 se=/etc/$t/sites-enabled
102 cert_dir=/etc/letsencrypt/live/$h
103
104 mkdir -p $root
105 vhost_file=$se/$h.conf
106 redir_file=$se/$h-redir.conf
107
108 if [[ $port == 80 ]]; then
109 ssl=false
110 # remove any thats hanging around
111 rm -f $redir_file
112 fi
113
114
115 if $ssl; then
116 f=$cert_dir/fullchain.pem
117 if [[ ! -e $f ]] || openssl x509 -checkend 86400 -noout -in $f; then
118 $0 -p 80 $t $h
119 # when generating an example config, add all relevant security options:
120 # --hsts --staple-ocsp --uir
121 certbot certonly -n --must-staple --email $email --no-self-upgrade \
122 --agree-tos --${t%2} -d $h
123 rm $vhost_file
124 fi
125 fi
126
127
128 if [[ $t == apache2 ]]; then
129 rm -f $se/000-default.conf
130 # note, we exepct ServerRoot of /etc/apache2
131 # apache requires exactly 1 listen directive per port (when no ip is also given),
132 # so we have to parse the config to do it programatically.
133 listen_80=false
134 listen_port=false
135 cd /etc/apache2
136 conf_files=(apache2.conf)
137
138
139 for (( i=0; i < ${#conf_files[@]}; i++ )); do
140 f="${conf_files[i]}"
141 # note: globs are expanded here.
142 conf_files+=( $(sed -rn "s,^\s*Include(Optional)?\s+(\S+).*,\2,p" "$f") )
143 case $(readlink -f "$f") in
144 $vhost_file|$redir_file) continue ;;
145 esac
146 echo "$f"
147 for p in $(sed -rn "s,^\s*listen\s+(\S+).*,\1,Ip" "$f"); do
148 case $p in
149 80) listen_80=true ;;&
150 $port) listen_port=true ;;
151 esac
152 done
153 done
154
155
156 cat >$vhost_file <<EOF
157 <VirtualHost *:$port>
158 ServerName $h
159 ServerAlias www.$h
160 DocumentRoot $root
161 EOF
162
163 if [[ $extra_settings ]]; then
164 cat -- $extra_settings >>$vhost_file
165 fi
166
167 # go faster!
168 if [[ -e /etc/apache2/mods-available/http2.load ]]; then
169 # https://httpd.apache.org/docs/2.4/mod/mod_http2.html
170 a2enmod http2
171 cat >>$vhost_file <<EOF
172 Protocols h2 http/1.1
173 EOF
174 fi
175
176 if [[ $proxy ]]; then
177 a2enmod proxy proxy_http
178 # fyi: trailing slash is important
179 # reference: https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html
180 cat >>$vhost_file <<EOF
181 ProxyPass "/" "http://$proxy/"
182 ProxyPassReverse "/" "http://$proxy/"
183 EOF
184 fi
185
186
187
188 if $ssl; then
189 https_arg=" https"
190 common_ssl_conf=/etc/apache2/common-ssl.conf
191 cat >>$vhost_file <<EOF
192 SSLCertificateFile $cert_dir/fullchain.pem
193 SSLCertificateKeyFile $cert_dir/privkey.pem
194 Include $common_ssl_conf
195 # From cerbot generated config example, taken 4/2017,
196 # should be rechecked once a year or so.
197 Header always set Strict-Transport-Security "max-age=31536000"
198 SSLUseStapling on
199 Header always set Content-Security-Policy upgrade-insecure-requests
200 EOF
201
202 cat >/etc/apache2/conf-enabled/local-custom.conf <<'EOF'
203 # vhost_combined with %D (request time in microseconds)
204 # this file is just a convenient place to drop it.
205 LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %D" vhost_time_combined
206 SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)
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 # Baseline setting to Include for SSL sites
234
235 SSLEngine on
236
237 # Intermediate configuration, tweak to your needs
238 SSLProtocol all -SSLv2 -SSLv3
239 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
240 SSLHonorCipherOrder on
241 SSLCompression off
242
243 SSLOptions +StrictRequire
244
245 # Add vhost name to log entries:
246 LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
247 LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
248
249 #CustomLog /var/log/apache2/access.log vhost_combined
250 #LogLevel warn
251 #ErrorLog /var/log/apache2/error.log
252
253 # Always ensure Cookies have "Secure" set (JAH 2012/1)
254 #Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4"
255 EOF
256
257 upstream=https://github.com/certbot/certbot/raw/master/certbot-apache/certbot_apache/options-ssl-apache.conf
258 if ! diff -c <(wget -q -O - $upstream) $common_ssl_conf; then
259 cat <<EOF
260 WARNING!!!!!!!!!
261 WARNING!!!!!!!!!
262 WARNING!!!!!!!!!
263 WARNING!!!!!!!!!
264 WARNING!!!!!!!!!
265 upstream ssl settings differ from the snapshot we have taken!!!
266 We diffed with this command:
267 diff -c <(wget -q -O - $upstream) $common_ssl_conf
268 Update this script to take care this warning!!!!!
269 EOF
270 sleep 1
271 fi
272 fi # end if $ssl
273
274 cat >>$vhost_file <<'EOF'
275 ErrorLog ${APACHE_LOG_DIR}/error.log
276 CustomLog ${APACHE_LOG_DIR}/access.log vhost_time_combined
277 </VirtualHost>
278 EOF
279
280 if ! $listen_port; then
281 # reference: https://httpd.apache.org/docs/2.4/mod/mpm_common.html#listen
282 cat >>$vhost_file <<EOF
283 listen ${port}${https_arg}
284 EOF
285 fi
286
287
288 a2enmod ssl rewrite # rewrite needed for httpredir
289 service apache2 restart
290
291 # I rarely look at how much traffic I get, so let's keep that info
292 # around for longer than the default of 2 weeks.
293 sed -ri --follow-symlinks 's/^(\s*rotate\s).*/\1 365/' /etc/logrotate.d/apache2
294 fi ###### end if apache
295
296 if [[ $t == nginx ]]; then
297 common_ssl_conf=/etc/nginx/common-ssl.conf
298
299 rm -f $se/default
300 cd /etc/nginx
301 [[ -e dh2048.pem ]] || openssl dhparam -out dh2048.pem 2048
302
303 if $ssl; then
304 ssl_arg=ssl
305 if nginx -V |& grep -- '--with-http_v2_module\b' &>/dev/null; then
306 # fun fact: nginx can be configured to do http2 without ssl.
307 ssl_arg+=" http2"
308 fi
309 fi
310
311 cat >$common_ssl_conf <<'EOF'
312 # let's encrypt gives us a bad nginx config, so use this:
313 # https://mozilla.github.io/server-side-tls/ssl-config-generator/
314 # using modern config. last checked 2017/4/22
315 ssl_session_timeout 1d;
316 ssl_session_cache shared:SSL:50m;
317 ssl_session_tickets off;
318
319 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
320 ssl_dhparam /etc/nginx/dh2048.pem;
321
322 # modern configuration. tweak to your needs.
323 ssl_protocols TLSv1.2;
324 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';
325 ssl_prefer_server_ciphers on;
326
327 # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
328 add_header Strict-Transport-Security max-age=15768000;
329
330 # OCSP Stapling ---
331 # fetch OCSP records from URL in ssl_certificate and cache them
332 ssl_stapling on;
333 ssl_stapling_verify on;
334
335 ## verify chain of trust of OCSP response using Root CA and Intermediate certs
336 # ian: commented out, unnecessary for le certs or my nginx ver.
337 #ssl_trusted_certificate $cert_dir/fullchain.pem;;
338
339 # ian: commented out, our local dns is expected to work fine.
340 #resolver <IP DNS resolver>;
341 EOF
342 cat >$vhost_file <<EOF
343 server {
344 server_name $h www.$h;
345 root $root;
346 listen $port $ssl_arg;
347 listen [::]:$port $ssl_arg;
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