This document describes a proof of concept that may be used for experiments to run the Postfix master(8) daemon without "root" privileges (and consequently, all Postfix daemon processes). As discussed below, this will make Postfix less secure.
This is where "unprivileged" Postfix can or must not be used:
Feasible for production use, with some weakened security:
Unprivileged master(8) deployments that forward all messages with SMTP or LMTP.
This does not make Postfix less secure than other mail systems. It just makes Postfix less secure than it could be.
MUST NOT BE USED FOR PRODUCTION:
Unprivileged master(8) deployments that can execute non-Postfix programs (with local(8), pipe(8) or spawn(8)) or that deliver to files (with local(8) or virtual(8)).
The remainder of this document is organized as follows:
This section summarizes the Postfix security model for privileged master(8) operation, and how it is weakened with an unprivileged master(8).
Over-all Postfix architecture:
Postfix consists of a) service daemons that receive and deliver email messages in the background, b) client commands that are invoked by users, and c) commands for system administrators. The privileged master(8) daemon runs all service daemons in a controlled environment. Postfix has no set-uid program files, eliminating a large class of attacks.
What changes with unprivileged master(8) operation:
The master(8) daemon runs with fixed "postfix" privilege instead of "root". This affects all Postfix service daemon processes as discussed next.
Postfix service daemon processes:
When launched by a privileged master(8) daemon, a Postfix service daemon can revoke "root" privileges after initialization, before it handles any requests. This allows each service process to open and read sensitive files (e.g. TLS private keys or database passwords) and to open and modify sensitive databases or logfiles. When a service process becomes compromised after it drops root privileges, it can no longer open sensitive files, databases or logfiles.
All but four Postfix service daemons irrevocably release root privileges, and therefore do not in any sense run as "root" once they're handling service requests. Here, "root" privilege is used to separate privileges within Postfix (internal privilege separation).
The only exceptions are local(8), pipe(8), spawn(8) and virtual(8) which run as "postfix" most of the time, but retain a saved effective uid of "root" so that they can temporarily run as the target user (the user that owns the target mailbox or maildir file, or that is configured to execute the given command). Here, root privilege is used to select different privileges for Postfix and non-Postfix resources ("postfix" privilege confinement).
If the Postfix service daemons in question do not access non-Postfix files and if they do not execute non-Postfix commands, then all the services launched by the master(8) daemon will irrevocably run unprivileged after initialization.
What changes with unprivileged master(8) operation:
Postfix cannot maintain internal privilege separation (service daemons must use the same Postfix privileges while initializing and while handling requests), and it cannot maintain "postfix" privilege confinement (service daemons must use the same "postfix" privileges to execute non-Postfix programs, to deliver to mailbox or maildir files, or to access non-Postfix files). Specifically:
These MUST NOT be used in production:
The local(8) deliver agent will execute "|command" etc. instructions in a user's ".forward" file with "postfix" privileges instead of the target user's privileges, allowing the user to take control of the Postfix mail system.
All local(8) and virtual(8) mailbox and maildir files will be owned by the "postfix" user instead of the receiving user. If a user can read one mailbox and maildir file, they can read, write, and delete all such files; additionally they can read, write, and delete all messages in the Postfix queue.
The local(8), pipe(8) or spawn(8) daemons will execute non-Postfix commands with "postfix" privileges instead of the target user's privileges; if the command has a vulnerability, then an attacker can take over the Postfix mail system.
The weaker protection of secret-key files and the optional Postfix "maillog_file" logging are discussed under the next item.
Postfix configuration, secrets, and logs:
Postfix configuration files and "maillog_file" logfiles are owned by the "root" user. Additionally, secret key files are readable only by "root". This protects secret keys, configuration files, and Postfix logging, after someone compromises an unprivileged Postfix service daemon process.
What changes with unprivileged master(8) operation:
Configuration files including secret keys will still be owned by "root", but keys will need to be readable by the "postfix" user. After someone compromises an unprivileged Postfix service daemon process, they will be able to read all Postfix secret key files.
The optional Postfix "maillog_file" logfiles will need to be owned by "postfix" instead of "root". After someone compromises an unprivileged Postfix service daemon process, they will be able to read, write, or delete Postfix "maillog_file" records.
This does not make Postfix less secure than other mail systems. It just makes Postfix less secure than it could be.
Handling requests from local users:
Postfix uses the set-gid postdrop(1) client command to submit messages into the mail queue; uses the set-gid postqueue(1) client command to list or mail queue, or to request delivery; and uses the set-gid postlog(1) client command to report events to the optional Postfix "maillog_file". Postfix provides sendmail-compatible helper programs sendmail(1) and mailq(1) that invoke postdrop(1) or postqueue(1).
What changes with unprivileged master(8) operation:
No change. The set-gid client programs are still needed.
Postfix queue files and directories:
The "postfix" user owns all Postfix queue directories and queue files, except for local submissions created with the postdrop(1) command. Those queue files are owned by the originating user, and are stored in the "maildrop" queue directory, from where they are ingested into Postfix by the pickup(8) service daemon. The Postfix queue is not directly accessible by users.
Commands for system administrators:
The postfix(1), postmulti(1), and postsuper(1) commands are reserved for the "root" user; these commands require "root" privileges to make configuration changes.
What changes with unprivileged master(8) operation:
As detailed in the next section, many commands may also be invoked by the "postfix" user, but "root" is still required for commands that make configuration changes.
The following instructions assume that the steps in the " Appendix have already been implemented.
To start, reload or stop Postfix, execute as the "root" or "postfix" user:
postfix start
postfix reload
postfix stop
Commands that do not change configuration may be executed as the "root" or "postfix" user:
postfix status
postfix non-bdb status
postfix tls output-server-csr (or output-server-tlsa)
postsuper options... arguments...
Commands that change Postfix permissions or configuration must be executed as the "root" user:
postfix set-permissions
postfix tls subcommand
postfix non-bdb subcommand
postmulti -e command
postconf "key = value"
postmap "maptype:mapname..."
postalias "maptype:mapname..."
Postfix configurations MUST NOT be owned by the postfix user: that would be too convenient for someone who compromises a Postfix daemon process.
Wietse Venema originally implemented unprivileged master(8) operation for integration tests.
Viktor Dukhovni contributed background on the postfix-users mailing list.
Unprivileged master(8) operation requires that Postfix is built with dynamically-linked libraries, that is, with "shared=yes" on the "make makefiles" command line.
After installing Postfix from a standard pre-compiled package or from source code, a few tweaks are needed to make unprivileged operation possible. These changes should persist after subsequent Postfix updates.
To revert to the privileged Postfix operation, stop Postfix and undo the changes described in the subsections that follow.
If you configure a non-default "maillog_file" setting, then Postfix will create that file with user "postfix" privileges (instead of "root"). The parent directory and logfile will need to be writable by the "postfix" user. Example:
postconf maillog_file=/var/log/postfix/maillog mkdir -p /var/log/postfix touch /var/log/postfix/maillog chown postfix /var/log/postfix /var/log/postfix/maillog
To revert to privileged Postfix master(8) operation, make the parent directory (in the example: /var/log/postfix) writable by "root" only.
As user "root", update the file "main.cf" with the following command:
postconf "import_environment = $(postconf -dh import_environment) \
LD_PRELOAD=\$shlib_directory/fake_eugid.so"
Do not omit the "\" in "\$shlib_directory".
The result is an import_environment setting that contains both the default import_environment value plus the literal string LD_PRELOAD=$shlib_directory/fake_eugid.so. With Postfix 3.12 the result looks like:
import_environment = MAIL_CONFIG MAIL_DEBUG MAIL_LOGTAG TZ XAUTHORITY
DISPLAY LANG=C POSTLOG_SERVICE POSTLOG_HOSTNAME XDG_RUNTIME_DIR
LD_PRELOAD=$shlib_directory/fake_eugid.so
(it will be all on one long line).
To revert to privileged Postfix master(8) operation, execute as the "root" user:
postconf -X import_environment
As root, execute the following command:
echo '$queue_directory/pid:d:$mail_owner:-:755:uc' \
> $(postconf -xh meta_directory)/postfix-files.d/unprivileged
NOTE: use single quotes (') not double quotes (").
This adds add a file "unprivileged" in a subdirectory "postfix-files.d" under the Postfix directory configured with "meta_directory". The result should look like this:
$queue_directory/pid:d:$mail_owner:-:755:uc
Execute as the "root" user:
postfix set-permissions
To revert to privileged Postfix master(8) operation, delete the file "unprivileged" that was created above, and execute as the "root" user:
rm $(postconf -xh meta_directory)/postfix-files.d/unprivileged
postfix set-permissions
Normally, Postfix daemons open secret key files before dropping "root" privileges. With unprivileged Postfix that is no longer possible.
Execute the following command as "root" for each such file:
chmod 644 /path/to/secret-key-file.pem
You may need to update Let'sEncrypt etc. key management automation to ensure correct file permissions after a future key update.
To revert to privileged Postfix master(8) operation, execute the following command as "root" for each of the above files:
chmod 600 /path/to/secret-key-file.pem
and revert the above changes to Let'sEncrypt etc. key management automation.