Commit Graph
52 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5.5 c4d0283300 feat(mail): publish a DMARC aggregate report address (rua)
Both mail providers published "v=DMARC1; p=reject; ruf=...; adkim=s;
aspf=s" without a rua tag, so the domain owner received no aggregate
reports and could not see who sends in the domain's name or whether
legitimate mail fails alignment.

services.<entity>.dmarc.rua_user in web-app-stalwart and web-app-mailu
now names the users-catalogue key whose mailbox receives the aggregate
reports. It is set to administrator, the one user every deployment
provisions with a mailbox (user-administrator), and an inventory may
name another key. The address is built as <username>@<mail domain>,
which is the exact account or alias the provider creates, so the record
never points at an address the server does not serve. An assert in the
DNS task fails the deploy before any record is written when the user
carries no mailbox account (Stalwart), or neither a mailbox account nor
a forward alias (Mailu), so reports cannot bounce. Mailu's DMARC_RUA
env, which only feeds the record its admin UI suggests, now carries the
same local part instead of the unprovisioned "admin".

production_facts.py takes a required --dmarc-rua. check_dmarc parses
the record's tags and fails unless rua names that mailbox (size
suffixes and several URIs are accepted). The policy check now reads the
p tag instead of matching the substring "p=", which "sp=" also matched.

Closes #1125

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 20:56:40 +01:00
kevinveenbirkenbachandClaude Opus 5.5 4d9c3c1cfc refactor(web-app-stalwart): group the store SQL files and use the vars SPOT constant
- test_prefix_grouped_files: files/sql/store_database.sql and
  store_layout.sql become files/sql/store/database.sql and layout.sql.
- test_file_spot: test_store_guard.py builds the vars path with
  ROLE_FILE_VARS_MAIN instead of a hard-coded "vars" / "main.yml" chain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 03:46:55 +01:00
kevinveenbirkenbachandClaude Opus 5.5 47060a72d3 fix(web-app-stalwart): register the WebUI OAuth client and harden the 0.16 store guard
Follow-up to the v1.0.0 port, closing holes a review and a local run found:

- WebUI sign-in: WebUI v2 sends an http:// client-metadata-document id
  on the onion origin, which Stalwart v1.0.0 rejects (unknown http ids
  get no CIMD fallback, trusted hosts require TLS). The recovery admin
  under SSO hit the same wall with the bare id 'stalwart-webui'. Register
  a public first-party x:OAuthClient 'stalwart-webui' with the WebUI's
  callback and login URIs on the served origin, and always inject it as
  the Application's oauthClientId. Native sign-in now shows Stalwart's
  consent step; test-login-native.js clicks it and also runs under SSO.
- Store guard: the previous guard only checked a running 0.16 server; a
  local run showed v1 writing into a 0.16 store whose server was stopped.
  The guard now reads the store whenever one exists (shared: pg_database,
  dedicated: volume) and fails on the 0.16 bigint layout. A host marker
  written at the end of a successful v1 deploy lets restores and
  redeploys with a stopped dedicated database pass; without the marker,
  a stopped dedicated database fails with an explicit message instead of
  a masked psql error. Queries retry while the database recovers.
- README: WebUI sign-in, guard semantics, restore and redeploy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:45:31 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c75648ccdd fix(web-app-stalwart): port the role to server v1.0.0 and guard 0.16 stores
Stalwart v1.0.0 rejects unknown JMAP properties ("Invalid property" ->
notCreated/notUpdated), validates OIDC tokens more strictly and refuses to
open a data store from before 1.0. Port the role and protect existing
installs:

- Milter: hostname/timeoutConnect/timeoutCommand/timeoutData become
  host/connectTimeout/commandTimeout/dataTimeout; the existing milter is
  looked up by host.
- Onion relay route: address becomes host.
- OIDC directory: requireAudience is gone. Set skipAudienceValidation and
  unscopedTokens=interoperable on create and update, so Keycloak tokens
  (no Stalwart audience, no mail scope) keep working for the WebUI and
  Roundcube XOAUTH2.
- WebUI: send oauthClientId only with SSO enabled; without SSO the WebUI
  falls back to its own client metadata document.
- Store guard: before anything changes, a running Stalwart whose table u
  still has the 0.16 bigint layout stops the deploy and points to the
  stalwart-migrate steps, which the README now documents under
  "Upgrading from 0.16".
