Stalwart: Difference between revisions

Auto update TLSA records: add https://codeberg.org/Cameo007/dyndns-tlsa-desec for deSEC.io
Eliasp (talk | contribs)
 
(7 intermediate revisions by 3 users not shown)
Line 1: Line 1:
[https://stalw.art Stalwart] is an open-source, all-in-one mail server solution that supports JMAP, IMAP4, and SMTP protocols. It's designed to be secure, fast, robust, and scalable, with features like built-in DMARC, DKIM, SPF, and ARC support for message authentication. It also provides strong transport security through DANE, MTA-STS, and SMTP TLS reporting. Stalwart is written in Rust, ensuring high performance and memory safety.
[https://stalw.art Stalwart] is an open-source, all-in-one mail server solution that supports JMAP, IMAP4, and SMTP protocols. It's designed to be secure, fast, robust, and scalable, with features like built-in DMARC, DKIM, SPF, and ARC support for message authentication. It also provides strong transport security through DANE, MTA-STS, and SMTP TLS reporting. Stalwart is written in Rust, ensuring high performance and memory safety.
{{Notice
  |icon=ⓘ
  |color=var(--border-color-warning)
  |background=var(--background-color-warning-subtle)
  |The content of this page are in reference to versions of Stalwart prior to v0.16. Later versions of Stalwart have adopted a new configuration format clearly described (and with examples for NixOS) in the upstream documentation: [https://stalw.art/docs/management/cli/apply/#nixos Stalwart Declarative bulk operations].
}}


== Setup ==
== Setup ==
The following example enables the Stalwart mail server for the domain ''example.org'', listening on mail delivery SMTP/Submission (<code>25, 465</code>), IMAPS (<code>993</code>) and JMAP ports (8080/443) for mail clients to connect to. Mailboxes for the accounts <code>postmaster@example.org</code> and <code>user1@example.org</code> get created if they don't exist yet.
The following example enables the Stalwart mail server for the domain ''example.org'', listening on mail delivery SMTP/Submission (<code>25, 465</code>), IMAPS (<code>993</code>) and JMAP ports (8080/443) for mail clients to connect to. Mailboxes for the accounts <code>postmaster@example.org</code> and <code>user1@example.org</code> get created if they don't exist yet.


{{file|/etc/nixos/configuration.nix|nix|3=environment.etc = {
{{file|||3=environment.etc = {
   "stalwart/mail-pw1".text = "foobar";
   "stalwart/mail-pw1".text = "foobar";
   "stalwart/mail-pw2".text = "foobar";
   "stalwart/mail-pw2".text = "foobar";
Line 11: Line 19:
};
};


services.stalwart-mail = {
services.stalwart = {
   enable = true;
   enable = true;
   openFirewall = true;
   openFirewall = true;
  credentials = {
    mail-pw1 = "/etc/stalwart/mail-pw1";
    mail-pw2 = "/etc/stalwart/mail-pw2";
    acme-secret = "/etc/stalwart/acme-secret";
    admin-pw = "/etc/stalwart/admin-pw";
  };
   settings = {
   settings = {
     server = {
     server = {
Line 57: Line 71:
       domains = [ "example.org" "mx1.example.org" ];
       domains = [ "example.org" "mx1.example.org" ];
       provider = "cloudflare";
       provider = "cloudflare";
       secret = "%{file:/etc/stalwart/acme-secret}%";
       secret = "%{file:/run/credentials/stalwart.service/acme-secret}%";
     };
     };
     session.auth = {
     session.auth = {
Line 72: Line 86:
           class = "individual";
           class = "individual";
           name = "User 1";
           name = "User 1";
           secret = "%{file:/etc/stalwart/mail-pw1}%";
           secret = "%{file:/run/credentials/stalwart.service/mail-pw1}%";
           email = [ "user1@example.org" ];
           email = [ "user1@example.org" ];
         }
         }
Line 78: Line 92:
           class = "individual";
           class = "individual";
           name = "postmaster";
           name = "postmaster";
           secret = "%{file:/etc/stalwart/mail-pw1}%";
           secret = "%{file:/run/credentials/stalwart.service/mail-pw2}%";
           email = [ "postmaster@example.org" ];
           email = [ "postmaster@example.org" ];
         }
         }
Line 85: Line 99:
     authentication.fallback-admin = {
     authentication.fallback-admin = {
       user = "admin";
       user = "admin";
       secret = "%{file:/etc/stalwart/admin-pw}%";
       secret = "%{file:/run/credentials/stalwart.service/admin-pw}%";
     };
     };
   };
   };
Line 105: Line 119:
     };
     };
   };
   };
};}}
};|name=/etc/nixos/configuration.nix|lang=nix}}


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].
Line 223: Line 237:


=== Running behind reverse proxy ===
=== Running behind reverse proxy ===
When running behind a load balancer or reverse proxy, Stalwart will not be able to see the "real" sender IP-addresses of incoming mails in case of simple port forwarding. [[HAProxy]] or Proxy Protocol solves this problem and should be used on the reverse proxy server to forward SMTP traffic. Stalwart will start parsing the Proxy Protocol packages if correctly configured on the listener.{{file|||3=services.stalwart-mail = {
When running behind a load balancer or reverse proxy, Stalwart will not be able to see the "real" sender IP-addresses of incoming mails in case of simple port forwarding. [[HAProxy]] or Proxy Protocol solves this problem and should be used on the reverse proxy server to forward SMTP traffic. Stalwart will start parsing the Proxy Protocol packages if correctly configured on the listener.{{file|||3=services.stalwart = {
   settings = {
   settings = {
     server = {
     server = {
Line 247: Line 261:
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>
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 = {
{{file|||3=services.stalwart = {
   settings = {
   settings = {
     [...]
     [...]
Line 256: Line 270:
           class = "individual";
           class = "individual";
           name = "User 1";
           name = "User 1";
           secret = "%{file:/etc/stalwart/mail-pw1}%";
           secret = "%{file:/run/credentials/stalwart.service/mail-pw1}%";
           email = [ "user1@example.org" "user1real@example.org ];
           email = [ "user1@example.org" "user1real@example.org ];
         }
         }
Line 262: Line 276:
     };
     };
   };
   };
};}}
};|name=/etc/nixos/configuration.nix|lang=nix}}
=== Blocking mail sender address ===
If you don't want to receive any mails from a specific address, even not into your spam folder, you can add it to the spam-trap array.{{file|||3=services.stalwart = {
  settings = {
    lookup = {
      spam-trap = {
        "malicious_sender1@spamhost.com" = "";
        "malicious_sender2@spamhost.com" = "";
      };
  };
};|name=/etc/nixos/configuration.nix|lang=nix}}


== Tips and tricks ==
== Tips and tricks ==
Line 270: Line 294:
Stalwart [https://github.com/stalwartlabs/stalwart/issues/1664 does not yet] automatically update the TLSA record if your ACME certificate changes.
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.
Following script is a possible workaround. 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">
<syntaxhighlight lang="nixos">systemd.services.tlsa-cloudflare-update = {
systemd.services.tlsa-cloudflare-update = {
   description = "Check and update TLSA/DANE record for mx1 from Stalwart ACME Cert";
   description = "Check and update TLSA/DANE record for mx1 from Stalwart ACME Cert";
    
    
   after = [
   after = [
     "network-online.target"
     "network-online.target"
     "stalwart-mail.service"
     "stalwart.service"
   ];
   ];
   wants = [
   wants = [
     "network-online.target"
     "network-online.target"
     "stalwart-mail.service"
     "stalwart.service"
   ];
   ];
    
    
   serviceConfig = {
   serviceConfig = {
     Type = "oneshot";
     Type = "oneshot";
     User = "stalwart-mail";
     User = "stalwart";
     Group = "stalwart-mail";
     Group = "stalwart";
     EnvironmentFile = config.age.secrets.gotlsaflare-cloudflare-token.path;
     EnvironmentFile = config.age.secrets.gotlsaflare-cloudflare-token.path;
     RuntimeDirectory = "stalwart-tlsa";
     RuntimeDirectory = "stalwart-tlsa";
Line 312: Line 335:


     TLSA_RECORD="_$PORT._tcp.$SUBDOMAIN.$DOMAIN"
     TLSA_RECORD="_$PORT._tcp.$SUBDOMAIN.$DOMAIN"
     DB_PATH="/var/lib/stalwart-mail/db"
     DB_PATH="/var/lib/stalwart/db"
     TEMP_RAW="/run/stalwart-tlsa/cert.bundle"
     TEMP_RAW="/run/stalwart-tlsa/cert.bundle"
     TEMP_CRT="/run/stalwart-tlsa/cert.crt"
     TEMP_CRT="/run/stalwart-tlsa/cert.crt"
Line 357: Line 380:
     Unit = "tlsa-cloudflare-update.service";
     Unit = "tlsa-cloudflare-update.service";
   };
   };
};
};</syntaxhighlight>
</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[...].
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[...].
Line 385: Line 407:
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.
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 = {
{{file|||3=services.stalwart = {
   settings = {
   settings = {
     [...]
     [...]
     session.auth.must-match-sender = false;
     session.auth.must-match-sender = false;
   };
   };
};}}
};|name=/etc/nixos/configuration.nix|lang=nix}}


A configuration option to customize the pattern of authorized sender addresses is a [https://github.com/stalwartlabs/stalwart/issues/394#issuecomment-3705990056 planned feature].
A configuration option to customize the pattern of authorized sender addresses is a [https://github.com/stalwartlabs/stalwart/issues/394#issuecomment-3705990056 planned feature].
Line 402: Line 424:


=== Unsecure setup for testing environments ===
=== Unsecure setup for testing environments ===
The following minimal configuration example is unsecure and for testing purpose only. It will run the Stalwart mail server on <code>localhost</code>, listening on port <code>143</code> (IMAP) and <code>587</code> (Submission). Users <code>alice</code> and <code>bob</code> are configured with the password <code>foobar</code>.{{file|/etc/nixos/configuration.nix|nix|3=services.stalwart-mail = {
The following minimal configuration example is unsecure and for testing purpose only. It will run the Stalwart mail server on <code>localhost</code>, listening on port <code>143</code> (IMAP) and <code>587</code> (Submission). Users <code>alice</code> and <code>bob</code> are configured with the password <code>foobar</code>.{{file|||3=services.stalwart = {
   enable = true;
   enable = true;
   settings = {
   settings = {
Line 445: Line 467:
     };
     };
   };
   };
};}}
};|name=/etc/nixos/configuration.nix|lang=nix}}


== See also ==
== See also ==