Skip to content

lss 2.0 stage 5: mail modules - #24

Draft
VectoDE wants to merge 13 commits into
feat/lss-4from
feat/lss-5
Draft

VectoDE wants to merge 13 commits into
feat/lss-4from
feat/lss-5

Conversation

@VectoDE

@VectoDE VectoDE commented Oct 15, 2026

Copy link
Copy Markdown
Owner

Stage 5 of the lss 2.0 rebuild: the mail module group.

  • mail-relay: send-only Postfix on the loopback addresses, via a smarthost (TLS verified, login) or direct (opportunistic TLS or DANE). The guard sends its alerts through it.
  • mailserver: Postfix, Dovecot (2.3 and 2.4), Rspamd (spam filter, DKIM signing, SPF/DKIM/DMARC checks). Prints the MX, A/AAAA, SPF, DKIM, DMARC and PTR records lss cannot set and keeps them in /etc/lss/mailserver-dns.txt.
  • webmail: Roundcube 1.7.4 (checksum-pinned) on the installed web server and PHP.
  • lib/mail.sh for the shared Postfix layer; all services in the systemd sandbox.
  • Integration job mail on every system: tests/integration/mail-flow.sh sends and receives mail, checks DKIM/SPF/DMARC against the printed records through Rspamd's fake DNS, GTUBE, refused logins and relaying, a Roundcube login, and the guard summary delivered through mail-relay.
  • Unit tests in tests/unit/mail.bats.

🤖 Generated with Claude Code

Refs #7

VectoDE and others added 5 commits October 15, 2026 06:21
Stage 5 of lss 2.0 adds the mail group. Servers need to send mail (the
guard's alerts, cron, applications), and the office profile needs a real
mail server with webmail, hardened the same way as every other module.

- mail-relay: Postfix as a send-only relay on the loopback addresses,
  through a smarthost with TLS (certificate checked) and login, or
  directly with opportunistic TLS or DANE. Mail for root stays in
  /var/mail/root or goes to a forward address; no delivery to programs,
  files or ~/.forward.
- mailserver: Postfix (SMTP; submission on 587 and 465 with TLS and
  login only, senders tied to their login), Dovecot 2.3 or 2.4 (IMAPS,
  passwords in the lss secrets), Rspamd from its official repositories
  (spam filter, DKIM signing, SPF/DKIM/DMARC checks). lss cannot set
  DNS, so it prints the MX, A/AAAA, SPF, DKIM, DMARC and PTR records and
  keeps them in /etc/lss/mailserver-dns.txt. verify sends a message
  through the whole server and checks the DKIM signature and the spam
  filter (GTUBE).
- webmail: Roundcube 1.7.4, checksum-pinned, as an HTTPS site of the
  web server, talking to Dovecot and Postfix on 127.0.0.1 only.
- lib/mail.sh holds what both Postfix modules share; every service runs
  in the systemd sandbox of svc_sandbox.
- nginx PHP sites pass PATH_INFO (Roundcube serves its assets through
  static.php/...); the PHP-FPM pool helpers move from phpmyadmin to the
  web layer; the tls deploy hook reloads Postfix and Dovecot; restic
  backs up mailboxes, DKIM keys and the webmail database by default.
- guard warns when LSS_GUARD_EMAIL is set but nothing can send mail.
- tests: unit tests in tests/unit/mail.bats; the integration job "mail"
  on every system runs tests/integration/mail-flow.sh: mail between two
  mailboxes, DKIM/SPF/DMARC checked against the printed records through
  Rspamd's fake DNS, GTUBE rejected, refused logins and relaying, a
  Roundcube login, and the guard's summary delivered through mail-relay.
- profiles: new office profile; all gains mailserver and webmail.

Refs #7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
Users need to know which DNS records the mail server needs, how to read
the mailbox passwords and which limits apply (port 25 blocked by many
providers), and the hardening guide lists what every module changes.

Refs #7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
The first CI run of the mail group showed four faults:

- Debian 13 and Ubuntu 26.04 ship Postfix 3.10, whose postfix.service
  runs the master itself; postfix@- there asserts /etc/postfix-/ and
  never starts. lss now reads the unit file to pick the unit.
- Postfix 3.5 on Rocky 9 does not know ">=TLSv1.2" and switched TLS off
  for submission; the exclusion list works on every version.
- texthash tables take "key value" lines, so the "key: value" lines of
  the alias tables never matched (Postfix warned about each one).
- The DKIM check of verify looked for the key in one piece, but the
  record holds it as 255-character strings.

Refs #7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
doveconf of Dovecot 2.4 (Debian 13, Ubuntu 26.04) refused the plain
IMAP listener for webmail: the setting "address" of 2.3 is "listen" in
2.4.

Refs #7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
Comment thread tests/unit/mail.bats Fixed
VectoDE and others added 8 commits October 15, 2026 11:40
…curl

- Roundcube's SQLite driver creates the schema on the first connection
  to a new database file, so initdb.sh then failed on the existing
  tables. updatedb.sh now creates and updates the database alike.
- Postfix 3.10 (Debian 13, Ubuntu 26.04) creates
  /var/spool/postfix/private only on its first start, after Dovecot;
  the Dovecot sandbox cannot bind a missing directory, so lss creates
  it with Postfix's owner and mode.
- curl-minimal of the RHEL family speaks no SMTP or IMAP; the mail flow
  test swaps in the full curl there.

Refs #7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
The unit test image has no openssl, so the Dovecot user file test now
uses a stand-in for "openssl passwd"; the systemctl stub carries the
SC2329 note the lint job asks for.

Refs #7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
mailserver sends with DANE when dns-resolver is installed. In profile
all it came before dns-resolver, so the second install switched the
TLS level and broke idempotency. The README says that lss configure
picks up a resolver added later.

Unit tests now cover the rest of lib/mail.sh (Postfix install, sandbox,
apply, the self-signed certificate and the delivery test), which the
coverage gate of lib/ needs.

Refs #7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
Positional parameters go into named locals, the header line of the
mailserver tables is one constant, and the message size limit uses a
shift, so the analyzer no longer reads the multiplication as a glob.

Refs #7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
The figures come from the RESOURCES lines of Integration run
38730695070 on all seven systems, x86_64 and arm64.

Refs #7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
…omain

A security review found that a logged-in user could put any hosted
address into the From header and get that domain's DKIM signature:
reject_sender_login_mismatch of Postfix checks only the envelope
sender, and dkim_signing had allow_username_mismatch = true.

- dkim_signing: allow_username_mismatch = false, so mail of a login is
  signed only with the key of the login's domain (use_esld stays).
- An Rspamd prefilter (lua.local.d/lss-sender.lua) rejects mail of a
  logged-in sender unless its single From address is one the login
  owns, from a copy of the Postfix sender map below /etc/rspamd that
  Rspamd can read under SELinux and AppArmor. Aliases leading to the
  login stay allowed.
- sign_local stays for mail of the server itself; a From of a domain
  the server does not host has no key and stays unsigned. The comment
  explains why local senders are trusted like the server.
- lss verify checks the prefilter both ways; the mail flow test sends
  with the From of another mailbox and of an unowned address in another
  hosted domain (rejected), as an owned alias (signed), and local mail
  with a hosted and an unhosted From (signed, unsigned).

Refs #7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
With "5.7.1" at the start of the text, the SMTP reply for a rejected
From header confused clients (curl: weird server reply). Rspamd adds
the status codes itself. The mail flow test now confirms from the
Postfix log that the header-From check refused the message.

Refs #7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants