summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2020-05-20 15:46:27 +0200
committerGuilhem Moulin <guilhem@fripost.org>2020-05-21 03:40:53 +0200
commit6d1daa0424c168eae4bfa9f6772add3f77ec506f (patch)
treea45e83f4fefa0a3976c534078d26d3ff003e9935
parent5118f8d3394579a245b355c863c69410fe92e26e (diff)
postfix-sender-login: Better hardening.
Run as a dedicated user, not ‘postfix’.
-rw-r--r--roles/MSA/files/etc/systemd/system/postfix-sender-login.service6
-rwxr-xr-xroles/MSA/files/usr/local/bin/postfix-sender-login.pl13
-rw-r--r--roles/MSA/tasks/main.yml16
-rw-r--r--roles/common-LDAP/templates/etc/ldap/database.ldif.j253
4 files changed, 59 insertions, 29 deletions
diff --git a/roles/MSA/files/etc/systemd/system/postfix-sender-login.service b/roles/MSA/files/etc/systemd/system/postfix-sender-login.service
index f5e6d89..d652f75 100644
--- a/roles/MSA/files/etc/systemd/system/postfix-sender-login.service
+++ b/roles/MSA/files/etc/systemd/system/postfix-sender-login.service
@@ -4,8 +4,7 @@ After=mail-transport-agent.target
Requires=postfix-sender-login.socket
[Service]
-User=postfix
-Group=postfix
+User=_postfix-sender-login
StandardInput=null
SyslogFacility=mail
ExecStart=/usr/local/bin/postfix-sender-login.pl
@@ -13,10 +12,9 @@ ExecStart=/usr/local/bin/postfix-sender-login.pl
# Hardening
NoNewPrivileges=yes
PrivateDevices=yes
+PrivateNetwork=yes
ProtectHome=yes
ProtectSystem=strict
-PrivateDevices=yes
-PrivateNetwork=yes
ProtectControlGroups=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
diff --git a/roles/MSA/files/usr/local/bin/postfix-sender-login.pl b/roles/MSA/files/usr/local/bin/postfix-sender-login.pl
index 374cc70..a37f872 100755
--- a/roles/MSA/files/usr/local/bin/postfix-sender-login.pl
+++ b/roles/MSA/files/usr/local/bin/postfix-sender-login.pl
@@ -3,7 +3,7 @@
#----------------------------------------------------------------------
# socketmap lookup table returning the SASL login name(s) owning a given
# sender address
-# Copyright © 2017 Guilhem Moulin <guilhem@fripost.org>
+# Copyright © 2017,2020 Guilhem Moulin <guilhem@fripost.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -33,12 +33,13 @@ use Authen::SASL ();
$ENV{PATH} = join ':', qw{/usr/bin /bin};
delete @ENV{qw/IFS CDPATH ENV BASH_ENV/};
-my $nProc = 2; # number of pre-forked servers
-my $POSTMASTER = 'postmaster@fripost.org'; # returned for forbidden envelope sender addresses
+my $nProc = 2; # number of pre-forked servers
+my $maxRequests = 32; # maximum number of requests per worker
+my $POSTMASTER = 'postmaster@fripost.org'; # returned for forbidden envelope sender addresses
-my $BASEDN = 'ou=virtual,dc=fripost,dc=org';
+my $BASEDN = "ou=virtual,dc=fripost,dc=org";
my $BUFSIZE = 65536; # try to read that many bytes at the time
-my $LDAPI = 'ldapi://%2Fvar%2Fspool%2Fpostfix-msa%2Fprivate%2Fldapi/';
+my $LDAPI = "ldapi://";
sub server();
@@ -66,7 +67,7 @@ exit $?;
#############################################################################
sub server() {
- for (my $n = 0; $n < 32; $n++) {
+ for (my $n = 0; $n < $maxRequests; $n++) {
accept(my $conn, $S) or do {
next if $! == EINTR;
die "accept: $!";
diff --git a/roles/MSA/tasks/main.yml b/roles/MSA/tasks/main.yml
index c78139a..2eee925 100644
--- a/roles/MSA/tasks/main.yml
+++ b/roles/MSA/tasks/main.yml
@@ -6,12 +6,28 @@
- postfix-pcre
- postfix-policyd-spf-python
+- name: Install Net::LDAP and Authen::SASL
+ apt: pkg={{ packages }}
+ vars:
+ packages:
+ - libnet-ldap-perl
+ - libauthen-sasl-perl
+
- name: Copy Postfix sender login socketmap
copy: src=usr/local/bin/postfix-sender-login.pl
dest=/usr/local/bin/postfix-sender-login.pl
owner=root group=staff
mode=0755
+- name: Create '_postfix-sender-login' user
+ user: name=_postfix-sender-login system=yes
+ group=nogroup
+ createhome=no
+ home=/nonexistent
+ shell=/usr/sbin/nologin
+ password=!
+ state=present
+
- name: Copy Postfix sender login socketmap systemd unit files
copy: src=etc/systemd/system/{{ item }}
dest=/etc/systemd/system/{{ item }}
diff --git a/roles/common-LDAP/templates/etc/ldap/database.ldif.j2 b/roles/common-LDAP/templates/etc/ldap/database.ldif.j2
index b640cbf..9b4633b 100644
--- a/roles/common-LDAP/templates/etc/ldap/database.ldif.j2
+++ b/roles/common-LDAP/templates/etc/ldap/database.ldif.j2
@@ -267,9 +267,12 @@ olcAccess: to dn.exact="ou=virtual,dc=fripost,dc=org"
{% if 'MDA' in group_names -%}
by dn.exact="username=_dovecot-auth-proxy,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =sd
{% endif -%}
- {% if 'MX' in group_names or 'MSA' in group_names -%}
+ {% if 'MX' in group_names -%}
by dn.exact="username=postfix,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =sd
{% endif -%}
+ {% if 'MSA' in group_names -%}
+ by dn.exact="username=_postfix-sender-login,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =sd
+ {% endif -%}
{% if 'LDAP_provider' in group_names -%}
by dn.exact="cn=nextcloud,ou=services,dc=fripost,dc=org" tls_ssf=128 =sd
{% endif -%}
@@ -282,30 +285,35 @@ olcAccess: to dn.exact="ou=virtual,dc=fripost,dc=org"
# using a TLS-protected connection.
# * So has Postfix, when connecting a local ldapi:// socket from the
# 'private' directory in one of the non-default instance's chroot.
-# * So has _dovecot-auth-proxy on the MDA (for the iterate logic), when
-# SASL-binding using the EXTERNAL mechanism and connecting to a local
-# ldapi:// socket.
-# * Amavis may use the entry as searchBase (required to look for the
-# per-user preferences) but doesn't have read access to the entry.
# * The 'nobody' UNIX user has read access on the MX:es, when using
# SASL-binding using the EXTERNAL mechanism and connecting to a local
# ldapi:// socket. This is required for the 'reserved-alias.pl'
# script.
+# * Amavis may use the entry as searchBase (required to look for the
+# per-user preferences) but doesn't have read access to the entry.
+# * So has _dovecot-auth-proxy on the MDA (for the iterate logic), when
+# SASL-binding using the EXTERNAL mechanism and connecting to a local
+# ldapi:// socket.
+# * So has _postfix-sender-login on the submission service to verify
+# envelope sender ownership
olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,dc=fripost,dc=org$"
attrs=entry,objectClass,fvd
filter=(&(objectClass=FripostVirtualDomain)(!(objectClass=FripostPendingEntry)))
- {% if 'LDAP_provider' in group_names -%}
- {% if groups.MX | difference([inventory_hostname]) -%}
+ {% if 'LDAP_provider' in group_names and groups.MX | difference([inventory_hostname]) -%}
by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd
{% endif -%}
- {% endif -%}
+ {% if 'MX' in group_names -%}
by dn.exact="username=postfix,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd
+ by dn.exact="username=nobody,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd
+ {% endif -%}
{% if 'MDA' in group_names -%}
- by dn.exact="username=_dovecot-auth-proxy,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd
by dn.exact="username=amavis,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =sd
{% endif -%}
- {% if 'MX' in group_names -%}
- by dn.exact="username=nobody,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd
+ {% if 'IMAP' in group_names -%}
+ by dn.exact="username=_dovecot-auth-proxy,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd
+ {% endif -%}
+ {% if 'MSA' in group_names -%}
+ by dn.exact="username=_postfix-sender-login,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd
{% endif -%}
by users =0 break
#
@@ -383,11 +391,18 @@ olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$"
{% if 'LDAP_provider' in group_names and groups.MX | difference([inventory_hostname]) -%}
by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd
{% endif -%}
+ {% if 'MX' in group_names -%}
by dn.exact="username=postfix,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd
+ {% endif -%}
{% if 'MDA' in group_names -%}
- by dn.exact="username=_dovecot-auth-proxy,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd
by dn.exact="username=amavis,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd
{% endif -%}
+ {% if 'IMAP' in group_names -%}
+ by dn.exact="username=_dovecot-auth-proxy,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd
+ {% endif -%}
+ {% if 'MSA' in group_names -%}
+ by dn.exact="username=_postfix-sender-login,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd
+ {% endif -%}
by users =0 break
#
# * The SyncRepl MX replicates can check whether a virtual user is
@@ -489,19 +504,19 @@ olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$"
by users =0 break
{% endif %}
#
-# * The MSA's postfix user can read entry ownership to dermine the SASL
-# login name(s) owning a given sender address
+# * The MSA's _postfix-sender-login user can read entry ownership to
+# dermine the SASL login name(s) owning a given sender address
{% if 'MSA' in group_names %}
olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,dc=fripost,dc=org$"
attrs=fripostOwner,fripostPostmaster
filter=(|(objectClass=FripostVirtualAliasDomain)(objectClass=FripostVirtualDomain))
- by dn.exact="username=postfix,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd
- by users =0 break
+ by dn.exact="username=_postfix-sender-login,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd
+ by users =0 break
olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$"
attrs=entry,objectClass,fvl,fripostOwner
filter=(|(objectClass=FripostVirtualAlias)(objectClass=FripostVirtualList)(objectClass=FripostVirtualUser))
- by dn.exact="username=postfix,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd
- by users =0 break
+ by dn.exact="username=_postfix-sender-login,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd
+ by users =0 break
{% endif %}
{% if 'LDAP_provider' in group_names %}
#