- Updater: pin the WebUI git_tags source to v2.* to match server 1.x.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 13:46:08 +01:00
kevinveenbirkenbachandClaude Opus 5.5 f47233b0a3 fix(web-app-stalwart): bump WebUI to v2.0.0 with server v1.0.0 and set its OAuth client
Stalwart server v1.0.0 and WebUI v2.0.0 only work as a pair:

- WebUI v2.0.0 declares minServerVersion 1.0.0.
- Server v1.0.0 rejects a bundle without the manifest that only WebUI 2.x
  ships ("Application bundle does not contain a manifest").

The separate bumps in #1227 (server only) and #1223 (WebUI only) were
each broken on their own, so this commit carries both.

WebUI v2.0.0 also dropped the hardcoded 'stalwart-webui' fallback client
id. Without an oauth-client-id meta tag (or CIMD support at the IdP) it
shows "Sign-in is not configured" and never redirects. That is the
test-sso.js failure in Jenkins#0 of #1223. Set the Application record's
oauthClientId to the configured Keycloak client so the server injects the
meta tag on unpack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 09:59:51 +01:00
kevinveenbirkenbach a3c8f31d07 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	roles/web-app-stalwart/README.md
2026-10-14 13:34:01 +02:00
kevinveenbirkenbachandClaude Opus 5.5 f92b7000af fix(web-app-stalwart): let address bans expire
Stalwart bans an address for good once it passes a failure rate: the four
*BanPeriod fields of its Security settings are null, and block_ip stores a
ban without a period with expires_at.unwrap_or(u64::MAX). Many clients reach
Stalwart through one address (the reverse proxy, published ports, Tor, the
swarm ingress), so one burst of failed logins locked that path out until
someone deleted the BlockedIp entry by hand.

The role now sets authBanPeriod, abuseBanPeriod, loiterBanPeriod and
scanBanPeriod to services.stalwart.ban_period_seconds, one hour by default,
and reloads the settings, as Stalwart's own security test does after it
changes a ban period. It reads the settings first and leaves them alone when
they already match, so a redeploy changes nothing.

Closes #916

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 12:01:36 +02:00
kevinveenbirkenbachandClaude Opus 5.5 731f3b240d fix(web-app-stalwart): fail on a refused app password destroy and match the ban tracer by its events
Two findings of the CodeRabbit review of #869.

jmap_call.yml counted notCreated and notUpdated as a refused call, but
not notDestroyed. A destroy that Stalwart rejected therefore passed, and
the post-load flush cleared stalwart_app_password_stale while the retired
app password stayed valid. A notDestroyed entry now fails the call,
except notFound, where the password is already gone. The password keeps
its retired description, so the next deploy collects it again and
retries the destroy.

tracing.yml took any Stdout tracer with an include policy for the ban
tracer, so one that includes other events kept the role from creating
its own and bans stayed off the container output. The selector now also
requires every event of STALWART_BAN_TRACER.

Verified with the real task files, a stubbed container command and
canned JMAP responses: a forbidden destroy now ends with failed=1, while
notFound and a clean destroy pass; with an unrelated include tracer the
role makes a second JMAP call that creates the ban tracer, with a
complete one it does not. The previous files pass the forbidden destroy
and skip the creation in both tracer cases.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 21:32:42 +02:00
kevinveenbirkenbachandClaude Opus 5.5 7383bf3a65 feat(web-app-stalwart): print bans and failed logins on the container output
Stalwart writes its log to a dated file in the data volume, which the rescue
diagnostics do not collect, so the failure of Baserow#0 in #869 showed the
banned address but not which account and listener tripped the ban. A Stdout
tracer limited to the four ban events and auth.failed now puts those lines
into `docker logs`, which the rescue already captures.

Verified on Stalwart v0.16.25: the tracer is accepted with these event ids and
buffered false, and after a settings reload a ban shows up on the container
output as "Banned due to authentication errors (security.authentication-ban)"
with listenerId, localPort, remoteIp and accountName.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 15:09:39 +02:00
kevinveenbirkenbachandClaude Opus 5.5 7bb0383e38 fix(web-app-stalwart): drop a replaced bot app password only after the load
The recover drill between the two CI passes restores /var/lib/infinito/secrets
with rsync --delete, so PASS 2 starts with an empty token store and the role
replaces the app password of every bot. It destroyed the old one at once,
while alertmanager, OpenWebUI and Baserow, rendered in PASS 1, kept sending
with it until the preload rendered them again 15 to 25 minutes later.
Stalwart counts every refused login against the client's address, which for
host-originated and cross-bridge clients is the Docker gateway 192.168.200.1.
After 100 refusals it banned that address for good, openresty's upstream,
alertmanager and the msmtp health check had their connections reset, and
Baserow#0 of #869 failed with exit 76. The rescue artifact shows 31 of those
refusals, all from alertmanager; Stalwart's own log, which names the account
behind the ban, was not collected.

A replaced app password is now renamed to
STALWART_BOT_APP_PASSWORD_RETIRED_DESCRIPTION once the new one is stored, and
destroyed in the service-loader post-load flush next to the deferred OIDC
restart, after the preload has rendered every consumer with the new one. Each
deploy also collects retired passwords an earlier deploy left behind, so a
deploy that stops before the flush neither replaces the password a second
time nor drops the one it handed out.

Verified: on Stalwart v0.16.25, 100 refused logins from one container address
banned it permanently (BlockedIp, reason authFailure, expiresAt null) and new
connections from it were reset, as in the CI log, and an app password's
description can be updated in place. A playbook running the changed task
files with JMAP stubbed (ansible-core 2.21.4) shows that a PASS 2 replacement
destroys the old ids only in the flush; that a deploy failing after the
Stalwart preload destroys nothing, while the next one mints nothing and
destroys only the retired ids; and that outside the service loader each bot's
old password is destroyed right after its new one is stored.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 15:09:39 +02:00
kevinveenbirkenbachandClaude Opus 5.5 08ef66d8de fix(web-app-stalwart): authenticate bots with an app password under SSO
Under SSO the role tried to let applications relay without SMTP AUTH by
widening the relay rule to the private networks. The rule compared the
peer address to the text of a CIDR, which never matches, so an
application could reach local mailboxes but got "550 5.1.2 Relay not
allowed" for every external recipient. Repairing the comparison would
have opened a relay, because published ports, Tor and the swarm mesh all
hand outside clients an address inside those networks.

Stalwart checks app passwords before it asks the external directory, so
a bot can authenticate while the directory is OIDC. With SSO enabled the
role now creates one app password per bot, acting for that account as
the recovery admin, and stores it in the token store the SMTP password
is already read from. A bot gets a new one when its entry is deleted in
Stalwart or its token is removed.

The relay rule is back to authenticated sessions only. The lookup loses
its SSO relay branch and the role its submission_via_relay flag, so an
SSO deployment submits on 465 with AUTH like every other one.

The role's CLI test now fails when an unauthenticated session is given
an external recipient, or when the bot credential is refused one.

