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

Setup mtail #388

Merged
merged 3 commits into from
Oct 14, 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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

## untagged

- add mtail support (new optional `mail_address` ini value)
This defines the address on which [`mtail`](https://google.github.io/mtail/)
exposes its metrics collected from the logs.
If you want to collect the metrics with Prometheus,
setup a private network (e.g. WireGuard interface)
and assign an IP address from this network to the host.
If you do not plan to collect metrics,
keep this setting unset.
([#388](https://github.com/deltachat/chatmail/pull/388))

- fix checking for required DNS records
([#412](https://github.com/deltachat/chatmail/pull/412))

Expand Down
1 change: 1 addition & 0 deletions chatmaild/src/chatmaild/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def __init__(self, inipath, params):
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.mtail_address = params.get("mtail_address")
self.disable_ipv6 = params.get("disable_ipv6", "false").lower() == "true"
self.imap_rawlog = params.get("imap_rawlog", "false").lower() == "true"
self.iroh_relay = params.get("iroh_relay")
Expand Down
24 changes: 17 additions & 7 deletions chatmaild/src/chatmaild/filtermail.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,29 @@ def check_DATA(self, envelope):
mail_encrypted = check_encrypted(message)

_, from_addr = parseaddr(message.get("from").strip())
envelope_from_domain = from_addr.split("@").pop()

logging.info(f"mime-from: {from_addr} envelope-from: {envelope.mail_from!r}")
if envelope.mail_from.lower() != from_addr.lower():
return f"500 Invalid FROM <{from_addr!r}> for <{envelope.mail_from!r}>"

if mail_encrypted:
print("Filtering encrypted mail.", file=sys.stderr)
else:
print("Filtering unencrypted mail.", file=sys.stderr)

if envelope.mail_from in self.config.passthrough_senders:
return

passthrough_recipients = self.config.passthrough_recipients
envelope_from_domain = from_addr.split("@").pop()

is_securejoin = message.get("secure-join") in [
"vc-request",
"vg-request",
]
if is_securejoin:
return

for recipient in envelope.rcpt_tos:
if envelope.mail_from == recipient:
# Always allow sending emails to self.
Expand All @@ -205,12 +219,8 @@ def check_DATA(self, envelope):

is_outgoing = recipient_domain != envelope_from_domain
if is_outgoing and not mail_encrypted:
is_securejoin = message.get("secure-join") in [
"vc-request",
"vg-request",
]
if not is_securejoin:
return f"500 Invalid unencrypted mail to <{recipient}>"
print("Rejected unencrypted mail.", file=sys.stderr)
return f"500 Invalid unencrypted mail to <{recipient}>"


class SendRateLimiter:
Expand Down
16 changes: 16 additions & 0 deletions chatmaild/src/chatmaild/ini/chatmail.ini.f
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,22 @@
# if set to "True" IPv6 is disabled
disable_ipv6 = False

# Address on which `mtail` listens,
# e.g. 127.0.0.1 or some private network
# address like 192.168.10.1.
# You can point Prometheus
# or some other OpenMetrics-compatible
# collector to
# http://{{mtail_address}}:3903/metrics
# and display collected metrics with Grafana.
#
# WARNING: do not expose this service
# to the public IP address.
#
# `mtail is not running if the setting is not set.

# mtail_address = 127.0.0.1

#
# Debugging options
#
Expand Down
40 changes: 40 additions & 0 deletions cmdeploy/src/cmdeploy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,44 @@ def check_config(config):
return config


def deploy_mtail(config):
apt.packages(
name="Install mtail",
packages=["mtail"],
)

# Using our own systemd unit instead of `/usr/lib/systemd/system/mtail.service`.
# This allows to read from journalctl instead of log files.
files.template(
src=importlib.resources.files(__package__).joinpath("mtail/mtail.service.j2"),
dest="/etc/systemd/system/mtail.service",
user="root",
group="root",
mode="644",
address=config.mtail_address or "127.0.0.1",
port=3903,
)

mtail_conf = files.put(
name="Mtail configuration",
src=importlib.resources.files(__package__).joinpath(
"mtail/delivered_mail.mtail"
),
dest="/etc/mtail/delivered_mail.mtail",
user="root",
group="root",
mode="644",
)

systemd.service(
name="Start and enable mtail",
service="mtail.service",
running=bool(config.mtail_address),
enabled=bool(config.mtail_address),
restarted=mtail_conf.changed,
)


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

Expand Down Expand Up @@ -636,3 +674,5 @@ def deploy_chatmail(config_path: Path) -> None:
name="Ensure cron is installed",
packages=["cron"],
)

deploy_mtail(config)
64 changes: 64 additions & 0 deletions cmdeploy/src/cmdeploy/mtail/delivered_mail.mtail
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
counter delivered_mail
/saved mail to INBOX$/ {
delivered_mail++
}

counter quota_exceeded
/Quota exceeded \(mailbox for user is full\)$/ {
quota_exceeded++
}

# Essentially the number of outgoing messages.
counter dkim_signed
/DKIM-Signature field added/ {
dkim_signed++
}

counter created_accounts
counter created_ci_accounts
counter created_nonci_accounts

/: Created address: (?P<addr>.*)$/ {
created_accounts++

$addr =~ /ci-/ {
created_ci_accounts++
} else {
created_nonci_accounts++
}
}

counter postfix_timeouts
/timeout after DATA/ {
postfix_timeouts++
}

counter postfix_noqueue
/postfix\/.*NOQUEUE/ {
postfix_noqueue++
}

counter warning_count
/warning/ {
warning_count++
}


counter filtered_mail_count

counter encrypted_mail_count
/Filtering encrypted mail\./ {
encrypted_mail_count++
filtered_mail_count++
}

counter unencrypted_mail_count
/Filtering unencrypted mail\./ {
unencrypted_mail_count++
filtered_mail_count++
}

counter rejected_unencrypted_mail_count
/Rejected unencrypted mail\./ {
rejected_unencrypted_mail_count++
}
10 changes: 10 additions & 0 deletions cmdeploy/src/cmdeploy/mtail/mtail.service.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[Unit]
Description=mtail

[Service]
Type=simple
ExecStart=/bin/sh -c "journalctl -f -o short-iso -n 0 | /usr/bin/mtail --address={{ address }} --port={{ port }} --progs /etc/mtail --logtostderr --logs -"
Restart=on-failure

[Install]
WantedBy=multi-user.target