summaryrefslogtreecommitdiffstats
path: root/roles/common/templates/etc
diff options
context:
space:
mode:
Diffstat (limited to 'roles/common/templates/etc')
-rw-r--r--roles/common/templates/etc/apt/preferences.j220
-rw-r--r--roles/common/templates/etc/apt/sources.list.j28
-rw-r--r--roles/common/templates/etc/bacula/bacula-fd.conf.j223
-rw-r--r--roles/common/templates/etc/clamav/freshclam.conf.j231
-rw-r--r--roles/common/templates/etc/default/debsecan.j217
-rw-r--r--roles/common/templates/etc/fail2ban/jail.local.j289
-rw-r--r--roles/common/templates/etc/ipsec.conf.j245
-rw-r--r--roles/common/templates/etc/ipsec.secrets.j25
-rw-r--r--roles/common/templates/etc/iptables/services.j281
-rw-r--r--roles/common/templates/etc/munin/munin-node.conf.j211
-rw-r--r--roles/common/templates/etc/munin/plugin-conf.d/munin-node.j23
-rwxr-xr-xroles/common/templates/etc/network/if-up.d/ipsec.j246
-rwxr-xr-xroles/common/templates/etc/nftables.conf.j2238
-rw-r--r--roles/common/templates/etc/ntp.conf.j230
-rw-r--r--roles/common/templates/etc/postfix/main.cf.j252
-rw-r--r--roles/common/templates/etc/postfix/master.cf.j2114
-rw-r--r--roles/common/templates/etc/postfix/tls_policy.j225
-rw-r--r--roles/common/templates/etc/rsyslog.d/postfix.conf.j22
-rw-r--r--roles/common/templates/etc/stunnel/bacula-fd.conf.j262
-rw-r--r--roles/common/templates/etc/stunnel/munin-node.conf.j253
-rw-r--r--roles/common/templates/etc/systemd/resolved.conf.d/local.conf.j211
-rw-r--r--roles/common/templates/etc/systemd/timesyncd.conf.d/fripost.conf.j29
-rw-r--r--roles/common/templates/etc/unbound/unbound.conf.j232
23 files changed, 617 insertions, 390 deletions
diff --git a/roles/common/templates/etc/apt/preferences.j2 b/roles/common/templates/etc/apt/preferences.j2
index ba10834..39b610e 100644
--- a/roles/common/templates/etc/apt/preferences.j2
+++ b/roles/common/templates/etc/apt/preferences.j2
@@ -1,38 +1,38 @@
# {{ ansible_managed }}
# Do NOT edit this file directly!
-# Install updates as soon as they're available
-Package: *
-Pin: release a={{ ansible_lsb.codename }}-updates
-Pin-Priority: 990
+## Install updates as soon as they're available
+#Package: *
+#Pin: release o=Debian, n={{ ansible_lsb.codename }}-updates
+#Pin-Priority: 990
{% if 'backports' in group_names -%}
# Automatically packages from backports (those manually installed)
Package: *
-Pin: release a={{ ansible_lsb.codename }}-backports
+Pin: release o=Debian Backports, n={{ ansible_lsb.codename }}-backports
Pin-Priority: 200
{% endif %}
-{% if 'non-free' in group_names -%}
+{% if inventory_hostname_short in non_free_packages.keys() -%}
# Automatically upgrade non-free firmwares (when manually installed)
-Package: firmware-linux-nonfree
+Package: {{ non_free_packages[inventory_hostname_short] | join (' ') }}
+Pin: release o=Debian
Pin-Priority: 200
{% endif %}
-{% if ansible_processor[0] | search('^(Genuine)?Intel.*') and
- not (ansible_virtualization_role == 'guest' and ansible_virtualization_type == 'xen') -%}
+{% if ansible_processor[1] is search('^(Genuine)?Intel.*') and not ansible_virtualization_role == 'guest' and ansible_lsb.major_release | int < 12 -%}
# Automatically upgrade the microcode (when manually installed)
Package: intel-microcode iucode-tool
-Pin: version *
+Pin: release o=Debian
Pin-Priority: 200
{% endif %}
# Never, ever install things from contrib or non-free unless they have been
# whitelisted above
Package: *
Pin: release c=contrib
Pin-Priority: -1
Package: *
Pin: release c=non-free
Pin-Priority: -1
diff --git a/roles/common/templates/etc/apt/sources.list.j2 b/roles/common/templates/etc/apt/sources.list.j2
index 5788ade..f524f2f 100644
--- a/roles/common/templates/etc/apt/sources.list.j2
+++ b/roles/common/templates/etc/apt/sources.list.j2
@@ -1,13 +1,13 @@
# {{ ansible_managed }}
# Do NOT edit this file directly!
# vim: set filetype=debsources :
-deb http://ftp.se.debian.org/debian/ {{ ansible_lsb.codename }} main{% if 'non-free' in group_names or (ansible_processor[0] | search("^(Genuine)?Intel.*") and not (ansible_virtualization_role == 'guest' and ansible_virtualization_type == 'xen')) %} contrib non-free{% endif %}
+deb https://deb.debian.org/debian {{ ansible_lsb.codename }} main{% if inventory_hostname_short in non_free_packages.keys() or (ansible_processor[1] is search("^(Genuine)?Intel.*") and not ansible_virtualization_role == 'guest' and ansible_lsb.major_release | int < 12) %} contrib non-free{% endif %}{% if ansible_lsb.major_release | int >= 12 %} non-free-firmware{% endif %}
-deb http://security.debian.org/ {{ ansible_lsb.codename }}/updates main{% if 'non-free' in group_names or (ansible_processor[0] | search("^(Genuine)?Intel.*") and not (ansible_virtualization_role == 'guest' and ansible_virtualization_type == 'xen')) %} contrib non-free{% endif %}
+deb https://deb.debian.org/debian-security {{ ansible_lsb.codename }}{% if ansible_lsb.major_release | int < 11 %}/updates{% else %}-security{% endif %} main{% if inventory_hostname_short in non_free_packages.keys() or (ansible_processor[1] is search("^(Genuine)?Intel.*") and not ansible_virtualization_role == 'guest' and ansible_lsb.major_release | int < 12) %} contrib non-free{% endif %}{% if ansible_lsb.major_release | int >= 12 %} non-free-firmware{% endif %}
-deb http://ftp.se.debian.org/debian/ {{ ansible_lsb.codename }}-updates main
+deb https://deb.debian.org/debian {{ ansible_lsb.codename }}-updates main{% if ansible_lsb.major_release | int >= 12 %} non-free-firmware{% endif %}
{% if 'backports' in group_names -%}
-deb http://ftp.debian.org/debian/ {{ ansible_lsb.codename }}-backports main
+deb https://deb.debian.org/debian {{ ansible_lsb.codename }}-backports main
{% endif %}
diff --git a/roles/common/templates/etc/bacula/bacula-fd.conf.j2 b/roles/common/templates/etc/bacula/bacula-fd.conf.j2
index a47bb90..d0af395 100644
--- a/roles/common/templates/etc/bacula/bacula-fd.conf.j2
+++ b/roles/common/templates/etc/bacula/bacula-fd.conf.j2
@@ -1,41 +1,46 @@
#
# Default Bacula File Daemon Configuration file
#
-# For Bacula release 5.2.6 (21 February 2012) -- debian jessie/sid
+# For Bacula release 9.6.7 (10 December 2020) -- debian bullseye/sid
+#
+# There is not much to change here except perhaps the
+# File daemon Name to
+#
+#
+# Copyright (C) 2000-2020 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
#
# List Directors who are permitted to contact this File daemon
#
-{% for dir in groups['bacula-dir'] | sort %}
+{% for dir in groups['bacula_dir'] | sort %}
Director {
Name = {{ hostvars[dir].inventory_hostname_short }}-dir
@|"sed -n '/^{{ hostvars[dir].inventory_hostname_short }}-dir\\s/ {s//Password = /p; q}' /etc/bacula/passwords-fd"
}
# Send all messages except skipped files back to Director
Messages {
Name = Standard
director = {{ hostvars[dir].inventory_hostname_short }}-dir = all, !skipped, !restored
}
{% endfor %}
#
# "Global" File daemon configuration specifications
#
FileDaemon { # define myself
Name = {{ inventory_hostname_short }}-fd
Working Directory = /var/lib/bacula
- Pid Directory = /var/run/bacula
+ Pid Directory = /run/bacula
Maximum Concurrent Jobs = 20
- FDAddress = 127.0.0.1
- FDPort = 9112
- FDSourceAddress = 127.0.0.1
-{% if 'bacula-dir' not in group_names or 'bacula-sd' not in group_names %}
- Heartbeat Interval = 60s
-{% endif %}
+ FDAddress = {{ ipsec[inventory_hostname_short] }}
+ FDPort = 9102
+ SDConnectTimeout = 5 min
PKI Signatures = Yes # Enable Data Signing
PKI Encryption = Yes # Enable Data Encryption
PKI Keypair = /etc/bacula/ssl/{{ inventory_hostname_short }}.pem # Public and Private Keys
PKI Master Key = /etc/bacula/ssl/master.pem # ONLY the Public Key
}
diff --git a/roles/common/templates/etc/clamav/freshclam.conf.j2 b/roles/common/templates/etc/clamav/freshclam.conf.j2
new file mode 100644
index 0000000..650a2b3
--- /dev/null
+++ b/roles/common/templates/etc/clamav/freshclam.conf.j2
@@ -0,0 +1,31 @@
+# Automatically created by the clamav-freshclam postinst
+# Comments will get lost when you reconfigure the clamav-freshclam package
+
+DatabaseOwner clamav
+UpdateLogFile /var/log/clamav/freshclam.log
+LogVerbose false
+LogSyslog false
+LogFacility LOG_LOCAL6
+LogFileMaxSize 0
+LogRotate true
+LogTime true
+Foreground false
+Debug false
+MaxAttempts 5
+DatabaseDirectory /var/lib/clamav
+DNSDatabaseInfo current.cvd.clamav.net
+ConnectTimeout 30
+ReceiveTimeout 30
+TestDatabases yes
+ScriptedUpdates yes
+CompressLocalDatabase no
+Bytecode true
+NotifyClamd /etc/clamav/clamd.conf
+# Check for new database 24 times a day
+Checks 24
+DatabaseMirror db.{{ geoip | default('local') }}.clamav.net
+{% if geoip is defined and ansible_default_ipv6 %}
+DatabaseMirror db.{{ geoip }}.ipv6.clamav.net
+{% endif %}
+DatabaseMirror database.clamav.net
+DatabaseMirror db.other.clamav.net
diff --git a/roles/common/templates/etc/default/debsecan.j2 b/roles/common/templates/etc/default/debsecan.j2
deleted file mode 100644
index 71fee1c..0000000
--- a/roles/common/templates/etc/default/debsecan.j2
+++ /dev/null
@@ -1,17 +0,0 @@
-# Configuration file for debsecan. Contents of this file should
-# adhere to the KEY=VALUE shell syntax. This file may be edited by
-# debsecan's scripts, but your modifications are preserved.
-
-# If true, enable daily reports, sent by email.
-REPORT=true
-
-# For better reporting, specify the correct suite here, using the code
-# name (that is, "sid" instead of "unstable").
-SUITE={{ ansible_lsb.codename }}
-
-# Mail address to which reports are sent.
-MAILTO=admin@fripost.org
-
-# The URL from which vulnerability data is downloaded. Empty for the
-# built-in default.
-SOURCE=
diff --git a/roles/common/templates/etc/fail2ban/jail.local.j2 b/roles/common/templates/etc/fail2ban/jail.local.j2
index a34cb70..3cd19cc 100644
--- a/roles/common/templates/etc/fail2ban/jail.local.j2
+++ b/roles/common/templates/etc/fail2ban/jail.local.j2
@@ -1,93 +1,46 @@
# {{ ansible_managed }}
# Do NOT edit this file directly!
[DEFAULT]
# Destination email address used solely for the interpolations in
# jail.{conf,local} configuration files.
destemail = admin@fripost.org
-# Specify chain where jumps would need to be added in iptables-* actions
-chain = fail2ban
+# "ignoreip" can be a list of IP addresses, CIDR masks or DNS hosts. Fail2ban
+# will not ban a host which matches an address in this list. Several addresses
+# can be defined using space (and/or comma) separator.
+ignoreip = 127.0.0.0/8, ::1, {{ ipsec_subnet }}
-# Choose default action.
-action = %(action_)s
+banaction = nftables-allports
-# Don't ban ourselves.
-ignoreip = 127.0.0.0/8 {{ groups.all | sort | join(' ') }}
+# must match nftables.conf's blackholes timeouts
+bantime = 10m
#
# JAILS
#
-[ssh]
-
-enabled = true
-port = {{ ansible_ssh_port|default('22') }}
-filter = sshd
-logpath = /var/log/auth.log
-maxretry = 5
-
-[ssh-ddos]
-
-enabled = true
-port = {{ ansible_ssh_port|default('22') }}
-filter = sshd-ddos
-logpath = /var/log/auth.log
-maxretry = 2
-
-
-# Generic filter for pam. Has to be used with action which bans all ports
-# such as iptables-allports, shorewall
-[pam-generic]
-
-enabled = true
-# pam-generic filter can be customized to monitor specific subset of 'tty's
-filter = pam-generic
-# port actually must be irrelevant but lets leave it all for some possible uses
-port = anyport
-banaction = iptables-allports
-logpath = /var/log/auth.log
-maxretry = 6
+[sshd]
+enabled = true
-{% if 'MX' in group_names %}
[postfix]
+enabled = {{ 'MX' in group_names }}
-enabled = true
-port = smtp
-filter = postfix
-logpath = /var/log/mail.log
-maxretry = 10
-{% endif %}
-
-
-{% if 'IMAP' in group_names %}
[dovecot]
+enabled = {{ 'IMAP' in group_names }}
-enabled = true
-port = imap2,imap3,imaps,pop3,pop3s
-filter = dovecot
-logpath = /var/log/mail.log
-{% endif %}
-
-
-{% if 'MSA' in group_names %}
-[sasl]
-
-enabled = true
-port = submission
-filter = postfix-sasl
-logpath = /var/log/mail.warn
-{% endif %}
-
+[postfix-sasl]
+enabled = {{ 'MSA' in group_names }}
-{% if 'webmail' in group_names %}
-[roundcube]
+[roundcube-auth]
+enabled = {{ 'webmail' in group_names }}
+logpath = /var/log/roundcube/errors.log
-enabled = true
-port = http,https
-filter = roundcube
-logpath = /var/log/roundcube/errors
-{% endif %}
+[nextcloud]
+enabled = {{ 'nextcloud' in group_names }}
+port = http,https
+filter = nextcloud
+logpath = /var/log/nextcloud/nextcloud.log
# vim: set filetype=dosini :
diff --git a/roles/common/templates/etc/ipsec.conf.j2 b/roles/common/templates/etc/ipsec.conf.j2
new file mode 100644
index 0000000..eaa9a08
--- /dev/null
+++ b/roles/common/templates/etc/ipsec.conf.j2
@@ -0,0 +1,45 @@
+# {{ ansible_managed }}
+# Do NOT edit this file directly!
+
+config setup
+ charondebug = "dmn 0, lib 0, cfg 0, ike 0, enc 0, net 0"
+
+conn %default
+ keyexchange = ikev2
+ keyingtries = %forever
+ ike = aes256gcm16-prfsha384-ecp384!
+ esp = aes256gcm16-ecp384!
+{% if 'NATed' not in group_names %}
+ mobike = no
+{% endif %}
+{% if 'DynDNS' in group_names %}
+ leftallowany = yes
+{% endif %}
+ leftauth = pubkey
+ left = %defaultroute
+ leftsubnet = {{ ipsec[inventory_hostname_short] | ansible.utils.ipv4 }}/32
+ leftid = {{ inventory_hostname }}
+ leftsigkey = {{ inventory_hostname_short }}.pem
+ leftfirewall = no
+ lefthostaccess = yes
+ rightauth = pubkey
+ auto = route
+ dpdaction = hold
+ inactivity = 30m
+ modeconfig = push
+
+{% for host in groups.all | difference([inventory_hostname]) | sort %}
+
+conn {{ hostvars[host].inventory_hostname_short }}
+ right = {{ hostvars[host].inventory_hostname }}
+{% if 'DynDNS' in hostvars[host].group_names %}
+ rightallowany = yes
+{% endif %}
+ rightsigkey = {{ hostvars[host].inventory_hostname_short }}.pem
+ rightsubnet = {{ ipsec[ hostvars[host].inventory_hostname_short ] | ansible.utils.ipv4 }}/32
+ reqid = {{ ipsec[ hostvars[host].inventory_hostname_short ].replace(":",".").split(".")[-1] }}
+{% if 'NATed' not in group_names and 'NATed' in hostvars[host].group_names %}
+ mobike = yes
+{% endif %}
+
+{%- endfor %}
diff --git a/roles/common/templates/etc/ipsec.secrets.j2 b/roles/common/templates/etc/ipsec.secrets.j2
new file mode 100644
index 0000000..6ed670d
--- /dev/null
+++ b/roles/common/templates/etc/ipsec.secrets.j2
@@ -0,0 +1,5 @@
+# {{ ansible_managed }}
+# Do NOT edit this file directly!
+
+# Our VPN uses RSA only.
+: RSA {{ inventory_hostname_short }}.key
diff --git a/roles/common/templates/etc/iptables/services.j2 b/roles/common/templates/etc/iptables/services.j2
deleted file mode 100644
index 8792771..0000000
--- a/roles/common/templates/etc/iptables/services.j2
+++ /dev/null
@@ -1,81 +0,0 @@
-# {{ 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)
-
-out tcp 80,443 # HTTP/HTTPS
-out tcp 9418 # GIT
-out udp 53 # DNS
-out udp 67 # DHCP
-out tcp 22 # SSH
-{% if 'NTP-master' in group_names %}
-in udp 123 # NTP
-out udp 123 # NTP
-{% else %}
-out udp 123 123 # NTP
-{% endif %}
-
-in tcp {{ ansible_ssh_port|default('22') }} # SSH
-{% if 'LDAP-provider' in group_names %}
-in tcp 636 # LDAPS
-{% elif 'MX' in group_names or 'lists' in group_names %}
-out tcp 636 # LDAPS
-{% endif %}
-{% if 'MX' in group_names %}
-in tcp 25 # SMTP
-{% if 'MDA' not in group_names %}
-out tcp {{ postfix_instance.IMAP.port }}
-{% endif %}
-{% if 'lists' not in group_names %}
-out tcp {{ postfix_instance.lists.port }}
-{% endif %}
-{% endif %}
-{% if 'out' in group_names %}
-{% if groups.all | difference([inventory_hostname]) %}
-in tcp {{ postfix_instance.out.port }}
-{% endif %}
-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 and 'MX' not in group_names %}
-in tcp {{ postfix_instance.IMAP.port }}
-{% endif %}
-{% if 'lists' in group_names and 'MX' not in group_names %}
-in tcp {{ postfix_instance.lists.port }}
-{% endif %}
-{% if 'MSA' in group_names %}
-in tcp 587 # SMTP-AUTH
-{% endif %}
-{% if 'webmail' in group_names or 'lists' in group_names or 'wiki' in group_names %}
-in tcp 80,443 # HTTP/HTTPS
-{% endif %}
-{% if 'webmail' in group_names and 'IMAP' not in group_names %}
-out tcp 993 # IMAP
-out tcp 4190 # MANAGESIEVE
-{% endif %}
-{% if 'bacula-dir' in group_names and groups.all | difference(groups['bacula-dir']) %}
-out tcp 9102 # BACULA-FD
-{% elif groups['bacula-dir'] | difference([inventory_hostname]) %}
-in tcp 9102 # BACULA-FD
-{% endif %}
-{% if 'bacula-sd' in group_names and groups.all | difference(groups['bacula-sd']) %}
-in tcp 9103 # BACULA-SD
-{% elif groups['bacula-sd'] | difference([inventory_hostname]) %}
-out tcp 9103 # BACULA-SD
-{% endif %}
-{% if 'munin-master' in group_names and groups.all | difference([inventory_hostname]) %}
-out tcp 4949 # MUNIN
-{% endif %}
-{% if groups['munin-master'] | difference([inventory_hostname]) %}
-in tcp 4949 # MUNIN
-{% endif %}
-{% if 'LDAP-provider' in group_names %}
-out tcp 11371 # HKP
-out tcp 43 # WHOIS
-{% endif %}
diff --git a/roles/common/templates/etc/munin/munin-node.conf.j2 b/roles/common/templates/etc/munin/munin-node.conf.j2
index de4098a..1aba053 100644
--- a/roles/common/templates/etc/munin/munin-node.conf.j2
+++ b/roles/common/templates/etc/munin/munin-node.conf.j2
@@ -1,51 +1,52 @@
#
# Example config-file for munin-node
#
log_level 4
log_file /var/log/munin/munin-node.log
-pid_file /var/run/munin/munin-node.pid
+pid_file /run/munin/munin-node.pid
background 1
setsid 1
user root
group root
# This is the timeout for the whole transaction.
# Units are in sec. Default is 15 min
#
# global_timeout 900
# This is the timeout for each plugin.
# Units are in sec. Default is 1 min
#
# timeout 60
# Regexps for files to ignore
ignore_file [\#~]$
ignore_file DEADJOE$
ignore_file \.bak$
ignore_file %$
ignore_file \.dpkg-(tmp|new|old|dist)$
ignore_file \.rpm(save|new)$
ignore_file \.pod$
# Set this if the client doesn't report the correct hostname when
-# telnetting to localhost, port 4949
+# telnetting to {{ ipsec[inventory_hostname_short] }}, port 4949
#
host_name {{ inventory_hostname_short }}
# A list of addresses that are allowed to connect. This must be a
# regular expression, since Net::Server does not understand CIDR-style
# network notation unless the perl module Net::CIDR is installed. You
# may repeat the allow line as many times as you'd like
-allow ^127\.0\.0\.1$
-allow ^::1$
+{% for host in groups['munin_master'] %}
+allow ^{{ ipsec[ hostvars[host].inventory_hostname_short ] | ansible.utils.ipv4 | replace(".","\.") }}$
+{% endfor %}
# Which address to bind to;
-host 127.0.0.1
+host {{ ipsec[inventory_hostname_short] }}
# And which port
port 4994
diff --git a/roles/common/templates/etc/munin/plugin-conf.d/munin-node.j2 b/roles/common/templates/etc/munin/plugin-conf.d/munin-node.j2
index 6cfa3f9..ec471eb 100644
--- a/roles/common/templates/etc/munin/plugin-conf.d/munin-node.j2
+++ b/roles/common/templates/etc/munin/plugin-conf.d/munin-node.j2
@@ -19,77 +19,78 @@ env.MUNIN_MKTEMP /bin/mktemp -p /tmp/ $1
env.amavislog /var/log/mail.info
[apt]
user root
[courier_mta_mailqueue]
group daemon
[courier_mta_mailstats]
group adm
[courier_mta_mailvolume]
group adm
[cps*]
user root
[df*]
env.warning 92
env.critical 98
+env.exclude_re ^/run/user
[exim_mailqueue]
group adm, (Debian-exim)
[exim_mailstats]
group adm, (Debian-exim)
env.logdir /var/log/exim4/
env.logname mainlog
[fw_conntrack]
user root
[fw_forwarded_local]
user root
[hddtemp_smartctl]
user root
[hddtemp2]
user root
[if_*]
user root
[if_err_*]
user nobody
[ip_*]
user root
[ipmi_*]
user root
[mysql*]
user root
env.mysqlopts --defaults-file=/etc/mysql/debian.cnf
-env.mysqluser debian-sys-maint
+env.mysqluser root
env.mysqlconnection DBI:mysql:mysql;mysql_read_default_file=/etc/mysql/debian.cnf
[postfix_mailqueue_*]
user postfix
[postfix_stats_*]
group adm
[postfix_sasl_*]
group adm
[postfix_mailvolume2]
group adm
env.postmulti postfix{% for g in postfix_instance.keys() | sort %}{% if g in group_names %} postfix-{{ postfix_instance[g].name }}{% endif %}{% endfor %}
[dovecot_logins]
group adm
[dovecot_stats_*]
diff --git a/roles/common/templates/etc/network/if-up.d/ipsec.j2 b/roles/common/templates/etc/network/if-up.d/ipsec.j2
new file mode 100755
index 0000000..9f183d3
--- /dev/null
+++ b/roles/common/templates/etc/network/if-up.d/ipsec.j2
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+# A post-up/down hook to automatically create/delete a virtual subnet
+# for IPsec (inet4 only).
+# Copyright © 2016 Guilhem Moulin <guilhem@fripost.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+set -ue
+PATH=/usr/sbin:/usr/bin:/sbin:/bin
+
+# Ignore the loopback interface and non inet4 families.
+[ "$IFACE" != lo -a "$ADDRFAM" = inet ] || exit 0
+
+# Only the device with the default, globally-scoped route, is of
+# interest here.
+iface="$( ip -o route show to default scope global \
+ | sed -nr '/^default via \S+ dev (\S+).*/ {s//\1/p;q}' )"
+[ "$iface" = "$IFACE" ] || exit 0
+
+vip="{{ ipsec[inventory_hostname_short] }}"
+vsubnet="{{ ipsec_subnet }}"
+
+case "$MODE" in
+ start) ip address add "$vip/32" dev "$IFACE" scope global || true
+ # Nullroute the subnet used for IPsec to avoid data leaks
+ # in the absence of xfrm lookup (i.e., when there is no
+ # matching IPsec Security Association).
+ ip route replace prohibit "$vsubnet" proto static || true
+ ip route replace table 220 to "$vsubnet" dev "$IFACE" proto static src "$vip" || true
+ ;;
+ stop) ip route del table 220 to "$vsubnet" dev "$IFACE" proto static src "$vip" || true
+ ip route del prohibit "$vsubnet" proto static || true
+ ip address del "$vip/32" dev "$IFACE" scope global || true
+esac
diff --git a/roles/common/templates/etc/nftables.conf.j2 b/roles/common/templates/etc/nftables.conf.j2
new file mode 100755
index 0000000..f603ed9
--- /dev/null
+++ b/roles/common/templates/etc/nftables.conf.j2
@@ -0,0 +1,238 @@
+#!/usr/sbin/nft -f
+
+define in-tcp-ports = {
+ {{ ansible_port|default(22) }}
+{% if 'MX' in group_names %}
+ , 25 # SMTP
+{% endif %}
+{% if 'LDAP_provider' in group_names %}
+ , 636 # ldaps
+{% endif %}
+{% if 'IMAP' in group_names %}
+ , 993 # imaps
+ , 4190 # ManageSieve
+{% endif %}
+{% if 'MSA' in group_names %}
+ , 587 # submission [RFC4409]
+ , 465 # submission over TLS [RFC8314]
+{% endif %}
+{% if 'webmail' in group_names or 'lists' in group_names or 'wiki' in group_names or 'nextcloud' in group_names %}
+ , 80 # HTTP
+ , 443 # HTTP over SSL/TLS
+{% endif %}
+}
+
+define out-tcp-ports = {
+ 22
+ , 80 # HTTP
+ , 443 # HTTP over SSL/TLS
+{% if 'out' in group_names or 'MSA' in group_names %}
+ , 25 # SMTP
+{% endif %}
+{% if 'LDAP_provider' in group_names %}
+ , 11371 # OpenPGP HTTP Keyserver
+ , 43 # whois
+{% elif 'MX' in group_names or 'lists' in group_names or 'nextcloud' in group_names %}
+ , 636 # ldaps
+{% endif %}
+{% if 'IMAP' in group_names %}
+ , 2703 # Razor2
+{% endif %}
+}
+
+
+###############################################################################
+
+flush ruleset
+
+table netdev filter {
+{% for if in ansible_interfaces %}
+{% if if != "lo" and ansible_facts[if].active %}
+{% set addr = (ansible_facts[if].ipv4 | default({'address': '0.0.0.0'})).address %}
+ chain INGRESS-{{ if }} {
+ type filter hook ingress device {{ if }} priority -499
+ policy accept
+
+ # IPsec traffic (refined later in the filter rule)
+ ip saddr {{ ipsec_subnet }} ip daddr {{ ipsec[inventory_hostname_short] }} meta secpath exists accept
+
+ # rate-limiting is done directly by the kernel (net.ipv4.icmp_{ratelimit,ratemask} runtime options)
+ icmp type { echo-reply, echo-request, destination-unreachable, time-exceeded } counter accept
+ icmpv6 type { echo-reply, echo-request, destination-unreachable,
+ packet-too-big, time-exceeded, parameter-problem } counter accept
+
+ # accept neighbour discovery for autoconfiguration, RFC 4890 sec. 4.4.1
+ ip6 hoplimit 255 icmpv6 type { 133,134,135,136,141,142 } counter accept
+
+ # accept link-local multicast receiver notification messages
+ ip6 saddr fe80::/10 ip6 daddr ff02::/16 ip6 hoplimit 1 icmpv6 type { 130,131,132,143 } counter accept
+
+ # drop all remaining ICMP/ICMPv6 traffic
+ meta l4proto { icmp, icmpv6 } counter drop
+
+ # bogon filter (cf. RFC 6890 for non-global ip addresses)
+ define bogon = {
+ 0.0.0.0/8 # this host, on this network (RFC 1122 sec. 3.2.1.3)
+{% if not addr | ansible.utils.ipaddr('10.0.0.0/8') %}
+ , 10.0.0.0/8 # private-use (RFC 1918)
+{% endif %}
+ , 100.64.0.0/10 # shared address space (RFC 6598)
+ , 127.0.0.0/8 # loopback (RFC 1122, sec. 3.2.1.3)
+ , 169.254.0.0/16 # link local (RFC 3927)
+ , 172.16.0.0/12 # private-use (RFC 1918)
+ , 192.0.0.0/24 # IETF protocol assignments (RFC 6890 sec. 2.1)
+ , 192.0.2.0/24 # documentation (RFC 5737)
+{% if not addr | ansible.utils.ipaddr('192.168.0.0/16') %}
+ , 192.168.0.0/16 # private-use (RFC 1918)
+{% endif %}
+ , 198.18.0.0/15 # benchmarking (RFC 2544)
+ , 198.51.100.0/24 # documentation (RFC 5737)
+ , 203.0.113.0/24 # documentation (RFC 5737)
+ , 224.0.0.0/3 # multicast - class D 224.0.0.0/4 + class E 240.0.0.0/4 (RFC 1112 sec. 4)
+ , 255.255.255.255/32 # limited broadcast (RFC 0919 sec. 7)
+ }
+
+ ip saddr $bogon counter drop
+ ip daddr $bogon counter drop
+
+ # See also https://www.team-cymru.org/Services/Bogons/fullbogons-ipv6.txt
+ define bogon6 = {
+ ::1/128 # loopback address (RFC 4291)
+ , ::/128 # unspecified (RFC 4291)
+ , ::ffff:0:0/96 # IPv4-mapped address (RFC 4291)
+ , 100::/64 # discard-only address block (RFC 6666)
+ , 2001::/23 # IETF protocol assignments (RFC 2928)
+ , 2001::/32 # TEREDO (RFC 4380)
+ , 2001:2::/48 # benchmarking (RFC 5180)
+ , 2001:db8::/32 # documentation (RFC 3849)
+ , 2001:10::/28 # ORCHID (RFC 4843)
+ , 2002::/16 # 6to4 (RFC 3056)
+ , fc00::/7 # unique-local (RFC 4193)
+ , fe80::/10 # linked-scoped unicast (RFC 4291)
+ }
+
+ ip6 saddr $bogon6 counter drop
+ ip6 saddr $bogon6 counter drop
+ }
+{% endif %}
+{% endfor %}
+}
+
+table inet raw {
+ chain PREROUTING-stateless {
+ # XXX can't add that to the ingress hook as that happens before IP defragmentation
+ # so we don't have the TCP header in later fragments (we don't want to drop IP
+ # fragments, see https://blog.cloudflare.com/ip-fragmentation-is-broken/ )
+ type filter hook prerouting priority -399 # > NF_IP_PRI_CONNTRACK_DEFRAG (-400)
+ policy accept
+
+ # stateless filter for bogus TCP packets
+ tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 counter drop # null packet
+ tcp flags & (fin|psh|urg) == fin|psh|urg counter drop # XMAS packet
+ tcp flags & (syn|rst) == syn|rst counter drop
+ tcp flags & (fin|rst) == fin|rst counter drop
+ tcp flags & (fin|syn) == fin|syn counter drop
+ tcp flags & (fin|psh|ack) == fin|psh counter drop
+ }
+
+ chain PREROUTING {
+ type filter hook prerouting priority -199 # > NF_IP_PRI_CONNTRACK (-200)
+ policy accept
+
+ # stateful filter
+ ct state invalid counter drop
+ }
+}
+
+table inet filter {
+ # blackholes (timeout must match /etc/fail2ban/jail.local)
+ set fail2ban { type ipv4_addr; timeout 10m; }
+ set fail2ban6 { type ipv6_addr; timeout 10m; }
+
+ chain input {
+ type filter hook input priority 0
+ policy drop
+
+ iif lo accept
+
+ meta l4proto esp accept
+ ip daddr {{ ipsec[inventory_hostname_short] }} jump ipsec-in
+
+ # incoming ICMP/ICMPv6 traffic was filtered in the ingress chain already
+ meta l4proto { icmp, icmpv6 } counter accept
+
+ # NTP (ntpd uses sport 123 but systemd-timesyncd does not)
+ udp sport 123 ct state related,established accept
+
+{% if groups.all | length > 1 %}
+ udp sport 500 udp dport 500 ct state new,related,established accept
+{% if groups.NATed | length > 0 %}
+ udp sport 4500 udp dport 4500 ct state new,related,established accept
+{% endif %}
+{% endif %}
+
+ udp sport 53 ct state related,established accept
+ tcp sport 53 ct state related,established accept
+{% if 'dhclient' in group_names %}
+ ip version 4 udp sport 67 udp dport 68 ct state related,established accept
+ ip6 version 6 udp sport 547 udp dport 546 ct state related,established accept
+{% endif %}
+
+ ip saddr @fail2ban counter drop
+ ip6 saddr @fail2ban6 counter drop
+
+ tcp dport $in-tcp-ports ct state related,established accept
+ tcp dport $in-tcp-ports ct state new counter accept
+ tcp sport $out-tcp-ports ct state related,established accept
+ }
+
+ chain output {
+ type filter hook output priority 0
+ policy drop
+
+ oif lo accept
+
+ meta l4proto esp accept
+ ip saddr {{ ipsec[inventory_hostname_short] }} jump ipsec-out
+
+ meta l4proto { icmp, icmpv6 } counter accept
+
+ # NTP (ntpd uses sport 123 but systemd-timesyncd does not)
+ udp dport 123 ct state new,related,established accept
+
+{% if groups.all | length > 1 %}
+ udp sport 500 udp dport 500 ct state new,related,established accept
+{% if groups.NATed | length > 0 %}
+ udp sport 4500 udp dport 4500 ct state new,related,established accept
+{% endif %}
+{% endif %}
+
+ udp dport 53 ct state new,related,established accept
+ tcp dport 53 ct state new,related,established accept
+{% if 'dhclient' in group_names %}
+ ip version 4 udp sport 68 udp dport 67 ct state new,related,established accept
+ ip6 version 6 udp sport 546 udp dport 547 ct state new,related,established accept
+{% endif %}
+
+ tcp sport $in-tcp-ports ct state related,established accept
+ tcp dport $out-tcp-ports ct state related,established accept
+ tcp dport $out-tcp-ports ct state new counter accept
+
+ meta l4proto tcp counter reject with tcp reset
+ meta l4proto udp counter reject
+ counter reject
+ }
+
+ chain ipsec-in {
+{% for h in ipsec.keys() | difference([inventory_hostname_short]) | sort %}
+ ip saddr {{ ipsec[h] }} ipsec in reqid {{ ipsec[h].replace(":",".").split(".")[-1] }} counter accept
+{% endfor %}
+ log prefix "ipsec-in " drop
+ }
+ chain ipsec-out {
+{% for h in ipsec.keys() | difference([inventory_hostname_short]) | sort %}
+ ip daddr {{ ipsec[h] }} ipsec out reqid {{ ipsec[h].replace(":",".").split(".")[-1] }} counter accept
+{% endfor %}
+ log prefix "ipsec-out " drop
+ }
+}
diff --git a/roles/common/templates/etc/ntp.conf.j2 b/roles/common/templates/etc/ntp.conf.j2
index d46ba18..b76f0dd 100644
--- a/roles/common/templates/etc/ntp.conf.j2
+++ b/roles/common/templates/etc/ntp.conf.j2
@@ -1,63 +1,59 @@
# /etc/ntp.conf, configuration for ntpd; see ntp.conf(5) for help
driftfile /var/lib/ntp/ntp.drift
+# Leap seconds definition provided by tzdata
+leapfile /usr/share/zoneinfo/leap-seconds.list
# Enable this if you want statistics to be logged.
#statsdir /var/log/ntpstats/
statistics loopstats peerstats clockstats
filegen loopstats file loopstats type day enable
filegen peerstats file peerstats type day enable
filegen clockstats file clockstats type day enable
# You do need to talk to an NTP server or two (or three).
-{% if 'NTP-master' in group_names %}
# Use Stratum One Time Servers:
# http://support.ntp.org/bin/view/Servers/StratumOneTimeServers
-server ntp1.sp.se iburst
-server ntp2.sp.se iburst
-server ntp2.gbg.netnod.se iburst
-server ntp1.sth.netnod.se iburst
-server ntp2.sth.netnod.se iburst
-{% else %}
-# Sychronize to our (stratum 2) NTP server, to ensure our network has a
-# consistent time.
-# TODO: use to pubkey authentication to unsure an attacker doesn't
-# impersonate our NTP server and gives us incorrect time.
-server ntp.fripost.org iburst
-server 0.se.pool.ntp.org iburst
-server 1.se.pool.ntp.org iburst
-{% endif %}
+server sth1.ntp.se iburst
+server sth2.ntp.se iburst
+server gbg1.ntp.se iburst
+server gbg2.ntp.se iburst
+server ntp1.sp.se iburst
+server ntp2.sp.se iburst
# Access control configuration; see /usr/share/doc/ntp-doc/html/accopt.html for
# details. The web page <http://support.ntp.org/bin/view/Support/AccessRestrictions>
# might also be helpful.
#
# Note that "restrict" applies to both servers and clients, so a configuration
# that might be intended to block requests from certain clients could also end
# up blocking replies from your own upstream servers.
# By default, exchange time with everybody, but don't allow configuration.
-restrict -4 default limited kod nomodify notrap nopeer noquery
-restrict -6 default limited kod nomodify notrap nopeer noquery
+restrict -4 default kod notrap nomodify nopeer noquery limited
+restrict -6 default kod notrap nomodify nopeer noquery limited
# Local users may interrogate the ntp server more closely.
restrict 127.0.0.1
restrict ::1
+# Needed for adding pool entries
+restrict source notrap nomodify noquery
+
# Clients from this (example!) subnet have unlimited access, but only if
# cryptographically authenticated.
#restrict 192.168.123.0 mask 255.255.255.0 notrust
# If you want to provide time to your local subnet, change the next line.
# (Again, the address is an example only.)
#broadcast 192.168.123.255
# If you want to listen to time broadcasts on your local subnet, de-comment the
# next lines. Please do this only if you trust everybody on the network!
#disable auth
#broadcastclient
diff --git a/roles/common/templates/etc/postfix/main.cf.j2 b/roles/common/templates/etc/postfix/main.cf.j2
index 1b0bc4a..5ac7920 100644
--- a/roles/common/templates/etc/postfix/main.cf.j2
+++ b/roles/common/templates/etc/postfix/main.cf.j2
@@ -1,70 +1,48 @@
########################################################################
# 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
+smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
+biff = no
+readme_directory = no
+compatibility_level = 2
+smtputf8_enable = no
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
# 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
+default_database_type = lmdb
+virtual_alias_maps = lmdb:/etc/aliases
alias_database = $virtual_alias_maps
# 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 %}
+relayhost = [{{ postfix_instance.out.addr | ansible.utils.ipaddr }}]:{{ postfix_instance.out.port }}
relay_domains =
-{% if 'out' in group_names %}
-smtp_tls_security_level = none
-smtp_bind_address = 127.0.0.1
-{% else %}
-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
-
-# 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
+smtp_tls_security_level = none
+smtpd_tls_security_level = none
-{% 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
+{% set instances = postfix_instance.keys() | intersect(group_names) | list %}
+{%- if instances | length > 0 -%}
+# 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 %}
+multi_instance_directories ={% for i in instances | sort %} /etc/postfix-{{ postfix_instance[i].name }}{% endfor %}
+{% endif %}
# vim: set filetype=pfmain :
diff --git a/roles/common/templates/etc/postfix/master.cf.j2 b/roles/common/templates/etc/postfix/master.cf.j2
new file mode 100644
index 0000000..3954085
--- /dev/null
+++ b/roles/common/templates/etc/postfix/master.cf.j2
@@ -0,0 +1,114 @@
+########################################################################
+# Postfix master process configuration file. For details on the format
+# of the file, see the master(5) manual page (command: "man 5 master").
+#
+# {{ ansible_managed }}
+# Do NOT edit this file directly!
+#
+# ==========================================================================
+# service type private unpriv chroot wakeup maxproc command + args
+# (yes) (yes) (yes) (never) (100)
+# ==========================================================================
+
+{% if inst is not defined %}
+[127.0.0.1]:16132 inet n - y - - smtpd
+{% elif inst == 'MX' %}
+smtpd pass - - y - - smtpd
+smtp inet n - y - 1 postscreen
+tlsproxy unix - - y - 0 tlsproxy
+dnsblog unix - - y - 0 dnsblog
+{% elif inst == 'MSA' %}
+submission inet n - y - - smtpd
+submissions inet n - y - - smtpd
+ -o smtpd_tls_wrappermode=yes
+{% if groups.webmail | difference([inventory_hostname]) | length > 0 %}
+[{{ postfix_instance.MSA.addr }}]:{{ postfix_instance.MSA.port }} inet n - y - - smtpd
+ -o broken_sasl_auth_clients=no
+ -o smtpd_tls_security_level=none
+ -o smtpd_sasl_security_options=noanonymous
+ -o smtpd_sasl_exceptions_networks=
+ -o smtpd_authorized_xforward_hosts=127.0.0.0/8,[::1]/128{{ ipsec_subnet is defined | ternary(','+ipsec_subnet, '') }}
+ -o smtpd_peername_lookup=no
+{% endif %}
+{% elif inst in ['IMAP', 'out', 'lists'] %}
+[{{ postfix_instance[inst].addr }}]:{{ postfix_instance[inst].port }} inet n - y - - smtpd
+ -o smtpd_authorized_xforward_hosts=127.0.0.0/8,[::1]/128{{ ipsec_subnet is defined | ternary(','+ipsec_subnet, '') }}
+ -o smtpd_peername_lookup=no
+{% endif %}
+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
+# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
+{% if inst is defined and inst == 'MSA' %}
+smtp_verify unix - - y - - smtp
+ -o smtp_helo_name=noreply.$mydomain
+ -o smtp_tls_security_level=may
+ -o smtp_tls_ciphers=medium
+ -o smtp_tls_protocols=!SSLv2,!SSLv3
+ -o smtp_tls_note_starttls_offer=yes
+ -o smtp_tls_session_cache_database=lmdb:$data_directory/smtp_tls_session_cache
+ -o smtp_tls_fingerprint_digest=sha256
+ -o smtp_tls_policy_maps=lmdb:$config_directory/smtp_tls_policy
+{% endif %}
+relay unix - - y - - smtp
+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
+{% if inst is defined and inst == 'MSA' %}
+policyd-spf unix - n n - 0 spawn
+ user=policyd-spf argv=/usr/bin/policyd-spf
+{% endif %}
+{% if inst is defined and inst == 'MX' %}
+reserved-alias unix - n n - - pipe
+ flags=Rhu user=nobody argv=/usr/local/bin/reserved-alias.pl ${sender} ${original_recipient} @fripost.org
+{% endif %}
+{% if inst is defined and inst == 'lists' %}
+sympa unix - n n - - pipe
+ flags=Rhu user=sympa argv=/usr/local/bin/sympa-queue ${user}
+{% endif %}
+
+{% if inst is defined and inst == 'out' %}
+# Client part (lmtp) - amavis
+amavisfeed unix - - y - 5 lmtp
+ -o lmtp_destination_recipient_limit=1000
+ -o lmtp_send_xforward_command=yes
+ -o lmtp_data_done_timeout=1200s
+ -o disable_dns_lookups=yes
+
+# Server part (smtpd) - amavis
+[127.0.0.1]:10025 inet n - y - - smtpd
+ -o content_filter=
+ -o smtpd_delay_reject=no
+ -o smtpd_client_restrictions=permit_mynetworks,reject
+ -o smtpd_helo_restrictions=
+ -o smtpd_sender_restrictions=
+ -o smtpd_relay_restrictions=permit_mynetworks,reject
+ -o smtpd_data_restrictions=reject_unauth_pipelining
+ -o smtpd_end_of_data_restrictions=
+ -o smtpd_restriction_classes=
+ -o mynetworks_style=host
+ -o smtpd_error_sleep_time=0
+ -o smtpd_soft_error_limit=1001
+ -o smtpd_hard_error_limit=1000
+ -o smtpd_client_connection_count_limit=0
+ -o smtpd_client_connection_rate_limit=0
+ -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters
+ -o local_header_rewrite_clients=
+ -o smtpd_authorized_xforward_hosts=127.0.0.0/8
+{% endif %}
diff --git a/roles/common/templates/etc/postfix/tls_policy.j2 b/roles/common/templates/etc/postfix/tls_policy.j2
deleted file mode 100644
index 5ff7d26..0000000
--- a/roles/common/templates/etc/postfix/tls_policy.j2
+++ /dev/null
@@ -1,25 +0,0 @@
-# {{ ansible_managed }}
-# /!\ WARNING: smtp_tls_fingerprint_digest MUST be sha256!
-
-{% if 'out' not in group_names %}
-[outgoing.fripost.org]:{{ postfix_instance.out.port }} fingerprint ciphers=high protocols=TLSv1.2
-{% for h in groups.out | sort %}
- match={{ lookup('pipe', 'openssl x509 -in certs/postfix/'+h+'.pem -noout -fingerprint -sha256 | cut -d= -f2') }}
-{% endfor %}
-{% endif %}
-
-{% if 'MX' in group_names %}
-{% if 'IMAP' not in group_names %}
-[mda.fripost.org]:{{ postfix_instance.IMAP.port }} fingerprint ciphers=high protocols=TLSv1.2
-{% for h in groups.IMAP | sort %}
- match={{ lookup('pipe', 'openssl x509 -in certs/postfix/'+h+'.pem -noout -fingerprint -sha256 | cut -d= -f2') }}
-{% endfor %}
-{% endif %}
-
-{% if 'lists' not in group_names %}
-[lists.fripost.org]:{{ postfix_instance.lists.port }} fingerprint ciphers=high protocols=TLSv1.2
-{% for h in groups.lists | sort %}
- match={{ lookup('pipe', 'openssl x509 -in certs/postfix/'+h+'.pem -noout -fingerprint -sha256 | cut -d= -f2') }}
-{% endfor %}
-{% endif %}
-{% endif %}
diff --git a/roles/common/templates/etc/rsyslog.d/postfix.conf.j2 b/roles/common/templates/etc/rsyslog.d/postfix.conf.j2
index 5acb56d..52f9ea3 100644
--- a/roles/common/templates/etc/rsyslog.d/postfix.conf.j2
+++ b/roles/common/templates/etc/rsyslog.d/postfix.conf.j2
@@ -1,17 +1,17 @@
# Create an additional socket in postfix's chroot in order not to break
# mail logging when rsyslog is restarted. If the directory is missing,
# rsyslog will silently skip creating the socket.
$AddUnixListenSocket /var/spool/postfix/dev/log
{% for g in postfix_instance.keys() | sort %}
{% if g in group_names %}
$AddUnixListenSocket /var/spool/postfix-{{ postfix_instance[g].name }}/dev/log
{% endif %}
{% endfor %}
{% if 'MSA' in group_names %}
# User of our Authenticated SMTP server can choose the envelope from and From:
# header of their choice. As the SASL username is not logged in the mail
# header, we keep a mapping Postfix's message ID -> SASL username in a separate
# log file that is only rotated monthly.
-if $programname == 'postfix-msa' and $syslogfacility-text == 'mail' and $msg contains 'sasl_username=' then /var/log/mail.sasl
+if $programname == 'postfix-{{ postfix_instance.MSA.name }}' and $syslogfacility-text == 'mail' and $msg contains 'sasl_username=' then /var/log/mail.sasl
{% endif %}
diff --git a/roles/common/templates/etc/stunnel/bacula-fd.conf.j2 b/roles/common/templates/etc/stunnel/bacula-fd.conf.j2
deleted file mode 100644
index 74364c9..0000000
--- a/roles/common/templates/etc/stunnel/bacula-fd.conf.j2
+++ /dev/null
@@ -1,62 +0,0 @@
-; **************************************************************************
-; * Global options *
-; **************************************************************************
-
-; setuid()/setgid() to the specified user/group in daemon mode
-setuid = stunnel4
-setgid = stunnel4
-
-; PID is created inside the chroot jail
-pid = /var/run/stunnel4/bacula-fd.pid
-
-; Only log messages at severity warning (4) and higher
-debug = 4
-
-; **************************************************************************
-; * Service defaults may also be specified in individual service sections *
-; **************************************************************************
-
-; Certificate/key is needed in server mode and optional in client mode
-cert = /etc/stunnel/certs/{{ inventory_hostname_short }}-fd.pem
-key = /etc/stunnel/certs/{{ inventory_hostname_short }}-fd.key
-
-; Some performance tunings
-socket = l:TCP_NODELAY=1
-socket = r:TCP_NODELAY=1
-
-; Prevent MITM attacks
-verify = 4
-
-; Disable support for insecure protocols
-options = NO_SSLv2
-options = NO_SSLv3
-options = NO_TLSv1
-options = NO_TLSv1.1
-
-; These options provide additional security at some performance degradation
-options = SINGLE_ECDH_USE
-options = SINGLE_DH_USE
-
-; Select permitted SSL ciphers
-ciphers = EECDH+AES:EDH+AES:!MEDIUM:!LOW:!EXP:!aNULL:!eNULL:!SSLv2:!SSLv3:!TLSv1:!TLSv1.1
-
-; **************************************************************************
-; * Service definitions (remove all services for inetd mode) *
-; **************************************************************************
-
-[{{ inventory_hostname_short }}-fd]
-client = no
-accept = 9102
-connect = 9112
-CAfile = /etc/stunnel/certs/bacula-dirs.pem
-
-{% if 'bacula-sd' not in group_names %}
-[{{ hostvars[ groups['bacula-sd'][0] ].inventory_hostname_short }}-sd]
-client = yes
-accept = 127.0.0.1:9113
-connect = {{ groups['bacula-sd'][0] }}:9103
-delay = yes
-CAfile = /etc/stunnel/certs/{{ hostvars[ groups['bacula-sd'][0] ].inventory_hostname_short }}-sd.pem
-{% endif %}
-
-; vim:ft=dosini
diff --git a/roles/common/templates/etc/stunnel/munin-node.conf.j2 b/roles/common/templates/etc/stunnel/munin-node.conf.j2
deleted file mode 100644
index de6c156..0000000
--- a/roles/common/templates/etc/stunnel/munin-node.conf.j2
+++ /dev/null
@@ -1,53 +0,0 @@
-; **************************************************************************
-; * Global options *
-; **************************************************************************
-
-; setuid()/setgid() to the specified user/group in daemon mode
-setuid = stunnel4
-setgid = stunnel4
-
-; PID is created inside the chroot jail
-pid = /var/run/stunnel4/munin-node.pid
-
-; Only log messages at severity warning (4) and higher
-debug = 4
-
-; **************************************************************************
-; * Service defaults may also be specified in individual service sections *
-; **************************************************************************
-
-; Certificate/key is needed in server mode and optional in client mode
-cert = /etc/stunnel/certs/munin-{{ inventory_hostname_short }}.pem
-key = /etc/stunnel/certs/munin-{{ inventory_hostname_short }}.key
-
-; Some performance tunings
-socket = l:TCP_NODELAY=1
-socket = r:TCP_NODELAY=1
-
-; Prevent MITM attacks
-verify = 4
-
-; Disable support for insecure protocols
-options = NO_SSLv2
-options = NO_SSLv3
-options = NO_TLSv1
-options = NO_TLSv1.1
-
-; These options provide additional security at some performance degradation
-options = SINGLE_ECDH_USE
-options = SINGLE_DH_USE
-
-; Select permitted SSL ciphers
-ciphers = EECDH+AES:EDH+AES:!MEDIUM:!LOW:!EXP:!aNULL:!eNULL:!SSLv2:!SSLv3:!TLSv1:!TLSv1.1
-
-; **************************************************************************
-; * Service definitions (remove all services for inetd mode) *
-; **************************************************************************
-
-[munin-node]
-client = no
-accept = 4949
-connect = 127.0.0.1:4994
-CAfile = /etc/stunnel/certs/munin-master.pem
-
-; vim:ft=dosini
diff --git a/roles/common/templates/etc/systemd/resolved.conf.d/local.conf.j2 b/roles/common/templates/etc/systemd/resolved.conf.d/local.conf.j2
new file mode 100644
index 0000000..044170a
--- /dev/null
+++ b/roles/common/templates/etc/systemd/resolved.conf.d/local.conf.j2
@@ -0,0 +1,11 @@
+[Resolve]
+LLMNR=no
+{% if ansible_processor[1] is search('^(Genuine)?Intel.*') and not ansible_virtualization_role == 'guest' %}
+DNS=127.0.0.1
+# Quad9
+FallbackDNS=9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net 2620:fe::fe#dns.quad9.net 2620:fe::9#dns.quad9.net
+{% else %}
+# Quad9
+DNS=9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net 2620:fe::fe#dns.quad9.net 2620:fe::9#dns.quad9.net
+{% endif %}
+Domains=fripost.org
diff --git a/roles/common/templates/etc/systemd/timesyncd.conf.d/fripost.conf.j2 b/roles/common/templates/etc/systemd/timesyncd.conf.d/fripost.conf.j2
new file mode 100644
index 0000000..f578cd9
--- /dev/null
+++ b/roles/common/templates/etc/systemd/timesyncd.conf.d/fripost.conf.j2
@@ -0,0 +1,9 @@
+[Time]
+# Sychronize to our (stratum 2) NTP server, to ensure our network has a
+# consistent time.
+{%- set ntp = [] -%}
+{%- for host in groups['NTP_master'] -%}
+{%- set _ = ntp.append(ipsec[ hostvars[host].inventory_hostname_short ]) -%}
+{%- endfor %}
+
+NTP={{ ntp | join(' ') }}
diff --git a/roles/common/templates/etc/unbound/unbound.conf.j2 b/roles/common/templates/etc/unbound/unbound.conf.j2
new file mode 100644
index 0000000..e75e66f
--- /dev/null
+++ b/roles/common/templates/etc/unbound/unbound.conf.j2
@@ -0,0 +1,32 @@
+# Unbound configuration file for Debian.
+#
+# See the unbound.conf(5) man page.
+#
+# See /usr/share/doc/unbound/examples/unbound.conf for a commented
+# reference config file.
+
+remote-control:
+ control-enable: no
+
+server:
+ interface: 127.0.0.1
+ root-hints: "/usr/share/dns/root.hints"
+ hide-identity: yes
+ hide-version: yes
+ prefetch: yes
+ qname-minimisation: yes
+ rrset-roundrobin: yes
+ use-caps-for-id: yes
+
+ # RFC 1918
+ private-address: 10.0.0.0/8
+ private-address: 172.16.0.0/12
+ private-address: 192.168.0.0/16
+ private-address: 169.254.0.0/16
+ private-address: fd00::/8
+ private-address: fe80::/10
+
+#
+# The following line includes additional configuration files from the
+# /etc/unbound/unbound.conf.d directory.
+include-toplevel: "/etc/unbound/unbound.conf.d/*.conf"