Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update iroh and remove iroh. subdomain #451

Merged
merged 1 commit into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

## untagged

- deploy `iroh-relay` (requires new "iroh.{mail_domain}" DNS entry),
also update "realtime relay services" in privacy policy.
- deploy `iroh-relay` and also update "realtime relay services" in privacy policy.
([#434](https://github.com/deltachat/chatmail/pull/434))
([#451](https://github.com/deltachat/chatmail/pull/451))

- add guide to migrate chatmail to a new server
([#429](https://github.com/deltachat/chatmail/pull/429))
Expand Down
2 changes: 1 addition & 1 deletion chatmaild/src/chatmaild/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def __init__(self, inipath, params):
self.disable_ipv6 = params.get("disable_ipv6", "false").lower() == "true"
self.imap_rawlog = params.get("imap_rawlog", "false").lower() == "true"
if "iroh_relay" not in params:
self.iroh_relay = "https://iroh." + params["mail_domain"]
self.iroh_relay = "https://" + params["mail_domain"]
self.enable_iroh_relay = True
else:
self.iroh_relay = params["iroh_relay"].strip()
Expand Down
31 changes: 15 additions & 16 deletions cmdeploy/src/cmdeploy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,8 +481,14 @@ def deploy_mtail(config):

def deploy_iroh_relay(config) -> None:
(url, sha256sum) = {
"x86_64": ("https://github.com/n0-computer/iroh/releases/download/v0.27.0/iroh-relay-v0.27.0-x86_64-unknown-linux-musl.tar.gz", "8af7f6d29d17476ce5c3053c3161db5793cb2ac49057d0bcaf689436cdccbeab"),
"aarch64": ("https://github.com/n0-computer/iroh/releases/download/v0.27.0/iroh-relay-v0.27.0-aarch64-unknown-linux-musl.tar.gz", "18039f0d39df78922a5055a0d4a5a8fa98a2a0e19b1eaa4c3fe6db73b8698697")
"x86_64": (
"https://github.com/n0-computer/iroh/releases/download/v0.28.1/iroh-relay-v0.28.1-x86_64-unknown-linux-musl.tar.gz",
"2ffacf7c0622c26b67a5895ee8e07388769599f60e5f52a3bd40a3258db89b2c",
),
"aarch64": (
"https://github.com/n0-computer/iroh/releases/download/v0.28.1/iroh-relay-v0.28.1-aarch64-unknown-linux-musl.tar.gz",
"b915037bcc1ff1110cc9fcb5de4a17c00ff576fd2f568cd339b3b2d54c420dc4",
),
}[host.get_fact(facts.server.Arch)]

apt.packages(
Expand All @@ -493,7 +499,7 @@ def deploy_iroh_relay(config) -> None:
server.shell(
name="Download iroh-relay",
commands=[
f"(echo '{sha256sum} /usr/local/bin/iroh-relay' | sha256sum -c) || curl -L {url} | gunzip | tar -x -f - ./iroh-relay -O >/usr/local/bin/iroh-relay",
f"(echo '{sha256sum} /usr/local/bin/iroh-relay' | sha256sum -c) || (curl -L {url} | gunzip | tar -x -f - ./iroh-relay -O >/usr/local/bin/iroh-relay.new && mv /usr/local/bin/iroh-relay.new /usr/local/bin/iroh-relay)",
"chmod 755 /usr/local/bin/iroh-relay",
],
)
Expand All @@ -502,9 +508,7 @@ def deploy_iroh_relay(config) -> None:

systemd_unit = files.put(
name="Upload iroh-relay systemd unit",
src=importlib.resources.files(__package__).joinpath(
"iroh-relay.service"
),
src=importlib.resources.files(__package__).joinpath("iroh-relay.service"),
dest="/etc/systemd/system/iroh-relay.service",
user="root",
group="root",
Expand All @@ -514,13 +518,11 @@ def deploy_iroh_relay(config) -> None:

iroh_config = files.put(
name=f"Upload iroh-relay config",
src=importlib.resources.files(__package__).joinpath(
"iroh-relay.toml"
),
src=importlib.resources.files(__package__).joinpath("iroh-relay.toml"),
dest=f"/etc/iroh-relay.toml",
user="iroh",
group="iroh",
mode="600",
user="root",
group="root",
mode="644",
)
need_restart |= iroh_config.changed

Expand All @@ -533,12 +535,11 @@ def deploy_iroh_relay(config) -> None:
)


def deploy_chatmail(config_path: Path, disable_mail: bool, require_iroh: bool) -> None:
def deploy_chatmail(config_path: Path, disable_mail: bool) -> None:
"""Deploy a chat-mail instance.

:param config_path: path to chatmail.ini
:param disable_mail: whether to disable postfix & dovecot
:param require_iroh: whether to request a TLS certificate for iroh.$mail_domain
"""
config = read_config(config_path)
check_config(config)
Expand Down Expand Up @@ -616,8 +617,6 @@ def deploy_chatmail(config_path: Path, disable_mail: bool, require_iroh: bool) -

# Deploy acmetool to have TLS certificates.
tls_domains = [mail_domain, f"mta-sts.{mail_domain}", f"www.{mail_domain}"]
if require_iroh:
tls_domains.append(f"iroh.{mail_domain}")
deploy_acmetool(
domains=tls_domains,
)
Expand Down
1 change: 0 additions & 1 deletion cmdeploy/src/cmdeploy/chatmail.zone.j2
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
_mta-sts.{{ mail_domain }}. TXT "v=STSv1; id={{ sts_id }}"
mta-sts.{{ mail_domain }}. CNAME {{ mail_domain }}.
www.{{ mail_domain }}. CNAME {{ mail_domain }}.
iroh.{{ mail_domain }}. CNAME {{ mail_domain }}.
{{ dkim_entry }}

;
Expand Down
7 changes: 3 additions & 4 deletions cmdeploy/src/cmdeploy/cmdeploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ def run_cmd(args, out):

sshexec = args.get_sshexec()
require_iroh = args.config.enable_iroh_relay
remote_data = dns.get_initial_remote_data(sshexec, args.config.mail_domain, require_iroh)
if not dns.check_initial_remote_data(remote_data, require_iroh, print=out.red):
remote_data = dns.get_initial_remote_data(sshexec, args.config.mail_domain)
if not dns.check_initial_remote_data(remote_data, print=out.red):
return 1

env = os.environ.copy()
Expand Down Expand Up @@ -111,8 +111,7 @@ def dns_cmd_options(parser):
def dns_cmd(args, out):
"""Check DNS entries and optionally generate dns zone file."""
sshexec = args.get_sshexec()
require_iroh = args.config.enable_iroh_relay
remote_data = dns.get_initial_remote_data(sshexec, args.config.mail_domain, require_iroh)
remote_data = dns.get_initial_remote_data(sshexec, args.config.mail_domain)
if not remote_data:
return 1

Expand Down
3 changes: 1 addition & 2 deletions cmdeploy/src/cmdeploy/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ def main():
importlib.resources.files("cmdeploy").joinpath("../../../chatmail.ini"),
)
disable_mail = bool(os.environ.get('CHATMAIL_DISABLE_MAIL'))
require_iroh = bool(os.environ.get('CHATMAIL_REQUIRE_IROH'))

deploy_chatmail(config_path, disable_mail, require_iroh)
deploy_chatmail(config_path, disable_mail)


if pyinfra.is_cli:
Expand Down
9 changes: 3 additions & 6 deletions cmdeploy/src/cmdeploy/dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,19 @@
from . import remote


def get_initial_remote_data(sshexec, mail_domain, iroh_enabled):
def get_initial_remote_data(sshexec, mail_domain):
return sshexec.logged(
call=remote.rdns.perform_initial_checks, kwargs=dict(mail_domain=mail_domain, iroh_enabled=iroh_enabled)
call=remote.rdns.perform_initial_checks, kwargs=dict(mail_domain=mail_domain)
)


def check_initial_remote_data(remote_data, require_iroh, *, print=print):
def check_initial_remote_data(remote_data, *, print=print):
mail_domain = remote_data["mail_domain"]
if not remote_data["A"] and not remote_data["AAAA"]:
print(f"Missing A and/or AAAA DNS records for {mail_domain}!")
elif remote_data["MTA_STS"] != f"{mail_domain}.":
print("Missing MTA-STS CNAME record:")
print(f"mta-sts.{mail_domain}. CNAME {mail_domain}.")
elif require_iroh and remote_data["IROH"] != f"{mail_domain}.":
print("Missing iroh CNAME record:")
print(f"iroh.{mail_domain}. CNAME {mail_domain}.")
elif remote_data["WWW"] != f"{mail_domain}.":
print("Missing www CNAME record:")
print(f"www.{mail_domain}. CNAME {mail_domain}.")
Expand Down
32 changes: 20 additions & 12 deletions cmdeploy/src/cmdeploy/nginx/nginx.conf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,26 @@ http {
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/lib/cgi-bin/newemail.py;
}

# Proxy to iroh-relay service.
location /relay {
proxy_pass http://127.0.0.1:3340;
proxy_http_version 1.1;

# Upgrade header is normally set to "iroh derp http" or "websocket".
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}

location /relay/probe {
proxy_pass http://127.0.0.1:3340;
proxy_http_version 1.1;
}

location /generate_204 {
proxy_pass http://127.0.0.1:3340;
proxy_http_version 1.1;
}
}

# Redirect www. to non-www
Expand All @@ -108,16 +128,4 @@ http {
return 301 $scheme://{{ config.domain_name }}$request_uri;
access_log syslog:server=unix:/dev/log,facility=local7;
}

# Pass iroh. to iroh-relay service.
server {
listen 8443 ssl;
{% if not disable_ipv6 %}
listen [::]:8443 ssl;
{% endif %}
server_name iroh.{{ config.domain_name }};
location / {
proxy_pass http://127.0.0.1:3340;
}
}
}
7 changes: 3 additions & 4 deletions cmdeploy/src/cmdeploy/remote/rdns.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,21 @@
from .rshell import CalledProcessError, shell


def perform_initial_checks(mail_domain, iroh_enabled):
def perform_initial_checks(mail_domain):
"""Collecting initial DNS settings."""
assert mail_domain
if not shell("dig", fail_ok=True):
shell("apt-get install -y dnsutils")
A = query_dns("A", mail_domain)
AAAA = query_dns("AAAA", mail_domain)
MTA_STS = query_dns("CNAME", f"mta-sts.{mail_domain}")
IROH = query_dns("CNAME", f"iroh.{mail_domain}")
WWW = query_dns("CNAME", f"www.{mail_domain}")

res = dict(mail_domain=mail_domain, A=A, AAAA=AAAA, MTA_STS=MTA_STS, IROH=IROH, WWW=WWW)
res = dict(mail_domain=mail_domain, A=A, AAAA=AAAA, MTA_STS=MTA_STS, WWW=WWW)
res["acme_account_url"] = shell("acmetool account-url", fail_ok=True)
res["dkim_entry"] = get_dkim_entry(mail_domain, dkim_selector="opendkim")

if not MTA_STS or (not IROH and not iroh_enabled) or not WWW or (not A and not AAAA):
if not MTA_STS or not WWW or (not A and not AAAA):
return res

# parse out sts-id if exists, example: "v=STSv1; id=2090123"
Expand Down
8 changes: 4 additions & 4 deletions cmdeploy/src/cmdeploy/tests/online/test_1_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ def test_ls(self, sshexec):

def test_perform_initial(self, sshexec, maildomain):
res = sshexec(
remote.rdns.perform_initial_checks, kwargs=dict(mail_domain=maildomain, iroh_enabled=True)
remote.rdns.perform_initial_checks, kwargs=dict(mail_domain=maildomain)
)
assert res["A"] or res["AAAA"]

def test_logged(self, sshexec, maildomain, capsys):
sshexec.logged(
remote.rdns.perform_initial_checks, kwargs=dict(mail_domain=maildomain, iroh_enabled=True)
remote.rdns.perform_initial_checks, kwargs=dict(mail_domain=maildomain)
)
out, err = capsys.readouterr()
assert err.startswith("Collecting")
Expand All @@ -33,7 +33,7 @@ def test_logged(self, sshexec, maildomain, capsys):

sshexec.verbose = True
sshexec.logged(
remote.rdns.perform_initial_checks, kwargs=dict(mail_domain=maildomain, iroh_enabled=True)
remote.rdns.perform_initial_checks, kwargs=dict(mail_domain=maildomain)
)
out, err = capsys.readouterr()
lines = err.split("\n")
Expand All @@ -44,7 +44,7 @@ def test_exception(self, sshexec, capsys):
try:
sshexec.logged(
remote.rdns.perform_initial_checks,
kwargs=dict(mail_domain=None, iroh_enabled=True),
kwargs=dict(mail_domain=None),
)
except sshexec.FuncError as e:
assert "rdns.py" in str(e)
Expand Down
12 changes: 5 additions & 7 deletions cmdeploy/src/cmdeploy/tests/test_dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ def mockdns(mockdns_base):
"AAAA": {"some.domain": "fde5:cd7a:9e1c:3240:5a99:936f:cdac:53ae"},
"CNAME": {
"mta-sts.some.domain": "some.domain.",
"iroh.some.domain": "some.domain.",
"www.some.domain": "some.domain.",
},
}
Expand All @@ -36,31 +35,30 @@ def mockdns(mockdns_base):

class TestPerformInitialChecks:
def test_perform_initial_checks_ok1(self, mockdns):
remote_data = remote.rdns.perform_initial_checks("some.domain", iroh_enabled=True)
remote_data = remote.rdns.perform_initial_checks("some.domain")
assert remote_data["A"] == mockdns["A"]["some.domain"]
assert remote_data["AAAA"] == mockdns["AAAA"]["some.domain"]
assert remote_data["MTA_STS"] == mockdns["CNAME"]["mta-sts.some.domain"]
assert remote_data["IROH"] == mockdns["CNAME"]["iroh.some.domain"]
assert remote_data["WWW"] == mockdns["CNAME"]["www.some.domain"]

@pytest.mark.parametrize("drop", ["A", "AAAA"])
def test_perform_initial_checks_with_one_of_A_AAAA(self, mockdns, drop):
del mockdns[drop]
remote_data = remote.rdns.perform_initial_checks("some.domain", iroh_enabled=True)
remote_data = remote.rdns.perform_initial_checks("some.domain")
assert not remote_data[drop]

l = []
res = check_initial_remote_data(remote_data, require_iroh=True, print=l.append)
res = check_initial_remote_data(remote_data, print=l.append)
assert res
assert not l

def test_perform_initial_checks_no_mta_sts(self, mockdns):
del mockdns["CNAME"]["mta-sts.some.domain"]
remote_data = remote.rdns.perform_initial_checks("some.domain", iroh_enabled=True)
remote_data = remote.rdns.perform_initial_checks("some.domain")
assert not remote_data["MTA_STS"]

l = []
res = check_initial_remote_data(remote_data, require_iroh=True, print=l.append)
res = check_initial_remote_data(remote_data, print=l.append)
assert not res
assert len(l) == 2

Expand Down