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 -p PORT Main port to listen on, default 443. 80 implies -i.
58 -s Allow symlinks from the doucment root
59 -t No settings on documentroot.
60 -h|--help Print help and exit
62 Note: Uses GNU getopt options parsing style
67 ##### begin command line parsing ########
74 temp
=$
(getopt
-l help a
:c
:e
:if:p
:r
:sth
"$@") || usage
1
83 -c) oob_cert_dir
="$2"; shift 2 ;;
84 -e) email
="$2"; shift 2 ;;
85 -f) proxy
="$2"; shift 2 ;;
86 -i) ssl
=false
; shift ;;
87 -p) port
="$2"; shift 2 ;;
88 -r) root
="$2"; shift 2 ;;
89 -t) do_root_settings
=false
; shift ;;
90 -s) symlinkarg
=+; shift ;;
93 *) echo "$0: Internal error!" ; exit 1 ;;
98 if (( ${#@} == 3 )); then
99 read -r extra_settings t h
<<<"${@}"
101 read -r t h
<<<"${@}"
106 *) echo "$0: error: expected apache2 or nginx arg"; usage
1 ;;
110 echo "$0: error: expected domain and type arg"
114 if [[ ! $root ]]; then
115 root
=/var
/www
/$h/html
118 if [[ $proxy ]]; then
119 [[ $proxy == *:* ]] || proxy
=127.0.0.1:$proxy
122 if [[ ! $email ]]; then
123 email
=root@$
(hostname
--fqdn)
127 ##### end command line parsing ########
129 se
=/etc
/$t/sites-enabled
130 if [[ $oob_cert_dir ]]; then
131 cert_dir
="$oob_cert_dir"
133 cert_dir
=/etc
/letsencrypt
/live
/$h
139 vhost_file
=$se/$h.conf
142 vhost_file
=$se/$h-$port.conf
145 redir_file
=$se/$h-redir.conf
147 if [[ $port == 80 ]]; then
149 # remove any thats hanging around
154 if [[ ! $oob_cert_dir ]] && $ssl; then
156 $this_dir/certbot-setup
$t
158 f
=$cert_dir/fullchain.pem
159 threedays
=259200 # in seconds
160 if [[ ! -e $f ]] ||
! openssl x509
-checkend $threedays -noout -in $f >/dev
/null
; then
161 # cerbot needs an existing virtualhost.
163 # when generating an example config, add all relevant security options:
164 # --hsts --staple-ocsp --uir --must-staple
165 certbot certonly
-n --email $email --no-self-upgrade \
166 --agree-tos --${t%2} -d $h
167 # cleanup the call to ourselves a short bit ago
170 # these scripts only run on renew, that is kinda dumb.
171 export RENEWED_LINEAGE
=/etc
/letsencrypt
/live
/$h
172 for script in /etc
/letsencrypt
/renewal-hooks
/deploy
/*; do
173 if [[ -x $script ]]; then
180 if [[ $t == apache2
]]; then
181 rm -f $se/000-default.conf
182 # note, we exepct ServerRoot of /etc/apache2
183 # apache requires exactly 1 listen directive per port (when no ip is also given),
184 # so we have to parse the config to do it programatically.
188 conf_files
=(apache2.conf
)
191 for (( i
=0; i
< ${#conf_files[@]}; i
++ )); do
193 # note: globs are expanded here.
194 conf_files
+=( $
(sed -rn "s,^\s*Include(Optional)?\s+(\S+).*,\2,p" "$f") )
195 case $
(readlink
-f "$f") in
196 $vhost_file|
$redir_file) continue ;;
198 for p
in $
(sed -rn "s,^\s*listen\s+(\S+).*,\1,Ip" "$f"); do
200 80) listen_80
=true
;;&
201 $port) listen_port
=true
;;
206 echo "$0: creating $vhost_file"
207 cat >$vhost_file <<EOF
208 <VirtualHost $vhostip:$port>
213 if $do_root_settings; then
214 cat >>$vhost_file <<EOF
216 Options -Indexes ${symlinkarg}FollowSymlinks
221 if [[ $extra_settings ]]; then
222 cat -- $extra_settings >>$vhost_file
226 if [[ -e /etc
/apache
2/mods-available
/http2.load
]]; then
227 # https://httpd.apache.org/docs/2.4/mod/mod_http2.html
229 cat >>$vhost_file <<EOF
230 Protocols h2 http/1.1
234 if [[ $proxy ]]; then
235 a2enmod
-q proxy proxy_http
236 # fyi: trailing slash is important
237 # reference: https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html
238 # retry=0: https://stackoverflow.com/questions/683052/why-am-i-getting-an-apache-proxy-503-error
239 cat >>$vhost_file <<EOF
240 ProxyPass "/" "http://$proxy/" retry=0
241 ProxyPassReverse "/" "http://$proxy/"
249 common_ssl_conf
=/etc
/apache
2/common-ssl.conf
250 cat >>$vhost_file <<EOF
251 SSLCertificateFile $cert_dir/fullchain.pem
252 SSLCertificateKeyFile $cert_dir/privkey.pem
253 Include $common_ssl_conf
254 # From cerbot generated config example, taken 4/2017,
255 # should be rechecked once a year or so.
256 Header always set Strict-Transport-Security "max-age=31536000"
258 Header always set Content-Security-Policy upgrade-insecure-requests
261 if (( port
== 443 )); then
262 echo "$0: creating $redir_file"
264 # note, alternatively:
265 cat >/dev
/null
<<'EOF'
266 #https://webmasters.stackexchange.com/questions/124635/apache-redirect-http-to-https-without-preventing-http
267 <If "%{req:Upgrade-Insecure-Requests} == '1'">
268 Redirect permanent "/" "https://mydomain.ltd/"
270 # or, with generic rewrite, we use this on gnu.org
272 RewriteCond %{HTTP:Upgrade-Insecure-Requests} "^1$"
273 RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=307]
276 cat >$redir_file <<EOF
279 ServerAdmin webmaster@localhost
280 DocumentRoot /var/www/html
282 ErrorLog \${APACHE_LOG_DIR}/error.log
283 CustomLog \${APACHE_LOG_DIR}/access.log vhost_time_combined
286 RewriteCond %{SERVER_NAME} =$h
287 RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]
290 if ! $listen_80; then
291 cat >>$redir_file <<'EOF'
297 # this is a copy of a file certbot, see below.
298 echo "$0: creating $common_ssl_conf"
299 cat >$common_ssl_conf <<'EOF'
300 # This file contains important security parameters. If you modify this file
301 # manually, Certbot will be unable to automatically provide future security
302 # updates. Instead, Certbot will print and log an error message with a path to
303 # the up-to-date file that you will need to refer to when manually updating
304 # this file. Contents are based on https://ssl-config.mozilla.org
308 # Intermediate configuration, tweak to your needs
309 SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
310 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
311 SSLHonorCipherOrder off
312 SSLSessionTickets off
314 SSLOptions +StrictRequire
316 # Add vhost name to log entries:
317 LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
318 LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
321 upstream
=https
://raw.githubusercontent.com
/certbot
/certbot
/master
/certbot-apache
/certbot_apache
/_internal
/tls_configs
/current-options-ssl-apache.conf
322 if ! diff -u <(wget
-q -O - $upstream) $common_ssl_conf; then
329 upstream ssl settings differ from the snapshot we have taken!!!
330 We diffed with this command:
331 diff -c <(wget -q -O - $upstream) $common_ssl_conf
332 Update this script to take care this warning!!!!!
338 cat >>$vhost_file <<'EOF'
339 ErrorLog ${APACHE_LOG_DIR}/error.log
340 CustomLog ${APACHE_LOG_DIR}/access.log vhost_time_combined
344 if ! $listen_port; then
345 # reference: https://httpd.apache.org/docs/2.4/mod/mpm_common.html#listen
346 cat >>$vhost_file <<EOF
347 listen ${listenip}${port}${https_arg}
352 a2enmod
-q ssl rewrite
# rewrite needed for httpredir
353 service apache2 restart
355 # I rarely look at how much traffic I get, so let's keep that info
356 # around for longer than the default of 2 weeks.
357 sed -ri --follow-symlinks 's/^(\s*rotate\s).*/\1 365/' /etc
/logrotate.d
/apache2
358 fi ###### end if apache
360 if [[ $t == nginx
]]; then
361 common_ssl_conf
=/etc
/nginx
/common-ssl.conf
365 [[ -e dh2048.pem
]] || openssl dhparam
-out dh2048.pem
2048
369 if nginx
-V |
& grep -- '--with-http_v2_module\b' &>/dev
/null
; then
370 # fun fact: nginx can be configured to do http2 without ssl.
375 cat >$common_ssl_conf <<'EOF'
376 # let's encrypt gives us a bad nginx config, so use this:
377 # https://mozilla.github.io/server-side-tls/ssl-config-generator/
378 # using modern config. last checked 2017/4/22
379 ssl_session_timeout 1d;
380 ssl_session_cache shared:SSL:50m;
381 ssl_session_tickets off;
383 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
384 ssl_dhparam /etc/nginx/dh2048.pem;
386 # modern configuration. tweak to your needs.
387 ssl_protocols TLSv1.2;
388 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';
389 ssl_prefer_server_ciphers on;
391 # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
392 add_header Strict-Transport-Security max-age=15768000;
395 # fetch OCSP records from URL in ssl_certificate and cache them
397 ssl_stapling_verify on;
399 ## verify chain of trust of OCSP response using Root CA and Intermediate certs
400 # ian: commented out, unnecessary for le certs or my nginx ver.
401 #ssl_trusted_certificate $cert_dir/fullchain.pem;;
403 # ian: commented out, our local dns is expected to work fine.
404 #resolver <IP DNS resolver>;
406 cat >$vhost_file <<EOF
408 server_name $h www.$h;
410 listen $listenip$port $ssl_arg;
412 if [[ ! $listenip ]]; then
413 cat >>$vhost_file <<EOF
414 listen [::]:$port $ssl_arg;
417 if $do_root_settings; then
418 cat >>$vhost_file <<EOF
425 cat >>$vhost_file <<EOF
426 ssl_certificate $cert_dir/fullchain.pem;
427 ssl_certificate_key $cert_dir/privkey.pem;
428 include $common_ssl_conf;
431 if (( port
== 443 )); then
432 cat >$redir_file <<EOF
434 server_name $h www.$h;
435 listen 80 $http2_arg;
436 listen [::]:80 $http2_arg;
437 return 301 https://\$server_name\$request_uri;
443 if [[ $extra_settings ]]; then
444 cat $extra_settings >>$vhost_file
447 if [[ $proxy ]]; then
448 cat >>$vhost_file <<EOF
450 proxy_set_header Host \$host;
451 proxy_set_header X-Real-IP \$remote_addr;
452 proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
453 proxy_set_header X-Forwarded-Ssl on;
454 proxy_set_header X-Forwarded-Port $port;
455 proxy_pass http://$proxy;
460 cat >>$vhost_file <<EOF
465 service nginx restart
467 fi ####### end if nginx
469 cat >/etc
/apache
2/conf-enabled
/local-custom.conf
<<'EOF'
470 # vhost_combined with %D (request time in microseconds)
471 # this file is just a convenient place to drop it.
472 LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %D" vhost_time_combined
473 SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)