diff options
author | Guilhem Moulin <guilhem@fripost.org> | 2014-07-01 23:02:45 +0200 |
---|---|---|
committer | Guilhem Moulin <guilhem@fripost.org> | 2015-06-07 02:52:13 +0200 |
commit | de4859456f1de54540c96ad97f62858dd089a980 (patch) | |
tree | 4b4904258ae3daf6a6b4f852cbc9821acdfa8cc4 | |
parent | 170dc68f9275dffb48fbe3f8ebb2183cd7ddf111 (diff) |
Replace IPSec tunnels by app-level ephemeral TLS sessions.
For some reason giraff doesn't like IPSec. App-level TLS sessions are
less efficient, but thanks to ansible it still scales well.
23 files changed, 283 insertions, 97 deletions
diff --git a/roles/IMAP/files/etc/dovecot/conf.d/10-master.conf b/roles/IMAP/files/etc/dovecot/conf.d/10-master.conf index d477d01..30a6f8b 100644 --- a/roles/IMAP/files/etc/dovecot/conf.d/10-master.conf +++ b/roles/IMAP/files/etc/dovecot/conf.d/10-master.conf @@ -1,42 +1,37 @@ #default_process_limit = 100 #default_client_limit = 1000 # Default VSZ (virtual memory size) limit for service processes. This is mainly # intended to catch and kill processes that leak memory before they eat up # everything. #default_vsz_limit = 256M # Login user is internally used by login processes. This is the most untrusted # user in Dovecot system. It shouldn't have access to anything at all. default_login_user = dovenull # Internal user is used by unprivileged processes. It should be separate from # login user, so that login processes can't disturb other processes. default_internal_user = dovecot service imap-login { - inet_listener imap { - address = 172.16.0.1 - port = 143 - ssl = no - } inet_listener imaps { port = 993 ssl = yes } # Number of connections to handle before starting a new process. Typically # the only useful values are 0 (unlimited) or 1. 1 is more secure, but 0 # is faster. <doc/wiki/LoginProcess.txt> service_count = 1 # Number of processes to always keep waiting for more connections. #process_min_avail = 0 # If you set service_count=0, you probably need to grow this. #vsz_limit = $default_vsz_limit } service pop3-login { inet_listener pop3 { #port = 110 diff --git a/roles/IMAP/files/etc/dovecot/conf.d/10-ssl.conf b/roles/IMAP/files/etc/dovecot/conf.d/10-ssl.conf index c5e61d7..526da9c 100644 --- a/roles/IMAP/files/etc/dovecot/conf.d/10-ssl.conf +++ b/roles/IMAP/files/etc/dovecot/conf.d/10-ssl.conf @@ -1,39 +1,27 @@ ## ## SSL settings ## # SSL/TLS support: yes, no, required. <doc/wiki/SSL.txt> ssl = required -# No need for SSL if the packets are protected by IPSec. -local 172.16.0.1 { - protocol imap { - disable_plaintext_auth = no - ssl = no - } - protocol sieve { - disable_plaintext_auth = no - ssl = no - } -} - # PEM encoded X.509 SSL/TLS certificate and private key. They're opened before # dropping root privileges, so keep the key file unreadable by anyone but # root. Included doc/mkcert.sh can be used to easily generate self-signed # certificate, just make sure to update the domains in dovecot-openssl.cnf ssl_cert = </etc/dovecot/ssl/imap.fripost.org.pem ssl_key = </etc/dovecot/ssl/imap.fripost.org.key # If key file is password protected, give the password here. Alternatively # give it when starting dovecot with -p parameter. Since this file is often # world-readable, you may want to place this setting instead to a different # root owned 0600 file by using ssl_key_password = <path. #ssl_key_password = # PEM encoded trusted certificate authority. Set this only if you intend to use # ssl_verify_client_cert=yes. The file should contain the CA certificate(s) # followed by the matching CRL(s). (e.g. ssl_ca = </etc/ssl/certs/ca.pem) #ssl_ca = # Require that CRL check succeeds for client certificates. #ssl_require_crl = yes diff --git a/roles/IMAP/tasks/imap.yml b/roles/IMAP/tasks/imap.yml index 5424485..3e93c53 100644 --- a/roles/IMAP/tasks/imap.yml +++ b/roles/IMAP/tasks/imap.yml @@ -45,51 +45,59 @@ - name: Create virtual mailboxes copy: src=etc/dovecot/virtual/{{ item }}/dovecot-virtual dest=/etc/dovecot/virtual/{{ item }}/dovecot-virtual owner=root group=root mode=0644 with_items: - all - flagged - recent - unseen - name: Create directory /home/mail/spamspool # There is no possibility for a name clash, since 'spamspool' isn't a # valid domain file: path=/home/mail/spamspool state=directory owner=vmail group=vmail mode=0700 +- name: Create directory /etc/dovecot/ssl + file: path=/etc/dovecot/ssl + state=directory + owner=root group=root + mode=0755 + - name: Generate a private key and a X.509 certificate for Dovecot command: genkeypair.sh x509 --pubkey=/etc/dovecot/ssl/imap.fripost.org.pem --privkey=/etc/dovecot/ssl/imap.fripost.org.key --dns=imap.fripost.org -t rsa -b 4096 -h sha512 register: r1 changed_when: r1.rc == 0 failed_when: r1.rc > 1 notify: - Restart Dovecot + tags: + - genkey - name: Configure Dovecot copy: src=etc/dovecot/{{ item }} dest=/etc/dovecot/{{ item }} owner=root group=root mode=0644 register: r2 with_items: - conf.d/10-auth.conf - conf.d/10-logging.conf - conf.d/10-mail.conf - conf.d/10-master.conf - conf.d/10-ssl.conf - conf.d/15-mailboxes.conf - conf.d/20-imap.conf - conf.d/20-lmtp.conf - conf.d/90-plugin.conf - conf.d/90-sieve.conf - conf.d/auth-ldap.conf.ext - dovecot-ldap.conf.ext diff --git a/roles/IMAP/tasks/mda.yml b/roles/IMAP/tasks/mda.yml index 0358f12..4a74ed3 100644 --- a/roles/IMAP/tasks/mda.yml +++ b/roles/IMAP/tasks/mda.yml @@ -1,42 +1,64 @@ - name: Install Postfix apt: pkg={{ item }} with_items: - postfix - postfix-ldap - name: Configure Postfix template: src=etc/postfix/main.cf.j2 dest=/etc/postfix-{{ postfix_instance[inst].name }}/main.cf owner=root group=root mode=0644 - register: r + register: r1 notify: - Restart Postfix - name: Create directory /etc/postfix-.../virtual file: path=/etc/postfix-{{ postfix_instance[inst].name }}/virtual state=directory owner=root group=root mode=0755 - name: Copy lookup tables copy: src=etc/postfix/virtual/{{ item }} dest=/etc/postfix-{{ postfix_instance[inst].name }}/virtual/{{ item }} owner=root group=root mode=0644 with_items: - mailbox_domains.cf - mailbox.cf - transport_content_filter.cf - name: Copy recipient canonical copy: src=etc/postfix/recipient_canonical.pcre dest=/etc/postfix-{{ postfix_instance[inst].name }}/recipient_canonical.pcre owner=root group=root mode=0644 +- name: Build the Postfix relay clientcerts map + sudo: False + # smtpd_tls_fingerprint_digest MUST be sha256! + local_action: shell openssl x509 -in certs/postfix/{{ item }}.pem -noout -fingerprint -sha256 | sed -nr 's/^.*=(.*)/\1 {{ item }}/p' + with_items: groups.MX | difference([inventory_hostname]) | sort + register: relay_clientcerts + changed_when: False + +- name: Copy the Postfix relay clientcerts map + template: src=etc/postfix/relay_clientcerts.j2 + dest=/etc/postfix-{{ postfix_instance[inst].name }}/relay_clientcerts + owner=root group=root + mode=0644 + +- name: Compile the Postfix relay clientcerts map + postmap: cmd=postmap src=/etc/postfix-{{ postfix_instance[inst].name }}/relay_clientcerts db=cdb + owner=root group=root + mode=0644 + register: r2 + notify: + - Restart Postfix + - name: Start Postfix service: name=postfix state=started - when: not r.changed + when: not (r1.changed or r2.changed) - meta: flush_handlers diff --git a/roles/IMAP/templates/etc/postfix/main.cf.j2 b/roles/IMAP/templates/etc/postfix/main.cf.j2 index 46f64aa..40c8d32 100644 --- a/roles/IMAP/templates/etc/postfix/main.cf.j2 +++ b/roles/IMAP/templates/etc/postfix/main.cf.j2 @@ -11,56 +11,95 @@ mail_owner = postfix delay_warning_time = 4h maximal_queue_lifetime = 5d myorigin = /etc/mailname myhostname = mda{{ imapno | default('') }}.$mydomain mydomain = fripost.org append_dot_mydomain = no # Turn off all TCP/IP listener ports except that necessary for the MDA. master_service_disable = !2526.inet inet queue_directory = /var/spool/postfix-{{ postfix_instance[inst].name }} data_directory = /var/lib/postfix-{{ postfix_instance[inst].name }} multi_instance_group = {{ postfix_instance[inst].group | default('') }} multi_instance_name = postfix-{{ postfix_instance[inst].name }} multi_instance_enable = yes # This server is a Mail Delivery Agent mynetworks_style = host -inet_interfaces = 172.16.0.1 -{% if 'MX' in group_names %} - 127.0.0.1 -{% endif %} -inet_protocols = ipv4 +inet_interfaces = all + # No local delivery mydestination = local_transport = error:5.1.1 Mailbox unavailable alias_maps = alias_database = local_recipient_maps = message_size_limit = 67108864 recipient_delimiter = + # No relay: this server is inbound-only relay_transport = error:5.1.1 Relay unavailable default_transport = error:5.1.1 Transport unavailable # Virtual transport (the alias resolution is already done by the MX:es) virtual_transport = lmtp:unix:private/dovecot-lmtpd lmtp_bind_address = 127.0.0.1 virtual_mailbox_domains = ldap:$config_directory/virtual/mailbox_domains.cf virtual_mailbox_maps = ldap:$config_directory/virtual/mailbox.cf transport_maps = ldap:$config_directory/virtual/transport_content_filter.cf # Restore the original envelope recipient relay_domains = $myhostname recipient_canonical_classes = envelope_recipient recipient_canonical_maps = pcre:$config_directory/recipient_canonical.pcre # Don't rewrite remote headers local_header_rewrite_clients = # Tolerate occasional high latency smtpd_timeout = 1200s + + +relay_clientcerts = cdb:$config_directory/relay_clientcerts +smtpd_tls_security_level = may +smtpd_tls_cert_file = /etc/postfix/ssl/{{ ansible_fqdn }}.pem +smtpd_tls_key_file = /etc/postfix/ssl/{{ ansible_fqdn }}.key +smtpd_tls_session_cache_database= btree:$data_directory/smtpd_tls_session_cache +smtpd_tls_received_header = yes +smtpd_tls_ask_ccert = yes +smtpd_tls_session_cache_timeout = 3600s +smtpd_tls_fingerprint_digest = sha256 + + +strict_rfc821_envelopes = yes +smtpd_delay_reject = yes +disable_vrfy_command = yes + +smtpd_client_restrictions = + permit_mynetworks + permit_tls_clientcerts + # We are the only ones using this proxy, but if things go wrong we + # want to know why + defer + +smtpd_helo_required = yes +smtpd_helo_restrictions = + reject_invalid_helo_hostname + +smtpd_sender_restrictions = + reject_non_fqdn_sender + reject_unknown_sender_domain + +smtpd_recipient_restrictions = + # RFC requirements + reject_non_fqdn_recipient + reject_unknown_recipient_domain + permit_mynetworks + permit_tls_clientcerts + reject + +smtpd_data_restrictions = + reject_unauth_pipelining diff --git a/roles/IMAP/templates/etc/postfix/relay_clientcerts.j2 b/roles/IMAP/templates/etc/postfix/relay_clientcerts.j2 new file mode 120000 index 0000000..b375aa0 --- /dev/null +++ b/roles/IMAP/templates/etc/postfix/relay_clientcerts.j2 @@ -0,0 +1 @@ +../../../../out/templates/etc/postfix/relay_clientcerts.j2
\ No newline at end of file diff --git a/roles/MSA/templates/etc/postfix/main.cf.j2 b/roles/MSA/templates/etc/postfix/main.cf.j2 index e3014aa..036a887 100644 --- a/roles/MSA/templates/etc/postfix/main.cf.j2 +++ b/roles/MSA/templates/etc/postfix/main.cf.j2 @@ -23,77 +23,80 @@ master_service_disable = !submission.inet inet queue_directory = /var/spool/postfix-{{ postfix_instance[inst].name }} data_directory = /var/lib/postfix-{{ postfix_instance[inst].name }} multi_instance_group = {{ postfix_instance[inst].group | default('') }} multi_instance_name = postfix-{{ postfix_instance[inst].name }} multi_instance_enable = yes # This server is a Mail Submission Agent mynetworks_style = host inet_interfaces = all # No local delivery mydestination = local_transport = error:5.1.1 Mailbox unavailable alias_maps = alias_database = local_recipient_maps = message_size_limit = 67108864 recipient_delimiter = + -# Forward everything to our internal mailhub +# Forward everything to our internal outgoing proxy {% if 'out' in group_names %} relayhost = [127.0.0.1]:{{ postfix_instance.out.port }} {% else %} relayhost = [outgoing.fripost.org]:{{ postfix_instance.out.port }} {% endif %} relay_domains = + # Don't rewrite remote headers local_header_rewrite_clients = # Avoid splitting the envelope and scanning messages multiple times smtp_destination_recipient_limit = 1000 # Tolerate occasional high latency smtp_data_done_timeout = 1200s # Anonymize the (authenticated) sender; pass the mail to the antivirus header_checks = pcre:$config_directory/anonymize_sender.pcre #content_filter = amavisfeed:unix:public/amavisfeed-antivirus -# Tunnel everything through IPSec -smtp_tls_security_level = none + +# TLS {% if 'out' in group_names %} -smtp_bind_address = 127.0.0.1 +smtp_tls_security_level = none +smtp_bind_address = 127.0.0.1 {% else %} -smtp_bind_address = 172.16.0.1 +smtp_tls_security_level = encrypt +smtp_tls_cert_file = /etc/postfix/ssl/{{ ansible_fqdn }}.pem +smtp_tls_key_file = /etc/postfix/ssl/{{ ansible_fqdn }}.key +smtp_tls_session_cache_database = btree:$data_directory/smtp_tls_session_cache +smtp_tls_policy_maps = cdb:/etc/postfix/tls_policy +smtp_tls_fingerprint_digest = sha256 {% endif %} -# TLS smtpd_tls_security_level = encrypt -smtpd_tls_cert_file = /etc/ssl/certs/ssl-cert-snakeoil.pem -smtpd_tls_key_file = /etc/ssl/private/ssl-cert-snakeoil.key +smtpd_tls_cert_file = /etc/postfix/ssl/smtp.fripost.org.pem +smtpd_tls_key_file = /etc/postfix/ssl/private/smtp.fripost.org.key smtpd_tls_session_cache_database= btree:$data_directory/smtpd_tls_session_cache smtpd_tls_received_header = yes smtpd_tls_ask_ccert = yes -smtpd_tls_fingerprint_digest = sha1 -smtpd_tls_eecdh_grade = strong -tls_random_source = dev:/dev/urandom # SASL smtpd_sasl_auth_enable = yes smtpd_sasl_authenticated_header = no smtpd_sasl_local_domain = smtpd_sasl_exceptions_networks = $mynetworks smtpd_sasl_security_options = noanonymous, noplaintext smtpd_sasl_tls_security_options = noanonymous broken_sasl_auth_clients = yes smtpd_sasl_type = dovecot smtpd_sasl_path = unix:private/dovecot-auth strict_rfc821_envelopes = yes smtpd_delay_reject = yes disable_vrfy_command = yes # UCE control unknown_client_reject_code = 554 diff --git a/roles/MX/templates/etc/postfix/main.cf.j2 b/roles/MX/templates/etc/postfix/main.cf.j2 index 34e38a0..4d8e53e 100644 --- a/roles/MX/templates/etc/postfix/main.cf.j2 +++ b/roles/MX/templates/etc/postfix/main.cf.j2 @@ -24,95 +24,99 @@ master_service_disable = !smtp.inet inet queue_directory = /var/spool/postfix-{{ postfix_instance[inst].name }} data_directory = /var/lib/postfix-{{ postfix_instance[inst].name }} multi_instance_group = {{ postfix_instance[inst].group | default('') }} multi_instance_name = postfix-{{ postfix_instance[inst].name }} multi_instance_enable = yes # This server is a Mail eXchange mynetworks_style = host inet_interfaces = all # No local delivery mydestination = local_transport = error:5.1.1 Mailbox unavailable alias_maps = alias_database = local_recipient_maps = message_size_limit = 67108864 recipient_delimiter = + -# Forward everything to our internal mailhub +# Forward everything to our internal outgoing proxy {% if 'out' in group_names %} relayhost = [127.0.0.1]:{{ postfix_instance.out.port }} {% else %} relayhost = [outgoing.fripost.org]:{{ postfix_instance.out.port }} {% endif %} relay_domains = + # Virtual transport # We use a dedicated "virtual" domain to decongestion potential # bottlenecks on trivial_rewrite(8) due to slow LDAP lookups in # tranport_maps. virtual_transport = error:5.1.1 Virtual transport unavailable virtual_mailbox_domains = ldap:$config_directory/virtual/mailbox_domains.cf virtual_alias_maps = pcre:$config_directory/virtual/reserved_alias.pcre # first we do the alias resolution... ldap:$config_directory/virtual/alias.cf # ...and unless there is matching mailbox/list... ldap:$config_directory/virtual/mailbox.cf ldap:$config_directory/virtual/list.cf # ...we resolve alias domains and catch alls ldap:$config_directory/virtual/alias_domains.cf ldap:$config_directory/virtual/catchall.cf virtual_mailbox_maps = transport_maps = cdb:$config_directory/virtual/transport + # Don't rewrite remote headers local_header_rewrite_clients = # Pass the client information along to the content filter smtp_send_xforward_command = yes # Avoid splitting the envelope and scanning messages multiple times smtp_destination_recipient_limit = 1000 reserved-alias_recipient_limit = 1 # Tolerate occasional high latency smtp_data_done_timeout = 1200s -# Tunnel everything through IPSec -smtp_tls_security_level = none + {% if 'out' in group_names %} -smtp_bind_address = 127.0.0.1 +smtp_tls_security_level = none +smtp_bind_address = 127.0.0.1 {% else %} -smtp_bind_address = 172.16.0.1 +smtp_tls_security_level = encrypt +smtp_tls_cert_file = /etc/postfix/ssl/{{ ansible_fqdn }}.pem +smtp_tls_key_file = /etc/postfix/ssl/{{ ansible_fqdn }}.key +smtp_tls_session_cache_database = btree:$data_directory/smtp_tls_session_cache +smtp_tls_policy_maps = cdb:/etc/postfix/tls_policy +smtp_tls_fingerprint_digest = sha256 {% endif %} +smtpd_tls_security_level = none -# TLS smtpd_tls_security_level = may smtpd_tls_cert_file = /etc/ssl/certs/ssl-cert-snakeoil.pem smtpd_tls_key_file = /etc/ssl/private/ssl-cert-snakeoil.key smtpd_tls_CApath = /etc/ssl/certs/ smtpd_tls_session_cache_database= btree:$data_directory/smtpd_tls_session_cache smtpd_tls_received_header = yes smtpd_tls_ask_ccert = yes -smtpd_tls_fingerprint_digest = sha1 -smtpd_tls_eecdh_grade = strong -tls_random_source = dev:/dev/urandom # http://en.linuxreviews.org/HOWTO_Stop_spam_using_Postfix # http://www.howtoforge.com/block_spam_at_mta_level_postfix strict_rfc821_envelopes = yes smtpd_delay_reject = yes disable_vrfy_command = yes # UCE control invalid_hostname_reject_code = 554 multi_recipient_bounce_reject_code = 554 non_fqdn_reject_code = 554 relay_domains_reject_code = 554 unknown_address_reject_code = 554 unknown_client_reject_code = 554 unknown_hostname_reject_code = 554 unknown_local_recipient_reject_code = 554 unknown_relay_recipient_reject_code = 554 unknown_virtual_alias_reject_code = 554 diff --git a/roles/MX/templates/etc/postfix/virtual/transport.j2 b/roles/MX/templates/etc/postfix/virtual/transport.j2 index 2250a71..a34dcad 100644 --- a/roles/MX/templates/etc/postfix/virtual/transport.j2 +++ b/roles/MX/templates/etc/postfix/virtual/transport.j2 @@ -1,13 +1,13 @@ reserved.locahost.localdomain reserved-alias: {% if 'LDA' in group_names %} mda.fripost.org smtpl:[127.0.0.1]:{{ postfix_instance.IMAP.port }} {% else %} -mda.fripost.org smtps:[mda.fripost.org]:{{ postfix_instance.IMAP.port }} +mda.fripost.org smtp:[mda.fripost.org]:{{ postfix_instance.IMAP.port }} {% endif %} {% if 'lists' in group_names %} lists.fripost.org smtpl:[127.0.0.1]:{{ postfix_instance.lists.port }} {% else %} -lists.fripost.org smtps:[lists.fripost.org]:{{ postfix_instance.lists.port }} +lists.fripost.org smtp:[lists.fripost.org]:{{ postfix_instance.lists.port }} {% endif %} diff --git a/roles/common/files/etc/postfix/master.cf b/roles/common/files/etc/postfix/master.cf index e845371..70f7f4e 100644 --- a/roles/common/files/etc/postfix/master.cf +++ b/roles/common/files/etc/postfix/master.cf @@ -8,49 +8,44 @@ # service type private unpriv chroot wakeup maxproc command + args # (yes) (yes) (yes) (never) (100) # ========================================================================== smtp inet n - - - - smtpd submission inet n - - - - smtpd pickup fifo n - - 60 1 pickup cleanup unix n - - - 0 cleanup qmgr fifo n - n 300 1 qmgr tlsmgr unix - - - 1000? 1 tlsmgr rewrite unix - - - - - trivial-rewrite bounce unix - - - - 0 bounce defer unix - - - - 0 bounce trace unix - - - - 0 bounce verify unix - - - - 1 verify flush unix n - - 1000? 0 flush proxymap unix - - n - - proxymap proxywrite unix - - n - 1 proxymap smtp unix - - - - - smtp smtpl unix - - - - - smtp -o smtp_bind_address=127.0.0.1 -smtps unix - - - - - smtp - -o smtp_bind_address=172.16.0.1 relay unix - - - - - smtp # -o smtp_helo_timeout=5 -o smtp_connect_timeout=5 showq unix n - - - - showq error unix - - - - - error retry unix - - - - - error discard unix - - - - - discard local unix - n n - - local virtual unix - n n - - virtual lmtp unix - - - - - lmtp anvil unix - - - - 1 anvil scache unix - - - - 1 scache 127.0.0.1:16132 inet n - - - - smtpd 2525 inet n - - - - smtpd 2526 inet n - - - - smtpd 2527 inet n - - - - smtpd - -o mynetworks=0.0.0.0/0 127.0.0.1:2580 inet n - - - - smtpd -127.0.0.1:smtp inet n - - - - smtpd - -o inet_interfaces=127.0.0.1 reserved-alias unix - n n - - pipe flags=Rhu user=nobody argv=/usr/local/sbin/reserved-alias.pl ${sender} ${original_recipient} @fripost.org mlmmj unix - n n - - pipe flags=Rhu user=mlmmj argv=/usr/bin/mlmmj-receive -L /var/spool/mlmmj/${domain}/${user} amavisfeed unix - - n - 2 lmtp -o lmtp_destination_recipient_limit=1000 -o lmtp_send_xforward_command=yes -o lmtp_data_done_timeout=1200s diff --git a/roles/common/tasks/ipsec.yml b/roles/common/tasks/ipsec.yml index 51d717f..36807d2 100644 --- a/roles/common/tasks/ipsec.yml +++ b/roles/common/tasks/ipsec.yml @@ -1,42 +1,46 @@ - name: Install strongSwan apt: pkg=strongswan-ikev2 - name: Generate a private key and a X.509 certificate for IPSec command: genkeypair.sh x509 --pubkey=/etc/ipsec.d/certs/{{ inventory_hostname }}.pem --privkey=/etc/ipsec.d/private/{{ inventory_hostname }}.key --dns={{ inventory_hostname }} -t ecdsa -b secp521r1 -h sha512 register: r1 changed_when: r1.rc == 0 failed_when: r1.rc > 1 notify: - Restart IPSec + tags: + - genkey - name: Fetch the public part of IPSec's host key - sudo: False # Ensure we don't fetch private data + sudo: False fetch: src=/etc/ipsec.d/certs/{{ inventory_hostname }}.pem dest=certs/ipsec/ fail_on_missing=yes flat=yes + tags: + - genkey # Don't copy our pubkey due to a possible race condition. Only the # remote machine has authority regarding its key. - name: Copy IPSec host pubkeys (except ours) copy: src=certs/ipsec/{{ item }}.pem dest=/etc/ipsec.d/certs/{{ item }}.pem owner=root group=root mode=0644 with_items: groups.all | difference([inventory_hostname]) register: r2 notify: - Restart IPSec - name: Configure IPSec's secrets template: src=etc/ipsec.secrets.j2 dest=/etc/ipsec.secrets owner=root group=root mode=0600 register: r3 notify: diff --git a/roles/common/tasks/mail.yml b/roles/common/tasks/mail.yml index 8572784..74919c8 100644 --- a/roles/common/tasks/mail.yml +++ b/roles/common/tasks/mail.yml @@ -27,34 +27,89 @@ notify: - Restart Postfix - name: Configure Postfix (1) copy: src=etc/postfix/master.cf dest=/etc/postfix/master.cf owner=root group=root mode=0644 notify: - Reload Postfix - name: Configure Postfix (2) template: src=etc/postfix/main.cf.j2 dest=/etc/postfix/main.cf owner=root group=root mode=0644 register: r3 notify: - Restart Postfix -- name: Update the static local Postfix database +- name: Create directory /etc/postfix/ssl + file: path=/etc/postfix/ssl + state=directory + owner=root group=root + mode=0755 + tags: + - genkey + +- name: Generate a private key and a X.509 certificate for Postfix + command: genkeypair.sh x509 + --pubkey=/etc/postfix/ssl/{{ ansible_fqdn }}.pem + --privkey=/etc/postfix/ssl/{{ ansible_fqdn }}.key + --dns={{ ansible_fqdn }} + -t ecdsa -b secp384r1 -h sha512 + register: r4 + changed_when: r4.rc == 0 + failed_when: r4.rc > 1 + tags: + - genkey + +- name: Fetch Postfix's X.509 certificate + # Ensure we don't fetch private data + sudo: False + fetch: src=/etc/postfix/ssl/{{ ansible_fqdn }}.pem + dest=certs/postfix/ + fail_on_missing=yes + flat=yes + tags: + - genkey + +- name: Compile the static local Postfix database postmap: cmd=postalias src=/etc/aliases db=cdb owner=root group=root mode=0644 # We're using CDB - name: Delete /etc/aliases.db file: path=/etc/aliases.db state=absent +- name: Build the Postfix TLS policy map + sudo: False + # smtp_tls_fingerprint_digest MUST be sha256! + local_action: shell openssl x509 -in certs/postfix/{{ item }}.pem -noout -fingerprint -sha256 | cut -d= -f2 + with_items: groups.out | sort + register: tls_policy + changed_when: False + when: "'out' not in group_names" + +- name: Copy the Postfix TLS policy map + template: src=etc/postfix/tls_policy.j2 + dest=/etc/postfix/tls_policy + owner=root group=root + mode=0644 + when: "'out' not in group_names" + +- name: Compile the Postfix TLS policy map + postmap: cmd=postmap src=/etc/postfix/tls_policy db=cdb + owner=root group=root + mode=0644 + when: "'out' not in group_names" + register: r5 + notify: + - Restart Postfix + - name: Start Postfix service: name=postfix state=started - when: not (r1.changed or r2.changed or r3.changed) + when: not (r1.changed or r2.changed or r3.changed or r5.changed) - meta: flush_handlers diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml index 0048443..464abd0 100644 --- a/roles/common/tasks/main.yml +++ b/roles/common/tasks/main.yml @@ -1,38 +1,37 @@ --- - include: sysctl.yml tags=sysctl - include: hosts.yml - include: apt.yml tags=apt - include: firewall.yml tags=firewall,iptables - include: samhain.yml tags=samhain - include: rkhunter.yml tags=rkhunter - include: clamav.yml tags=clamav - include: fail2ban.yml tags=fail2ban - include: smart.yml tags=smartmontools,smart - include: haveged.yml tags=haveged,entropy - name: Copy genkeypair.sh copy: src=usr/local/bin/genkeypair.sh dest=/usr/local/bin/genkeypair.sh owner=root group=root mode=0755 tags: - - genkeypair -- include: ipsec.yml tags=strongswan,ipsec + - genkey - include: logging.yml tags=logging - include: ntp.yml tags=ntp - include: mail.yml tags=mail,postfix - name: Install common packages apt: pkg={{ item }} with_items: - ca-certificates - daemontools - etckeeper - ethtool - git - harden-clients - harden-servers - htop - molly-guard - rsync - screen - telnet-ssl diff --git a/roles/common/templates/etc/iptables/services.j2 b/roles/common/templates/etc/iptables/services.j2 index 923aa35..3e31f04 100644 --- a/roles/common/templates/etc/iptables/services.j2 +++ b/roles/common/templates/etc/iptables/services.j2 @@ -1,34 +1,39 @@ # {{ ansible_managed }} # Do NOT edit this file directly! # # direction protocol destination port source port # (in|out|inout)[46]? (tcp|udp|..) (port|port:port|port,port) (port|port:port|port,port) -inout udp 500 500 # ISAKMP -#inout udp 4500 4500 # IPSec NAT Traversal - out tcp 80,443 # HTTP/HTTPS out udp 53 # DNS out udp 67 # DHCP {% if 'NTP-master' in group_names %} out udp 123 123 # NTP {% endif %} in tcp {{ ansible_ssh_port|default('22') }} # SSH {% if 'MX' in group_names %} in tcp 25 # SMTP {% endif %} {% if 'out' in group_names %} -#out tcp 25 # SMTP +in tcp {{ postfix_instance.out.port }} +out tcp 25 # SMTP +{% else %} +out tcp {{ postfix_instance.out.port }} {% endif %} {% if 'IMAP' in group_names %} in tcp 993 # IMAPS in tcp 4190 # ManageSieve {% endif %} +{% if 'MDA' in group_names %} +in tcp {{ postfix_instance.mda.port }} +{% endif %} {% if 'MSA' in group_names %} in tcp 587 # SMTP-AUTH {% endif %} {% if 'webmail' in group_names %} in tcp 80,443 # HTTP/HTTPS +out tcp 993 # IMAP # TODO imapc +out tcp 4190 {% endif %} diff --git a/roles/common/templates/etc/postfix/main.cf.j2 b/roles/common/templates/etc/postfix/main.cf.j2 index 70d4b98..1abce71 100644 --- a/roles/common/templates/etc/postfix/main.cf.j2 +++ b/roles/common/templates/etc/postfix/main.cf.j2 @@ -1,65 +1,68 @@ ######################################################################## # Nullmailer configuration # # {{ ansible_managed }} # Do NOT edit this file directly! smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) biff = no readme_directory = no mail_owner = postfix myorigin = /etc/mailname myhostname = {{ ansible_fqdn }} mydomain = {{ ansible_domain }} append_dot_mydomain = no # This server is for internal use only mynetworks_style = host inet_interfaces = loopback-only -inet_protocols = ipv4 # No local delivery mydestination = local_transport = error:5.1.1 Mailbox unavailable alias_maps = local_recipient_maps = # All aliases are virtual default_database_type = cdb virtual_alias_maps = cdb:/etc/aliases alias_database = $virtual_alias_maps -# Forward everything to our internal mailhub +# Forward everything to our internal outgoing proxy {% if 'out' in group_names %} relayhost = [127.0.0.1]:{{ postfix_instance.out.port }} {% else %} relayhost = [outgoing.fripost.org]:{{ postfix_instance.out.port }} {% endif %} relay_domains = -# Tunnel everything through IPSec -smtp_tls_security_level = none {% if 'out' in group_names %} -smtp_bind_address = 127.0.0.1 +smtp_tls_security_level = none +smtp_bind_address = 127.0.0.1 {% else %} -smtp_bind_address = 172.16.0.1 +smtp_tls_security_level = encrypt +smtp_tls_cert_file = $config_directory/ssl/{{ ansible_fqdn }}.pem +smtp_tls_key_file = $config_directory/ssl/{{ ansible_fqdn }}.key +smtp_tls_session_cache_database = btree:$data_directory/smtp_tls_session_cache +smtp_tls_policy_maps = cdb:$config_directory/tls_policy +smtp_tls_fingerprint_digest = sha256 {% endif %} -smtpd_tls_security_level = none +smtpd_tls_security_level = none # Turn off all TCP/IP listener ports except that dedicated to # samhain(8), which sadly cannot use pickup through the sendmail binary. master_service_disable = !127.0.0.1:16132.inet inet {% set multi_instance = False %} {%- for g in postfix_instance.keys() | sort -%} {%- if g in group_names -%} {%- if not multi_instance -%} {%- set multi_instance = True -%} ## Other postfix instances multi_instance_wrapper = $command_directory/postmulti -p -- multi_instance_enable = yes multi_instance_directories = {%- endif %} /etc/postfix-{{ postfix_instance[g].name }} {%- endif %} {% endfor %} diff --git a/roles/common/templates/etc/postfix/tls_policy.j2 b/roles/common/templates/etc/postfix/tls_policy.j2 new file mode 100644 index 0000000..b4fc453 --- /dev/null +++ b/roles/common/templates/etc/postfix/tls_policy.j2 @@ -0,0 +1,6 @@ +# {{ ansible_managed }} + +[outgoing.fripost.org]:{{ postfix_instance.out.port }} fingerprint ciphers=high protocols=TLSv1.2 +{% for x in tls_policy.results %} + match={{ x.stdout }} +{% endfor %} diff --git a/roles/lists/templates/etc/postfix/main.cf.j2 b/roles/lists/templates/etc/postfix/main.cf.j2 index 083fa2b..b7a82fe 100644 --- a/roles/lists/templates/etc/postfix/main.cf.j2 +++ b/roles/lists/templates/etc/postfix/main.cf.j2 @@ -49,28 +49,41 @@ recipient_delimiter = + # Forward everything to our internal mailhub {% if 'out' in group_names %} relayhost = [127.0.0.1]:{{ postfix_instance.out.port }} {% else %} relayhost = [outgoing.fripost.org]:{{ postfix_instance.out.port }} {% endif %} relay_domains = # Virtual transport (the alias resolution is already done by the MX:es) transport_maps = ldap:$config_directory/virtual/transport_list.cf mlmmj_destination_recipient_limit = 1 # Don't rewrite remote headers local_header_rewrite_clients = # Avoid splitting the envelope and scanning messages multiple times smtp_destination_recipient_limit = 1000 # Tolerate occasional high latency smtp_data_done_timeout = 1200s smtpd_timeout = 1200s -# Tunnel everything through IPSec -smtp_tls_security_level = none + +# Forward everything to our internal outgoing proxy +{% if 'out' in group_names %} +relayhost = [127.0.0.1]:{{ postfix_instance.out.port }} +{% else %} +relayhost = [outgoing.fripost.org]:{{ postfix_instance.out.port }} +{% endif %} +relay_domains = + {% if 'out' in group_names %} -smtp_bind_address = 127.0.0.1 +smtp_tls_security_level = none +smtp_bind_address = 127.0.0.1 {% else %} -smtp_bind_address = 172.16.0.1 +smtp_tls_security_level = encrypt +smtp_tls_cert_file = $config_directory/ssl/{{ ansible_fqdn }}.pem +smtp_tls_key_file = $config_directory/ssl/{{ ansible_fqdn }}.key +smtp_tls_session_cache_database = btree:$data_directory/smtp_tls_session_cache +smtp_tls_policy_maps = cdb:$config_directory/tls_policy +smtp_tls_fingerprint_digest = sha256 {% endif %} -smtpd_tls_security_level = none +smtpd_tls_security_level = none diff --git a/roles/out/tasks/main.yml b/roles/out/tasks/main.yml index 4bf4363..8bd8bbb 100644 --- a/roles/out/tasks/main.yml +++ b/roles/out/tasks/main.yml @@ -1,17 +1,39 @@ - name: Install Postfix apt: pkg=postfix - name: Configure Postfix template: src=etc/postfix/main.cf.j2 dest=/etc/postfix-{{ postfix_instance[inst].name }}/main.cf owner=root group=root mode=0644 register: r notify: - Restart Postfix +- name: Build the Postfix relay clientcerts map + sudo: False + # smtpd_tls_fingerprint_digest MUST be sha256! + local_action: shell openssl x509 -in certs/postfix/{{ item }}.pem -noout -fingerprint -sha256 | sed -nr 's/^.*=(.*)/\1 {{ item }}/p' + with_items: groups.all | difference([inventory_hostname]) | sort + register: relay_clientcerts + changed_when: False + +- name: Copy the Postfix relay clientcerts map + template: src=etc/postfix/relay_clientcerts.j2 + dest=/etc/postfix-{{ postfix_instance[inst].name }}/relay_clientcerts + owner=root group=root + mode=0644 + +- name: Compile the Postfix relay clientcerts map + postmap: cmd=postmap src=/etc/postfix-{{ postfix_instance[inst].name }}/relay_clientcerts db=cdb + owner=root group=root + mode=0644 + register: r2 + notify: + - Restart Postfix + - name: Start Postfix service: name=postfix state=started - when: not r.changed + when: not (r1.changed or r2.changed) - meta: flush_handlers diff --git a/roles/out/templates/etc/postfix/main.cf.j2 b/roles/out/templates/etc/postfix/main.cf.j2 index 1a7985f..11bcc10 100644 --- a/roles/out/templates/etc/postfix/main.cf.j2 +++ b/roles/out/templates/etc/postfix/main.cf.j2 @@ -1,78 +1,93 @@ ######################################################################## -# Outgoing MTA configuration +# Outgoing MTA (outgoing SMTP proxy) configuration # # {{ ansible_managed }} # Do NOT edit this file directly! smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) biff = no readme_directory = no mail_owner = postfix delay_warning_time = 1d maximal_queue_lifetime = 5d myorigin = /etc/mailname myhostname = outgoing{{ outgoingno | default('') }}.$mydomain mydomain = fripost.org append_dot_mydomain = no # Turn off all TCP/IP listener ports except that necessary for the # outgoing SMTP proxy. -master_service_disable = !2525.inet inet +master_service_disable = !{{ postfix_instance.out.port }}.inet inet queue_directory = /var/spool/postfix-{{ postfix_instance[inst].name }} data_directory = /var/lib/postfix-{{ postfix_instance[inst].name }} multi_instance_group = {{ postfix_instance[inst].group | default('') }} multi_instance_name = postfix-{{ postfix_instance[inst].name }} multi_instance_enable = yes -# Accept everything coming through IPSec. -# TODO: this should our virtual private subnetwork -mynetworks = 0.0.0.0/0 -inet_interfaces = 172.16.0.1, 127.0.0.1 +mynetworks_style = host +inet_interfaces = all # No local delivery mydestination = local_transport = error:5.1.1 Mailbox unavailable alias_maps = alias_database = local_recipient_maps = message_size_limit = 67108864 recipient_delimiter = + -relay_domains = -relay_transport = error:5.3.2 Relay Transport unavailable +relay_domains = +relay_transport = error:5.3.2 Relay Transport unavailable # All header rewriting happens upstream local_header_rewrite_clients = smtp_tls_security_level = may smtp_tls_note_starttls_offer = yes -smtp_tls_cert_file = /etc/postfix-out/ssl/smtp.fripost.org.pem -smtp_tls_key_file = /etc/postfix-out/ssl/smtp.fripost.org.key -smtp_tls_CApath = /etc/ssl/certs/ smtp_tls_session_cache_database = btree:$data_directory/smtp_tls_session_cache -smtp_tls_fingerprint_digest = sha1 -tls_random_source = dev:/dev/urandom +relay_clientcerts = cdb:$config_directory/relay_clientcerts +smtpd_tls_security_level = may +smtpd_tls_cert_file = /etc/postfix/ssl/{{ ansible_fqdn }}.pem +smtpd_tls_key_file = /etc/postfix/ssl/{{ ansible_fqdn }}.key +smtpd_tls_session_cache_database= btree:$data_directory/smtpd_tls_session_cache +smtpd_tls_received_header = yes +smtpd_tls_ask_ccert = yes +smtpd_tls_session_cache_timeout = 3600s +smtpd_tls_fingerprint_digest = sha256 + + +strict_rfc821_envelopes = yes +smtpd_delay_reject = yes +disable_vrfy_command = yes + +smtpd_client_restrictions = + permit_mynetworks + permit_tls_clientcerts + # We are the only ones using this proxy, but if things go wrong we + # want to know why + defer smtpd_helo_required = yes smtpd_helo_restrictions = reject_invalid_helo_hostname smtpd_sender_restrictions = reject_non_fqdn_sender reject_unknown_sender_domain smtpd_recipient_restrictions = # RFC requirements reject_non_fqdn_recipient reject_unknown_recipient_domain permit_mynetworks - reject_unauth_destination + permit_tls_clientcerts + reject smtpd_data_restrictions = reject_unauth_pipelining diff --git a/roles/out/templates/etc/postfix/relay_clientcerts.j2 b/roles/out/templates/etc/postfix/relay_clientcerts.j2 new file mode 100644 index 0000000..3f724ea --- /dev/null +++ b/roles/out/templates/etc/postfix/relay_clientcerts.j2 @@ -0,0 +1,5 @@ +# {{ ansible_managed }} + +{% for x in relay_clientcerts.results %} +{{ x.stdout }} +{% endfor %} diff --git a/roles/webmail/tasks/roundcube.yml b/roles/webmail/tasks/roundcube.yml index 2085974..feb38ee 100644 --- a/roles/webmail/tasks/roundcube.yml +++ b/roles/webmail/tasks/roundcube.yml @@ -72,40 +72,42 @@ mode=0644 with_items: - additional_message_headers - managesieve - password - name: Start php5-fpm service: name=php5-fpm state=started - name: Generate a private key and a X.509 certificate for Nginx command: genkeypair.sh x509 --pubkey=/etc/nginx/ssl/mail.fripost.org.pem --privkey=/etc/nginx/ssl/mail.fripost.org.key --dns=mail.fripost.org -t rsa -b 4096 -h sha512 register: r1 changed_when: r1.rc == 0 failed_when: r1.rc > 1 notify: - Restart Nginx + tags: + - genkey - name: Copy /etc/nginx/sites-available/roundcube copy: src=etc/nginx/sites-available/roundcube dest=/etc/nginx/sites-available/roundcube owner=root group=root mode=0644 register: r2 notify: - Restart Nginx - name: Create /etc/nginx/sites-enabled/roundcube file: src=../sites-available/roundcube dest=/etc/nginx/sites-enabled/roundcube owner=root group=root state=link force=yes register: r3 notify: - Restart Nginx - name: Start Nginx diff --git a/roles/webmail/templates/etc/postfix/main.cf.j2 b/roles/webmail/templates/etc/postfix/main.cf.j2 index b070881..595f618 100644 --- a/roles/webmail/templates/etc/postfix/main.cf.j2 +++ b/roles/webmail/templates/etc/postfix/main.cf.j2 @@ -23,66 +23,68 @@ master_service_disable = !127.0.0.1:2580.inet inet queue_directory = /var/spool/postfix-{{ postfix_instance[inst].name }} data_directory = /var/lib/postfix-{{ postfix_instance[inst].name }} multi_instance_group = {{ postfix_instance[inst].group | default('') }} multi_instance_name = postfix-{{ postfix_instance[inst].name }} multi_instance_enable = yes # This server is a nullclient mynetworks_style = host inet_interfaces = loopback-only # No local delivery mydestination = local_transport = error:5.1.1 Mailbox unavailable alias_maps = alias_database = local_recipient_maps = message_size_limit = 67108864 recipient_delimiter = + -# Forward everything to our internal mailhub +# Forward everything to our internal outgoing proxy {% if 'out' in group_names %} relayhost = [127.0.0.1]:{{ postfix_instance.out.port }} {% else %} relayhost = [outgoing.fripost.org]:{{ postfix_instance.out.port }} {% endif %} relay_domains = + # Don't rewrite remote headers local_header_rewrite_clients = # Avoid splitting the envelope and scanning messages multiple times smtp_destination_recipient_limit = 1000 # Tolerate occasional high latency smtp_data_done_timeout = 1200s -# Pass the mail to the antivirus -#content_filter = amavisfeed:unix:public/amavisfeed-antivirus - -# Tunnel everything through IPSec -smtp_tls_security_level = none {% if 'out' in group_names %} -smtp_bind_address = 127.0.0.1 +smtp_tls_security_level = none +smtp_bind_address = 127.0.0.1 {% else %} -smtp_bind_address = 172.16.0.1 +smtp_tls_security_level = encrypt +smtp_tls_cert_file = /etc/postfix/ssl/{{ ansible_fqdn }}.pem +smtp_tls_key_file = /etc/postfix/ssl/{{ ansible_fqdn }}.key +smtp_tls_session_cache_database = btree:$data_directory/smtp_tls_session_cache +smtp_tls_policy_maps = cdb:/etc/postfix/tls_policy +smtp_tls_fingerprint_digest = sha256 {% endif %} -smtpd_tls_security_level = none +smtpd_tls_security_level = none strict_rfc821_envelopes = yes smtpd_delay_reject = yes disable_vrfy_command = yes # UCE control unknown_client_reject_code = 554 smtpd_client_restrictions = permit_mynetworks reject smtpd_helo_required = yes smtpd_helo_restrictions = permit_mynetworks reject_non_fqdn_helo_hostname reject_invalid_helo_hostname smtpd_sender_restrictions = diff --git a/roles/webmail/templates/usr/share/roundcube/plugins/managesieve/config.inc.php.j2 b/roles/webmail/templates/usr/share/roundcube/plugins/managesieve/config.inc.php.j2 index c716ddc..d88a09a 100644 --- a/roles/webmail/templates/usr/share/roundcube/plugins/managesieve/config.inc.php.j2 +++ b/roles/webmail/templates/usr/share/roundcube/plugins/managesieve/config.inc.php.j2 @@ -9,41 +9,41 @@ $rcmail_config['managesieve_port'] = 4190; // %n - http hostname ($_SERVER['SERVER_NAME']) // %d - domain (http hostname without the first part) // For example %n = mail.domain.tld, %d = domain.tld $rcmail_config['managesieve_host'] = 'imap.fripost.org'; // authentication method. Can be CRAM-MD5, DIGEST-MD5, PLAIN, LOGIN, EXTERNAL // or none. Optional, defaults to best method supported by server. $rcmail_config['managesieve_auth_type'] = 'PLAIN'; // Optional managesieve authentication identifier to be used as authorization proxy. // Authenticate as a different user but act on behalf of the logged in user. // Works with PLAIN and DIGEST-MD5 auth. $rcmail_config['managesieve_auth_cid'] = null; // Optional managesieve authentication password to be used for imap_auth_cid $rcmail_config['managesieve_auth_pw'] = null; // use or not TLS for managesieve server connection // it's because I've problems with TLS and dovecot's managesieve plugin // and it's not needed on localhost -$rcmail_config['managesieve_usetls'] = FALSE; +$rcmail_config['managesieve_usetls'] = TRUE; // default contents of filters script (eg. default spam filter) $rcmail_config['managesieve_default'] = '/etc/dovecot/sieve/global'; // The name of the script which will be used when there's no user script $rcmail_config['managesieve_script_name'] = 'managesieve'; // Sieve RFC says that we should use UTF-8 endcoding for mailbox names, // but some implementations does not covert UTF-8 to modified UTF-7. // Defaults to UTF7-IMAP $rcmail_config['managesieve_mbox_encoding'] = 'UTF-8'; // I need this because my dovecot (with listescape plugin) uses // ':' delimiter, but creates folders with dot delimiter $rcmail_config['managesieve_replace_delimiter'] = ''; // disabled sieve extensions (body, copy, date, editheader, encoded-character, // envelope, environment, ereject, fileinto, ihave, imap4flags, index, // mailbox, mboxmetadata, regex, reject, relational, servermetadata, // spamtest, spamtestplus, subaddress, vacation, variables, virustest, etc. |