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 doucmentroot
55 -h|--help Print help and exit
57 Note: Uses GNU getopt options parsing style
62 ##### begin command line parsing ########
68 temp
=$
(getopt
-l help a
:c
:e
:if:p
:r
:sh
"$@") || usage
1
77 -c) oob_cert_dir
="$2"; shift 2 ;;
78 -e) email
="$2"; shift 2 ;;
79 -f) proxy
="$2"; shift 2 ;;
80 -i) ssl
=false
; shift ;;
81 -p) port
="$2"; shift 2 ;;
82 -r) root
="$2"; shift 2 ;;
83 -s) symlinkarg
=+; shift ;;
86 *) echo "$0: Internal error!" ; exit 1 ;;
91 if (( ${#@} == 3 )); then
92 read -r extra_settings t h
<<<"${@}"
99 *) echo "$0: error: expected apache2 or nginx arg"; usage
1 ;;
103 echo "$0: error: expected domain and type arg"
107 if [[ ! $root ]]; then
108 root
=/var
/www
/$h/html
111 if [[ $proxy ]]; then
112 [[ $proxy == *:* ]] || proxy
=127.0.0.1:$proxy
115 if [[ ! $email ]]; then
116 email
=root@$
(hostname
--fqdn)
120 ##### end command line parsing ########
122 se
=/etc
/$t/sites-enabled
123 if [[ $oob_cert_dir ]]; then
124 cert_dir
="$oob_cert_dir"
126 cert_dir
=/etc
/letsencrypt
/live
/$h
132 vhost_file
=$se/$h.conf
135 vhost_file
=$se/$h-$port.conf
138 redir_file
=$se/$h-redir.conf
140 if [[ $port == 80 ]]; then
142 # remove any thats hanging around
147 if [[ ! $oob_cert_dir ]] && $ssl; then
149 $this_dir/certbot-setup
$t
151 f
=$cert_dir/fullchain.pem
152 threedays
=259200 # in seconds
153 if [[ ! -e $f ]] ||
! openssl x509
-checkend $threedays -noout -in $f >/dev
/null
; then
154 # cerbot needs an existing virtualhost.
156 # when generating an example config, add all relevant security options:
157 # --hsts --staple-ocsp --uir --must-staple
158 certbot certonly
-n --email $email --no-self-upgrade \
159 --agree-tos --${t%2} -d $h
160 # cleanup the call to ourselves a short bit ago
163 # these scripts only run on renew, that is kinda dumb.
164 export RENEWED_LINEAGE
=/etc
/letsencrypt
/live
/$h
165 for script in /etc
/letsencrypt
/renewal-hooks
/deploy
/*; do
166 if [[ -x $script ]]; then
173 if [[ $t == apache2
]]; then
174 rm -f $se/000-default.conf
175 # note, we exepct ServerRoot of /etc/apache2
176 # apache requires exactly 1 listen directive per port (when no ip is also given),
177 # so we have to parse the config to do it programatically.
181 conf_files
=(apache2.conf
)
184 for (( i
=0; i
< ${#conf_files[@]}; i
++ )); do
186 # note: globs are expanded here.
187 conf_files
+=( $
(sed -rn "s,^\s*Include(Optional)?\s+(\S+).*,\2,p" "$f") )
188 case $
(readlink
-f "$f") in
189 $vhost_file|
$redir_file) continue ;;
191 for p
in $
(sed -rn "s,^\s*listen\s+(\S+).*,\1,Ip" "$f"); do
193 80) listen_80
=true
;;&
194 $port) listen_port
=true
;;
199 echo "$0: creating $vhost_file"
200 cat >$vhost_file <<EOF
201 <VirtualHost $vhostip:$port>
206 Options -Indexes ${symlinkarg}FollowSymlinks
210 if [[ $extra_settings ]]; then
211 cat -- $extra_settings >>$vhost_file
215 if [[ -e /etc
/apache
2/mods-available
/http2.load
]]; then
216 # https://httpd.apache.org/docs/2.4/mod/mod_http2.html
218 cat >>$vhost_file <<EOF
219 Protocols h2 http/1.1
223 if [[ $proxy ]]; then
224 a2enmod
-q proxy proxy_http
225 # fyi: trailing slash is important
226 # reference: https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html
227 # retry=0: https://stackoverflow.com/questions/683052/why-am-i-getting-an-apache-proxy-503-error
228 cat >>$vhost_file <<EOF
229 ProxyPass "/" "http://$proxy/" retry=0
230 ProxyPassReverse "/" "http://$proxy/"
238 common_ssl_conf
=/etc
/apache
2/common-ssl.conf
239 cat >>$vhost_file <<EOF
240 SSLCertificateFile $cert_dir/fullchain.pem
241 SSLCertificateKeyFile $cert_dir/privkey.pem
242 Include $common_ssl_conf
243 # From cerbot generated config example, taken 4/2017,
244 # should be rechecked once a year or so.
245 Header always set Strict-Transport-Security "max-age=31536000"
247 Header always set Content-Security-Policy upgrade-insecure-requests
250 if (( port
== 443 )); then
251 echo "$0: creating $redir_file"
253 # note, alternatively:
254 cat >/dev
/null
<<'EOF'
255 #https://webmasters.stackexchange.com/questions/124635/apache-redirect-http-to-https-without-preventing-http
256 <If "%{req:Upgrade-Insecure-Requests} == '1'">
257 Redirect permanent "/" "https://mydomain.ltd/"
259 # or, with generic rewrite, we use this on gnu.org
261 RewriteCond %{HTTP:Upgrade-Insecure-Requests} "^1$"
262 RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=307]
265 cat >$redir_file <<EOF
268 ServerAdmin webmaster@localhost
269 DocumentRoot /var/www/html
271 ErrorLog \${APACHE_LOG_DIR}/error.log
272 CustomLog \${APACHE_LOG_DIR}/access.log vhost_time_combined
275 RewriteCond %{SERVER_NAME} =$h
276 RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]
279 if ! $listen_80; then
280 cat >>$redir_file <<'EOF'
286 # this is a copy of a file certbot, see below.
287 echo "$0: creating $common_ssl_conf"
288 cat >$common_ssl_conf <<'EOF'
289 # This file contains important security parameters. If you modify this file
290 # manually, Certbot will be unable to automatically provide future security
291 # updates. Instead, Certbot will print and log an error message with a path to
292 # the up-to-date file that you will need to refer to when manually updating
293 # this file. Contents are based on https://ssl-config.mozilla.org
297 # Intermediate configuration, tweak to your needs
298 SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
299 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
300 SSLHonorCipherOrder off
301 SSLSessionTickets off
303 SSLOptions +StrictRequire
305 # Add vhost name to log entries:
306 LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
307 LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
310 upstream
=https
://raw.githubusercontent.com
/certbot
/certbot
/master
/certbot-apache
/certbot_apache
/_internal
/tls_configs
/current-options-ssl-apache.conf
311 if ! diff -u <(wget
-q -O - $upstream) $common_ssl_conf; then
318 upstream ssl settings differ from the snapshot we have taken!!!
319 We diffed with this command:
320 diff -c <(wget -q -O - $upstream) $common_ssl_conf
321 Update this script to take care this warning!!!!!
327 cat >>$vhost_file <<'EOF'
328 ErrorLog ${APACHE_LOG_DIR}/error.log
329 CustomLog ${APACHE_LOG_DIR}/access.log vhost_time_combined
333 if ! $listen_port; then
334 # reference: https://httpd.apache.org/docs/2.4/mod/mpm_common.html#listen
335 cat >>$vhost_file <<EOF
336 listen ${listenip}${port}${https_arg}
341 a2enmod
-q ssl rewrite
# rewrite needed for httpredir
342 service apache2 restart
344 # I rarely look at how much traffic I get, so let's keep that info
345 # around for longer than the default of 2 weeks.
346 sed -ri --follow-symlinks 's/^(\s*rotate\s).*/\1 365/' /etc
/logrotate.d
/apache2
347 fi ###### end if apache
349 if [[ $t == nginx
]]; then
350 common_ssl_conf
=/etc
/nginx
/common-ssl.conf
354 [[ -e dh2048.pem
]] || openssl dhparam
-out dh2048.pem
2048
358 if nginx
-V |
& grep -- '--with-http_v2_module\b' &>/dev
/null
; then
359 # fun fact: nginx can be configured to do http2 without ssl.
364 cat >$common_ssl_conf <<'EOF'
365 # let's encrypt gives us a bad nginx config, so use this:
366 # https://mozilla.github.io/server-side-tls/ssl-config-generator/
367 # using modern config. last checked 2017/4/22
368 ssl_session_timeout 1d;
369 ssl_session_cache shared:SSL:50m;
370 ssl_session_tickets off;
372 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
373 ssl_dhparam /etc/nginx/dh2048.pem;
375 # modern configuration. tweak to your needs.
376 ssl_protocols TLSv1.2;
377 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';
378 ssl_prefer_server_ciphers on;
380 # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
381 add_header Strict-Transport-Security max-age=15768000;
384 # fetch OCSP records from URL in ssl_certificate and cache them
386 ssl_stapling_verify on;
388 ## verify chain of trust of OCSP response using Root CA and Intermediate certs
389 # ian: commented out, unnecessary for le certs or my nginx ver.
390 #ssl_trusted_certificate $cert_dir/fullchain.pem;;
392 # ian: commented out, our local dns is expected to work fine.
393 #resolver <IP DNS resolver>;
395 cat >$vhost_file <<EOF
397 server_name $h www.$h;
399 listen $listenip$port $ssl_arg;
401 if [[ ! $listenip ]]; then
402 cat >>$vhost_file <<EOF
403 listen [::]:$port $ssl_arg;
406 cat >>$vhost_file <<EOF
412 cat >>$vhost_file <<EOF
413 ssl_certificate $cert_dir/fullchain.pem;
414 ssl_certificate_key $cert_dir/privkey.pem;
415 include $common_ssl_conf;
418 if (( port
== 443 )); then
419 cat >$redir_file <<EOF
421 server_name $h www.$h;
422 listen 80 $http2_arg;
423 listen [::]:80 $http2_arg;
424 return 301 https://\$server_name\$request_uri;
430 if [[ $extra_settings ]]; then
431 cat $extra_settings >>$vhost_file
434 if [[ $proxy ]]; then
435 cat >>$vhost_file <<EOF
437 proxy_set_header Host \$host;
438 proxy_set_header X-Real-IP \$remote_addr;
439 proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
440 proxy_set_header X-Forwarded-Ssl on;
441 proxy_set_header X-Forwarded-Port $port;
442 proxy_pass http://$proxy;
447 cat >>$vhost_file <<EOF
452 service nginx restart
454 fi ####### end if nginx
456 cat >/etc
/apache
2/conf-enabled
/local-custom.conf
<<'EOF'
457 # vhost_combined with %D (request time in microseconds)
458 # this file is just a convenient place to drop it.
459 LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %D" vhost_time_combined
460 SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)