diff --git a/docs/releasenotes.rst b/docs/releasenotes.rst index 5f5a898386a67da178934487b9ff54de5f61026a..d229b0f173732cbf57b83d5751588b49ee7fec6d 100644 --- a/docs/releasenotes.rst +++ b/docs/releasenotes.rst @@ -68,6 +68,14 @@ Dropped support for Debian 10 (Buster). * Environment indicator can now be collapsed by clicking on the arrows on the left side. +* ``mail_server`` role + + * Protection against forging of sender addresses has been + implemented, preventing logged-in users from using arbitrary + sender mail addresses, even if authenticated. Authenticated users + can use either their own login as sender, or one of the aliases + that are associated with their mail account. + * ``web_server`` role * Added parameter ``environment_indicator`` which is used on the diff --git a/docs/rolereference.rst b/docs/rolereference.rst index 2f184fd709029d6cdaf3981b5f5dff545b8e372f..cd3158fb57d17ef3b70261888e6924095be362c7 100644 --- a/docs/rolereference.rst +++ b/docs/rolereference.rst @@ -1073,6 +1073,9 @@ Deployed services are configured as follows: below). * Postfix is configured to deliver undeliverable bounces to postmaster. This helps with detecting misconfigured applications and servers. +* Postfix is configured to prevent forging of sender addresses for + logged-in users. Logged-in users can use either their own e-mail as + sender, or one of the aliases associated with their e-mail. Both Postfix and Dovecot expect a specific directory structure in LDAP when doing look-ups: diff --git a/roles/mail_server/molecule/default/tests/test_client2.py b/roles/mail_server/molecule/default/tests/test_client2.py index 8ba434f92df4d0ed687a20a10349c278bef357f0..583dfc5533cc52c151e66c13f166b80f87b40d8b 100644 --- a/roles/mail_server/molecule/default/tests/test_client2.py +++ b/roles/mail_server/molecule/default/tests/test_client2.py @@ -126,11 +126,30 @@ def test_smtp_authentication(host): anywhere. """ - send = host.run('swaks -tls --port 587 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-mandatory') + send = host.run('swaks -tls --port 587 --auth-user john.doe@domain1 --auth-password johnpassword ' + '--from john.doe@domain1 --to root@client1 --server parameters-mandatory') assert send.rc == 0 assert "Ok: queued as" in send.stdout - send = host.run('swaks -tls --port 587 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-optional') + send = host.run('swaks -tls --port 587 --auth-user john.doe@domain1 --auth-password johnpassword ' + '--from john.doe@domain1 --to root@client1 --server parameters-optional') + assert send.rc == 0 + assert "Ok: queued as" in send.stdout + + +def test_smtp_authentication_with_alias_sender(host): + """ + Tests if SMTP authentication works via TLS and allows sending mails to + anywhere while using sender alias. + """ + + send = host.run('swaks -tls --port 587 --auth-user john.doe@domain1 --auth-password johnpassword ' + '--from postmaster@domain1 --to root@client1 --server parameters-mandatory') + assert send.rc == 0 + assert "Ok: queued as" in send.stdout + + send = host.run('swaks -tls --port 587 --auth-user john.doe@domain1 --auth-password johnpassword ' + '--from postmaster@domain1 --to root@client1 --server parameters-optional') assert send.rc == 0 assert "Ok: queued as" in send.stdout @@ -142,11 +161,13 @@ def test_smtp_authentication_requires_tls(host): auth_error = 28 - send = host.run('swaks --port 587 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-mandatory') + send = host.run('swaks --port 587 --auth-user john.doe@domain1 --auth-password johnpassword ' + '--from john.doe@domain1 --to root@client1 --server parameters-mandatory') assert send.rc == auth_error assert "Host did not advertise authentication" in send.stderr - send = host.run('swaks --port 587 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-optional') + send = host.run('swaks --port 587 --auth-user john.doe@domain1 --auth-password johnpassword ' + '--from john.doe@domain1 --to root@client1 --server parameters-optional') assert send.rc == auth_error assert "Host did not advertise authentication" in send.stderr @@ -158,19 +179,23 @@ def test_smtp_authentication_requires_submission_port(host): auth_error = 28 - send = host.run('swaks --port 25 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-mandatory') + send = host.run('swaks --port 25 --auth-user john.doe@domain1 --auth-password johnpassword ' + '--from john.doe@domain1 --to root@client1 --server parameters-mandatory') assert send.rc == auth_error assert "Host did not advertise authentication" in send.stderr - send = host.run('swaks -tls --port 25 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-mandatory') + send = host.run('swaks -tls --port 25 --auth-user john.doe@domain1 --auth-password johnpassword ' + '--from john.doe@domain1 --to root@client1 --server parameters-mandatory') assert send.rc == auth_error assert "Host did not advertise authentication" in send.stderr - send = host.run('swaks --port 25 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-optional') + send = host.run('swaks --port 25 --auth-user john.doe@domain1 --auth-password johnpassword ' + '--from john.doe@domain1 --to root@client1 --server parameters-optional') assert send.rc == auth_error assert "Host did not advertise authentication" in send.stderr - send = host.run('swaks -tls --port 25 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-optional') + send = host.run('swaks -tls --port 25 --auth-user john.doe@domain1 --auth-password johnpassword ' + '--from john.doe@domain1 --to root@client1 --server parameters-optional') assert send.rc == auth_error assert "Host did not advertise authentication" in send.stderr @@ -289,11 +314,13 @@ def test_port_forwarding(host): assert "Ok: queued as" in send.stdout # Submission port. - send = host.run('swaks -tls --port 26 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-mandatory') + send = host.run('swaks -tls --port 26 --auth-user john.doe@domain1 --auth-password johnpassword ' + '--from john.doe@domain1 --to root@client1 --server parameters-mandatory') assert send.rc == 0 assert "Ok: queued as" in send.stdout - send = host.run('swaks -tls --port 26 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-optional') + send = host.run('swaks -tls --port 26 --auth-user john.doe@domain1 --auth-password johnpassword ' + '--from john.doe@domain1 --to root@client1 --server parameters-optional') assert send.rc == 0 assert "Ok: queued as" in send.stdout @@ -318,3 +345,29 @@ def test_dovecot_sieve(host): command = host.run('echo janepassword | sieve-connect --list -s parameters-optional -p 4190 -u jane.doe@domain1 --password 0 || /bin/false') assert command.rc != 0 assert "Authentication refused by server" in command.stderr + + +def test_smtp_sender_forging(host): + """ + Tests if SMTP sender forging is possible. + """ + + send = host.run('swaks -tls --port 587 --auth-user john.doe@domain1 --auth-password johnpassword ' + '--from jane.doe@domain2 --to root@client1 --server parameters-mandatory') + assert send.rc == 24 + assert "Sender address rejected: not owned by user john.doe@domain1" in send.stdout + + send = host.run('swaks -tls --port 587 --auth-user john.doe@domain1 --auth-password johnpassword ' + '--from jane.doe@domain2 --to root@client1 --server parameters-optional') + assert send.rc == 24 + assert "Sender address rejected: not owned by user john.doe@domain1" in send.stdout + + send = host.run('swaks -tls --port 587 --auth-user john.doe@domain1 --auth-password johnpassword ' + '--from webmaster@domain2 --to root@client1 --server parameters-mandatory') + assert send.rc == 24 + assert "Sender address rejected: not owned by user john.doe@domain1" in send.stdout + + send = host.run('swaks -tls --port 587 --auth-user john.doe@domain1 --auth-password johnpassword ' + '--from webmaster@domain2 --to root@client1 --server parameters-optional') + assert send.rc == 24 + assert "Sender address rejected: not owned by user john.doe@domain1" in send.stdout diff --git a/roles/mail_server/templates/main.cf.j2 b/roles/mail_server/templates/main.cf.j2 index 5c7b1d1a99fa7a86206508d6daedce1ec2591f8b..61180ecee06dc7661b4e8c6b20a45ffe6b8289e1 100644 --- a/roles/mail_server/templates/main.cf.j2 +++ b/roles/mail_server/templates/main.cf.j2 @@ -73,6 +73,11 @@ smtp_tls_security_level = may smtpd_relay_restrictions = permit_mynetworks reject_unauth_destination +# Look-up for list of SASL login names that are allowed to send mails +# using the passed-in sender address. Allow sending from both original +# mailbox name _and_ associated aliases. +smtpd_sender_login_maps = ldap:/etc/postfix/ldap-virtual-mailbox-maps.cf, ldap:/etc/postfix/ldap-virtual-alias-maps.cf + # Reject delivery of mails for domains for which the local server is # not responsible, as well as any mails coming from addresses in one # of the configured RBL's. diff --git a/roles/mail_server/templates/master.cf.j2 b/roles/mail_server/templates/master.cf.j2 index 94a9551c62e405ac1b30975a7fde5d93fd28b0ee..f341c416246438053998df2eda376b9acba12a5d 100644 --- a/roles/mail_server/templates/master.cf.j2 +++ b/roles/mail_server/templates/master.cf.j2 @@ -131,8 +131,12 @@ submission inet n - y - - smtpd -o smtpd_tls_security_level=encrypt -o smtpd_sasl_auth_enable=yes -o smtpd_tls_auth_only=yes - -o smtpd_recipient_restrictions= - -o smtpd_relay_restrictions=permit_sasl_authenticated,reject + -o smtpd_reject_unlisted_recipient=no + -o smtpd_client_restrictions= + -o smtpd_helo_restrictions= + -o smtpd_relay_restrictions= + -o smtpd_sender_restrictions=reject_sender_login_mismatch,permit_sasl_authenticated,reject + -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject -o smtpd_tls_mandatory_protocols={{ mail_server_smtpd_submission_protocols[mail_server_minimum_tls_protocol] | join(',') }} -o smtpd_tls_mandatory_ciphers=high -o tls_high_cipherlist={{ mail_server_tls_ciphers }}