From fc1f211af56a3d49a77a933c069d49cd5fbddeaf Mon Sep 17 00:00:00 2001 From: John Supplee Date: Sun, 10 Feb 2019 23:39:38 +0200 Subject: [PATCH] initial work on extended configuration --- conf/nginx.conf | 6 ++++-- management/auth.py | 2 +- management/daemon.py | 28 ++++++++++++++++++++++++++++ management/web_update.py | 18 ++++++++++++++++++ setup/mail-postfix.sh | 6 +++++- setup/start.sh | 16 ++++++++++++++++ setup/web.sh | 9 ++++++--- tools/dns-auth.sh | 2 ++ 8 files changed, 80 insertions(+), 7 deletions(-) create mode 100755 tools/dns-auth.sh diff --git a/conf/nginx.conf b/conf/nginx.conf index fafd3409..25910764 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -1,5 +1,6 @@ ## $HOSTNAME +#BEGIN_HTTP # Redirect all HTTP to HTTPS *except* the ACME challenges (Let's Encrypt TLS certificate # domain validation challenges) path, which must be served over HTTP per the ACME spec # (due to some Apache vulnerability). @@ -28,11 +29,12 @@ server { alias $STORAGE_ROOT/ssl/lets_encrypt/webroot/.well-known/acme-challenge/; } } +#END_HTTP # The secure HTTPS server. server { - listen 443 ssl http2; - listen [::]:443 ssl http2; + listen $HTTP_SSL_PORT ssl http2; + listen [::]:$HTTP_SSL_PORT ssl http2; server_name $HOSTNAME; diff --git a/management/auth.py b/management/auth.py index 55f59664..0b082580 100644 --- a/management/auth.py +++ b/management/auth.py @@ -59,7 +59,7 @@ class KeyAuthService: credentials = decode(credentials) if ":" not in credentials: - return None, None + return credentials, None username, password = credentials.split(':', maxsplit=1) return username, password diff --git a/management/daemon.py b/management/daemon.py index 572b6b4a..3869cbaa 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -324,6 +324,34 @@ def dns_get_dump(): from dns_update import build_recommended_dns return json_response(build_recommended_dns(env)) +@app.route('/letsencrypt/dns-auth//', methods=['GET']) +@authorized_personnel_only +def letsencrypt_dns_auth(domain, token): + from dns_update import do_dns_update, set_custom_dns_record + try: + qname = '_acme-challenge.' + domain + if set_custom_dns_record(qname, 'TXT', token, 'add', env): + if not do_dns_update(env): + return ("Error updating DNS", 400) + return "OK" + + except ValueError as e: + return (str(e), 400) + +@app.route('/letsencrypt/dns-cleanup/', methods=['GET']) +@authorized_personnel_only +def letsencrypt_dns_cleanup(domain): + from dns_update import do_dns_update, set_custom_dns_record + try: + qname = '_acme-challenge.' + domain + if set_custom_dns_record(qname, 'TXT', None, 'remove', env): + if not do_dns_update(env): + return ("Error updating DNS", 400) + return "OK" + + except ValueError as e: + return (str(e), 400) + # SSL @app.route('/ssl/status') diff --git a/management/web_update.py b/management/web_update.py index 61b38a7b..616698ba 100644 --- a/management/web_update.py +++ b/management/web_update.py @@ -94,6 +94,20 @@ def do_web_update(env): # Add default 'www.' redirect. nginx_conf += make_domain_config(domain, [template0, template3], ssl_certificates, env) + if str(env['HTTP_SSL_PORT']) != "443": + in_http = False + new_conf = '' + for line in nginx_conf.split('\n'): + if line.strip() == '#BEGIN_HTTP': + in_http = True + elif line.strip() == '#END_HTTP': + in_http = False + + if not in_http: + new_conf += line + '\n' + + nginx_conf = new_conf + # Did the file change? If not, don't bother writing & restarting nginx. nginx_conf_fn = "/etc/nginx/conf.d/local.conf" if os.path.exists(nginx_conf_fn): @@ -178,8 +192,12 @@ def make_domain_config(domain, templates, ssl_certificates, env): nginx_conf = re.sub("[ \t]*# ADDITIONAL DIRECTIVES HERE *\n", t, nginx_conf) # Replace substitution strings in the template & return. + if int(env['HTTP_SSL_PORT']) != 443: + # disable the regular HTTP server + nginx_conf = re.sub(r'#BEGIN_HTTP.*?#END_HTTP', repl='', string=nginx_conf, flags=re.MULTILINE) nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT']) nginx_conf = nginx_conf.replace("$HOSTNAME", domain) + nginx_conf = nginx_conf.replace("$HTTP_SSL_PORT", env['HTTP_SSL_PORT']) nginx_conf = nginx_conf.replace("$ROOT", root) nginx_conf = nginx_conf.replace("$SSL_KEY", tls_cert["private-key"]) nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", tls_cert["certificate"]) diff --git a/setup/mail-postfix.sh b/setup/mail-postfix.sh index 0c9bc97c..1ea64a50 100755 --- a/setup/mail-postfix.sh +++ b/setup/mail-postfix.sh @@ -193,9 +193,13 @@ tools/editconf.py /etc/postfix/main.cf virtual_transport=lmtp:[127.0.0.1]:10025 # so these IPs get mail delivered quickly. But when an IP is not listed in the permit_dnswl_client list (i.e. it is not #NODOC # whitelisted) then postfix does a DEFER_IF_REJECT, which results in all "unknown user" sorts of messages turning into #NODOC # "450 4.7.1 Client host rejected: Service unavailable". This is a retry code, so the mail doesn't properly bounce. #NODOC +RECIPIENT_RESTRICTIONS=permit_sasl_authenticated,permit_mynetworks,\"reject_rbl_client zen.spamhaus.org\",reject_unlisted_recipient +if [ $NO_GREYLISTING != "1" ]; then + RECIPIENT_RESTRICTIONS=${RECIPIENT_RESTRICTIONS},\"check_policy_service inet:127.0.0.1:10023\" +fi tools/editconf.py /etc/postfix/main.cf \ smtpd_sender_restrictions="reject_non_fqdn_sender,reject_unknown_sender_domain,reject_authenticated_sender_login_mismatch,reject_rhsbl_sender dbl.spamhaus.org" \ - smtpd_recipient_restrictions=permit_sasl_authenticated,permit_mynetworks,"reject_rbl_client zen.spamhaus.org",reject_unlisted_recipient,"check_policy_service inet:127.0.0.1:10023" + smtpd_recipient_restrictions=$RECIPIENT_RESTRICTIONS # Postfix connects to Postgrey on the 127.0.0.1 interface specifically. Ensure that # Postgrey listens on the same interface (and not IPv6, for instance). diff --git a/setup/start.sh b/setup/start.sh index 0b145022..60a0349e 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -42,6 +42,20 @@ else FIRST_TIME_SETUP=1 fi +if [ -z "${DEFAULT_HTTP_SSL_PORT:-}" ]; then + HTTP_SSL_PORT=443 +else + HTTP_SSL_PORT=$DEFAULT_HTTP_SSL_PORT +fi + +if [ -z "${DEFAULT_NO_GREYLISTING:-}" ]; then + NO_GREYLISTING=0 +elif (($DEFAULT_NO_GREYLISTING > 0)); then + NO_GREYLISTING=1 +else + NO_GREYLISTING=0 +fi + # Put a start script in a global location. We tell the user to run 'mailinabox' # in the first dialog prompt, so we should do this before that starts. cat > /usr/local/bin/mailinabox << EOF; @@ -93,6 +107,8 @@ PUBLIC_IP=$PUBLIC_IP PUBLIC_IPV6=$PUBLIC_IPV6 PRIVATE_IP=$PRIVATE_IP PRIVATE_IPV6=$PRIVATE_IPV6 +HTTP_SSL_PORT=$HTTP_SSL_PORT +NO_GREYLISTING=$NO_GREYLISTING EOF # Start service configuration. diff --git a/setup/web.sh b/setup/web.sh index ed37e5e3..ad7ad310 100755 --- a/setup/web.sh +++ b/setup/web.sh @@ -96,6 +96,9 @@ restart_service nginx restart_service php7.2-fpm # Open ports. -ufw_allow http -ufw_allow https - +if [ $HTTP_SSL_PORT == 443 ]; then + ufw_allow http + ufw_allow https +else + ufw_allow $HTTP_SSL_PORT +fi diff --git a/tools/dns-auth.sh b/tools/dns-auth.sh new file mode 100755 index 00000000..20d602bd --- /dev/null +++ b/tools/dns-auth.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +