Stalwart: Difference between revisions

Onny (talk | contribs)
Cleanup default config
Onny (talk | contribs)
Sending from subaddresses: Add note on planned feature
 
(7 intermediate revisions by the same user not shown)
Line 108: Line 108:


TLS key generation is done using DNS-01 challenge through Cloudflare domain provider, see dns-update library for [https://github.com/stalwartlabs/dns-update further providers] or configure [https://stalw.art/docs/server/tls/certificates manual certificates].
TLS key generation is done using DNS-01 challenge through Cloudflare domain provider, see dns-update library for [https://github.com/stalwartlabs/dns-update further providers] or configure [https://stalw.art/docs/server/tls/certificates manual certificates].
== Configuration ==


=== DNS records ===
=== DNS records ===
Line 118: Line 116:
Review the list of which DNS records are required including their values for the mail server to work at https://webadmin.example.org/manage/directory/domains/tuxtux.com.co/view. Especially following records are essential:
Review the list of which DNS records are required including their values for the mail server to work at https://webadmin.example.org/manage/directory/domains/tuxtux.com.co/view. Especially following records are essential:


* Record type: A, Name: example.org
{| class="wikitable"
* Record type: AAAA, Name: example.org
! Record Type
* Record type: CNAME, Name: autoconfig Value: example.org
! Name
* Record type: CNAME, Name: autodiscover, Value: example.org
! Value / Target
* Record type: CNAME, Name: mail, Value: example.org
! Notes
* Record type: CNAME, Name: mta-sts, Value: example.org
|-
* Record type: CNAME, Name: mail, Value: example.org
| A
* Record type: CNAME, Name: webadmin, Value: example.org
| example.org
* Record type: MX, Name: example.org, Value: mx1.example.org
| ''IPv4 address of the mail server''
* Record type: SRV, Name: _imaps._tcp
| Required
* Record type: SRV, Name: _submissions._tcp
|-
* Record type: TLSA, Name: _25._tcp.example.org., Value: Only the one starting with "3 1 1" required
| AAAA
* Record type: TLSA, Name: _25._tcp.mx1.example.org., Value: Only the one starting with "3 1 1" required
| example.org
* Record type: TXT, Name: 202409e._domainkey
| ''IPv6 address of the mail server''
* Record type: TXT, Name: 202409r._domainkey
| Required
* Record type: TXT, Name: _dmarc
|-
* Record type: TXT, Name: mx1
| CNAME
* Record type: TXT, Name: _smtp._tls
| autoconfig
* Record type: TXT, Name: example.org
| example.org
| Mail client autoconfiguration
|-
| CNAME
| autodiscover
| example.org
| Outlook / Exchange compatibility
|-
| CNAME
| mail
| example.org
| Mail host
|-
| CNAME
| mta-sts
| example.org
| MTA-STS
|-
| CNAME
| webadmin
| example.org
| Stalwart web administration interface
|-
| MX
| example.org
| mx1.example.org
| Mail delivery
|-
| SRV
| _imaps._tcp
| ''See Web Admin for exact values''
| IMAPS service
|-
| SRV
| _submissions._tcp
| ''See Web Admin for exact values''
| SMTP Submission service
|-
| TLSA
| _25._tcp.example.org.
| 3 1 1 …
| Only the record starting with <code>3 1 1</code> is required
|-
| TLSA
| _25._tcp.mx1.example.org.
| 3 1 1 …
| Only the record starting with <code>3 1 1</code> is required
|-
| TXT
| 202409e._domainkey
| ''DKIM public key''
| DKIM
|-
| TXT
| 202409r._domainkey
| ''DKIM public key''
| DKIM
|-
| TXT
| _dmarc
| ''DMARC policy''
| DMARC
|-
| TXT
| mx1
| ''SPF or server information''
| Depends on configuration
|-
| TXT
| _smtp._tls
| ''MTA-STS policy''
| SMTP TLS reporting
|-
| TXT
| example.org
| ''SPF record''
| SPF
|}


=== DNSSEC ===
=== DNSSEC ===
Line 146: Line 221:
  _25._tcp.mx1.example.org. 10800 IN TLSA 3 1 1 7f59d873a70e224b184c95a4eb54caa9621e47d48b4a25d312d83d96 e3498238
  _25._tcp.mx1.example.org. 10800 IN TLSA 3 1 1 7f59d873a70e224b184c95a4eb54caa9621e47d48b4a25d312d83d96 e3498238
  _25._tcp.mx1.example.org. 10800 IN RRSIG TLSA 13 5 10800 20230601000000 20230511000000 39688 example.org. He9VYZ35xTC3fNo8GJa6swPrZodSnjjIWPG6Th2YbsOEKTV1E8eGtJ2A +eyBd9jgG+B3cA/jw8EJHmpvy/buCw==
  _25._tcp.mx1.example.org. 10800 IN RRSIG TLSA 13 5 10800 20230601000000 20230511000000 39688 example.org. He9VYZ35xTC3fNo8GJa6swPrZodSnjjIWPG6Th2YbsOEKTV1E8eGtJ2A +eyBd9jgG+B3cA/jw8EJHmpvy/buCw==
== Configuration ==
=== Mail aliases ===
Considering the configuration above, we could add a mail alias for <code>user1@example.org</code> by simply adding further addresses to the <code>email</code>-array such as <code>user1real@example.org</code>
{{file|/etc/nixos/configuration.nix|nix|3=services.stalwart-mail = {
  settings = {
    [...]
    directory."in-memory" = {
      type = "memory";
      principals = [
        {
          class = "individual";
          name = "User 1";
          secret = "%{file:/etc/stalwart/mail-pw1}%";
          email = [ "user1@example.org" "user1real@example.org ];
        }
      ];
    };
  };
};}}


== Tips and tricks ==
== Tips and tricks ==


=== Auto update TLSA records ===
Stalwart [https://github.com/stalwartlabs/stalwart/issues/1664 does not yet] automatically update the TLSA record if your ACME certificate changes.
Following script is a possible workaounrd. It extracts the ACME cert every five minute, calculates the TLSA hash and compares it with the upstream record. If it doesn't match, it uses [https://github.com/Stenstromen/gotlsaflare gotlsaflare] to update the TLSA record on Cloudflare.
<syntaxhighlight lang="nixos">
systemd.services.tlsa-cloudflare-update = {
  description = "Check and update TLSA/DANE record for mx1 from Stalwart ACME Cert";
 
  after = [
    "network-online.target"
    "stalwart-mail.service"
  ];
  wants = [
    "network-online.target"
    "stalwart-mail.service"
  ];
 
  serviceConfig = {
    Type = "oneshot";
    User = "stalwart-mail";
    Group = "stalwart-mail";
    EnvironmentFile = config.age.secrets.gotlsaflare-cloudflare-token.path;
    RuntimeDirectory = "stalwart-tlsa";
  };
  environment = {
    DOMAIN = "example.org";
    SUBDOMAIN = "mail";
    PORT = "25";
    ACME_PROVIDER_ID = "cloudflare";
  };
  path = with pkgs; [
    bash
    coreutils
    openssl
    dnsutils
    gotlsaflare
    rocksdb.tools
    gawk
  ];
  script = ''
    set -eu
    TLSA_RECORD="_$PORT._tcp.$SUBDOMAIN.$DOMAIN"
    DB_PATH="/var/lib/stalwart-mail/db"
    TEMP_RAW="/run/stalwart-tlsa/cert.bundle"
    TEMP_CRT="/run/stalwart-tlsa/cert.crt"
    echo "Starting TLSA update process for $DOMAIN"
    ldb --db="$DB_PATH" --column_family=s get "acme.$ACME_PROVIDER_ID.cert" | base64 -d > "$TEMP_RAW"
    if [ ! -s "$TEMP_RAW" ]; then
      echo "ERROR: ACME certificate extraction failed"
      exit 1
    fi
    openssl x509 -in "$TEMP_RAW" -out "$TEMP_CRT"
    LOCAL_HASH=$(openssl x509 -in "$TEMP_CRT" -pubkey -noout | openssl pkey -pubin -outform DER | openssl sha256 | awk '{print tolower($2)}')
    echo "Local hash: $LOCAL_HASH"
    UPSTREAM_HASH=$(dig +nosplit +short TLSA "$TLSA_RECORD" | awk '{print tolower($4)}' | head -n1)
    echo "Upstream hash: $UPSTREAM_HASH"
    if [ "$LOCAL_HASH" = "$UPSTREAM_HASH" ]; then
      echo "Hashes match. DNS is up to date."
      exit 0
    fi
    echo "Hashes differ! Updating Cloudflare..."
    gotlsaflare update \
      --url "$DOMAIN" \
      --subdomain "$SUBDOMAIN" \
      --tcp"$PORT" \
      --cert "$TEMP_CRT"
    echo "TLSA update completed successfully."
  '';
};
systemd.timers.tlsa-cloudflare-update = {
  description = "Run TLSA check and update every 5 minutes";
  wantedBy = [ "timers.target" ];
  timerConfig = {
    OnBootSec = "2m";
    OnUnitActiveSec = "5m";
    Unit = "tlsa-cloudflare-update.service";
  };
};
</syntaxhighlight>
Adapt the variables <code>DOMAIN</code>, <code>SUBDOMAIN</code>, and <code>PORT</code> according to your needs. The variable <code>ACME_PROVIDER_ID</code> corresponds to the ACME profile name you've setup in the Stalwart webadmin interface. <code>EnvironmentFile</code> points to a file containing the secret Cloudflare api token in the format: TOKEN=12345678[...].
=== Sending from subaddresses ===
Receiving mails to subaddresses like <code>john+secondary@example.org</code> is enabled by default. Sending from subaddresses will fail with "You are not allowed to send from this address" as long as they are not an configured alias address. You can disable this check but it will allow any authenticated user to send from any other address.
{{file|/etc/nixos/configuration.nix|nix|3=services.stalwart-mail = {
  settings = {
    [...]
    session.auth.must-match-sender = false;
  };
};}}
A configuration option to customize the pattern of authorized sender addresses is a [https://github.com/stalwartlabs/stalwart/issues/394#issuecomment-3705990056 planned feature].
=== Test mail server ===
=== Test mail server ===
You can use several online tools to test your mail server configuration:
You can use several online tools to test your mail server configuration: