From 1331e7e77a8a55367e60d77ba394f51ae22c2763 Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Sun, 28 Jul 2024 20:06:24 +0200 Subject: [PATCH] Add config option for ipv6 usage (#312) * add allow_ipv6 config option * add ipv6 config changes to cmdeploy * fix name of config option for ipv6 in config.py * move configure ipv6 before service start * Use templates for disabling ipv6 * lint * fix parameters in _configure_dovecot * dont pass domain to _configure_nginx * make disable_ipv6 boolean Co-authored-by: missytake * implement namis suggestions reg boolean for ipv6 * Update chatmaild/src/chatmaild/config.py Co-authored-by: missytake * ruff * ruff again :) * fix merge conflict * CI: add CI machine with IPv6 disabled * CI: fix sed statement * CI: fix ubuntu reset * CI: separate cert storage for staging2 and staging-ipv4 * add DNS records to proper zone * CI: ignore if folders are missing * CI: renames are not needed like this * CI: fix default DNS zone for ipv4 * CI: use debian 12 instead of ubuntu, tired of trying to guess the correct image * remove duplicared listen on 8443 * use jinja templates for disable_ipv6 * remove unused variable * add missing % sign in jinja tempalte * more fun with jinja syntax * CI: proper rsync paths for acme & DKIM caching * Changelog: add disable_ipv6 config option --------- Co-authored-by: missytake Co-authored-by: holger krekel --- .../staging-ipv4.testrun.org-default.zone | 20 ++++ .../workflows/test-and-deploy-ipv4only.yaml | 98 +++++++++++++++++++ CHANGELOG.md | 4 + chatmaild/src/chatmaild/config.py | 3 + chatmaild/src/chatmaild/ini/chatmail.ini.f | 3 + cmdeploy/src/cmdeploy/__init__.py | 13 ++- cmdeploy/src/cmdeploy/dovecot/dovecot.conf.j2 | 4 + cmdeploy/src/cmdeploy/nginx/nginx.conf.j2 | 5 + cmdeploy/src/cmdeploy/postfix/main.cf.j2 | 4 + 9 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/staging-ipv4.testrun.org-default.zone create mode 100644 .github/workflows/test-and-deploy-ipv4only.yaml diff --git a/.github/workflows/staging-ipv4.testrun.org-default.zone b/.github/workflows/staging-ipv4.testrun.org-default.zone new file mode 100644 index 00000000..785b71aa --- /dev/null +++ b/.github/workflows/staging-ipv4.testrun.org-default.zone @@ -0,0 +1,20 @@ +;; Zone file for staging-ipv4.testrun.org + +$ORIGIN staging-ipv4.testrun.org. +$TTL 300 + +@ IN SOA ns.testrun.org. root.nine.testrun.org ( + 2023010101 ; Serial + 7200 ; Refresh + 3600 ; Retry + 1209600 ; Expire + 3600 ; Negative response caching TTL +) + +;; Nameservers. +@ IN NS ns.testrun.org. + +;; DNS records. +@ IN A 37.27.95.249 +mta-sts.staging-ipv4.testrun.org. CNAME staging-ipv4.testrun.org. +www.staging-ipv4.testrun.org. CNAME staging-ipv4.testrun.org. diff --git a/.github/workflows/test-and-deploy-ipv4only.yaml b/.github/workflows/test-and-deploy-ipv4only.yaml new file mode 100644 index 00000000..e2866a25 --- /dev/null +++ b/.github/workflows/test-and-deploy-ipv4only.yaml @@ -0,0 +1,98 @@ +name: deploy on staging-ipv4.testrun.org, and run tests + +on: + push: + branches: + - main + pull_request: + paths-ignore: + - 'scripts/**' + - '**/README.md' + - 'CHANGELOG.md' + - 'LICENSE' + +jobs: + deploy: + name: deploy on staging-ipv4.testrun.org, and run tests + runs-on: ubuntu-latest + timeout-minutes: 30 + concurrency: + group: ci-ipv4-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ !contains(github.ref, '$GITHUB_REF') }} + steps: + - uses: jsok/serialize-workflow-action@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v4 + + - name: prepare SSH + run: | + mkdir ~/.ssh + echo "${{ secrets.STAGING_SSH_KEY }}" >> ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan staging-ipv4.testrun.org > ~/.ssh/known_hosts + # save previous acme & dkim state + rsync -avz root@staging-ipv4.testrun.org:/var/lib/acme acme-ipv4 || true + rsync -avz root@staging-ipv4.testrun.org:/etc/dkimkeys dkimkeys-ipv4 || true + # store previous acme & dkim state on ns.testrun.org, if it contains useful certs + if [ -f dkimkeys-ipv4/dkimkeys/opendkim.private ]; then rsync -avz -e "ssh -o StrictHostKeyChecking=accept-new" dkimkeys-ipv4 root@ns.testrun.org:/tmp/ || true; fi + if [ "$(ls -A acme-ipv4/acme/certs)" ]; then rsync -avz -e "ssh -o StrictHostKeyChecking=accept-new" acme-ipv4 root@ns.testrun.org:/tmp/ || true; fi + # make sure CAA record isn't set + ssh -o StrictHostKeyChecking=accept-new root@ns.testrun.org sed -i '/CAA/d' /etc/nsd/staging-ipv4.testrun.org.zone + ssh root@ns.testrun.org systemctl reload nsd + + - name: rebuild staging-ipv4.testrun.org to have a clean VPS + run: | + curl -X POST \ + -H "Authorization: Bearer ${{ secrets.HETZNER_API_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d '{"image":"debian-12"}' \ + "https://api.hetzner.cloud/v1/servers/${{ secrets.STAGING_SERVER_ID }}/actions/rebuild" + + - run: scripts/initenv.sh + + - name: append venv/bin to PATH + run: echo venv/bin >>$GITHUB_PATH + + - name: upload TLS cert after rebuilding + run: | + echo " --- wait until staging-ipv4.testrun.org VPS is rebuilt --- " + rm ~/.ssh/known_hosts + while ! ssh -o ConnectTimeout=180 -o StrictHostKeyChecking=accept-new -v root@staging-ipv4.testrun.org id -u ; do sleep 1 ; done + ssh -o StrictHostKeyChecking=accept-new -v root@staging-ipv4.testrun.org id -u + # download acme & dkim state from ns.testrun.org + rsync -e "ssh -o StrictHostKeyChecking=accept-new" -avz root@ns.testrun.org:/tmp/acme-ipv4 acme-restore || true + rsync -avz root@ns.testrun.org:/tmp/dkimkeys-ipv4 dkimkeys-restore || true + # restore acme & dkim state to staging2.testrun.org + rsync -avz acme-restore/acme-ipv4/acme root@staging-ipv4.testrun.org:/var/lib/acme || true + rsync -avz dkimkeys-restore/dkimkeys-ipv4/dkimkeys root@staging-ipv4.testrun.org:/etc/dkimkeys || true + ssh -o StrictHostKeyChecking=accept-new -v root@staging-ipv4.testrun.org chown root:root -R /var/lib/acme || true + + - name: run formatting checks + run: cmdeploy fmt -v + + - name: run deploy-chatmail offline tests + run: pytest --pyargs cmdeploy + + - run: | + cmdeploy init staging-ipv4.testrun.org + sed -i 's#disable_ipv6 = False#disable_ipv6 = True#' chatmail.ini + + - run: cmdeploy run + + - name: set DNS entries + run: | + ssh -o StrictHostKeyChecking=accept-new -v root@staging-ipv4.testrun.org chown opendkim:opendkim -R /etc/dkimkeys + cmdeploy dns --zonefile staging-generated.zone + cat staging-generated.zone >> .github/workflows/staging-ipv4.testrun.org-default.zone + cat .github/workflows/staging-ipv4.testrun.org-default.zone + scp .github/workflows/staging-ipv4.testrun.org-default.zone root@ns.testrun.org:/etc/nsd/staging-ipv4.testrun.org.zone + ssh root@ns.testrun.org nsd-checkzone staging-ipv4.testrun.org /etc/nsd/staging-ipv4.testrun.org.zone + ssh root@ns.testrun.org systemctl reload nsd + + - name: cmdeploy test + run: CHATMAIL_DOMAIN2=nine.testrun.org cmdeploy test --slow + + - name: cmdeploy dns (try 3 times) + run: cmdeploy dns || cmdeploy dns || cmdeploy dns + diff --git a/CHANGELOG.md b/CHANGELOG.md index c89309d1..ec116cdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## untagged +- Add `disable_ipv6` config option to chatmail.ini. + Required if the server doesn't have IPv6 connectivity. + ([#312](https://github.com/deltachat/chatmail/pull/312)) + - allow current K9/Thunderbird-mail releases to send encrypted messages outside by accepting their localized "encrypted subject" strings. ([#370](https://github.com/deltachat/chatmail/pull/370)) diff --git a/chatmaild/src/chatmaild/config.py b/chatmaild/src/chatmaild/config.py index 0fcc494d..696ef66c 100644 --- a/chatmaild/src/chatmaild/config.py +++ b/chatmaild/src/chatmaild/config.py @@ -30,6 +30,9 @@ class Config: self.passthrough_recipients = params["passthrough_recipients"].split() self.filtermail_smtp_port = int(params["filtermail_smtp_port"]) self.postfix_reinject_port = int(params["postfix_reinject_port"]) + self.disable_ipv6 = ( + True if params.get("disable_ipv6").lower() == "true" else False + ) self.iroh_relay = params.get("iroh_relay") self.privacy_postal = params.get("privacy_postal") self.privacy_mail = params.get("privacy_mail") diff --git a/chatmaild/src/chatmaild/ini/chatmail.ini.f b/chatmaild/src/chatmaild/ini/chatmail.ini.f index ab65775e..b76cb478 100644 --- a/chatmaild/src/chatmaild/ini/chatmail.ini.f +++ b/chatmaild/src/chatmaild/ini/chatmail.ini.f @@ -51,6 +51,9 @@ filtermail_smtp_port = 10080 # postfix accepts on the localhost reinject SMTP port postfix_reinject_port = 10025 +# if set to "True" IPv6 is disabled +disable_ipv6 = False + # # Privacy Policy # diff --git a/cmdeploy/src/cmdeploy/__init__.py b/cmdeploy/src/cmdeploy/__init__.py index c453c420..0fdc3e0a 100644 --- a/cmdeploy/src/cmdeploy/__init__.py +++ b/cmdeploy/src/cmdeploy/__init__.py @@ -268,6 +268,7 @@ def _configure_postfix(config: Config, debug: bool = False) -> bool: group="root", mode="644", config=config, + disable_ipv6=config.disable_ipv6, ) need_restart |= main_config.changed @@ -318,6 +319,7 @@ def _configure_dovecot(config: Config, debug: bool = False) -> bool: mode="644", config=config, debug=debug, + disable_ipv6=config.disable_ipv6, ) need_restart |= main_config.changed auth_config = files.put( @@ -362,7 +364,7 @@ def _configure_dovecot(config: Config, debug: bool = False) -> bool: return need_restart -def _configure_nginx(domain: str, debug: bool = False) -> bool: +def _configure_nginx(config: Config, debug: bool = False) -> bool: """Configures nginx HTTP server.""" need_restart = False @@ -372,7 +374,8 @@ def _configure_nginx(domain: str, debug: bool = False) -> bool: user="root", group="root", mode="644", - config={"domain_name": domain}, + config={"domain_name": config.mail_domain}, + disable_ipv6=config.disable_ipv6, ) need_restart |= main_config.changed @@ -382,7 +385,7 @@ def _configure_nginx(domain: str, debug: bool = False) -> bool: user="root", group="root", mode="644", - config={"domain_name": domain}, + config={"domain_name": config.mail_domain}, ) need_restart |= autoconfig.changed @@ -392,7 +395,7 @@ def _configure_nginx(domain: str, debug: bool = False) -> bool: user="root", group="root", mode="644", - config={"domain_name": domain}, + config={"domain_name": config.mail_domain}, ) need_restart |= mta_sts_config.changed @@ -556,7 +559,7 @@ def deploy_chatmail(config_path: Path) -> None: dovecot_need_restart = _configure_dovecot(config, debug=debug) postfix_need_restart = _configure_postfix(config, debug=debug) mta_sts_need_restart = _install_mta_sts_daemon() - nginx_need_restart = _configure_nginx(mail_domain) + nginx_need_restart = _configure_nginx(config) _remove_rspamd() opendkim_need_restart = _configure_opendkim(mail_domain, "opendkim") diff --git a/cmdeploy/src/cmdeploy/dovecot/dovecot.conf.j2 b/cmdeploy/src/cmdeploy/dovecot/dovecot.conf.j2 index ae9d74c6..be7c23fb 100644 --- a/cmdeploy/src/cmdeploy/dovecot/dovecot.conf.j2 +++ b/cmdeploy/src/cmdeploy/dovecot/dovecot.conf.j2 @@ -1,5 +1,9 @@ ## Dovecot configuration file +{% if disable_ipv6 %} +listen = * +{% endif %} + protocols = imap lmtp auth_mechanisms = plain diff --git a/cmdeploy/src/cmdeploy/nginx/nginx.conf.j2 b/cmdeploy/src/cmdeploy/nginx/nginx.conf.j2 index 6f8d7dfd..5c36a15f 100644 --- a/cmdeploy/src/cmdeploy/nginx/nginx.conf.j2 +++ b/cmdeploy/src/cmdeploy/nginx/nginx.conf.j2 @@ -43,8 +43,11 @@ http { gzip on; server { + listen 8443 ssl default_server; + {% if not disable_ipv6 %} listen [::]:8443 ssl default_server; + {% endif %} root /var/www/html; @@ -96,7 +99,9 @@ http { # Redirect www. to non-www server { listen 8443 ssl; + {% if not disable_ipv6 %} listen [::]:8443 ssl; + {% endif %} server_name www.{{ config.domain_name }}; return 301 $scheme://{{ config.domain_name }}$request_uri; access_log syslog:server=unix:/dev/log,facility=local7; diff --git a/cmdeploy/src/cmdeploy/postfix/main.cf.j2 b/cmdeploy/src/cmdeploy/postfix/main.cf.j2 index cfa95cfc..973ca713 100644 --- a/cmdeploy/src/cmdeploy/postfix/main.cf.j2 +++ b/cmdeploy/src/cmdeploy/postfix/main.cf.j2 @@ -65,7 +65,11 @@ mailbox_size_limit = 0 message_size_limit = {{config.max_message_size}} recipient_delimiter = + inet_interfaces = all +{% if disable_ipv6 %} +inet_protocols = ipv4 +{% else %} inet_protocols = all +{% endif %} virtual_transport = lmtp:unix:private/dovecot-lmtp virtual_mailbox_domains = {{ config.mail_domain }}