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 -e EMAIL Contact address for let's encrypt. Default is
47 root@\$(hostname --fqdn')
48 which is root@$(hostname --fqdn) on this host.
49 -f [ADDR:]PORT Enable proxy to [ADDR:]PORT. ADDR default is 127.0.0.1
51 -p PORT Main port to listen on, default 443. 80 implies -i.
53 -s Allow symlinks from the doucmentroot
54 -h|--help Print help and exit
56 Note: Uses GNU getopt options parsing style
61 ##### begin command line parsing ########
67 temp
=$
(getopt
-l help a
:e
:if:p
:r
:sh
"$@") || usage
1
76 -e) email
="$2"; shift 2 ;;
77 -f) proxy
="$2"; shift 2 ;;
78 -i) ssl
=false
; shift ;;
79 -p) port
="$2"; shift 2 ;;
80 -r) root
="$2"; shift 2 ;;
81 -s) symlinkarg
=+; shift ;;
84 *) echo "$0: Internal error!" ; exit 1 ;;
89 if (( ${#@} == 3 )); then
90 read -r extra_settings t h
<<<"${@}"
97 *) echo "$0: error: expected apache2 or nginx arg"; usage
1 ;;
101 echo "$0: error: expected domain and type arg"
105 if [[ ! $root ]]; then
106 root
=/var
/www
/$h/html
109 if [[ $proxy ]]; then
110 [[ $proxy == *:* ]] || proxy
=127.0.0.1:$proxy
113 if [[ ! $email ]]; then
114 email
=root@$
(hostname
--fqdn)
118 ##### end command line parsing ########
120 se
=/etc
/$t/sites-enabled
121 cert_dir
=/etc
/letsencrypt
/live
/$h
126 vhost_file
=$se/$h.conf
129 vhost_file
=$se/$h-$port.conf
132 redir_file
=$se/$h-redir.conf
134 if [[ $port == 80 ]]; then
136 # remove any thats hanging around
143 $this_dir/certbot-setup
$t
145 f
=$cert_dir/fullchain.pem
146 threedays
=259200 # in seconds
147 if [[ ! -e $f ]] ||
! openssl x509
-checkend $threedays -noout -in $f >/dev
/null
; then
148 # cerbot needs an existing virtualhost.
150 # when generating an example config, add all relevant security options:
151 # --hsts --staple-ocsp --uir --must-staple
152 certbot certonly
-n --email $email --no-self-upgrade \
153 --agree-tos --${t%2} -d $h
154 # cleanup the call to ourselves a short bit ago
157 # these scripts only run on renew, that is kinda dumb.
158 export RENEWED_LINEAGE
=/etc
/letsencrypt
/live
/$h
159 for script in /etc
/letsencrypt
/renewal-hooks
/deploy
/*; do
160 if [[ -x $script ]]; then
167 if [[ $t == apache2
]]; then
168 rm -f $se/000-default.conf
169 # note, we exepct ServerRoot of /etc/apache2
170 # apache requires exactly 1 listen directive per port (when no ip is also given),
171 # so we have to parse the config to do it programatically.
175 conf_files
=(apache2.conf
)
178 for (( i
=0; i
< ${#conf_files[@]}; i
++ )); do
180 # note: globs are expanded here.
181 conf_files
+=( $
(sed -rn "s,^\s*Include(Optional)?\s+(\S+).*,\2,p" "$f") )
182 case $
(readlink
-f "$f") in
183 $vhost_file|
$redir_file) continue ;;
185 for p
in $
(sed -rn "s,^\s*listen\s+(\S+).*,\1,Ip" "$f"); do
187 80) listen_80
=true
;;&
188 $port) listen_port
=true
;;
193 echo "$0: creating $vhost_file"
194 cat >$vhost_file <<EOF
195 <VirtualHost $vhostip:$port>
200 Options -Indexes ${symlinkarg}FollowSymlinks
204 if [[ $extra_settings ]]; then
205 cat -- $extra_settings >>$vhost_file
209 if [[ -e /etc
/apache
2/mods-available
/http2.load
]]; then
210 # https://httpd.apache.org/docs/2.4/mod/mod_http2.html
212 cat >>$vhost_file <<EOF
213 Protocols h2 http/1.1
217 if [[ $proxy ]]; then
218 a2enmod
-q proxy proxy_http
219 # fyi: trailing slash is important
220 # reference: https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html
221 # retry=0: https://stackoverflow.com/questions/683052/why-am-i-getting-an-apache-proxy-503-error
222 cat >>$vhost_file <<EOF
223 ProxyPass "/" "http://$proxy/" retry=0
224 ProxyPassReverse "/" "http://$proxy/"
232 common_ssl_conf
=/etc
/apache
2/common-ssl.conf
233 cat >>$vhost_file <<EOF
234 SSLCertificateFile $cert_dir/fullchain.pem
235 SSLCertificateKeyFile $cert_dir/privkey.pem
236 Include $common_ssl_conf
237 # From cerbot generated config example, taken 4/2017,
238 # should be rechecked once a year or so.
239 Header always set Strict-Transport-Security "max-age=31536000"
241 Header always set Content-Security-Policy upgrade-insecure-requests
244 if (( port
== 443 )); then
245 echo "$0: creating $redir_file"
247 # note, alternatively:
248 cat >/dev
/null
<<'EOF'
249 #https://webmasters.stackexchange.com/questions/124635/apache-redirect-http-to-https-without-preventing-http
250 <If "%{req:Upgrade-Insecure-Requests} == '1'">
251 Redirect permanent "/" "https://mydomain.ltd/"
253 # or, with generic rewrite, we use this on gnu.org
255 RewriteCond %{HTTP:Upgrade-Insecure-Requests} "^1$"
256 RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=307]
259 cat >$redir_file <<EOF
262 ServerAdmin webmaster@localhost
263 DocumentRoot /var/www/html
265 ErrorLog \${APACHE_LOG_DIR}/error.log
266 CustomLog \${APACHE_LOG_DIR}/access.log vhost_time_combined
269 RewriteCond %{SERVER_NAME} =$h
270 RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]
273 if ! $listen_80; then
274 cat >>$redir_file <<'EOF'
280 # this is a copy of a file certbot, see below.
281 echo "$0: creating $common_ssl_conf"
282 cat >$common_ssl_conf <<'EOF'
283 # This file contains important security parameters. If you modify this file
284 # manually, Certbot will be unable to automatically provide future security
285 # updates. Instead, Certbot will print and log an error message with a path to
286 # the up-to-date file that you will need to refer to when manually updating
287 # this file. Contents are based on https://ssl-config.mozilla.org
291 # Intermediate configuration, tweak to your needs
292 SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
293 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
294 SSLHonorCipherOrder off
295 SSLSessionTickets off
297 SSLOptions +StrictRequire
299 # Add vhost name to log entries:
300 LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
301 LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
304 upstream
=https
://raw.githubusercontent.com
/certbot
/certbot
/master
/certbot-apache
/certbot_apache
/_internal
/tls_configs
/current-options-ssl-apache.conf
305 if ! diff -u <(wget
-q -O - $upstream) $common_ssl_conf; then
312 upstream ssl settings differ from the snapshot we have taken!!!
313 We diffed with this command:
314 diff -c <(wget -q -O - $upstream) $common_ssl_conf
315 Update this script to take care this warning!!!!!
321 cat >>$vhost_file <<'EOF'
322 ErrorLog ${APACHE_LOG_DIR}/error.log
323 CustomLog ${APACHE_LOG_DIR}/access.log vhost_time_combined
327 if ! $listen_port; then
328 # reference: https://httpd.apache.org/docs/2.4/mod/mpm_common.html#listen
329 cat >>$vhost_file <<EOF
330 listen ${listenip}${port}${https_arg}
335 a2enmod
-q ssl rewrite
# rewrite needed for httpredir
336 service apache2 restart
338 # I rarely look at how much traffic I get, so let's keep that info
339 # around for longer than the default of 2 weeks.
340 sed -ri --follow-symlinks 's/^(\s*rotate\s).*/\1 365/' /etc
/logrotate.d
/apache2
341 fi ###### end if apache
343 if [[ $t == nginx
]]; then
344 common_ssl_conf
=/etc
/nginx
/common-ssl.conf
348 [[ -e dh2048.pem
]] || openssl dhparam
-out dh2048.pem
2048
352 if nginx
-V |
& grep -- '--with-http_v2_module\b' &>/dev
/null
; then
353 # fun fact: nginx can be configured to do http2 without ssl.
358 cat >$common_ssl_conf <<'EOF'
359 # let's encrypt gives us a bad nginx config, so use this:
360 # https://mozilla.github.io/server-side-tls/ssl-config-generator/
361 # using modern config. last checked 2017/4/22
362 ssl_session_timeout 1d;
363 ssl_session_cache shared:SSL:50m;
364 ssl_session_tickets off;
366 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
367 ssl_dhparam /etc/nginx/dh2048.pem;
369 # modern configuration. tweak to your needs.
370 ssl_protocols TLSv1.2;
371 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';
372 ssl_prefer_server_ciphers on;
374 # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
375 add_header Strict-Transport-Security max-age=15768000;
378 # fetch OCSP records from URL in ssl_certificate and cache them
380 ssl_stapling_verify on;
382 ## verify chain of trust of OCSP response using Root CA and Intermediate certs
383 # ian: commented out, unnecessary for le certs or my nginx ver.
384 #ssl_trusted_certificate $cert_dir/fullchain.pem;;
386 # ian: commented out, our local dns is expected to work fine.
387 #resolver <IP DNS resolver>;
389 cat >$vhost_file <<EOF
391 server_name $h www.$h;
393 listen $listenip$port $ssl_arg;
395 if [[ ! $listenip ]]; then
396 cat >>$vhost_file <<EOF
397 listen [::]:$port $ssl_arg;
400 cat >>$vhost_file <<EOF
406 cat >>$vhost_file <<EOF
407 ssl_certificate $cert_dir/fullchain.pem;
408 ssl_certificate_key $cert_dir/privkey.pem;
409 include $common_ssl_conf;
412 if (( port
== 443 )); then
413 cat >$redir_file <<EOF
415 server_name $h www.$h;
416 listen 80 $http2_arg;
417 listen [::]:80 $http2_arg;
418 return 301 https://$server_name$request_uri;
424 if [[ $extra_settings ]]; then
425 cat $extra_settings >>$vhost_file
428 if [[ $proxy ]]; then
429 cat >>$vhost_file <<EOF
431 proxy_set_header Host \$host;
432 proxy_set_header X-Real-IP \$remote_addr;
433 proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
434 proxy_set_header X-Forwarded-Ssl on;
435 proxy_set_header X-Forwarded-Port $port;
436 proxy_pass http://$proxy;
441 cat >>$vhost_file <<EOF
446 service nginx restart
448 fi ####### end if nginx
450 cat >/etc
/apache
2/conf-enabled
/local-custom.conf
<<'EOF'
451 # vhost_combined with %D (request time in microseconds)
452 # this file is just a convenient place to drop it.
453 LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %D" vhost_time_combined
454 SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)