2 # Copyright (C) 2016 Ian Kelling
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
16 [[ $EUID == 0 ]] ||
exec sudo
-E "$BASH_SOURCE" "$@"
19 trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
21 readonly this_file
="$(readlink -f -- "${BASH_SOURCE[0]}")"
22 readonly this_dir
="${this_file%/*}"
24 shopt -s nullglob
# used in apache config file expansion
28 Usage: ${0##*/} [OPTIONS] [EXTRA_SETTINGS_FILE] apache2|nginx DOMAIN
29 apache/nginx config & let's encrypt
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.
39 /a/bin/distro-setup/certbot-renew-hook
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 -c CERT_FOLDER No letsencrypt. use fullchain.pem and privkey.pem in this folder.
47 -e EMAIL Contact address for let's encrypt. Default is
48 root@\$(hostname --fqdn')
49 which is root@$(hostname --fqdn) on this host.
50 -f [ADDR:]PORT Enable proxy to [ADDR:]PORT. ADDR default is 127.0.0.1
52 -p PORT Main port to listen on, default 443. 80 implies -i.
54 -s Allow symlinks from the doucment root
55 -t No settings on documentroot.
56 -h|--help Print help and exit
58 Note: Uses GNU getopt options parsing style
63 ##### begin command line parsing ########
70 temp
=$
(getopt
-l help a
:c
:e
:if:p
:r
:sth
"$@") || usage
1
79 -c) oob_cert_dir
="$2"; shift 2 ;;
80 -e) email
="$2"; shift 2 ;;
81 -f) proxy
="$2"; shift 2 ;;
82 -i) ssl
=false
; shift ;;
83 -p) port
="$2"; shift 2 ;;
84 -r) root
="$2"; shift 2 ;;
85 -t) do_root_settings
=false
; shift ;;
86 -s) symlinkarg
=+; shift ;;
89 *) echo "$0: Internal error!" ; exit 1 ;;
94 if (( ${#@} == 3 )); then
95 read -r extra_settings t h
<<<"${@}"
102 *) echo "$0: error: expected apache2 or nginx arg"; usage
1 ;;
106 echo "$0: error: expected domain and type arg"
110 if [[ ! $root ]]; then
111 root
=/var
/www
/$h/html
114 if [[ $proxy ]]; then
115 [[ $proxy == *:* ]] || proxy
=127.0.0.1:$proxy
118 if [[ ! $email ]]; then
119 email
=root@$
(hostname
--fqdn)
123 ##### end command line parsing ########
125 se
=/etc
/$t/sites-enabled
126 if [[ $oob_cert_dir ]]; then
127 cert_dir
="$oob_cert_dir"
129 cert_dir
=/etc
/letsencrypt
/live
/$h
135 vhost_file
=$se/$h.conf
138 vhost_file
=$se/$h-$port.conf
141 redir_file
=$se/$h-redir.conf
143 if [[ $port == 80 ]]; then
145 # remove any thats hanging around
150 if [[ ! $oob_cert_dir ]] && $ssl; then
152 $this_dir/certbot-setup
$t
154 f
=$cert_dir/fullchain.pem
155 threedays
=259200 # in seconds
156 if [[ ! -e $f ]] ||
! openssl x509
-checkend $threedays -noout -in $f >/dev
/null
; then
157 # cerbot needs an existing virtualhost.
159 # when generating an example config, add all relevant security options:
160 # --hsts --staple-ocsp --uir --must-staple
161 certbot certonly
-n --email $email --no-self-upgrade \
162 --agree-tos --${t%2} -d $h
163 # cleanup the call to ourselves a short bit ago
166 # these scripts only run on renew, that is kinda dumb.
167 export RENEWED_LINEAGE
=/etc
/letsencrypt
/live
/$h
168 for script in /etc
/letsencrypt
/renewal-hooks
/deploy
/*; do
169 if [[ -x $script ]]; then
176 if [[ $t == apache2
]]; then
177 rm -f $se/000-default.conf
178 # note, we exepct ServerRoot of /etc/apache2
179 # apache requires exactly 1 listen directive per port (when no ip is also given),
180 # so we have to parse the config to do it programatically.
184 conf_files
=(apache2.conf
)
187 for (( i
=0; i
< ${#conf_files[@]}; i
++ )); do
189 # note: globs are expanded here.
190 conf_files
+=( $
(sed -rn "s,^\s*Include(Optional)?\s+(\S+).*,\2,p" "$f") )
191 case $
(readlink
-f "$f") in
192 $vhost_file|
$redir_file) continue ;;
194 for p
in $
(sed -rn "s,^\s*listen\s+(\S+).*,\1,Ip" "$f"); do
196 80) listen_80
=true
;;&
197 $port) listen_port
=true
;;
202 echo "$0: creating $vhost_file"
203 cat >$vhost_file <<EOF
204 <VirtualHost $vhostip:$port>
209 if $do_root_settings; then
210 cat >>$vhost_file <<EOF
212 Options -Indexes ${symlinkarg}FollowSymlinks
217 if [[ $extra_settings ]]; then
218 cat -- $extra_settings >>$vhost_file
222 if [[ -e /etc
/apache
2/mods-available
/http2.load
]]; then
223 # https://httpd.apache.org/docs/2.4/mod/mod_http2.html
225 cat >>$vhost_file <<EOF
226 Protocols h2 http/1.1
230 if [[ $proxy ]]; then
231 a2enmod
-q proxy proxy_http
232 # fyi: trailing slash is important
233 # reference: https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html
234 # retry=0: https://stackoverflow.com/questions/683052/why-am-i-getting-an-apache-proxy-503-error
235 cat >>$vhost_file <<EOF
236 ProxyPass "/" "http://$proxy/" retry=0
237 ProxyPassReverse "/" "http://$proxy/"
245 common_ssl_conf
=/etc
/apache
2/common-ssl.conf
246 cat >>$vhost_file <<EOF
247 SSLCertificateFile $cert_dir/fullchain.pem
248 SSLCertificateKeyFile $cert_dir/privkey.pem
249 Include $common_ssl_conf
250 # From cerbot generated config example, taken 4/2017,
251 # should be rechecked once a year or so.
252 Header always set Strict-Transport-Security "max-age=31536000"
254 Header always set Content-Security-Policy upgrade-insecure-requests
257 if (( port
== 443 )); then
258 echo "$0: creating $redir_file"
260 # note, alternatively:
261 cat >/dev
/null
<<'EOF'
262 #https://webmasters.stackexchange.com/questions/124635/apache-redirect-http-to-https-without-preventing-http
263 <If "%{req:Upgrade-Insecure-Requests} == '1'">
264 Redirect permanent "/" "https://mydomain.ltd/"
266 # or, with generic rewrite, we use this on gnu.org
268 RewriteCond %{HTTP:Upgrade-Insecure-Requests} "^1$"
269 RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=307]
272 cat >$redir_file <<EOF
275 ServerAdmin webmaster@localhost
276 DocumentRoot /var/www/html
278 ErrorLog \${APACHE_LOG_DIR}/error.log
279 CustomLog \${APACHE_LOG_DIR}/access.log vhost_time_combined
282 RewriteCond %{SERVER_NAME} =$h
283 RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]
286 if ! $listen_80; then
287 cat >>$redir_file <<'EOF'
293 # this is a copy of a file certbot, see below.
294 echo "$0: creating $common_ssl_conf"
295 cat >$common_ssl_conf <<'EOF'
296 # This file contains important security parameters. If you modify this file
297 # manually, Certbot will be unable to automatically provide future security
298 # updates. Instead, Certbot will print and log an error message with a path to
299 # the up-to-date file that you will need to refer to when manually updating
300 # this file. Contents are based on https://ssl-config.mozilla.org
304 # Intermediate configuration, tweak to your needs
305 SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
306 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
307 SSLHonorCipherOrder off
308 SSLSessionTickets off
310 SSLOptions +StrictRequire
312 # Add vhost name to log entries:
313 LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
314 LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
317 upstream
=https
://raw.githubusercontent.com
/certbot
/certbot
/master
/certbot-apache
/certbot_apache
/_internal
/tls_configs
/current-options-ssl-apache.conf
318 if ! diff -u <(wget
-q -O - $upstream) $common_ssl_conf; then
325 upstream ssl settings differ from the snapshot we have taken!!!
326 We diffed with this command:
327 diff -c <(wget -q -O - $upstream) $common_ssl_conf
328 Update this script to take care this warning!!!!!
334 cat >>$vhost_file <<'EOF'
335 ErrorLog ${APACHE_LOG_DIR}/error.log
336 CustomLog ${APACHE_LOG_DIR}/access.log vhost_time_combined
340 if ! $listen_port; then
341 # reference: https://httpd.apache.org/docs/2.4/mod/mpm_common.html#listen
342 cat >>$vhost_file <<EOF
343 listen ${listenip}${port}${https_arg}
348 a2enmod
-q ssl rewrite
# rewrite needed for httpredir
349 service apache2 restart
351 # I rarely look at how much traffic I get, so let's keep that info
352 # around for longer than the default of 2 weeks.
353 sed -ri --follow-symlinks 's/^(\s*rotate\s).*/\1 365/' /etc
/logrotate.d
/apache2
354 fi ###### end if apache
356 if [[ $t == nginx
]]; then
357 common_ssl_conf
=/etc
/nginx
/common-ssl.conf
361 [[ -e dh2048.pem
]] || openssl dhparam
-out dh2048.pem
2048
365 if nginx
-V |
& grep -- '--with-http_v2_module\b' &>/dev
/null
; then
366 # fun fact: nginx can be configured to do http2 without ssl.
371 cat >$common_ssl_conf <<'EOF'
372 # let's encrypt gives us a bad nginx config, so use this:
373 # https://mozilla.github.io/server-side-tls/ssl-config-generator/
374 # using modern config. last checked 2017/4/22
375 ssl_session_timeout 1d;
376 ssl_session_cache shared:SSL:50m;
377 ssl_session_tickets off;
379 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
380 ssl_dhparam /etc/nginx/dh2048.pem;
382 # modern configuration. tweak to your needs.
383 ssl_protocols TLSv1.2;
384 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';
385 ssl_prefer_server_ciphers on;
387 # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
388 add_header Strict-Transport-Security max-age=15768000;
391 # fetch OCSP records from URL in ssl_certificate and cache them
393 ssl_stapling_verify on;
395 ## verify chain of trust of OCSP response using Root CA and Intermediate certs
396 # ian: commented out, unnecessary for le certs or my nginx ver.
397 #ssl_trusted_certificate $cert_dir/fullchain.pem;;
399 # ian: commented out, our local dns is expected to work fine.
400 #resolver <IP DNS resolver>;
402 cat >$vhost_file <<EOF
404 server_name $h www.$h;
406 listen $listenip$port $ssl_arg;
408 if [[ ! $listenip ]]; then
409 cat >>$vhost_file <<EOF
410 listen [::]:$port $ssl_arg;
413 if $do_root_settings; then
414 cat >>$vhost_file <<EOF
421 cat >>$vhost_file <<EOF
422 ssl_certificate $cert_dir/fullchain.pem;
423 ssl_certificate_key $cert_dir/privkey.pem;
424 include $common_ssl_conf;
427 if (( port
== 443 )); then
428 cat >$redir_file <<EOF
430 server_name $h www.$h;
431 listen 80 $http2_arg;
432 listen [::]:80 $http2_arg;
433 return 301 https://\$server_name\$request_uri;
439 if [[ $extra_settings ]]; then
440 cat $extra_settings >>$vhost_file
443 if [[ $proxy ]]; then
444 cat >>$vhost_file <<EOF
446 proxy_set_header Host \$host;
447 proxy_set_header X-Real-IP \$remote_addr;
448 proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
449 proxy_set_header X-Forwarded-Ssl on;
450 proxy_set_header X-Forwarded-Port $port;
451 proxy_pass http://$proxy;
456 cat >>$vhost_file <<EOF
461 service nginx restart
463 fi ####### end if nginx
465 cat >/etc
/apache
2/conf-enabled
/local-custom.conf
<<'EOF'
466 # vhost_combined with %D (request time in microseconds)
467 # this file is just a convenient place to drop it.
468 LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %D" vhost_time_combined
469 SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)