Closes #793

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 08:57:22 +02:00
kevinveenbirkenbachandClaude Opus 5.5 52fe3c9c2c style: apply make autoformat to the merged Stalwart and Tor files
Formatter output only: the parsed YAML and the Python AST of every touched file equal their previous versions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 19:57:30 +02:00
Alejandro Roman a390fea9e9 fix(swarm-stalwart): fix CI issues, cmments mapache 2026-09-28 18:23:16 +04:00
Alejandro Roman fadf36e0a6 fix(swarm-stalwart): fix CI issues 2026-09-26 18:05:56 +04:00
Alejandro Roman c8894b2dd2 Merge remote-tracking branch 'upstream/main' into feature/role-stalwart-mail-swarm 2026-09-22 22:33:13 +04:00
Alejandro Roman 4287dee96b fix(swarm-stalwart): fix new issue on CI and allow videos on logic playwright 2026-09-09 22:45:08 +04:00
Alejandro Roman e6596d51f2 fix(stalwart): add the fix PR into main PR 2026-09-09 00:21:58 +04:00
Alejandro Roman 7b573748d4 fix(stalwart): Fix nextcloud issues related to mail + gitlab 2026-09-07 03:50:25 +04:00
Alejandro Roman 9e05e04573 fix(stalwart): CI issues related to Stalwart +temporal test to cehck issues locally not reproduceble 2026-09-06 13:58:01 +04:00
Alejandro Roman 1351c7ae96 fix(stalwart): fix mailu issues, add more playwrithg with full cehcks, fix some SPOT 2026-09-04 14:57:08 +04:00
Alejandro Roman f9d3a1345f fix(stalwart): fix TOr and testing properly, local pass 2026-09-04 02:43:39 +04:00
Alejandro Roman 17e2b151ab fix(stalwart): fix some spots and issues found 2026-09-02 22:21:31 +04:00
Alejandro Roman 942fdb5fad fix(stalwart): fix all comments, fix tor approach, SPOT fixes and chagne some values to follow standars + remove comemnts 2026-09-02 22:05:45 +04:00
Alejandro Roman e809765e23 fix(stalwart): fix all comments, fix a tor issue fouind and fix all SPOT issues 2026-09-02 15:02:46 +04:00
Alejandro Roman accc9e4c7c feat(UI): fix migration, errors happening in local deploy and swarm integraton issues, tor merged and implement stalwart with tor 2026-09-01 12:34:57 +04:00
Alejandro Roman 333079cc2a feat(UI): fix migration, errors happening in local deploy and swarm integraton + merged main 2026-08-30 21:39:52 +04:00
Alejandro Roman 026b28f9a7 fix(swar-stalwart): migration process 2026-08-30 14:43:00 +04:00
Alejandro Roman 3c7c3239be feat(swarm): fix CI and mirations issues 2026-08-21 00:25:38 +04:00
Alejandro Roman ed1f35d6ae feat(swarm): add new test appraoch + fix swarm issues 2026-08-19 20:08:59 +04:00
Alejandro Roman 8a4f4142ff feat(swarm): Fix issues with mailu swarm 2026-08-19 14:32:56 +04:00
Alejandro Roman d436f994e9 feat(swarm): Add Stalwart to swarm 2026-08-19 02:36:12 +04:00
Alejandro Roman 0276eb2397 feat(swarm-stalwart): add stalwart to swarm 2026-08-18 02:49:36 +04:00
Alejandro Roman 871b00670a Merge kevinveenbirkenbach/feature/swarm-nfs into stalwart integration branch 2026-07-27 11:25:42 +02:00
Alejandro Roman ac101d2efa Merge kevinveenbirkenbach/feature/swarm-nfs into stalwart integration branch 2026-07-21 10:00:17 +02:00
Alejandro Roman ab25684714 adrees all commens and fix CI usses on prev run 2026-07-16 19:19:23 +02:00
Alejandro Roman 889d3a370f fix SPot and issues found 2026-07-16 14:26:11 +02:00
Alejandro Roman df71fb9d40 fixes in CI 2026-07-13 18:20:35 +02:00
Alejandro Roman 9cbd8a3bad fix CI issues 2026-07-12 00:52:34 +02:00
Alejandro Roman d70d8581ad fix CI isssues 2026-07-11 01:08:42 +02:00
Alejandro Roman d9ed2b9db9 fixed stalwart CI issues 2026-07-09 19:47:45 +02:00
Alejandro Roman 1b547ccac0 fix(stalwart): pin image/webui versions and defer OIDC restart until issuer is up 2026-07-09 10:01:34 +02:00
Alejandro Roman ecd067fb7b fix issues with order 2026-07-08 22:03:14 +02:00
Alejandro Roman 933b8cebcf fix errors in test wiht guard 2026-07-08 12:11:04 +02:00
Alejandro Roman f9319863cf removed guras and make local/CI DinD use .test isntead of .example for the whole stack 2026-07-08 10:11:17 +02:00
Alejandro Roman 948b5f2f84 fix error in deploy 2026-07-03 06:58:17 +02:00
Alejandro Roman 500e5fef34 fix playwright tests 2026-07-02 23:08:03 +02:00
Alejandro Roman 564c0ab4df fix palywirght issues part 1 2026-07-01 22:33:20 +02:00
Alejandro Roman 3a47cf48c7 fix AV issues 2026-07-01 13:44:24 +02:00
Alejandro Roman 9104b34795 add AV, Expose CalDAV/CardDAV/WebDAV and final testing refactoring 2026-07-01 13:04:15 +02:00
Alejandro Roman 233f15f3e0 fix OIDC and add more scenarios to tests 2026-07-01 11:19:49 +02:00