2 # This file is part of web-conf which configures web servers
3 # Copyright (C) 2024 Ian Kelling
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 # SPDX-License-Identifier: GPL-3.0-or-later
20 [[ $EUID == 0 ]] ||
exec sudo
-E "$BASH_SOURCE" "$@"
23 trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
25 readonly this_file
="$(readlink -f -- "${BASH_SOURCE[0]}")"
26 readonly this_dir
="${this_file%/*}"
28 shopt -s nullglob
# used in apache config file expansion
32 Usage: ${0##*/} [OPTIONS] [EXTRA_SETTINGS_FILE] apache2|nginx DOMAIN
33 apache/nginx config & let's encrypt
35 If using tls then it expects certbot to be installed and in PATH. Also,
36 certbot cronjob should be taken care of outside this script. In the
37 debian package, it installs a systemd timer. If a script exists (I
38 expect it only on my , Ian Kelling's sytem) we install a systemd timer
39 to on failure. You can see the relevant script in my git repo
40 distro-setup, and log-quiet.
43 /a/bin/distro-setup/certbot-renew-hook
47 EXTRA_SETTINGS_FILE can be - for stdin
48 -a IPv4_ADDR IP address to listen on. Default all addresses.
49 ipv6 address support could be added to this script.
50 -c CERT_FOLDER No letsencrypt. use fullchain.pem and privkey.pem in this folder.
51 -e EMAIL Contact address for let's encrypt. Default is
52 root@\$(hostname --fqdn')
53 which is root@$(hostname --fqdn) on this host.
54 -f [ADDR:]PORT Enable proxy to [ADDR:]PORT. ADDR default is 127.0.0.1
56 -l Allow failure of restarting apache/nginx. Useful for scripts where
57 we want to do the configuration, but don't mind if the web
58 server has some preexisting problem or other problem to fix later.
59 -p PORT Main port to listen on, default 443. 80 implies -i.
61 -s Allow symlinks from the doucment root
62 -t No settings on documentroot.
63 -h|--help Print help and exit
65 Note: Uses GNU getopt options parsing style
70 ##### begin command line parsing ########
77 temp
=$
(getopt
-l help a
:c
:e
:if:lp:r
:sth
"$@") || usage
1
79 allow_server_fail
=false
87 -c) oob_cert_dir
="$2"; shift 2 ;;
88 -e) email
="$2"; shift 2 ;;
89 -f) proxy
="$2"; shift 2 ;;
90 -i) ssl
=false
; shift ;;
91 -l) allow_server_fail
=true
; shift ;;
92 -p) port
="$2"; shift 2 ;;
93 -r) root
="$2"; shift 2 ;;
94 -t) do_root_settings
=false
; shift ;;
95 -s) symlinkarg
=+; shift ;;
98 *) echo "$0: Internal error!" ; exit 1 ;;
103 if (( ${#@} == 3 )); then
104 read -r extra_settings t h
<<<"${@}"
106 read -r t h
<<<"${@}"
111 *) echo "$0: error: expected apache2 or nginx arg"; usage
1 ;;
115 echo "$0: error: expected domain and type arg"
119 if [[ ! $root ]]; then
120 root
=/var
/www
/$h/html
123 if [[ $proxy ]]; then
124 [[ $proxy == *:* ]] || proxy
=127.0.0.1:$proxy
127 if [[ ! $email ]]; then
128 email
=root@$
(hostname
--fqdn)
132 ##### end command line parsing ########
134 se
=/etc
/$t/sites-enabled
135 if [[ $oob_cert_dir ]]; then
136 cert_dir
="$oob_cert_dir"
138 cert_dir
=/etc
/letsencrypt
/live
/$h
144 vhost_file
=$se/$h.conf
147 vhost_file
=$se/$h-$port.conf
150 redir_file
=$se/$h-redir.conf
152 if [[ $port == 80 ]]; then
154 # remove any thats hanging around
159 if [[ ! $oob_cert_dir ]] && $ssl; then
161 $this_dir/certbot-setup
$t
163 f
=$cert_dir/fullchain.pem
164 threedays
=259200 # in seconds
165 if [[ ! -e $f ]] ||
! openssl x509
-checkend $threedays -noout -in $f >/dev
/null
; then
166 # cerbot needs an existing virtualhost.
168 # when generating an example config, add all relevant security options:
169 # --hsts --staple-ocsp --uir --must-staple
170 certbot certonly
-n --email $email --no-self-upgrade \
171 --agree-tos --${t%2} -d $h
172 # cleanup the call to ourselves a short bit ago
175 # these scripts only run on renew, that is kinda dumb.
176 export RENEWED_LINEAGE
=/etc
/letsencrypt
/live
/$h
177 for script in /etc
/letsencrypt
/renewal-hooks
/deploy
/*; do
178 if [[ -x $script ]]; then
185 if [[ $t == apache2
]]; then
186 rm -f $se/000-default.conf
187 # note, we exepct ServerRoot of /etc/apache2
188 # apache requires exactly 1 listen directive per port (when no ip is also given),
189 # so we have to parse the config to do it programatically.
193 conf_files
=(apache2.conf
)
196 for (( i
=0; i
< ${#conf_files[@]}; i
++ )); do
198 # note: globs are expanded here.
199 conf_files
+=( $
(sed -rn "s,^\s*Include(Optional)?\s+(\S+).*,\2,p" "$f") )
200 case $
(readlink
-f "$f") in
201 $vhost_file|
$redir_file) continue ;;
203 for p
in $
(sed -rn "s,^\s*listen\s+(\S+).*,\1,Ip" "$f"); do
205 80) listen_80
=true
;;&
206 $port) listen_port
=true
;;
211 echo "$0: creating $vhost_file"
212 cat >$vhost_file <<EOF
213 <VirtualHost $vhostip:$port>
218 if $do_root_settings; then
219 cat >>$vhost_file <<EOF
221 Options -Indexes ${symlinkarg}FollowSymlinks
226 if [[ $extra_settings ]]; then
227 cat -- $extra_settings >>$vhost_file
231 if [[ -e /etc
/apache
2/mods-available
/http2.load
]]; then
232 # https://httpd.apache.org/docs/2.4/mod/mod_http2.html
234 cat >>$vhost_file <<EOF
235 Protocols h2 http/1.1
239 if [[ $proxy ]]; then
240 a2enmod
-q proxy proxy_http
241 # fyi: trailing slash is important
242 # reference: https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html
243 # retry=0: https://stackoverflow.com/questions/683052/why-am-i-getting-an-apache-proxy-503-error
244 cat >>$vhost_file <<EOF
245 ProxyPass "/" "http://$proxy/" retry=0
246 ProxyPassReverse "/" "http://$proxy/"
254 common_ssl_conf
=/etc
/apache
2/common-ssl.conf
255 cat >>$vhost_file <<EOF
256 SSLCertificateFile $cert_dir/fullchain.pem
257 SSLCertificateKeyFile $cert_dir/privkey.pem
258 Include $common_ssl_conf
259 # From cerbot generated config example, taken 4/2017,
260 # should be rechecked once a year or so.
261 Header always set Strict-Transport-Security "max-age=31536000"
263 Header always set Content-Security-Policy upgrade-insecure-requests
266 if (( port
== 443 )); then
267 echo "$0: creating $redir_file"
269 # note, alternatively:
270 cat >/dev
/null
<<'EOF'
271 #https://webmasters.stackexchange.com/questions/124635/apache-redirect-http-to-https-without-preventing-http
272 <If "%{req:Upgrade-Insecure-Requests} == '1'">
273 Redirect permanent "/" "https://mydomain.ltd/"
275 # or, with generic rewrite, we use this on gnu.org
277 RewriteCond %{HTTP:Upgrade-Insecure-Requests} "^1$"
278 RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=307]
281 cat >$redir_file <<EOF
284 ServerAdmin webmaster@localhost
285 DocumentRoot /var/www/html
287 ErrorLog \${APACHE_LOG_DIR}/error.log
288 CustomLog \${APACHE_LOG_DIR}/access.log vhost_time_combined
291 RewriteCond %{SERVER_NAME} =$h
292 RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]
295 if ! $listen_80; then
296 cat >>$redir_file <<'EOF'
302 # this is a copy of a file certbot, see below.
303 echo "$0: creating $common_ssl_conf"
304 cat >$common_ssl_conf <<'EOF'
305 # This file contains important security parameters. If you modify this file
306 # manually, Certbot will be unable to automatically provide future security
307 # updates. Instead, Certbot will print and log an error message with a path to
308 # the up-to-date file that you will need to refer to when manually updating
309 # this file. Contents are based on https://ssl-config.mozilla.org
313 # Intermediate configuration, tweak to your needs
314 SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
315 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
316 SSLHonorCipherOrder off
317 SSLSessionTickets off
319 SSLOptions +StrictRequire
321 # Add vhost name to log entries:
322 LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
323 LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
326 upstream
=https
://raw.githubusercontent.com
/certbot
/certbot
/master
/certbot-apache
/certbot_apache
/_internal
/tls_configs
/current-options-ssl-apache.conf
327 if ! diff -u <(wget
-q -O - $upstream) $common_ssl_conf; then
334 upstream ssl settings differ from the snapshot we have taken!!!
335 We diffed with this command:
336 diff -c <(wget -q -O - $upstream) $common_ssl_conf
337 Update this script to take care this warning!!!!!
343 cat >>$vhost_file <<'EOF'
344 ErrorLog ${APACHE_LOG_DIR}/error.log
345 CustomLog ${APACHE_LOG_DIR}/access.log vhost_time_combined
349 if ! $listen_port; then
350 # reference: https://httpd.apache.org/docs/2.4/mod/mpm_common.html#listen
351 cat >>$vhost_file <<EOF
352 listen ${listenip}${port}${https_arg}
357 a2enmod
-q ssl rewrite
# rewrite needed for httpredir
359 if $allow_server_fail; then
360 if ! service apache2 restart
; then
361 echo "$0: warning: apache2 restart failed. ignoring due to -l flag" >&2
364 service apache2 restart
367 # I rarely look at how much traffic I get, so let's keep that info
368 # around for longer than the default of 2 weeks.
369 sed -ri --follow-symlinks 's/^(\s*rotate\s).*/\1 365/' /etc
/logrotate.d
/apache2
370 fi ###### end if apache
372 if [[ $t == nginx
]]; then
373 common_ssl_conf
=/etc
/nginx
/common-ssl.conf
377 [[ -e dh2048.pem
]] || openssl dhparam
-out dh2048.pem
2048
381 if nginx
-V |
& grep -- '--with-http_v2_module\b' &>/dev
/null
; then
382 # fun fact: nginx can be configured to do http2 without ssl.
387 cat >$common_ssl_conf <<'EOF'
388 # let's encrypt gives us a bad nginx config, so use this:
389 # https://mozilla.github.io/server-side-tls/ssl-config-generator/
390 # using modern config. last checked 2017/4/22
391 ssl_session_timeout 1d;
392 ssl_session_cache shared:SSL:50m;
393 ssl_session_tickets off;
395 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
396 ssl_dhparam /etc/nginx/dh2048.pem;
398 # modern configuration. tweak to your needs.
399 ssl_protocols TLSv1.2;
400 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';
401 ssl_prefer_server_ciphers on;
403 # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
404 add_header Strict-Transport-Security max-age=15768000;
407 # fetch OCSP records from URL in ssl_certificate and cache them
409 ssl_stapling_verify on;
411 ## verify chain of trust of OCSP response using Root CA and Intermediate certs
412 # ian: commented out, unnecessary for le certs or my nginx ver.
413 #ssl_trusted_certificate $cert_dir/fullchain.pem;;
415 # ian: commented out, our local dns is expected to work fine.
416 #resolver <IP DNS resolver>;
418 cat >$vhost_file <<EOF
420 server_name $h www.$h;
422 listen $listenip$port $ssl_arg;
424 if [[ ! $listenip ]]; then
425 cat >>$vhost_file <<EOF
426 listen [::]:$port $ssl_arg;
429 if $do_root_settings; then
430 cat >>$vhost_file <<EOF
437 cat >>$vhost_file <<EOF
438 ssl_certificate $cert_dir/fullchain.pem;
439 ssl_certificate_key $cert_dir/privkey.pem;
440 include $common_ssl_conf;
443 if (( port
== 443 )); then
444 cat >$redir_file <<EOF
446 server_name $h www.$h;
447 listen 80 $http2_arg;
448 listen [::]:80 $http2_arg;
449 return 301 https://\$server_name\$request_uri;
455 if [[ $extra_settings ]]; then
456 cat $extra_settings >>$vhost_file
459 if [[ $proxy ]]; then
460 cat >>$vhost_file <<EOF
462 proxy_set_header Host \$host;
463 proxy_set_header X-Real-IP \$remote_addr;
464 proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
465 proxy_set_header X-Forwarded-Ssl on;
466 proxy_set_header X-Forwarded-Port $port;
467 proxy_pass http://$proxy;
472 cat >>$vhost_file <<EOF
477 if $allow_server_fail; then
478 if ! service nginx restart
; then
479 echo "$0: warning: nginx restart failed. ignoring due to -l flag" >&2
482 service nginx restart
485 fi ####### end if nginx
487 cat >/etc
/apache
2/conf-enabled
/local-custom.conf
<<'EOF'
488 # vhost_combined with %D (request time in microseconds)
489 # this file is just a convenient place to drop it.
490 LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %D" vhost_time_combined
491 SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)