commit 11b84aa8721afef6cba75482287efe1e13d0e836 Author: phil Date: Mon Mar 20 20:01:04 2023 +0100 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..66ca771 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +Postfix +======= + +Use this role to setup a Postfix mail server. It comes with the following additions: +- [Mail-TLS-Helper](https://github.com/systemli/mail-tls-helper) +- [MTA-STS-Resolver](https://github.com/Snawoot/postfix-mta-sts-resolver) +- Fail2ban +- optional: [OnionMX](https://github.com/ehloonion/onionmx) +- optional: Unbound diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..9f21dac --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,12 @@ +--- +mail_sasl_log: mail-sasl.log +postfix_conf_dir: /etc/postfix/conf.d +postfix_daemon_dir: /usr/lib/postfix/sbin +postfix_default_db_type: cdb +tls_helper_domains_file: tls_domains +tls_helper_postfix_dir: /var/lib/postfix/tls-helper + +postfix_dhparam_file: /etc/ssl/private/dhparam.pem + +fail2ban_jail_dir: /etc/fail2ban/jail.d +fail2ban_filter_dir: /etc/fail2ban/filter.d diff --git a/files/fail2ban/postfix-sasl.conf b/files/fail2ban/postfix-sasl.conf new file mode 100644 index 0000000..ebb7285 --- /dev/null +++ b/files/fail2ban/postfix-sasl.conf @@ -0,0 +1,7 @@ +[sasl] +enabled = true +findtime = 3600 +bantime = 3600 +port = submission +filter = postfix-sasl +logpath = /var/log/mail-sasl.log \ No newline at end of file diff --git a/files/fail2ban/postfix-sasl.local b/files/fail2ban/postfix-sasl.local new file mode 100644 index 0000000..4a6ceaa --- /dev/null +++ b/files/fail2ban/postfix-sasl.local @@ -0,0 +1,21 @@ +# Fail2Ban filter for postfix authentication failures +# + +[INCLUDES] + +before = common.conf + +[Definition] + +_daemon = postfix(-\w+)?/(?:submission/|smtps/)?smtp[ds] + +failregex = ^%(__prefix_line)swarning: [-._\w]+\[\]: SASL ((?i)LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(: [ A-Za-z0-9+/:]*={0,2})?\s*$ + +ignoreregex = authentication failed: Connection lost to authentication server$ + +[Init] + +journalmatch = _SYSTEMD_UNIT=postfix.service + + +# Author: Yaroslav Halchenko diff --git a/files/mail-tls-helper/readme.txt b/files/mail-tls-helper/readme.txt new file mode 100644 index 0000000..50870be --- /dev/null +++ b/files/mail-tls-helper/readme.txt @@ -0,0 +1,4 @@ +Logrotat runs with hardening features (/lib/systemd/system/logrotate.service) +which prevents write access to /etc. + +You will find the tls_domains file at /var/lib. diff --git a/files/monit/mta-sts-daemon b/files/monit/mta-sts-daemon new file mode 100644 index 0000000..5fa28a1 --- /dev/null +++ b/files/monit/mta-sts-daemon @@ -0,0 +1,9 @@ +# Managed by Ansible + +check process mta-sts-daemon + matching "/usr/bin/python3 /usr/bin/mta-sts-daemon" + group mailserver + start program = "/usr/sbin/service postfix-mta-sts-resolver start" + stop program = "/usr/sbin/service postfix-mta-sts-resolver stop" + if 2 restarts within 3 cycles then timeout + if failed host 127.0.0.1 port 8461 for 3 cycles then restart diff --git a/files/monit/postfix b/files/monit/postfix new file mode 100644 index 0000000..c963980 --- /dev/null +++ b/files/monit/postfix @@ -0,0 +1,7 @@ +# Managed by Ansible + +check process postfix with pidfile /var/spool/postfix/pid/master.pid + group mailserver + start program = "/usr/sbin/service postfix@- start" + stop program = "/usr/sbin/service postfix@- stop" + if failed port 25 protocol smtp with timeout 15 seconds for 3 cycles then restart diff --git a/files/postfix/conf.d/bogus_mx b/files/postfix/conf.d/bogus_mx new file mode 100644 index 0000000..76e1e06 --- /dev/null +++ b/files/postfix/conf.d/bogus_mx @@ -0,0 +1,10 @@ +# Manages by Ansible + +# bogus networks +0.0.0.0/8 550 Mail server in broadcast network +1.0.0.0/8 550 Mail server in IANA reserved network +#10.0.0.0/8 550 No route to your RFC 1918 network +127.0.0.0/8 550 Mail server in loopback network +224.0.0.0/4 550 Mail server in class D multicast network +#172.16.0.0/12 550 No route to your RFC 1918 network +192.168.0.0/16 550 No route to your RFC 1918 network diff --git a/files/postfix/conf.d/header_checks b/files/postfix/conf.d/header_checks new file mode 100644 index 0000000..771ccc6 --- /dev/null +++ b/files/postfix/conf.d/header_checks @@ -0,0 +1,10 @@ +# Managed by Ansible + +/^Content-(Disposition|Type).*name\s*=\s*"?([^;]*(\.|=2E)( + ade|adp|asp|bas|bat|chm|cmd|com|cpl|dll|exe| + hlp|ht[at]| + inf|ins|isp|js|jse?|lnk|md[betw]|ms[cipt]|nws| + \{[[:xdigit:]]{8}(?:-[[:xdigit:]]{4}){3}-[[:xdigit:]]{12}\}| + ops|pcd|pif|prf|reg|sc[frt]|sh[bsm]|swf| + vb[esx]?|vxd|ws[cfh]))(\?=)?"?\s*(;|$)/x + REJECT Attachment name "$2" may not end with ".$4" diff --git a/files/postfix/conf.d/header_checks_inbound b/files/postfix/conf.d/header_checks_inbound new file mode 100644 index 0000000..5171a88 --- /dev/null +++ b/files/postfix/conf.d/header_checks_inbound @@ -0,0 +1,7 @@ +# Managed by Ansible + +# Remove external mail processing headers +/^X-Spam-.*: .*$/ IGNORE +/^X-Amavis-.*: .*$/ IGNORE +/^X-Virus-.*: .*$/ IGNORE +/^X-Rc-(Spam|Virus)+: .*$/ IGNORE diff --git a/files/smtp_tor b/files/smtp_tor new file mode 100644 index 0000000..6238215 --- /dev/null +++ b/files/smtp_tor @@ -0,0 +1,6 @@ +#!/bin/sh + +# This script will be called by Postfix master.cf. +# It runs a service for OnionMX. + +exec /usr/bin/torsocks -i /usr/lib/postfix/sbin/smtp "$@" diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..46b2e17 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,25 @@ +--- +- name: restart rsyslog + ansible.builtin.service: + name: rsyslog + state: restarted + +- name: reload fail2ban + ansible.builtin.service: + name: fail2ban + state: reloaded + +- name: reload postfix + ansible.builtin.service: + name: postfix + state: reloaded + +- name: restart postfix + ansible.builtin.service: + name: postfix + state: restarted + +- name: reload monit + ansible.builtin.service: + name: monit + state: reloaded diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..e7b1864 --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,10 @@ +galaxy_info: + author: Sense.Lab e.V. administrators + description: Role to setup Postfix + company: Sense.Lab e.V. + license: GPLv3 + min_ansible_version: "2.9" + platforms: + - name: Debian + versions: + - "bullseye" diff --git a/tasks/fail2ban.yml b/tasks/fail2ban.yml new file mode 100644 index 0000000..5321173 --- /dev/null +++ b/tasks/fail2ban.yml @@ -0,0 +1,26 @@ +--- +- name: "Fail2ban | Copy jaiil file" + ansible.builtin.copy: + src: files/fail2ban/postfix-sasl.conf + dest: "{{ fail2ban_jail_dir }}/postfix-sasl.conf" + mode: "0644" + notify: reload fail2ban + +- name: "fail2ban | Copy SASL filter" + ansible.builtin.copy: + src: files/fail2ban/postfix-sasl.local + dest: "{{ fail2ban_filter_dir }}/postfix-sasl.local" + mode: "0644" + notify: reload fail2ban + +- name: "Fail2ban | Setup SASL logging" + ansible.builtin.lineinfile: + dest: /etc/rsyslog.d/postfix.conf + line: ':msg, contains, \"SASL\" /var/log/{{ mail_sasl_log }}' + notify: restart rsyslog + +- name: "Fail2ban | Setup logrotate" + ansible.builtin.template: + src: mail-sasl.j2 + dest: /etc/logrotate.d/mail-sasl + mode: "0644" diff --git a/tasks/hostname.yml b/tasks/hostname.yml new file mode 100644 index 0000000..6fdf329 --- /dev/null +++ b/tasks/hostname.yml @@ -0,0 +1,10 @@ +--- +- name: "Hostname | Set hostname" + ansible.builtin.hostname: + name: "{{ inventory_hostname }}" + use: systemd + +- name: "Hostname | Set hostname in /etc/hosts" + ansible.builtin.lineinfile: + path: /etc/hosts + line: '127.0.0.1 {{ ansible_hostname }}' diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..4674001 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,29 @@ +--- +- name: Packages + ansible.builtin.import_tasks: packages.yml + tags: packages + +- name: Postfix + ansible.builtin.import_tasks: postfix.yml + tags: postfix + +- name: OnionMX + ansible.builtin.import_tasks: onionmx.yml + tags: onionmx + when: postfix_onionmx is defined and postfix_onionmx + +- name: Fail2ban + ansible.builtin.import_tasks: fail2ban.yml + tags: fail2ban + +- name: TLS-helper + ansible.builtin.import_tasks: tls-helper.yml + tags: tls-helper + +- name: Hostname + ansible.builtin.import_tasks: hostname.yml + tags: hostname + +- name: Monitoring + ansible.builtin.import_tasks: monitoring.yml + tags: monitoring diff --git a/tasks/monitoring.yml b/tasks/monitoring.yml new file mode 100644 index 0000000..20742ec --- /dev/null +++ b/tasks/monitoring.yml @@ -0,0 +1,16 @@ +--- +- name: "Monitoring | Enable Monit monitoring for Postfix" + ansible.builtin.copy: + src: monit/postix + dest: /etc/monit/conf-enabled/postfix + mode: "0644" + notify: reload monit + when: "'monit' in ansible_facts.packages" + +- name: "Monitoring | Enable Monit Monitoring for MTA-STS" + ansible.builtin.copy: + src: monit/mta-sts-daemon + dest: /etc/monit/conf-enabled/mta-sts-daemon + mode: "0644" + notify: reload monit + when: "'monit' in ansible_facts.packages" diff --git a/tasks/onionmx.yml b/tasks/onionmx.yml new file mode 100644 index 0000000..53af659 --- /dev/null +++ b/tasks/onionmx.yml @@ -0,0 +1,22 @@ +--- +- name: "OnionMX | Install torsocks" + ansible.builtin.apt: + pkg: torsocks + cache_valid_time: 3600 + +- name: "OnionMX | Copy script" + ansible.builtin.copy: + src: smtp_tor + dest: "{{ postfix_daemon_dir }}" + mode: "0755" + +- name: "OnionMX | Copy transport table" + ansible.builtin.template: + src: postfix/conf.d/transport_tor.j2 + dest: "{{ postfix_conf_dir }}/transport_tor" + mode: "0644" + +- name: "OnionMX | Run postmap" + ansible.builtin.command: + cmd: "postmap {{ postfix_default_db_type }}:transport_tor" + chdir: "{{ postfix_conf_dir }}" diff --git a/tasks/packages.yml b/tasks/packages.yml new file mode 100644 index 0000000..2206f43 --- /dev/null +++ b/tasks/packages.yml @@ -0,0 +1,23 @@ +--- +- name: "Packages | Get installed packages" + ansible.builtin.package_facts: + manager: apt + +- name: "Packages | Install packages" + ansible.builtin.apt: + pkg: + - fail2ban + - libsasl2-modules + - pflogsumm + - pfqueue + - postfix + - postfix-cdb + - postfix-pcre + - postfix-mta-sts-resolver + cache_valid_time: 3600 + +- name: "Packages | Install Unbound" + ansible.builtin.apt: + pkg: + - unbound + when: unbound_install is defined and unbound_install diff --git a/tasks/postfix.yml b/tasks/postfix.yml new file mode 100644 index 0000000..5fffeb4 --- /dev/null +++ b/tasks/postfix.yml @@ -0,0 +1,77 @@ +--- +- name: "Postfix | Copy main.cf" + ansible.builtin.template: + src: postfix/main.cf.j2 + dest: /etc/postfix/main.cf + mode: "0644" + notify: reload postfix + +- name: "Postfix | Copy master.cf" + ansible.builtin.template: + src: postfix/master.cf + dest: /etc/postfix/master.cf + mode: "0644" + notify: restart postfix + +- name: "Postfix | Create configuration directory" + ansible.builtin.file: + path: "{{ postfix_conf_dir }}" + state: directory + mode: "0755" + +- name: "Postfix | Copy lookup tables" + ansible.builtin.copy: + src: "postfix/conf.d/{{ item }}" + dest: "{{ postfix_conf_dir }}/{{ item }}" + mode: "0644" + loop: + - bogus_mx + - header_checks + - header_checks_inbound + +- name: "Postfix | Copy lookup tables from templates" + ansible.builtin.template: + src: "postfix/conf.d/{{ item }}.j2" + dest: "{{ postfix_conf_dir }}/{{ item }}" + mode: "0644" + loop: + - bad_smtp_auth_users + - client_checks + - destination_limit + - header_add + - header_treatment + - permit_sasl_login_mismatch + - postscreen_access + - relay_by_sender + - relay_checks + - sender_checks + - transport_global_exceptions + - transport_relay + notify: reload postfix + +- name: "Postfix | Run postmap" + ansible.builtin.command: "postmap {{ item.table | default('cdb') }}:{{ item.file }}" + args: + chdir: "{{ postfix_conf_dir }}" + changed_when: false + notify: reload postfix + loop: + - file: bad_smtp_auth_users + - file: client_checks + - file: destination_limit + - file: permit_sasl_login_mismatch + - file: relay_checks + - file: sender_checks + - file: transport_relay + +- name: "Postfix | Create dhparam file" + community.crypto.openssl_dhparam: + path: "{{ postfix_dhparam_file }}" + size: 4096 + +- name: "Postfix | Setup cron job for pflogsum" + ansible.builtin.cron: + name: "Ansible: Daily pflogsum statistics" + job: /usr/sbin/pflogsumm --detail 8 --problems-first --no-no-msg-size --reject-detail 12 /var/log/mail.log.1 | mail -s "{{ postfix_pflogsum_mail_subject }} ({{ inventory_hostname }})" {{ postfix_pflogsum_recipient }} + hour: "06" + minute: "24" diff --git a/tasks/tls-helper.yml b/tasks/tls-helper.yml new file mode 100644 index 0000000..6aa7324 --- /dev/null +++ b/tasks/tls-helper.yml @@ -0,0 +1,65 @@ +--- +- name: "TLS-helper | Clone repository" + ansible.builtin.git: + repo: "https://github.com/systemli/mail-tls-helper.git" + dest: "/opt/mail-tls-helper" + version: main + +- name: "TLS-helper | Copy Readme" + ansible.builtin.copy: + src: mail-tls-helper/readme.txt + dest: /opt/mail-tls-helper/ + mode: "0644" + +- name: "TLS-help | Copy allowlist" + ansible.builtin.template: + src: postfix/allowlist.txt + dest: /opt/mail-tls-helper/allowlist.txt + mode: "0644" + +- name: "TLS-helper | Create directory" + ansible.builtin.file: + path: "{{ tls_helper_postfix_dir }}" + state: directory + owner: postfix + group: postfix + mode: "0755" + +- name: "TLS-helper | Create transport map" + ansible.builtin.file: + path: "{{ tls_helper_postfix_dir }}/{{ tls_helper_domains_file }}" + state: touch + owner: postfix + group: postfix + mode: "0644" + +- name: "TLS-helper | Run postmap" + ansible.builtin.command: + cmd: "postmap {{ postfix_default_db_type }}:{{ tls_helper_domains_file }}" + chdir: "{{ tls_helper_postfix_dir }}" + +- name: "TLS-helper | Link files" + ansible.builtin.file: + path: "{{ postfix_conf_dir }}/{{ item }}" + src: "{{ tls_helper_postfix_dir }}/{{ item }}" + state: link + loop: + - "{{ tls_helper_domains_file }}" + - "{{ tls_helper_domains_file }}.{{ postfix_default_db_type }}" + +- name: "TLS-helper | Remove default logrotate configuration for mail logging" + ansible.builtin.lineinfile: + path: /etc/logrotate.d/rsyslog + line: "{{ item }}" + state: absent + loop: + - /var/log/mail.info + - /var/log/mail.warn + - /var/log/mail.err + - /var/log/mail.log + +- name: "TLS-helper | Create new logrotate configuration" + ansible.builtin.template: + src: logrotate.conf + dest: /etc/logrotate.d/maillog + mode: "0644" diff --git a/templates/logrotate.conf b/templates/logrotate.conf new file mode 100644 index 0000000..b432526 --- /dev/null +++ b/templates/logrotate.conf @@ -0,0 +1,17 @@ +/var/log/mail.log +/var/log/mail.info +/var/log/mail.warn +/var/log/mail.err +{ + rotate 1 + daily + missingok + notifempty + compress + delaycompress + sharedscripts + postrotate + /usr/lib/rsyslog/rsyslog-rotate + python3 /opt/mail-tls-helper/mail-tls-helper.py -s /opt/mail-tls-helper/domains.sqlite -p /var/lib/postfix/tls-helper/tls_domains -t cdb --allowlist /opt/mail-tls-helper/allowlist.txt -d {{ postfix_tls_helper_domain }} -r {{ postfix_tls_helper_recipient }} -f {{ postfix_tls_helper_sender }} + endscript +} diff --git a/templates/mail-sasl.j2 b/templates/mail-sasl.j2 new file mode 100644 index 0000000..f9efb6a --- /dev/null +++ b/templates/mail-sasl.j2 @@ -0,0 +1,10 @@ +### {{ ansible_managed }} + +/var/log/{{ mail_sasl_log }} { + rotate 2 + daily + missingok + notifempty + delaycompress + compress +} diff --git a/templates/postfix/allowlist.txt b/templates/postfix/allowlist.txt new file mode 100644 index 0000000..69fd1ff --- /dev/null +++ b/templates/postfix/allowlist.txt @@ -0,0 +1,5 @@ +# {{ ansible_managed }} + +{% for domain in postfix_tls_helper_allowlist %} +{{ domain }} +{% endfor %} diff --git a/templates/postfix/conf.d/bad_smtp_auth_users.j2 b/templates/postfix/conf.d/bad_smtp_auth_users.j2 new file mode 100644 index 0000000..55ba7ae --- /dev/null +++ b/templates/postfix/conf.d/bad_smtp_auth_users.j2 @@ -0,0 +1,11 @@ +### {{ ansible_managed }} + +# Use this file to block SMTP-Auth access for users. +# Example: +# username REJECT + +{% if postfix_bad_smtp_auth_users is defined %} +{% for user in postfix_bad_smtp_auth_users %} +{{ "%-30s %s" | format(user, "REJECT") }} +{% endfor %} +{% endif %} diff --git a/templates/postfix/conf.d/client_checks.j2 b/templates/postfix/conf.d/client_checks.j2 new file mode 100644 index 0000000..963eac2 --- /dev/null +++ b/templates/postfix/conf.d/client_checks.j2 @@ -0,0 +1,10 @@ +### {{ ansible_managed }} + +### With this file you can allow or disallow clients to connect to the SMTP server. + +{% if postfix_client_checks is defined %} +{% for client in postfix_client_checks %} +{{ client.comment }} +{{ "%-30s %s" | format(client.name, client.state) }} +{% endfor %} +{% endif %} diff --git a/templates/postfix/conf.d/destination_limit.j2 b/templates/postfix/conf.d/destination_limit.j2 new file mode 100644 index 0000000..68f122f --- /dev/null +++ b/templates/postfix/conf.d/destination_limit.j2 @@ -0,0 +1,7 @@ +### {{ ansible_managed }} + +{% if postfix_dlimit_domains is defined %} +{% for domain in postfix_dlimit_domains %} +{{ "%-50s %s" | format(domain, "dlimit:") }} +{% endfor %} +{% endif %} diff --git a/templates/postfix/conf.d/header_add.j2 b/templates/postfix/conf.d/header_add.j2 new file mode 100644 index 0000000..79ce3a6 --- /dev/null +++ b/templates/postfix/conf.d/header_add.j2 @@ -0,0 +1,7 @@ +### {{ ansible_managed }} + +{% if postfix_header_add is defined %} +{% for header in postfix_header_add %} +{{ "%-50s %s" | format(header.destination, header.header) }} +{% endfor %} +{% endif %} diff --git a/templates/postfix/conf.d/header_treatment.j2 b/templates/postfix/conf.d/header_treatment.j2 new file mode 100644 index 0000000..a5bd4ec --- /dev/null +++ b/templates/postfix/conf.d/header_treatment.j2 @@ -0,0 +1,21 @@ +### {{ ansible_managed }} + +# Remove sensible headers +/^Mail-System-Version:/ IGNORE +/^Mailer:/ IGNORE +/^Originating-Client:/ IGNORE +/^User-Agent:/ IGNORE +/^X-Enigmail-Version:/ IGNORE +/^X-Mailer:/ IGNORE +/^X-MimeOLE:/ IGNORE +/^X-Newsreader:/ IGNORE +/^X-Originating-IP:/ IGNORE +/^X-Sender:/ IGNORE + +/^\s*(Received: from)[^\n]*(.*)/ REPLACE $1 [127.0.0.1] (localhost [127.0.0.1]) + +{% if postfix_header_treatment is defined %} +{% for header in postfix_header_treatment %} +{{ header }} +{% endfor %} +{% endif %} diff --git a/templates/postfix/conf.d/helo_checks.j2 b/templates/postfix/conf.d/helo_checks.j2 new file mode 100644 index 0000000..2833e1d --- /dev/null +++ b/templates/postfix/conf.d/helo_checks.j2 @@ -0,0 +1,13 @@ +### {{ ansible_managed }} + +{% if postfix_default_helo_checks is defined %} +{% for check in postfix_default_helo_checks %} +{{ "%-30s %s" | format(check.client, check.state) }} +{% endfor %} +{% endif %} + +{% if postfix_helo_checks is defined %} +{% for check in postfix_default_helo_checks %} +{{ "%-30s %s" | format(check.client, check.state) }} +{% endfor %} +{% endif %} diff --git a/templates/postfix/conf.d/permit_sasl_login_mismatch.j2 b/templates/postfix/conf.d/permit_sasl_login_mismatch.j2 new file mode 100644 index 0000000..0bab62b --- /dev/null +++ b/templates/postfix/conf.d/permit_sasl_login_mismatch.j2 @@ -0,0 +1,8 @@ +### {{ ansible_managed }} +### Enable sender spoofing for selected accounts + +{% if postfix_permit_login_mismatch is defined %} +{% for user in postfix_permit_login_mismatch %} +{{ "%-50s %s" | format(user, "permit_login_mismatch") }} +{% endfor %} +{% endif %} diff --git a/templates/postfix/conf.d/postscreen_access.j2 b/templates/postfix/conf.d/postscreen_access.j2 new file mode 100644 index 0000000..1c1ce37 --- /dev/null +++ b/templates/postfix/conf.d/postscreen_access.j2 @@ -0,0 +1,19 @@ +### {{ ansible_managed }} + +{% if postfix_default_postscreen_access is defined %} +{% for source in postfix_default_postscreen_access %} +{% if source.comment is defined %} +# {{ source.comment }} +{% endif %} +{{ "%-40s %s" | format(source.source, source.state) }} +{% endfor %} +{% endif %} + +{% if postfix_postscreen_access is defined %} +{% for source in postfix_postscreen_access %} +{% if source.comment is defined %} +# {{ source.comment }} +{% endif %} +{{ "%-40s %s" | format(source.source, source.state) }} +{% endfor %} +{% endif %} diff --git a/templates/postfix/conf.d/relay_by_sender.j2 b/templates/postfix/conf.d/relay_by_sender.j2 new file mode 100644 index 0000000..3a6a616 --- /dev/null +++ b/templates/postfix/conf.d/relay_by_sender.j2 @@ -0,0 +1,9 @@ +# {{ ansible_managed }} + +# http://www.postfix.org/postconf.5.html#sender_dependent_relayhost_maps + +{% if postfix_relay_by_sender is defined %} +{% for map in postfix_relay_by_sender %} +{{ map }} +{% endfor %} +{% endif %} diff --git a/templates/postfix/conf.d/relay_checks.j2 b/templates/postfix/conf.d/relay_checks.j2 new file mode 100644 index 0000000..75dfe63 --- /dev/null +++ b/templates/postfix/conf.d/relay_checks.j2 @@ -0,0 +1 @@ +### {{ ansible_managed }} diff --git a/templates/postfix/conf.d/sender_checks.j2 b/templates/postfix/conf.d/sender_checks.j2 new file mode 100644 index 0000000..8ba408e --- /dev/null +++ b/templates/postfix/conf.d/sender_checks.j2 @@ -0,0 +1,20 @@ +### {{ ansible_managed }} + +{% if postfix_sender_checks is defined %} +{% for sender in postfix_sender_checks %} +{% if sender.comment is defined %} +# {{ sender.comment }} +{% endif %} +{{ "%-50s %s" | format(sender.client, sender.state) }} +{% endfor %} +{% endif %} + +# Blocked Domains +{% for domain in postfix_blocked_domains %} +{{ "%-50s %s" | format(domain, "REJECT No spammers") }} +{% endfor %} + +# Blocked Sender +{% for sender in postfix_blocked_senders %} +{{ "%-50s %s" | format(sender, "REJECT No spammers") }} +{% endfor %} diff --git a/templates/postfix/conf.d/transport_global_exceptions.j2 b/templates/postfix/conf.d/transport_global_exceptions.j2 new file mode 100644 index 0000000..effd885 --- /dev/null +++ b/templates/postfix/conf.d/transport_global_exceptions.j2 @@ -0,0 +1,12 @@ +### {{ ansible_managed }} + +# Use this file for mail addresses that should be treated specially. + +{% if postfix_transport_global_exceptions is defined %} +{% for address in postfix_transport_global_exceptions %} +{% if address.comment is defined %} +# {{ address.comment }} +{% endif %} +{{ "%-50s %s" | format(address.adress, address.state) }} +{% endfor %} +{% endif %} diff --git a/templates/postfix/conf.d/transport_relay.j2 b/templates/postfix/conf.d/transport_relay.j2 new file mode 100644 index 0000000..b7c466c --- /dev/null +++ b/templates/postfix/conf.d/transport_relay.j2 @@ -0,0 +1,10 @@ +### {{ ansible_managed }} + +{% if postfix_transport_relay is defined %} +{% for transport in postfix_transport_relay %} +{% if transport.comment is defined %} +# {{ transport.comment }} +{% endif %} +{{ "%-50s %s" | format(transport.destination, transport.relay) }} +{% endfor %} +{% endif %} diff --git a/templates/postfix/conf.d/transport_tor.j2 b/templates/postfix/conf.d/transport_tor.j2 new file mode 100644 index 0000000..b802048 --- /dev/null +++ b/templates/postfix/conf.d/transport_tor.j2 @@ -0,0 +1,13 @@ +### {{ ansible_managed }} + +{% if postfix_default_transport_tor_domains is defined %} +{% for domain in postfix_default_transport_tor_domains %} +{{ "%-50s %s" | format(domain.name, domain.relay) }} +{% endfor %} +{% endif %} + +{% if postfix_transport_tor_domains is defined %} +{% for domain in postfix_transport_tor_domains %} +{{ "%-50s %s" | format(domain.name, domain.relay) }} +{% endfor %} +{% endif %} diff --git a/templates/postfix/main.cf.j2 b/templates/postfix/main.cf.j2 new file mode 100644 index 0000000..46a7e97 --- /dev/null +++ b/templates/postfix/main.cf.j2 @@ -0,0 +1,307 @@ +### {{ ansible_managed }} + +smtpd_banner = {{ postfix_smtpd_banner }} + +### Debug Logging +#debug_peer_list = + +### Protocols and destinations +inet_interfaces = all +inet_protocols = ipv4 + +myhostname = $myorigin +myorigin = {{ postfix_myorigin }} +mydestination = {{ postfix_mydestination | join(', ') }} +mynetworks = {{ postfix_mynetworks | join(', ') }} + +### TLS settings +tls_ssl_options = NO_COMPRESSION, NO_RENEGOTIATION +tls_preempt_cipherlist = no +tls_medium_cipherlist = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA +tls_high_cipherlist=EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA + +### TLS settings for SMTP server +smtpd_tls_security_level = may +smtpd_tls_auth_only = yes +smtpd_tls_cert_file = {{ postfix_smtpd_tls_cert_file }} +smtpd_tls_key_file = {{ postfix_smtpd_tls_key_file }} +smtpd_tls_ciphers = medium +smtpd_tls_mandatory_ciphers = medium +smtpd_tls_exclude_ciphers = aNULL, eNULL, MD5, DES, 3DES, DES-CBC3-SHA, RC4-SHA, AES256-SHA, AES128-SHA, DHE-RSA-AES256-SHA +#Einige berechtigte Mailserver nutzen nur TLSv1 +#smtpd_tls_mandatory_protocols = !TLSv1 +#smtpd_tls_protocols = !TLSv1 +smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache +smtpd_tls_session_cache_timeout = 7200s +smtpd_tls_loglevel = 1 +smtpd_tls_CAfile = /etc/ssl/certs/ca-certificates.crt +smtpd_tls_dh1024_param_file = {{ postfix_dhparam_file }} +smtpd_tls_eecdh_grade = strong + +### TLS settings for SMTP client +smtp_tls_security_level = dane +smtp_dns_support_level = dnssec +smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache +smtp_tls_session_cache_timeout = 7200s +#Some mailserver use only TLSv1. Hence we can't disable it. +#smtp_tls_protocols = !TLSv1 +{% if postfix_smtp_tls_policy_maps is defined %} +smtp_tls_policy_maps = +{% for map in postfix_smtp_tls_policy_maps %} + {{ map.type }}:{{ map.path }} +{% endfor %} +{% endif %} +smtp_tls_ciphers = medium +smtp_tls_fingerprint_digest = sha1 +smtp_tls_loglevel = 1 +smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt +smtp_pix_workarounds = disable_esmtp + +### Domains and recipients +{% if postfix_virtual_mailbox_domains is defined %} +virtual_mailbox_domains = +{% for map in postfix_virtual_mailbox_domains %} + {{ map.type }}:{{ map.path }} +{% endfor %} +{% endif %} +{% if postfix_virtual_mailbox_maps is defined %} +# Check for existing recipients +virtual_mailbox_maps = +{% for map in postfix_virtual_mailbox_maps %} + {{ map.type }}:{{ map.path }} +{% endfor %} +{% endif %} +{% if postfix_virtual_alias_maps is defined %} +virtual_alias_maps = +{% for map in postfix_virtual_alias_maps %} + {{ map.type }}:{{ map.path}} +{% endfor %} +{% endif %} +{% if postfix_virtual_transport is defined %} +virtual_transport = +{% for map in postfix_virtual_transport %} + {{ map.type }}:{{ map.address }} +{% endfor %} +{% endif %} + +{% if postfix_relay_domains is defined %} +# Relay mails without recipient check +relay_domains = +{% for map in postfix_relay_domains %} + {{ map.type}}:{{ map.path }} +{% endfor %} +{% endif %} +{% if postfix_sender_dependent_relayhost_maps is defined %} +sender_dependent_relayhost_maps = +{% for map in postfix_sender_dependent_relayhost_maps %} + {{ map.type }}:{{ map.path }} +{% endfor %} +{% endif %} + +{% if postfix_transport_maps is defined %} +transport_maps = +{% for map in postfix_transport_maps %} + {{ map.type }}:{{ map.path }} +{% endfor %} +{% endif %} + +{% if postfix_smtpd_sasl_type is defined %} +### SMTP Auth, server side +broken_sasl_auth_clients = yes +smtpd_sasl_type = {{ postfix_smtpd_sasl_type }} +smtpd_sasl_path = {{ postfix_smtpd_sasl_path }} +smtpd_sasl_auth_enable = no +{% if postfix_smtpd_sasl_exceptions_networks is defined %} +# Some clients demand SASL Auth if the server offers it +smtpd_sasl_exceptions_networks = {{ postfix_smtpd_sasl_exceptions_networks | join(', ') }} +{% endif %} +{% endif %} + +{% if postfix_smtp_sasl_auth is defined and postfix_smtp_sasl_auth %} +### SMTP Auth client side +smtp_sasl_auth_enable = yes +smtp_sasl_auth_soft_bounce = no +smtp_sasl_password_maps = hash:/etc/postfix/sasl/sasl_passwd +smtp_sasl_security_options = noanonymous +{% endif %} + +#### Postscreen +postscreen_access_list = + permit_mynetworks + cidr:/etc/postfix/conf.d/postscreen_access.cidr +postscreen_blacklist_action = drop + +# Pregreeting +postscreen_greet_action = drop + +# DNS block lists +postscreen_dnsbl_threshold = 2 +postscreen_dnsbl_sites = + bl.spamcop.net*1 + ix.dnsbl.manitu.net*2 + zen.spamhaus.org*2 +postscreen_dnsbl_action = drop + +# Postfix runs restrictions with the following order +# - smtpd_client_restriction +# - smtpd_helo_restriction +# - smtpd_sender_restrition +# - smtpd_relay_restrictions +# - smtpd_recipient_restrictions +# - smtpd_data_restriction +# - smtpd_end_of_data_restriction +# - smtpd_etrn_restriction + +smtpd_restriction_classes = permit_login_mismatch + +smtpd_client_restrictions = + permit_mynetworks, + permit_sasl_authenticated, + check_client_access cdb:/etc/postfix/conf.d/client_checks, + reject_unknown_client_hostname, + permit + +# Reject only after RCPT-TO +smtpd_delay_reject = yes +# Enforce helo to apply restrictions +smtpd_helo_required = yes +smtpd_helo_restrictions = + permit_mynetworks, + permit_sasl_authenticated, + check_helo_access pcre:/etc/postfix/conf.d/helo_checks, + reject_non_fqdn_helo_hostname, + reject_invalid_helo_hostname, + permit + +smtpd_sender_restrictions = + permit_mynetworks, + check_sender_access cdb:/etc/postfix/conf.d/sender_checks, + reject_non_fqdn_sender, + reject_unknown_sender_domain, + check_sender_mx_access cidr:/etc/postfix/conf.d/bogus_mx, + #Uncomment the next two lines to block mails from other servers with our domain as MAIL FROM + #permit_sasl_authenticated, + #check_sender_access hash:/etc/postfix/conf.d/sender_checks_domain, + permit + +# Restrictions for submission port +mua_sender_restrictions = + check_sasl_access cdb:/etc/postfix/conf.d/permit_sasl_login_mismatch, + permit_mynetworks, + check_sender_access cdb:/etc/postfix/conf.d/sender_checks, + reject_non_fqdn_sender, + reject_unknown_sender_domain, + reject_sender_login_mismatch, + permit_sasl_authenticated + +permit_login_mismatch = + permit_mynetworks, + reject_non_fqdn_sender, + reject_unknown_sender_domain, + permit_sasl_authenticated + +submission_bad_smtp_user_check = + check_sasl_access cdb:/etc/postfix/conf.d/bad_smtp_auth_users + +smtpd_relay_restrictions = + permit_mynetworks, + permit_sasl_authenticated, + check_client_access cdb:/etc/postfix/conf.d/relay_checks, + reject_unauth_destination + +smtpd_recipient_restrictions = + reject_unauth_pipelining, + reject_non_fqdn_recipient, + reject_unknown_recipient_domain, + permit_mynetworks, + permit_sasl_authenticated, + reject_unverified_recipient + +# Configuration for reject_unverified_recipient +unverified_recipient_reject_reason = User unknown / Nutzer unbekannt +unverified_recipient_reject_code = 550 + +smtpd_data_restrictions = + reject_multi_recipient_bounce, + check_recipient_access pcre:/etc/postfix/conf.d/header_add, + +### Connection limits +smtpd_client_connection_rate_limit = 100 +smtpd_client_event_limit_exceptions = {{ postfix_smtpd_client_event_limit_exceptions | join(', ') }} +smtpd_client_message_rate_limit = 25 +smtpd_client_new_tls_session_rate_limit = 100 +smtpd_client_auth_rate_limit = 100 + +default_destination_rate_delay = 60s +default_destination_recipient_limit = 1 +default_destination_concurrency_failed_cohort_limit = 10 + +header_checks = pcre:/etc/postfix/conf.d/header_checks + +### Spam and DKIM +{% if postfix_smtpd_milters is defined %} +smtpd_milters = +{% for map in postfix_smtpd_milters %} + {{ map.type }}:{{ map.address }} +{% endfor %} +{% endif %} +{% if postfix_non_smtpd_milters is defined %} +non_smtpd_milters = +{% for map in postfix_non_smtpd_milters %} + {{ map.type }}:{{ map.address }} +{% endfor %} +{% endif %} +milter_default_action = accept +milter_connect_macros = i j {daemon_name} v {if_name} _ +milter_mail_macros = i {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer} {client_addr} {client_name} +milter_rcpt_macros = i j _ {auth_type} {rcpt_addr} {rcpt_host} {rcpt_mailer} +milter_connect_timeout = 20s + +# Prevent DKIM signatures (don't allow spam header to change the signature) +disable_mime_output_conversion = yes + +### Misc + +readme_directory = /usr/share/doc/postfix +html_directory = /usr/share/doc/postfix/html + +disable_vrfy_command = yes +recipient_delimiter = + + +# Postfix-default: alias_maps = hash:/etc/mail/aliases nis:mail.aliases +# Prevents delays caused by NIS queries +alias_maps = + +# Error notifications +# http://www.postfix.org/postconf.5.html#notify_classes +notify_classes = data, delay, resource, software +minimal_backoff_time = 1000s +maximal_backoff_time = 4h + +compatibility_level = 2 +biff = no +append_dot_mydomain = no +# Uncomment the next line to generate "delayed mail" warnings +#delay_warning_time = 4h + +# Add missing headers +always_add_missing_headers = yes +local_header_rewrite_clients = permit_mynetworks + +mailbox_size_limit = 0 +message_size_limit = {{ postfix_message_size_limit }} + +### Destination limits +# Some mail server limit concurrent connections +# Solves: Maximum parallel connections for your IP-Address +dlimit_destination_concurrency_limit = 4 +dlimit_destination_recipient_limit = 3 +dlimit_destination_rate_delay = 5s + +{% if postfix_proxy_read_maps is defined %} +### Proxy maps +proxy_read_maps = +{% for map in postfix_proxy_read_maps %} + {{ map }} +{% endfor %} +{% endif %} diff --git a/templates/postfix/master.cf b/templates/postfix/master.cf new file mode 100644 index 0000000..1e9951a --- /dev/null +++ b/templates/postfix/master.cf @@ -0,0 +1,90 @@ +### {{ ansible_managed }} + +# ========================================================================== +# service type private unpriv chroot wakeup maxproc command + args +# (yes) (yes) (no) (never) (100) +# ========================================================================== +smtp inet n - y - 1 postscreen +smtpd pass - - y - 100 smtpd + -o cleanup_service_name=smtpd-in +dnsblog unix - - y - 0 dnsblog +tlsproxy unix - - y - 0 tlsproxy +{% if postfix_submission is defined and postfix_submission %} +smtps inet n - y - 100 smtpd + -o syslog_name=postfix/smtps + -o smtpd_tls_wrappermode=yes + -o smtpd_tls_cert_file={{ postfix_submission_smtpd_tls_cert_file }} + -o smtpd_tls_key_file={{ postfix_submission_smtpd_tls_key_file }} + -o smtpd_tls_dh1024_param_file={{ dhparam_file }} + -o smtpd_tls_mandatory_protocols=!TLSv1,!TLSv1.1 + -o smtpd_tls_protocols=!TLSv1,!TLSv1.1 + -o smtpd_client_restrictions=$submission_bad_smtp_user_check,permit_sasl_authenticated,reject + -o smtpd_sasl_auth_enable=yes +{% if postfix_smtpd_sender_login_maps is defined %} + -o smtpd_sender_login_maps={{ postfix_smtpd_sender_login_maps | join(', ') }} +{% endif %} + -o smtpd_sender_restrictions=$mua_sender_restrictions + -o cleanup_service_name=subclean +submission inet n - y - - smtpd + -o syslog_name=postfix/submission + -o smtpd_tls_security_level=encrypt + -o smtpd_tls_cert_file={{ postfix_submission_smtpd_tls_cert_file }} + -o smtpd_tls_key_file={{ postfix_submission_smtpd_tls_key_file }} + -o smtpd_tls_dh1024_param_file={{ dhparam_file }} + -o smtpd_client_restrictions=$submission_bad_smtp_user_check,permit_sasl_authenticated,reject + -o smtpd_sasl_auth_enable=yes +{% if postfix_smtpd_sender_login_maps is defined %} + -o smtpd_sender_login_maps={{ postfix_smtpd_sender_login_maps | join(', ') }} +{% endif %} + -o smtpd_sender_restrictions=$mua_sender_restrictions + -o cleanup_service_name=subclean +{% if postfix_submission_alternative_port is defined %} +{{ postfix_submission_alternative_port }} inet n - y - - smtpd + -o syslog_name=postfix/submission-local + -o smtpd_tls_security_level=none + -o smtpd_client_restrictions=permit_mynetworks,reject + -o smtpd_sasl_auth_enable=no + -o cleanup_service_name=subclean +{% endif %} +{% endif %} +dlimit unix - - n - - smtp + -o syslog_name=postfix-dlimit +pickup unix n - y 60 1 pickup +cleanup unix n - y - 0 cleanup +qmgr unix n - n 300 1 qmgr +tlsmgr unix - - y 1000? 1 tlsmgr +rewrite unix - - y - - trivial-rewrite +bounce unix - - y - 0 bounce +defer unix - - y - 0 bounce +trace unix - - y - 0 bounce +verify unix - - y - 1 verify +flush unix n - y 1000? 0 flush +proxymap unix - - n - - proxymap +proxywrite unix - - n - 1 proxymap +smtp unix - - y - - smtp +smtptor unix - - n - - smtp_tor + -o smtp_dns_support_level=disabled + -o smtp_tls_security_level=none + -o smtp_tls_policy_maps= +relay unix - - y - - smtp + -o syslog_name=postfix/$service_name +# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5 +showq unix n - y - - showq +error unix - - y - - error +retry unix - - y - - error +discard unix - - y - - discard +local unix - n n - - local +virtual unix - n n - - virtual +lmtp unix - - y - - lmtp +anvil unix - - y - 1 anvil +scache unix - - y - 1 scache +postlog unix-dgram n - n - 1 postlogd + +# Outbound: Remove sensible headers +subclean unix n - y - 0 cleanup + -o header_checks=regexp:/etc/postfix/conf.d/header_treatment + +# Inbound: Remove some headers +smtpd-in unix n - y - 0 cleanup + -o syslog_name=postfix/smtpd-in + -o header_checks=pcre:/etc/postfix/conf.d/header_checks_inbound