From 6d1daa0424c168eae4bfa9f6772add3f77ec506f Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Wed, 20 May 2020 15:46:27 +0200 Subject: postfix-sender-login: Better hardening. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run as a dedicated user, not ‘postfix’. --- .../systemd/system/postfix-sender-login.service | 6 +-- .../files/usr/local/bin/postfix-sender-login.pl | 13 +++--- roles/MSA/tasks/main.yml | 16 +++++++ .../templates/etc/ldap/database.ldif.j2 | 53 ++++++++++++++-------- 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 +# Copyright © 2017,2020 Guilhem Moulin # # 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 %} # -- cgit v1.2.3