Compare commits

...

14 Commits

Author SHA1 Message Date
link2xt
b0a2491649 Authenticate echobot by passing /run/echobot/password to doveauth 2024-05-05 13:27:59 +00:00
link2xt
013ed8a3fa Move echobot into /var/lib/echobot 2024-05-05 13:27:59 +00:00
link2xt
e4f8c78efe Merge pull request #276 from deltachat/acmetool-tos
acmetool: accept new terms of services
2024-05-02 13:29:28 +00:00
missytake
e2cbf4e3e4 changelog for #276 2024-05-02 13:28:42 +00:00
missytake
f35d98bb40 acmetool: enable debugging 2024-05-01 10:45:21 +02:00
missytake
7ce1a5e841 ci: don't fail if /var/lib/acme isn't present 2024-05-01 00:41:11 +02:00
missytake
0a72c2fba7 acmetool: accept new terms of services
closes #275
2024-05-01 00:21:58 +02:00
link2xt
824f70f463 Document email authentication requirements 2024-04-19 21:12:54 +02:00
link2xt
39f5f64998 Reload Dovecot and Postfix when TLS certificate updates (#271) 2024-04-15 14:08:32 +00:00
Christian Hagenest
1752803199 changelog for #270 2024-04-11 19:41:43 +02:00
Christian Hagenest
e372599ce7 change location of changes per nami's recommendation 2024-04-11 19:15:28 +02:00
Christian Hagenest
ce9fb02a75 correct key for obs home deltachat 2024-04-11 19:15:28 +02:00
Christian Hagenest
4526f5e772 apt update after adding new repository 2024-04-11 19:15:28 +02:00
Christian Hagenest
616a42c8f3 add our obs repo to cmdeploy init 2024-04-11 19:15:28 +02:00
11 changed files with 134 additions and 27 deletions

View File

@@ -56,7 +56,7 @@ jobs:
# restore acme & dkim state to staging.testrun.org # restore acme & dkim state to staging.testrun.org
rsync -avz acme-restore/acme/ root@staging.testrun.org:/var/lib/acme || true rsync -avz acme-restore/acme/ root@staging.testrun.org:/var/lib/acme || true
rsync -avz dkimkeys-restore/dkimkeys/ root@staging.testrun.org:/etc/dkimkeys || true rsync -avz dkimkeys-restore/dkimkeys/ root@staging.testrun.org:/etc/dkimkeys || true
ssh -o StrictHostKeyChecking=accept-new -v root@staging.testrun.org chown root:root -R /var/lib/acme ssh -o StrictHostKeyChecking=accept-new -v root@staging.testrun.org chown root:root -R /var/lib/acme || true
- name: run formatting checks - name: run formatting checks
run: cmdeploy fmt -v run: cmdeploy fmt -v

View File

@@ -2,6 +2,15 @@
## untagged ## untagged
- Accept Let's Encrypt's new Terms of Services
([#275](https://github.com/deltachat/chatmail/pull/276))
- Reload Dovecot and Postfix when TLS certificate updates
([#271](https://github.com/deltachat/chatmail/pull/271))
- Use forked version of dovecot without hardcoded delays
([#270](https://github.com/deltachat/chatmail/pull/270))
## 1.2.0 - 2024-04-04 ## 1.2.0 - 2024-04-04
- Install dig on the server to resolve DNS records - Install dig on the server to resolve DNS records

View File

@@ -159,4 +159,27 @@ While this file is present, account creation will be blocked.
Delta Chat apps will, however, discover all ports and configurations Delta Chat apps will, however, discover all ports and configurations
automatically by reading the [autoconfig XML file](https://www.ietf.org/archive/id/draft-bucksch-autoconfig-00.html) from the chatmail service. automatically by reading the [autoconfig XML file](https://www.ietf.org/archive/id/draft-bucksch-autoconfig-00.html) from the chatmail service.
## Email authentication
chatmail servers rely on [DKIM](https://www.rfc-editor.org/rfc/rfc6376)
to authenticate incoming emails.
Incoming emails must have a valid DKIM signature with
Signing Domain Identifier (SDID, `d=` parameter in the DKIM-Signature header)
equal to the `From:` header domain.
This property is checked by OpenDKIM screen policy script
before validating the signatures.
This correpsonds to strict [DMARC](https://www.rfc-editor.org/rfc/rfc7489) alignment (`adkim=s`),
but chatmail does not rely on DMARC and does not consult the sender policy published in DMARC records.
Other legacy authentication mechanisms such as [iprev](https://www.rfc-editor.org/rfc/rfc8601#section-2.7.3)
and [SPF](https://www.rfc-editor.org/rfc/rfc7208) are also not taken into account.
If there is no valid DKIM signature on the incoming email,
the sender receives a "5.7.1 No valid DKIM signature found" error.
Outgoing emails must be sent over authenticated connection
with envelope MAIL FROM (return path) corresponding to the login.
This is ensured by Postfix which maps login username
to MAIL FROM with
[`smtpd_sender_login_maps`](https://www.postfix.org/postconf.5.html#smtpd_sender_login_maps)
and rejects incorrectly authenticated emails with [`reject_sender_login_mismatch`](reject_sender_login_mismatch) policy.
`From:` header must correspond to envelope MAIL FROM,
this is ensured by `filtermail` proxy.

View File

@@ -4,6 +4,7 @@ import time
import sys import sys
import json import json
import crypt import crypt
from pathlib import Path
from socketserver import ( from socketserver import (
UnixStreamServer, UnixStreamServer,
StreamRequestHandler, StreamRequestHandler,
@@ -45,23 +46,32 @@ def is_allowed_to_create(config: Config, user, cleartext_password) -> bool:
return False return False
localpart, domain = parts localpart, domain = parts
if localpart == "echo":
# echobot account should not be created in the database
return False
if ( if (
len(localpart) > config.username_max_length len(localpart) > config.username_max_length
or len(localpart) < config.username_min_length or len(localpart) < config.username_min_length
): ):
if localpart != "echo": logging.warning(
logging.warning( "localpart %s has to be between %s and %s chars long",
"localpart %s has to be between %s and %s chars long", localpart,
localpart, config.username_min_length,
config.username_min_length, config.username_max_length,
config.username_max_length, )
)
return False
return True return True
def get_user_data(db, config: Config, user): def get_user_data(db, config: Config, user):
if user == f"echo@{config.mail_domain}":
return dict(
home=f"/home/vmail/mail/{config.mail_domain}/echo@{config.mail_domain}",
uid="vmail",
gid="vmail",
)
with db.read_connection() as conn: with db.read_connection() as conn:
result = conn.get_user(user) result = conn.get_user(user)
if result: if result:
@@ -76,6 +86,21 @@ def lookup_userdb(db, config: Config, user):
def lookup_passdb(db, config: Config, user, cleartext_password): def lookup_passdb(db, config: Config, user, cleartext_password):
if user == f"echo@{config.mail_domain}":
# Echobot writes password it wants to log in with into /run/echobot/password
try:
password = Path("/run/echobot/password").read_text()
except Exception:
logging.exception("Exception when trying to read /run/echobot/password")
return None
return dict(
home=f"/home/vmail/mail/{config.mail_domain}/echo@{config.mail_domain}",
uid="vmail",
gid="vmail",
password=encrypt_password(password),
)
with db.write_transaction() as conn: with db.write_transaction() as conn:
userdata = conn.get_user(user) userdata = conn.get_user(user)
if userdata: if userdata:

View File

@@ -3,14 +3,17 @@
it will echo back any message that has non-empty text and also supports the /help command. it will echo back any message that has non-empty text and also supports the /help command.
""" """
import logging import logging
import os import os
import sys import sys
import subprocess
from deltachat_rpc_client import Bot, DeltaChat, EventType, Rpc, events from deltachat_rpc_client import Bot, DeltaChat, EventType, Rpc, events
from pathlib import Path
from chatmaild.newemail import create_newemail_dict
from chatmaild.config import read_config from chatmaild.config import read_config
from chatmaild.newemail import create_newemail_dict
hooks = events.HookCollection() hooks = events.HookCollection()
@@ -75,9 +78,23 @@ def main():
account = accounts[0] if accounts else deltachat.add_account() account = accounts[0] if accounts else deltachat.add_account()
bot = Bot(account, hooks) bot = Bot(account, hooks)
config = read_config(sys.argv[1])
# Create password file
if bot.is_configured():
password = bot.account.get_config("mail_pw")
else:
password = create_newemail_dict(config)["password"]
Path("/run/echobot/password").write_text(password)
# Give the user which doveauth runs as access to the password file.
subprocess.run(
["/usr/bin/setfacl", "-m", "user:vmail:r", "/run/echobot/password"],
check=True,
)
if not bot.is_configured(): if not bot.is_configured():
config = read_config(sys.argv[1])
password = create_newemail_dict(config).get("password")
email = "echo@" + config.mail_domain email = "echo@" + config.mail_domain
bot.configure(email, password) bot.configure(email, password)
bot.run_forever() bot.run_forever()

View File

@@ -477,12 +477,30 @@ def deploy_chatmail(config_path: Path) -> None:
groups=["opendkim"], groups=["opendkim"],
system=True, system=True,
) )
server.user(name="Create echobot user", user="echobot", system=True)
server.shell( server.shell(
name="Fix file owner in /home/vmail", name="Fix file owner in /home/vmail",
commands=["test -d /home/vmail && chown -R vmail:vmail /home/vmail"], commands=["test -d /home/vmail && chown -R vmail:vmail /home/vmail"],
) )
# Add our OBS repository for dovecot_no_delay
files.put(
name = "Add Deltachat OBS GPG key to apt keyring",
src = importlib.resources.files(__package__).joinpath("obs-home-deltachat.gpg"),
dest = "/etc/apt/keyrings/obs-home-deltachat.gpg",
user="root",
group="root",
mode="644",
)
files.line(
name = "Add DeltaChat OBS home repository to sources.list",
path = "/etc/apt/sources.list",
line = "deb [signed-by=/etc/apt/keyrings/obs-home-deltachat.gpg] https://download.opensuse.org/repositories/home:/deltachat/Debian_12/ ./",
ensure_newline = True,
)
apt.update(name="apt update", cache_time=24 * 3600) apt.update(name="apt update", cache_time=24 * 3600)
apt.packages( apt.packages(
@@ -513,7 +531,6 @@ def deploy_chatmail(config_path: Path) -> None:
# Deploy acmetool to have TLS certificates. # Deploy acmetool to have TLS certificates.
deploy_acmetool( deploy_acmetool(
nginx_hook=True,
domains=[mail_domain, f"mta-sts.{mail_domain}", f"www.{mail_domain}"], domains=[mail_domain, f"mta-sts.{mail_domain}", f"www.{mail_domain}"],
) )

View File

@@ -5,7 +5,7 @@ from pyinfra import host
from pyinfra.facts.systemd import SystemdStatus from pyinfra.facts.systemd import SystemdStatus
def deploy_acmetool(nginx_hook=False, email="", domains=[]): def deploy_acmetool(email="", domains=[]):
"""Deploy acmetool.""" """Deploy acmetool."""
apt.packages( apt.packages(
name="Install acmetool", name="Install acmetool",
@@ -20,16 +20,13 @@ def deploy_acmetool(nginx_hook=False, email="", domains=[]):
mode="644", mode="644",
) )
if nginx_hook: files.put(
files.put( src=importlib.resources.files(__package__).joinpath("acmetool.hook").open("rb"),
src=importlib.resources.files(__package__) dest="/usr/lib/acme/hooks/nginx",
.joinpath("acmetool.hook") user="root",
.open("rb"), group="root",
dest="/usr/lib/acme/hooks/nginx", mode="744",
user="root", )
group="root",
mode="744",
)
files.template( files.template(
src=importlib.resources.files(__package__).joinpath("response-file.yaml.j2"), src=importlib.resources.files(__package__).joinpath("response-file.yaml.j2"),
@@ -74,5 +71,5 @@ def deploy_acmetool(nginx_hook=False, email="", domains=[]):
server.shell( server.shell(
name=f"Request certificate for: { ', '.join(domains) }", name=f"Request certificate for: { ', '.join(domains) }",
commands=[f"acmetool want { ' '.join(domains)}"], commands=[f"acmetool want --xlog.severity=debug { ' '.join(domains)}"],
) )

View File

@@ -3,3 +3,5 @@ set -e
EVENT_NAME="$1" EVENT_NAME="$1"
[ "$EVENT_NAME" = "live-updated" ] || exit 42 [ "$EVENT_NAME" = "live-updated" ] || exit 42
systemctl restart nginx.service systemctl restart nginx.service
systemctl reload dovecot.service
systemctl reload postfix.service

View File

@@ -1,2 +1,2 @@
"acme-enter-email": "{{ email }}" "acme-enter-email": "{{ email }}"
"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf": true "acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.4-April-3-2024.pdf": true

Binary file not shown.

View File

@@ -7,6 +7,20 @@ Environment="PATH={remote_venv_dir}:$PATH"
Restart=always Restart=always
RestartSec=30 RestartSec=30
User=echobot
Group=echobot
# Create /var/lib/echobot
StateDirectory=echobot
# Create /run/echobot
#
# echobot stores /run/echobot/password
# with a password there, which doveauth then reads.
RuntimeDirectory=echobot
WorkingDirectory=/var/lib/echobot
# Apply security restrictions suggested by # Apply security restrictions suggested by
# systemd-analyze security echobot.service # systemd-analyze security echobot.service
CapabilityBoundingSet= CapabilityBoundingSet=
@@ -16,7 +30,10 @@ NoNewPrivileges=true
PrivateDevices=true PrivateDevices=true
PrivateMounts=true PrivateMounts=true
PrivateTmp=true PrivateTmp=true
PrivateUsers=true
# We need to know about doveauth user to give it access to /run/echobot/password
PrivateUsers=false
ProtectClock=true ProtectClock=true
ProtectControlGroups=true ProtectControlGroups=true
ProtectHostname=true ProtectHostname=true