diff options
Diffstat (limited to 'roles')
269 files changed, 7182 insertions, 6006 deletions
diff --git a/roles/IMAP-proxy/files/etc/dovecot/conf.d/10-auth.conf b/roles/IMAP-proxy/files/etc/dovecot/conf.d/10-auth.conf deleted file mode 100644 index 1abea0c..0000000 --- a/roles/IMAP-proxy/files/etc/dovecot/conf.d/10-auth.conf +++ /dev/null @@ -1,129 +0,0 @@ -## -## Authentication processes -## - -# Disable LOGIN command and all other plaintext authentications unless -# SSL/TLS is used (LOGINDISABLED capability). Note that if the remote IP -# matches the local IP (ie. you're connecting from the same computer), the -# connection is considered secure and plaintext authentication is allowed. -# See also ssl=required setting. -disable_plaintext_auth = yes - -# Authentication cache size (e.g. 10M). 0 means it's disabled. Note that -# bsdauth, PAM and vpopmail require cache_key to be set for caching to be used. -#auth_cache_size = 0 -# Time to live for cached data. After TTL expires the cached record is no -# longer used, *except* if the main database lookup returns internal failure. -# We also try to handle password changes automatically: If user's previous -# authentication was successful, but this one wasn't, the cache isn't used. -# For now this works only with plaintext authentication. -#auth_cache_ttl = 1 hour -# TTL for negative hits (user not found, password mismatch). -# 0 disables caching them completely. -#auth_cache_negative_ttl = 1 hour - -# Space separated list of realms for SASL authentication mechanisms that need -# them. You can leave it empty if you don't want to support multiple realms. -# Many clients simply use the first one listed here, so keep the default realm -# first. -#auth_realms = - -# Default realm/domain to use if none was specified. This is used for both -# SASL realms and appending @domain to username in plaintext logins. -auth_default_realm = fripost.org - -# List of allowed characters in username. If the user-given username contains -# a character not listed in here, the login automatically fails. This is just -# an extra check to make sure user can't exploit any potential quote escaping -# vulnerabilities with SQL/LDAP databases. If you want to allow all characters, -# set this value to empty. -#auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@ - -# Username character translations before it's looked up from databases. The -# value contains series of from -> to characters. For example "#@/@" means -# that '#' and '/' characters are translated to '@'. -#auth_username_translation = - -# Username formatting before it's looked up from databases. You can use -# the standard variables here, eg. %Lu would lowercase the username, %n would -# drop away the domain if it was given, or "%n-AT-%d" would change the '@' into -# "-AT-". This translation is done after auth_username_translation changes. -auth_username_format = %Lu - -# If you want to allow master users to log in by specifying the master -# username within the normal username string (ie. not using SASL mechanism's -# support for it), you can specify the separator character here. The format -# is then <username><separator><master username>. UW-IMAP uses "*" as the -# separator, so that could be a good choice. -#auth_master_user_separator = - -# Username to use for users logging in with ANONYMOUS SASL mechanism -#auth_anonymous_username = anonymous - -# Maximum number of dovecot-auth worker processes. They're used to execute -# blocking passdb and userdb queries (eg. MySQL and PAM). They're -# automatically created and destroyed as needed. -#auth_worker_max_count = 30 - -# Host name to use in GSSAPI principal names. The default is to use the -# name returned by gethostname(). Use "$ALL" (with quotes) to allow all keytab -# entries. -#auth_gssapi_hostname = - -# Kerberos keytab to use for the GSSAPI mechanism. Will use the system -# default (usually /etc/krb5.keytab) if not specified. You may need to change -# the auth service to run as root to be able to read this file. -#auth_krb5_keytab = - -# Do NTLM and GSS-SPNEGO authentication using Samba's winbind daemon and -# ntlm_auth helper. <doc/wiki/Authentication/Mechanisms/Winbind.txt> -#auth_use_winbind = no - -# Path for Samba's ntlm_auth helper binary. -#auth_winbind_helper_path = /usr/bin/ntlm_auth - -# Time to delay before replying to failed authentications. -#auth_failure_delay = 2 secs - -# Require a valid SSL client certificate or the authentication fails. -#auth_ssl_require_client_cert = no - -# Take the username from client's SSL certificate, using -# X509_NAME_get_text_by_NID() which returns the subject's DN's -# CommonName. -#auth_ssl_username_from_cert = no - -# Space separated list of wanted authentication mechanisms: -# plain login digest-md5 cram-md5 ntlm rpa apop anonymous gssapi otp skey -# gss-spnego -# NOTE: See also disable_plaintext_auth setting. -auth_mechanisms = plain - -## -## Password and user databases -## - -# -# Password database is used to verify user's password (and nothing more). -# You can have multiple passdbs and userdbs. This is useful if you want to -# allow both system users (/etc/passwd) and virtual users to login without -# duplicating the system users into virtual database. -# -# <doc/wiki/PasswordDatabase.txt> -# -# User database specifies where mails are located and what user/group IDs -# own them. For single-UID configuration use "static" userdb. -# -# <doc/wiki/UserDatabase.txt> - -#!include auth-deny.conf.ext -#!include auth-master.conf.ext - -#!include auth-system.conf.ext -#!include auth-sql.conf.ext -#!include auth-ldap.conf.ext -#!include auth-passwdfile.conf.ext -#!include auth-checkpassword.conf.ext -#!include auth-vpopmail.conf.ext -#!include auth-static.conf.ext -!include auth-imap.conf.ext diff --git a/roles/IMAP-proxy/files/etc/dovecot/conf.d/10-logging.conf b/roles/IMAP-proxy/files/etc/dovecot/conf.d/10-logging.conf deleted file mode 120000 index fc1f820..0000000 --- a/roles/IMAP-proxy/files/etc/dovecot/conf.d/10-logging.conf +++ /dev/null @@ -1 +0,0 @@ -../../../../../IMAP/files/etc/dovecot/conf.d/10-logging.conf
\ No newline at end of file diff --git a/roles/IMAP-proxy/files/etc/dovecot/conf.d/10-mail.conf b/roles/IMAP-proxy/files/etc/dovecot/conf.d/10-mail.conf deleted file mode 100644 index 53e45b5..0000000 --- a/roles/IMAP-proxy/files/etc/dovecot/conf.d/10-mail.conf +++ /dev/null @@ -1,371 +0,0 @@ -## -## Mailbox locations and namespaces -## - -# Location for users' mailboxes. The default is empty, which means that Dovecot -# tries to find the mailboxes automatically. This won't work if the user -# doesn't yet have any mail, so you should explicitly tell Dovecot the full -# location. -# -# If you're using mbox, giving a path to the INBOX file (eg. /var/mail/%u) -# isn't enough. You'll also need to tell Dovecot where the other mailboxes are -# kept. This is called the "root mail directory", and it must be the first -# path given in the mail_location setting. -# -# There are a few special variables you can use, eg.: -# -# %u - username -# %n - user part in user@domain, same as %u if there's no domain -# %d - domain part in user@domain, empty if there's no domain -# %h - home directory -# -# See doc/wiki/Variables.txt for full list. Some examples: -# -# mail_location = maildir:~/Maildir -# mail_location = mbox:~/mail:INBOX=/var/mail/%u -# mail_location = mbox:/var/mail/%d/%1n/%n:INDEX=/var/indexes/%d/%1n/%n -# -# <doc/wiki/MailLocation.txt> -# -mail_location = imapc:~/imapc - -# If you need to set multiple mailbox locations or want to change default -# namespace settings, you can do it by defining namespace sections. -# -# You can have private, shared and public namespaces. Private namespaces -# are for user's personal mails. Shared namespaces are for accessing other -# users' mailboxes that have been shared. Public namespaces are for shared -# mailboxes that are managed by sysadmin. If you create any shared or public -# namespaces you'll typically want to enable ACL plugin also, otherwise all -# users can access all the shared mailboxes, assuming they have permissions -# on filesystem level to do so. -namespace inbox { - # Namespace type: private, shared or public - #type = private - - # Hierarchy separator to use. You should use the same separator for all - # namespaces or some clients get confused. '/' is usually a good one. - # The default however depends on the underlying mail storage format. - separator = / - - # Prefix required to access this namespace. This needs to be different for - # all namespaces. For example "Public/". - #prefix = - - # Physical location of the mailbox. This is in same format as - # mail_location, which is also the default for it. - #location = - - # There can be only one INBOX, and this setting defines which namespace - # has it. - inbox = yes - - # If namespace is hidden, it's not advertised to clients via NAMESPACE - # extension. You'll most likely also want to set list=no. This is mostly - # useful when converting from another server with different namespaces which - # you want to deprecate but still keep working. For example you can create - # hidden namespaces with prefixes "~/mail/", "~%u/mail/" and "mail/". - #hidden = no - - # Show the mailboxes under this namespace with LIST command. This makes the - # namespace visible for clients that don't support NAMESPACE extension. - # "children" value lists child mailboxes, but hides the namespace prefix. - #list = yes - - # Namespace handles its own subscriptions. If set to "no", the parent - # namespace handles them (empty prefix should always have this as "yes") - #subscriptions = yes -} - -# Example shared namespace configuration -#namespace { - #type = shared - #separator = / - - # Mailboxes are visible under "shared/user@domain/" - # %%n, %%d and %%u are expanded to the destination user. - #prefix = shared/%%u/ - - # Mail location for other users' mailboxes. Note that %variables and ~/ - # expands to the logged in user's data. %%n, %%d, %%u and %%h expand to the - # destination user's data. - #location = maildir:%%h/Maildir:INDEX=~/Maildir/shared/%%u - - # Use the default namespace for saving subscriptions. - #subscriptions = no - - # List the shared/ namespace only if there are visible shared mailboxes. - #list = children -#} -# Should shared INBOX be visible as "shared/user" or "shared/user/INBOX"? -#mail_shared_explicit_inbox = no - -# System user and group used to access mails. If you use multiple, userdb -# can override these by returning uid or gid fields. You can use either numbers -# or names. <doc/wiki/UserIds.txt> -mail_uid = imapproxy -mail_gid = imapproxy - -# Group to enable temporarily for privileged operations. Currently this is -# used only with INBOX when either its initial creation or dotlocking fails. -# Typically this is set to "mail" to give access to /var/mail. -#mail_privileged_group = - -# Grant access to these supplementary groups for mail processes. Typically -# these are used to set up access to shared mailboxes. Note that it may be -# dangerous to set these if users can create symlinks (e.g. if "mail" group is -# set here, ln -s /var/mail ~/mail/var could allow a user to delete others' -# mailboxes, or ln -s /secret/shared/box ~/mail/mybox would allow reading it). -#mail_access_groups = - -# Allow full filesystem access to clients. There's no access checks other than -# what the operating system does for the active UID/GID. It works with both -# maildir and mboxes, allowing you to prefix mailboxes names with eg. /path/ -# or ~user/. -#mail_full_filesystem_access = no - -# Dictionary for key=value mailbox attributes. Currently used by URLAUTH, but -# soon intended to be used by METADATA as well. -#mail_attribute_dict = - -## -## Mail processes -## - -# Don't use mmap() at all. This is required if you store indexes to shared -# filesystems (NFS or clustered filesystem). -#mmap_disable = no - -# Rely on O_EXCL to work when creating dotlock files. NFS supports O_EXCL -# since version 3, so this should be safe to use nowadays by default. -#dotlock_use_excl = yes - -# When to use fsync() or fdatasync() calls: -# optimized (default): Whenever necessary to avoid losing important data -# always: Useful with e.g. NFS when write()s are delayed -# never: Never use it (best performance, but crashes can lose data) -#mail_fsync = optimized - -# Locking method for index files. Alternatives are fcntl, flock and dotlock. -# Dotlocking uses some tricks which may create more disk I/O than other locking -# methods. NFS users: flock doesn't work, remember to change mmap_disable. -#lock_method = fcntl - -# Directory in which LDA/LMTP temporarily stores incoming mails >128 kB. -#mail_temp_dir = /tmp - -# Valid UID range for users, defaults to 500 and above. This is mostly -# to make sure that users can't log in as daemons or other system users. -# Note that denying root logins is hardcoded to dovecot binary and can't -# be done even if first_valid_uid is set to 0. -first_valid_uid = 1 -#last_valid_uid = 0 - -# Valid GID range for users, defaults to non-root/wheel. Users having -# non-valid GID as primary group ID aren't allowed to log in. If user -# belongs to supplementary groups with non-valid GIDs, those groups are -# not set. -#first_valid_gid = 1 -#last_valid_gid = 0 - -# Maximum allowed length for mail keyword name. It's only forced when trying -# to create new keywords. -#mail_max_keyword_length = 50 - -# ':' separated list of directories under which chrooting is allowed for mail -# processes (ie. /var/mail will allow chrooting to /var/mail/foo/bar too). -# This setting doesn't affect login_chroot, mail_chroot or auth chroot -# settings. If this setting is empty, "/./" in home dirs are ignored. -# WARNING: Never add directories here which local users can modify, that -# may lead to root exploit. Usually this should be done only if you don't -# allow shell access for users. <doc/wiki/Chrooting.txt> -#valid_chroot_dirs = - -# Default chroot directory for mail processes. This can be overridden for -# specific users in user database by giving /./ in user's home directory -# (eg. /home/./user chroots into /home). Note that usually there is no real -# need to do chrooting, Dovecot doesn't allow users to access files outside -# their mail directory anyway. If your home directories are prefixed with -# the chroot directory, append "/." to mail_chroot. <doc/wiki/Chrooting.txt> -#mail_chroot = - -# UNIX socket path to master authentication server to find users. -# This is used by imap (for shared users) and lda. -#auth_socket_path = /var/run/dovecot/auth-userdb - -# Directory where to look up mail plugins. -#mail_plugin_dir = /usr/lib/dovecot/modules - -# Space separated list of plugins to load for all services. Plugins specific to -# IMAP, LDA, etc. are added to this list in their own .conf files. -mail_plugins = virtual zlib - -## -## Mailbox handling optimizations -## - -# Mailbox list indexes can be used to optimize IMAP STATUS commands. They are -# also required for IMAP NOTIFY extension to be enabled. -mailbox_list_index = yes - -# The minimum number of mails in a mailbox before updates are done to cache -# file. This allows optimizing Dovecot's behavior to do less disk writes at -# the cost of more disk reads. -#mail_cache_min_mail_count = 0 - -# When IDLE command is running, mailbox is checked once in a while to see if -# there are any new mails or other changes. This setting defines the minimum -# time to wait between those checks. Dovecot can also use dnotify, inotify and -# kqueue to find out immediately when changes occur. -#mailbox_idle_check_interval = 30 secs - -# Save mails with CR+LF instead of plain LF. This makes sending those mails -# take less CPU, especially with sendfile() syscall with Linux and FreeBSD. -# But it also creates a bit more disk I/O which may just make it slower. -# Also note that if other software reads the mboxes/maildirs, they may handle -# the extra CRs wrong and cause problems. -#mail_save_crlf = no - -# Max number of mails to keep open and prefetch to memory. This only works with -# some mailbox formats and/or operating systems. -#mail_prefetch_count = 0 - -# How often to scan for stale temporary files and delete them (0 = never). -# These should exist only after Dovecot dies in the middle of saving mails. -#mail_temp_scan_interval = 1w - -## -## Maildir-specific settings -## - -# By default LIST command returns all entries in maildir beginning with a dot. -# Enabling this option makes Dovecot return only entries which are directories. -# This is done by stat()ing each entry, so it causes more disk I/O. -# (For systems setting struct dirent->d_type, this check is free and it's -# done always regardless of this setting) -#maildir_stat_dirs = no - -# When copying a message, do it with hard links whenever possible. This makes -# the performance much better, and it's unlikely to have any side effects. -#maildir_copy_with_hardlinks = yes - -# Assume Dovecot is the only MUA accessing Maildir: Scan cur/ directory only -# when its mtime changes unexpectedly or when we can't find the mail otherwise. -#maildir_very_dirty_syncs = no - -# If enabled, Dovecot doesn't use the S=<size> in the Maildir filenames for -# getting the mail's physical size, except when recalculating Maildir++ quota. -# This can be useful in systems where a lot of the Maildir filenames have a -# broken size. The performance hit for enabling this is very small. -#maildir_broken_filename_sizes = no - -# Always move mails from new/ directory to cur/, even when the \Recent flags -# aren't being reset. -#maildir_empty_new = no - -## -## mbox-specific settings -## - -# Which locking methods to use for locking mbox. There are four available: -# dotlock: Create <mailbox>.lock file. This is the oldest and most NFS-safe -# solution. If you want to use /var/mail/ like directory, the users -# will need write access to that directory. -# dotlock_try: Same as dotlock, but if it fails because of permissions or -# because there isn't enough disk space, just skip it. -# fcntl : Use this if possible. Works with NFS too if lockd is used. -# flock : May not exist in all systems. Doesn't work with NFS. -# lockf : May not exist in all systems. Doesn't work with NFS. -# -# You can use multiple locking methods; if you do the order they're declared -# in is important to avoid deadlocks if other MTAs/MUAs are using multiple -# locking methods as well. Some operating systems don't allow using some of -# them simultaneously. -# -# The Debian value for mbox_write_locks differs from upstream Dovecot. It is -# changed to be compliant with Debian Policy (section 11.6) for NFS safety. -# Dovecot: mbox_write_locks = dotlock fcntl -# Debian: mbox_write_locks = fcntl dotlock -# -#mbox_read_locks = fcntl -#mbox_write_locks = fcntl dotlock - -# Maximum time to wait for lock (all of them) before aborting. -#mbox_lock_timeout = 5 mins - -# If dotlock exists but the mailbox isn't modified in any way, override the -# lock file after this much time. -#mbox_dotlock_change_timeout = 2 mins - -# When mbox changes unexpectedly we have to fully read it to find out what -# changed. If the mbox is large this can take a long time. Since the change -# is usually just a newly appended mail, it'd be faster to simply read the -# new mails. If this setting is enabled, Dovecot does this but still safely -# fallbacks to re-reading the whole mbox file whenever something in mbox isn't -# how it's expected to be. The only real downside to this setting is that if -# some other MUA changes message flags, Dovecot doesn't notice it immediately. -# Note that a full sync is done with SELECT, EXAMINE, EXPUNGE and CHECK -# commands. -#mbox_dirty_syncs = yes - -# Like mbox_dirty_syncs, but don't do full syncs even with SELECT, EXAMINE, -# EXPUNGE or CHECK commands. If this is set, mbox_dirty_syncs is ignored. -#mbox_very_dirty_syncs = no - -# Delay writing mbox headers until doing a full write sync (EXPUNGE and CHECK -# commands and when closing the mailbox). This is especially useful for POP3 -# where clients often delete all mails. The downside is that our changes -# aren't immediately visible to other MUAs. -#mbox_lazy_writes = yes - -# If mbox size is smaller than this (e.g. 100k), don't write index files. -# If an index file already exists it's still read, just not updated. -#mbox_min_index_size = 0 - -# Mail header selection algorithm to use for MD5 POP3 UIDLs when -# pop3_uidl_format=%m. For backwards compatibility we use apop3d inspired -# algorithm, but it fails if the first Received: header isn't unique in all -# mails. An alternative algorithm is "all" that selects all headers. -#mbox_md5 = apop3d - -## -## mdbox-specific settings -## - -# Maximum dbox file size until it's rotated. -#mdbox_rotate_size = 2M - -# Maximum dbox file age until it's rotated. Typically in days. Day begins -# from midnight, so 1d = today, 2d = yesterday, etc. 0 = check disabled. -#mdbox_rotate_interval = 0 - -# When creating new mdbox files, immediately preallocate their size to -# mdbox_rotate_size. This setting currently works only in Linux with some -# filesystems (ext4, xfs). -#mdbox_preallocate_space = no - -## -## Mail attachments -## - -# sdbox and mdbox support saving mail attachments to external files, which -# also allows single instance storage for them. Other backends don't support -# this for now. - -# Directory root where to store mail attachments. Disabled, if empty. -#mail_attachment_dir = - -# Attachments smaller than this aren't saved externally. It's also possible to -# write a plugin to disable saving specific attachments externally. -#mail_attachment_min_size = 128k - -# Filesystem backend to use for saving attachments: -# posix : No SiS done by Dovecot (but this might help FS's own deduplication) -# sis posix : SiS with immediate byte-by-byte comparison during saving -# sis-queue posix : SiS with delayed comparison and deduplication -#mail_attachment_fs = sis posix - -# Hash format to use in attachment filenames. You can add any text and -# variables: %{md4}, %{md5}, %{sha1}, %{sha256}, %{sha512}, %{size}. -# Variables can be truncated, e.g. %{sha256:80} returns only first 80 bits -#mail_attachment_hash = %{sha1} diff --git a/roles/IMAP-proxy/files/etc/dovecot/conf.d/10-master.conf b/roles/IMAP-proxy/files/etc/dovecot/conf.d/10-master.conf deleted file mode 100644 index 12ec736..0000000 --- a/roles/IMAP-proxy/files/etc/dovecot/conf.d/10-master.conf +++ /dev/null @@ -1,77 +0,0 @@ -#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 = 127.0.0.1 ::1 - port = 143 - ssl = no - } - inet_listener imaps { - port = 0 - } - - # 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 = 4 - - # If you set service_count=0, you probably need to grow this. - #vsz_limit = $default_vsz_limit -} - -service imap { - # Most of the memory goes to mmap()ing files. You may need to increase this - # limit if you have huge mailboxes. - #vsz_limit = $default_vsz_limit - - # Max. number of IMAP processes (connections) - #process_limit = 1024 -} - -service auth { - # auth_socket_path points to this userdb socket by default. It's typically - # used by dovecot-lda, doveadm, possibly imap process, etc. Users that have - # full permissions to this socket are able to get a list of all usernames and - # get the results of everyone's userdb lookups. - # - # The default 0666 mode allows anyone to connect to the socket, but the - # userdb lookups will succeed only if the userdb returns an "uid" field that - # matches the caller process's UID. Also if caller's uid or gid matches the - # socket's uid or gid the lookup succeeds. Anything else causes a failure. - # - # To give the caller full permissions to lookup all users, set the mode to - # something else than 0666 and Dovecot lets the kernel enforce the - # permissions (e.g. 0777 allows everyone full permissions). - unix_listener auth-userdb { - #mode = 0666 - #user = $default_internal_user - #group = - } - - # Auth process is run as this user. - user = $default_internal_user -} - -service auth-worker { - # Auth worker process is run as root by default, so that it can access - # /etc/shadow. If this isn't necessary, the user should be changed to - # $default_internal_user. - user = $default_internal_user -} diff --git a/roles/IMAP-proxy/files/etc/dovecot/conf.d/15-mailboxes.conf b/roles/IMAP-proxy/files/etc/dovecot/conf.d/15-mailboxes.conf deleted file mode 120000 index 1883eda..0000000 --- a/roles/IMAP-proxy/files/etc/dovecot/conf.d/15-mailboxes.conf +++ /dev/null @@ -1 +0,0 @@ -../../../../../IMAP/files/etc/dovecot/conf.d/15-mailboxes.conf
\ No newline at end of file diff --git a/roles/IMAP-proxy/files/etc/dovecot/conf.d/20-imapc.conf b/roles/IMAP-proxy/files/etc/dovecot/conf.d/20-imapc.conf deleted file mode 100644 index ea39a32..0000000 --- a/roles/IMAP-proxy/files/etc/dovecot/conf.d/20-imapc.conf +++ /dev/null @@ -1,17 +0,0 @@ -# Smart IMAP proxying with imapc storage -# -# http://dovecot.org/pipermail/dovecot/2011-January/056975.html -# http://wiki2.dovecot.org/HowTo/ImapcProxy -# http://wiki2.dovecot.org/Migration/Dsync - -imapc_host = localhost -imapc_port = 993 - -# Read multiple mails in parallel, improves performance -mail_prefetch_count = 20 - -# The list of valid features can be found there -# http://hg.dovecot.org/dovecot-2.2/file/tip/src/lib-storage/index/imapc/imapc-settings.c -# (in the struct 'imapc_feature_list imapc_feature_list') -imapc_features = rfc822.size -#imapc_features = rfc822.size fetch-headers diff --git a/roles/IMAP-proxy/files/etc/dovecot/conf.d/auth-imap.conf.ext b/roles/IMAP-proxy/files/etc/dovecot/conf.d/auth-imap.conf.ext deleted file mode 100644 index 7ab096f..0000000 --- a/roles/IMAP-proxy/files/etc/dovecot/conf.d/auth-imap.conf.ext +++ /dev/null @@ -1,17 +0,0 @@ -# Authentication via remote IMAP server. Included from auth.conf. -# -# <doc/wiki/PasswordDatabase.IMAP.txt> - -passdb { - driver = imap - args = host=localhost port=993 - default_fields = userdb_imapc_password=%w -} - -# "prefetch" user database means that the passdb already provided the -# needed information and there's no need to do a separate userdb lookup. -# <doc/wiki/UserDatabase.Prefetch.txt> -userdb { - driver = prefetch - default_fields = home=/home/imapproxy/%d/%n -} diff --git a/roles/IMAP-proxy/files/etc/stunnel/roundcube.conf b/roles/IMAP-proxy/files/etc/stunnel/roundcube.conf deleted file mode 100644 index c14bac3..0000000 --- a/roles/IMAP-proxy/files/etc/stunnel/roundcube.conf +++ /dev/null @@ -1,59 +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/roundcube.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/mail.pem -;key = /etc/stunnel/mail.pem -client = yes -socket = a:SO_BINDTODEVICE=lo - -; 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) * -; ************************************************************************** - -[imaps] -accept = localhost:143 -connect = imap.fripost.org:993 -CAfile = /etc/stunnel/certs/imap.fripost.org.pem - -[ldaps] -accept = localhost:389 -connect = ldap.fripost.org:636 -CAfile = /etc/stunnel/certs/ldap.fripost.org.pem - -; vim:ft=dosini diff --git a/roles/IMAP-proxy/handlers/main.yml b/roles/IMAP-proxy/handlers/main.yml deleted file mode 100644 index 5249a7e..0000000 --- a/roles/IMAP-proxy/handlers/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: Restart stunnel - service: name=stunnel4 pattern=/usr/bin/stunnel4 state=restarted - -- name: Restart Dovecot - service: name=dovecot state=restarted diff --git a/roles/IMAP-proxy/tasks/main.yml b/roles/IMAP-proxy/tasks/main.yml deleted file mode 100644 index 587fc62..0000000 --- a/roles/IMAP-proxy/tasks/main.yml +++ /dev/null @@ -1,97 +0,0 @@ -- name: Install Dovecot - #apt: pkg={{ item }} default_release={{ ansible_lsb.codename }}-backports - apt: pkg={{ item }} - with_items: - - dovecot-core - - dovecot-imapd - -- name: Create a user 'imapproxy' - user: name=imapproxy system=yes - createhome=no - home=/home/imapproxy - shell=/usr/sbin/nologin - password=! - state=present - -- name: Create a home directory for user 'imapproxy' - file: path=/home/imapproxy - state=directory - owner=imapproxy group=imapproxy - mode=0700 - -- name: Configure Dovecot - copy: src=etc/dovecot/conf.d/{{ item }} - dest=/etc/dovecot/conf.d/{{ item }} - owner=root group=root - mode=0644 - register: r - with_items: - - 10-auth.conf - - 10-logging.conf - - 10-mail.conf - - 10-master.conf - - 15-mailboxes.conf - - 20-imapc.conf - - auth-imap.conf.ext - notify: - - Restart Dovecot - -- name: Start Dovecot - service: name=dovecot state=started - when: not r.changed - -- meta: flush_handlers - - -- name: Install stunnel - apt: pkg=stunnel4 - -- name: Auto-enable stunnel - lineinfile: dest=/etc/default/stunnel4 - regexp='^(\s*#)?\s*ENABLED=' - line='ENABLED=1' - owner=root group=root - mode=0644 - -- name: Create /etc/stunnel/certs - file: path=/etc/stunnel/certs - state=directory - owner=root group=root - mode=0755 - -- name: Copy Dovecot's X.509 certificate - # XXX: it's unfortunate that we have to store the whole CA chain... - # for some reason stunnel's level 4 "verify" (CA chain and only verify - # peer certificate) doesn't always work: - # https://www.stunnel.org/pipermail/stunnel-users/2013-July/004249.html - assemble: src=certs/dovecot remote_src=no - dest=/etc/stunnel/certs/imap.fripost.org.pem - owner=root group=root - mode=0644 - register: r1 - notify: - - Restart stunnel - -- name: Copy slapd's X.509 certificate - copy: src=certs/ldap/ldap.fripost.org.pem - dest=/etc/stunnel/certs/ldap.fripost.org.pem - owner=root group=root - mode=0644 - register: r2 - notify: - - Restart stunnel - -- name: Configure stunnel - copy: src=etc/stunnel/roundcube.conf - dest=/etc/stunnel/roundcube.conf - owner=root group=root - mode=0644 - register: r3 - notify: - - Restart stunnel - -- name: Start stunnel - service: name=stunnel4 pattern=/usr/bin/stunnel4 state=started - when: not (r1.changed or r2.changed or r3.changed) - -- meta: flush_handlers diff --git a/roles/IMAP/files/etc/cron.d/doveadm b/roles/IMAP/files/etc/cron.d/doveadm new file mode 100644 index 0000000..b0551e4 --- /dev/null +++ b/roles/IMAP/files/etc/cron.d/doveadm @@ -0,0 +1,4 @@ +MAILTO=root + +59 * * * * vmail test -x /usr/bin/doveadm && nice -n 19 /usr/bin/doveadm sis deduplicate /home/mail/attachments /home/mail/attachments/queue +37 5 * * * vmail test -x /usr/bin/doveadm && nice -n 19 /usr/bin/doveadm purge -A diff --git a/roles/IMAP/files/etc/dovecot/conf.d/10-auth.conf b/roles/IMAP/files/etc/dovecot/conf.d/10-auth.conf index d4f323d..f34bdeb 100644 --- a/roles/IMAP/files/etc/dovecot/conf.d/10-auth.conf +++ b/roles/IMAP/files/etc/dovecot/conf.d/10-auth.conf @@ -1,71 +1,71 @@ ## ## Authentication processes ## # Disable LOGIN command and all other plaintext authentications unless # SSL/TLS is used (LOGINDISABLED capability). Note that if the remote IP # matches the local IP (ie. you're connecting from the same computer), the # connection is considered secure and plaintext authentication is allowed. # See also ssl=required setting. #disable_plaintext_auth = yes # Authentication cache size (e.g. 10M). 0 means it's disabled. Note that -# bsdauth, PAM and vpopmail require cache_key to be set for caching to be used. +# bsdauth and PAM require cache_key to be set for caching to be used. #auth_cache_size = 0 # Time to live for cached data. After TTL expires the cached record is no # longer used, *except* if the main database lookup returns internal failure. # We also try to handle password changes automatically: If user's previous # authentication was successful, but this one wasn't, the cache isn't used. # For now this works only with plaintext authentication. #auth_cache_ttl = 1 hour # TTL for negative hits (user not found, password mismatch). # 0 disables caching them completely. #auth_cache_negative_ttl = 1 hour # Space separated list of realms for SASL authentication mechanisms that need # them. You can leave it empty if you don't want to support multiple realms. # Many clients simply use the first one listed here, so keep the default realm # first. #auth_realms = # Default realm/domain to use if none was specified. This is used for both # SASL realms and appending @domain to username in plaintext logins. -auth_default_realm = fripost.org +#auth_default_realm = # List of allowed characters in username. If the user-given username contains # a character not listed in here, the login automatically fails. This is just # an extra check to make sure user can't exploit any potential quote escaping # vulnerabilities with SQL/LDAP databases. If you want to allow all characters, # set this value to empty. #auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@ # Username character translations before it's looked up from databases. The # value contains series of from -> to characters. For example "#@/@" means # that '#' and '/' characters are translated to '@'. #auth_username_translation = # Username formatting before it's looked up from databases. You can use # the standard variables here, eg. %Lu would lowercase the username, %n would # drop away the domain if it was given, or "%n-AT-%d" would change the '@' into # "-AT-". This translation is done after auth_username_translation changes. -auth_username_format = %Lu +#auth_username_format = %Lu # If you want to allow master users to log in by specifying the master # username within the normal username string (ie. not using SASL mechanism's # support for it), you can specify the separator character here. The format # is then <username><separator><master username>. UW-IMAP uses "*" as the # separator, so that could be a good choice. #auth_master_user_separator = # Username to use for users logging in with ANONYMOUS SASL mechanism #auth_anonymous_username = anonymous # Maximum number of dovecot-auth worker processes. They're used to execute # blocking passdb and userdb queries (eg. MySQL and PAM). They're # automatically created and destroyed as needed. #auth_worker_max_count = 30 # Host name to use in GSSAPI principal names. The default is to use the # name returned by gethostname(). Use "$ALL" (with quotes) to allow all keytab # entries. #auth_gssapi_hostname = @@ -77,52 +77,51 @@ auth_username_format = %Lu # Do NTLM and GSS-SPNEGO authentication using Samba's winbind daemon and # ntlm_auth helper. <doc/wiki/Authentication/Mechanisms/Winbind.txt> #auth_use_winbind = no # Path for Samba's ntlm_auth helper binary. #auth_winbind_helper_path = /usr/bin/ntlm_auth # Time to delay before replying to failed authentications. #auth_failure_delay = 2 secs # Require a valid SSL client certificate or the authentication fails. #auth_ssl_require_client_cert = no # Take the username from client's SSL certificate, using # X509_NAME_get_text_by_NID() which returns the subject's DN's # CommonName. #auth_ssl_username_from_cert = no # Space separated list of wanted authentication mechanisms: -# plain login digest-md5 cram-md5 ntlm rpa apop anonymous gssapi otp skey +# plain login digest-md5 cram-md5 ntlm rpa apop anonymous gssapi otp # gss-spnego # NOTE: See also disable_plaintext_auth setting. -auth_mechanisms = plain login +auth_mechanisms = plain ## ## Password and user databases ## # # Password database is used to verify user's password (and nothing more). # You can have multiple passdbs and userdbs. This is useful if you want to # allow both system users (/etc/passwd) and virtual users to login without # duplicating the system users into virtual database. # # <doc/wiki/PasswordDatabase.txt> # # User database specifies where mails are located and what user/group IDs # own them. For single-UID configuration use "static" userdb. # # <doc/wiki/UserDatabase.txt> #!include auth-deny.conf.ext #!include auth-master.conf.ext #!include auth-system.conf.ext #!include auth-sql.conf.ext !include auth-ldap.conf.ext #!include auth-passwdfile.conf.ext #!include auth-checkpassword.conf.ext -#!include auth-vpopmail.conf.ext #!include auth-static.conf.ext diff --git a/roles/IMAP/files/etc/dovecot/conf.d/10-logging.conf b/roles/IMAP/files/etc/dovecot/conf.d/10-logging.conf deleted file mode 100644 index c611bfc..0000000 --- a/roles/IMAP/files/etc/dovecot/conf.d/10-logging.conf +++ /dev/null @@ -1,84 +0,0 @@ -## -## Log destination. -## - -# Log file to use for error messages. "syslog" logs to syslog, -# /dev/stderr logs to stderr. -#log_path = syslog - -# Log file to use for informational messages. Defaults to log_path. -#info_log_path = -# Log file to use for debug messages. Defaults to info_log_path. -#debug_log_path = - -# Syslog facility to use if you're logging to syslog. Usually if you don't -# want to use "mail", you'll use local0..local7. Also other standard -# facilities are supported. -#syslog_facility = mail - -## -## Logging verbosity and debugging. -## - -# Log unsuccessful authentication attempts and the reasons why they failed. -#auth_verbose = no - -# In case of password mismatches, log the attempted password. Valid values are -# no, plain and sha1. sha1 can be useful for detecting brute force password -# attempts vs. user simply trying the same password over and over again. -# You can also truncate the value to n chars by appending ":n" (e.g. sha1:6). -#auth_verbose_passwords = no - -# Even more verbose logging for debugging purposes. Shows for example SQL -# queries. -#auth_debug = no - -# In case of password mismatches, log the passwords and used scheme so the -# problem can be debugged. Enabling this also enables auth_debug. -#auth_debug_passwords = no - -# Enable mail process debugging. This can help you figure out why Dovecot -# isn't finding your mails. -#mail_debug = no - -# Show protocol level SSL errors. -#verbose_ssl = no - -# mail_log plugin provides more event logging for mail processes. -plugin { - # Events to log. Also available: flag_change append - #mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename - # Available fields: uid, box, msgid, from, subject, size, vsize, flags - # size and vsize are available only for expunge and copy events. - #mail_log_fields = uid box msgid size -} - -## -## Log formatting. -## - -# Prefix for each line written to log file. % codes are in strftime(3) -# format. -log_timestamp = "%Y-%m-%d %H:%M:%S " - -# Space-separated list of elements we want to log. The elements which have -# a non-empty variable value are joined together to form a comma-separated -# string. -#login_log_format_elements = user=<%u> method=%m rip=%r lip=%l mpid=%e %c - -# Login log format. %s contains login_log_format_elements string, %$ contains -# the data we want to log. -#login_log_format = %$: %s - -# Log prefix for mail processes. See doc/wiki/Variables.txt for list of -# possible variables you can use. -#mail_log_prefix = "%s(%u): " - -# Format to use for logging mail deliveries. You can use variables: -# %$ - Delivery status message (e.g. "saved to INBOX") -# %m - Message-ID -# %s - Subject -# %f - From address -# %p - Physical size -# %w - Virtual size -#deliver_log_format = msgid=%m: %$ diff --git a/roles/IMAP/files/etc/dovecot/conf.d/10-mail.conf b/roles/IMAP/files/etc/dovecot/conf.d/10-mail.conf deleted file mode 100644 index 902f58b..0000000 --- a/roles/IMAP/files/etc/dovecot/conf.d/10-mail.conf +++ /dev/null @@ -1,380 +0,0 @@ -## -## Mailbox locations and namespaces -## - -# Location for users' mailboxes. The default is empty, which means that Dovecot -# tries to find the mailboxes automatically. This won't work if the user -# doesn't yet have any mail, so you should explicitly tell Dovecot the full -# location. -# -# If you're using mbox, giving a path to the INBOX file (eg. /var/mail/%u) -# isn't enough. You'll also need to tell Dovecot where the other mailboxes are -# kept. This is called the "root mail directory", and it must be the first -# path given in the mail_location setting. -# -# There are a few special variables you can use, eg.: -# -# %u - username -# %n - user part in user@domain, same as %u if there's no domain -# %d - domain part in user@domain, empty if there's no domain -# %h - home directory -# -# See doc/wiki/Variables.txt for full list. Some examples: -# -# mail_location = maildir:~/Maildir -# mail_location = mbox:~/mail:INBOX=/var/mail/%u -# mail_location = mbox:/var/mail/%d/%1n/%n:INDEX=/var/indexes/%d/%1n/%n -# -# <doc/wiki/MailLocation.txt> -# -mail_location = mdbox:~/mail - -# If you need to set multiple mailbox locations or want to change default -# namespace settings, you can do it by defining namespace sections. -# -# You can have private, shared and public namespaces. Private namespaces -# are for user's personal mails. Shared namespaces are for accessing other -# users' mailboxes that have been shared. Public namespaces are for shared -# mailboxes that are managed by sysadmin. If you create any shared or public -# namespaces you'll typically want to enable ACL plugin also, otherwise all -# users can access all the shared mailboxes, assuming they have permissions -# on filesystem level to do so. -namespace inbox { - # Namespace type: private, shared or public - #type = private - - # Hierarchy separator to use. You should use the same separator for all - # namespaces or some clients get confused. '/' is usually a good one. - # The default however depends on the underlying mail storage format. - separator = / - - # Prefix required to access this namespace. This needs to be different for - # all namespaces. For example "Public/". - #prefix = - - # Physical location of the mailbox. This is in same format as - # mail_location, which is also the default for it. - #location = - - # There can be only one INBOX, and this setting defines which namespace - # has it. - inbox = yes - - # If namespace is hidden, it's not advertised to clients via NAMESPACE - # extension. You'll most likely also want to set list=no. This is mostly - # useful when converting from another server with different namespaces which - # you want to deprecate but still keep working. For example you can create - # hidden namespaces with prefixes "~/mail/", "~%u/mail/" and "mail/". - #hidden = no - - # Show the mailboxes under this namespace with LIST command. This makes the - # namespace visible for clients that don't support NAMESPACE extension. - # "children" value lists child mailboxes, but hides the namespace prefix. - #list = yes - - # Namespace handles its own subscriptions. If set to "no", the parent - # namespace handles them (empty prefix should always have this as "yes") - #subscriptions = yes -} - -namespace virtual { - prefix = virtual/ - separator = / - location = virtual:/etc/dovecot/virtual:INDEX=~/virtual - list = yes - hidden = no - subscriptions = no -} - -# Example shared namespace configuration -#namespace { - #type = shared - #separator = / - - # Mailboxes are visible under "shared/user@domain/" - # %%n, %%d and %%u are expanded to the destination user. - #prefix = shared/%%u/ - - # Mail location for other users' mailboxes. Note that %variables and ~/ - # expands to the logged in user's data. %%n, %%d, %%u and %%h expand to the - # destination user's data. - #location = maildir:%%h/Maildir:INDEX=~/Maildir/shared/%%u - - # Use the default namespace for saving subscriptions. - #subscriptions = no - - # List the shared/ namespace only if there are visible shared mailboxes. - #list = children -#} -# Should shared INBOX be visible as "shared/user" or "shared/user/INBOX"? -#mail_shared_explicit_inbox = no - -# System user and group used to access mails. If you use multiple, userdb -# can override these by returning uid or gid fields. You can use either numbers -# or names. <doc/wiki/UserIds.txt> -mail_uid = vmail -mail_gid = vmail - -# Group to enable temporarily for privileged operations. Currently this is -# used only with INBOX when either its initial creation or dotlocking fails. -# Typically this is set to "mail" to give access to /var/mail. -#mail_privileged_group = - -# Grant access to these supplementary groups for mail processes. Typically -# these are used to set up access to shared mailboxes. Note that it may be -# dangerous to set these if users can create symlinks (e.g. if "mail" group is -# set here, ln -s /var/mail ~/mail/var could allow a user to delete others' -# mailboxes, or ln -s /secret/shared/box ~/mail/mybox would allow reading it). -#mail_access_groups = - -# Allow full filesystem access to clients. There's no access checks other than -# what the operating system does for the active UID/GID. It works with both -# maildir and mboxes, allowing you to prefix mailboxes names with eg. /path/ -# or ~user/. -#mail_full_filesystem_access = no - -# Dictionary for key=value mailbox attributes. Currently used by URLAUTH, but -# soon intended to be used by METADATA as well. -#mail_attribute_dict = - -## -## Mail processes -## - -# Don't use mmap() at all. This is required if you store indexes to shared -# filesystems (NFS or clustered filesystem). -#mmap_disable = no - -# Rely on O_EXCL to work when creating dotlock files. NFS supports O_EXCL -# since version 3, so this should be safe to use nowadays by default. -#dotlock_use_excl = yes - -# When to use fsync() or fdatasync() calls: -# optimized (default): Whenever necessary to avoid losing important data -# always: Useful with e.g. NFS when write()s are delayed -# never: Never use it (best performance, but crashes can lose data) -#mail_fsync = optimized - -# Locking method for index files. Alternatives are fcntl, flock and dotlock. -# Dotlocking uses some tricks which may create more disk I/O than other locking -# methods. NFS users: flock doesn't work, remember to change mmap_disable. -#lock_method = fcntl - -# Directory in which LDA/LMTP temporarily stores incoming mails >128 kB. -#mail_temp_dir = /tmp - -# Valid UID range for users, defaults to 500 and above. This is mostly -# to make sure that users can't log in as daemons or other system users. -# Note that denying root logins is hardcoded to dovecot binary and can't -# be done even if first_valid_uid is set to 0. -first_valid_uid = 1 -#last_valid_uid = 0 - -# Valid GID range for users, defaults to non-root/wheel. Users having -# non-valid GID as primary group ID aren't allowed to log in. If user -# belongs to supplementary groups with non-valid GIDs, those groups are -# not set. -#first_valid_gid = 1 -#last_valid_gid = 0 - -# Maximum allowed length for mail keyword name. It's only forced when trying -# to create new keywords. -#mail_max_keyword_length = 50 - -# ':' separated list of directories under which chrooting is allowed for mail -# processes (ie. /var/mail will allow chrooting to /var/mail/foo/bar too). -# This setting doesn't affect login_chroot, mail_chroot or auth chroot -# settings. If this setting is empty, "/./" in home dirs are ignored. -# WARNING: Never add directories here which local users can modify, that -# may lead to root exploit. Usually this should be done only if you don't -# allow shell access for users. <doc/wiki/Chrooting.txt> -#valid_chroot_dirs = - -# Default chroot directory for mail processes. This can be overridden for -# specific users in user database by giving /./ in user's home directory -# (eg. /home/./user chroots into /home). Note that usually there is no real -# need to do chrooting, Dovecot doesn't allow users to access files outside -# their mail directory anyway. If your home directories are prefixed with -# the chroot directory, append "/." to mail_chroot. <doc/wiki/Chrooting.txt> -#mail_chroot = - -# UNIX socket path to master authentication server to find users. -# This is used by imap (for shared users) and lda. -#auth_socket_path = /var/run/dovecot/auth-userdb - -# Directory where to look up mail plugins. -#mail_plugin_dir = /usr/lib/dovecot/modules - -# Space separated list of plugins to load for all services. Plugins specific to -# IMAP, LDA, etc. are added to this list in their own .conf files. -mail_plugins = stats virtual zlib - -## -## Mailbox handling optimizations -## - -# Mailbox list indexes can be used to optimize IMAP STATUS commands. They are -# also required for IMAP NOTIFY extension to be enabled. -mailbox_list_index = yes - -# The minimum number of mails in a mailbox before updates are done to cache -# file. This allows optimizing Dovecot's behavior to do less disk writes at -# the cost of more disk reads. -#mail_cache_min_mail_count = 0 - -# When IDLE command is running, mailbox is checked once in a while to see if -# there are any new mails or other changes. This setting defines the minimum -# time to wait between those checks. Dovecot can also use dnotify, inotify and -# kqueue to find out immediately when changes occur. -#mailbox_idle_check_interval = 30 secs - -# Save mails with CR+LF instead of plain LF. This makes sending those mails -# take less CPU, especially with sendfile() syscall with Linux and FreeBSD. -# But it also creates a bit more disk I/O which may just make it slower. -# Also note that if other software reads the mboxes/maildirs, they may handle -# the extra CRs wrong and cause problems. -#mail_save_crlf = no - -# Max number of mails to keep open and prefetch to memory. This only works with -# some mailbox formats and/or operating systems. -#mail_prefetch_count = 0 - -# How often to scan for stale temporary files and delete them (0 = never). -# These should exist only after Dovecot dies in the middle of saving mails. -#mail_temp_scan_interval = 1w - -## -## Maildir-specific settings -## - -# By default LIST command returns all entries in maildir beginning with a dot. -# Enabling this option makes Dovecot return only entries which are directories. -# This is done by stat()ing each entry, so it causes more disk I/O. -# (For systems setting struct dirent->d_type, this check is free and it's -# done always regardless of this setting) -#maildir_stat_dirs = no - -# When copying a message, do it with hard links whenever possible. This makes -# the performance much better, and it's unlikely to have any side effects. -#maildir_copy_with_hardlinks = yes - -# Assume Dovecot is the only MUA accessing Maildir: Scan cur/ directory only -# when its mtime changes unexpectedly or when we can't find the mail otherwise. -#maildir_very_dirty_syncs = no - -# If enabled, Dovecot doesn't use the S=<size> in the Maildir filenames for -# getting the mail's physical size, except when recalculating Maildir++ quota. -# This can be useful in systems where a lot of the Maildir filenames have a -# broken size. The performance hit for enabling this is very small. -#maildir_broken_filename_sizes = no - -# Always move mails from new/ directory to cur/, even when the \Recent flags -# aren't being reset. -#maildir_empty_new = no - -## -## mbox-specific settings -## - -# Which locking methods to use for locking mbox. There are four available: -# dotlock: Create <mailbox>.lock file. This is the oldest and most NFS-safe -# solution. If you want to use /var/mail/ like directory, the users -# will need write access to that directory. -# dotlock_try: Same as dotlock, but if it fails because of permissions or -# because there isn't enough disk space, just skip it. -# fcntl : Use this if possible. Works with NFS too if lockd is used. -# flock : May not exist in all systems. Doesn't work with NFS. -# lockf : May not exist in all systems. Doesn't work with NFS. -# -# You can use multiple locking methods; if you do the order they're declared -# in is important to avoid deadlocks if other MTAs/MUAs are using multiple -# locking methods as well. Some operating systems don't allow using some of -# them simultaneously. -# -# The Debian value for mbox_write_locks differs from upstream Dovecot. It is -# changed to be compliant with Debian Policy (section 11.6) for NFS safety. -# Dovecot: mbox_write_locks = dotlock fcntl -# Debian: mbox_write_locks = fcntl dotlock -# -#mbox_read_locks = fcntl -#mbox_write_locks = fcntl dotlock - -# Maximum time to wait for lock (all of them) before aborting. -#mbox_lock_timeout = 5 mins - -# If dotlock exists but the mailbox isn't modified in any way, override the -# lock file after this much time. -#mbox_dotlock_change_timeout = 2 mins - -# When mbox changes unexpectedly we have to fully read it to find out what -# changed. If the mbox is large this can take a long time. Since the change -# is usually just a newly appended mail, it'd be faster to simply read the -# new mails. If this setting is enabled, Dovecot does this but still safely -# fallbacks to re-reading the whole mbox file whenever something in mbox isn't -# how it's expected to be. The only real downside to this setting is that if -# some other MUA changes message flags, Dovecot doesn't notice it immediately. -# Note that a full sync is done with SELECT, EXAMINE, EXPUNGE and CHECK -# commands. -#mbox_dirty_syncs = yes - -# Like mbox_dirty_syncs, but don't do full syncs even with SELECT, EXAMINE, -# EXPUNGE or CHECK commands. If this is set, mbox_dirty_syncs is ignored. -#mbox_very_dirty_syncs = no - -# Delay writing mbox headers until doing a full write sync (EXPUNGE and CHECK -# commands and when closing the mailbox). This is especially useful for POP3 -# where clients often delete all mails. The downside is that our changes -# aren't immediately visible to other MUAs. -#mbox_lazy_writes = yes - -# If mbox size is smaller than this (e.g. 100k), don't write index files. -# If an index file already exists it's still read, just not updated. -#mbox_min_index_size = 0 - -# Mail header selection algorithm to use for MD5 POP3 UIDLs when -# pop3_uidl_format=%m. For backwards compatibility we use apop3d inspired -# algorithm, but it fails if the first Received: header isn't unique in all -# mails. An alternative algorithm is "all" that selects all headers. -#mbox_md5 = apop3d - -## -## mdbox-specific settings -## - -# Maximum dbox file size until it's rotated. -#mdbox_rotate_size = 2M - -# Maximum dbox file age until it's rotated. Typically in days. Day begins -# from midnight, so 1d = today, 2d = yesterday, etc. 0 = check disabled. -#mdbox_rotate_interval = 0 - -# When creating new mdbox files, immediately preallocate their size to -# mdbox_rotate_size. This setting currently works only in Linux with some -# filesystems (ext4, xfs). -#mdbox_preallocate_space = no - -## -## Mail attachments -## - -# sdbox and mdbox support saving mail attachments to external files, which -# also allows single instance storage for them. Other backends don't support -# this for now. - -# Directory root where to store mail attachments. Disabled, if empty. -#mail_attachment_dir = - -# Attachments smaller than this aren't saved externally. It's also possible to -# write a plugin to disable saving specific attachments externally. -#mail_attachment_min_size = 128k - -# Filesystem backend to use for saving attachments: -# posix : No SiS done by Dovecot (but this might help FS's own deduplication) -# sis posix : SiS with immediate byte-by-byte comparison during saving -# sis-queue posix : SiS with delayed comparison and deduplication -#mail_attachment_fs = sis posix - -# Hash format to use in attachment filenames. You can add any text and -# variables: %{md4}, %{md5}, %{sha1}, %{sha256}, %{sha512}, %{size}. -# Variables can be truncated, e.g. %{sha256:80} returns only first 80 bits -#mail_attachment_hash = %{sha1} diff --git a/roles/IMAP/files/etc/dovecot/conf.d/10-ssl.conf b/roles/IMAP/files/etc/dovecot/conf.d/10-ssl.conf deleted file mode 100644 index b401c93..0000000 --- a/roles/IMAP/files/etc/dovecot/conf.d/10-ssl.conf +++ /dev/null @@ -1,58 +0,0 @@ -## -## SSL settings -## - -# SSL/TLS support: yes, no, required. <doc/wiki/SSL.txt> -ssl = required - -# 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 - -# Directory and/or file for trusted SSL CA certificates. These are used only -# when Dovecot needs to act as an SSL client (e.g. imapc backend). The -# directory is usually /etc/ssl/certs in Debian-based systems and the file is -# /etc/pki/tls/cert.pem in RedHat-based systems. -#ssl_client_ca_dir = -#ssl_client_ca_file = - -# Request client to send a certificate. If you also want to require it, set -# auth_ssl_require_client_cert=yes in auth section. -#ssl_verify_client_cert = no - -# Which field from certificate to use for username. commonName and -# x500UniqueIdentifier are the usual choices. You'll also need to set -# auth_ssl_username_from_cert=yes. -#ssl_cert_username_field = commonName - -# DH parameters length to use. -ssl_dh_parameters_length = 2048 - -# SSL protocols to use -ssl_protocols = !SSLv2 !SSLv3 - -# SSL ciphers to use -ssl_cipher_list = HIGH:!SSLv2:!aNULL:!eNULL:!3DES:!MD5:@STRENGTH - -# Prefer the server's order of ciphers over client's. -#ssl_prefer_server_ciphers = no - -# SSL crypto device to use, for valid values run "openssl engine" -#ssl_crypto_device = diff --git a/roles/IMAP/files/etc/dovecot/conf.d/15-mailboxes.conf b/roles/IMAP/files/etc/dovecot/conf.d/15-mailboxes.conf deleted file mode 100644 index 6aa5c22..0000000 --- a/roles/IMAP/files/etc/dovecot/conf.d/15-mailboxes.conf +++ /dev/null @@ -1,45 +0,0 @@ -## -## Mailbox definitions -## - -# NOTE: Assumes "namespace inbox" has been defined in 10-mail.conf. -namespace inbox { - - #mailbox name { - # auto=create will automatically create this mailbox. - # auto=subscribe will both create and subscribe to the mailbox. - #auto = no - - # Space separated list of IMAP SPECIAL-USE attributes as specified by - # RFC 6154: \All \Archive \Drafts \Flagged \Junk \Sent \Trash - #special_use = - #} - - # These mailboxes are widely used and could perhaps be created automatically: - mailbox Trash { - auto = create - special_use = \Trash - } - mailbox Drafts { - auto = create - special_use = \Drafts - } - mailbox Sent { - auto = subscribe - special_use = \Sent - } - mailbox Junk { - auto = create - special_use = \Junk - } - - # If you have a virtual "All messages" mailbox: - mailbox virtual/All { - special_use = \All - } - - # If you have a virtual "Flagged" mailbox: - mailbox virtual/Flagged { - special_use = \Flagged - } -} diff --git a/roles/IMAP/files/etc/dovecot/conf.d/20-imap.conf b/roles/IMAP/files/etc/dovecot/conf.d/20-imap.conf deleted file mode 100644 index b62f6ef..0000000 --- a/roles/IMAP/files/etc/dovecot/conf.d/20-imap.conf +++ /dev/null @@ -1,71 +0,0 @@ -## -## IMAP specific settings -## - -# Maximum IMAP command line length. Some clients generate very long command -# lines with huge mailboxes, so you may need to raise this if you get -# "Too long argument" or "IMAP command line too large" errors often. -#imap_max_line_length = 64k - -# IMAP logout format string: -# %i - total number of bytes read from client -# %o - total number of bytes sent to client -#imap_logout_format = in=%i out=%o - -# Override the IMAP CAPABILITY response. If the value begins with '+', -# add the given capabilities on top of the defaults (e.g. +XFOO XBAR). -#imap_capability = - -# How long to wait between "OK Still here" notifications when client is -# IDLEing. -#imap_idle_notify_interval = 2 mins - -# ID field names and values to send to clients. Using * as the value makes -# Dovecot use the default value. The following fields have default values -# currently: name, version, os, os-version, support-url, support-email. -#imap_id_send = - -# ID fields sent by client to log. * means everything. -#imap_id_log = - -# Workarounds for various client bugs: -# delay-newmail: -# Send EXISTS/RECENT new mail notifications only when replying to NOOP -# and CHECK commands. Some clients ignore them otherwise, for example OSX -# Mail (<v2.1). Outlook Express breaks more badly though, without this it -# may show user "Message no longer in server" errors. Note that OE6 still -# breaks even with this workaround if synchronization is set to -# "Headers Only". -# tb-extra-mailbox-sep: -# Thunderbird gets somehow confused with LAYOUT=fs (mbox and dbox) and -# adds extra '/' suffixes to mailbox names. This option causes Dovecot to -# ignore the extra '/' instead of treating it as invalid mailbox name. -# tb-lsub-flags: -# Show \Noselect flags for LSUB replies with LAYOUT=fs (e.g. mbox). -# This makes Thunderbird realize they aren't selectable and show them -# greyed out, instead of only later giving "not selectable" popup error. -# -# The list is space-separated. -#imap_client_workarounds = - -# Host allowed in URLAUTH URLs sent by client. "*" allows all. -#imap_urlauth_host = - -protocol imap { - # Space separated list of plugins to load (default is global mail_plugins). - mail_plugins = $mail_plugins imap_stats imap_zlib - - # Maximum number of IMAP connections allowed for a user from each IP address. - # NOTE: The username is compared case-sensitively. - mail_max_userip_connections = 16 - -# # TODO Load the 'antispam' plugin for people using the content filter. -# # (Otherwise fallback to the static userdb.) -# userdb { -# driver = ldap -# args = /etc/dovecot/dovecot-ldap-userdb.conf.ext -# -# # Default fields can be used to specify defaults that LDAP may override -# default_fields = home=/home/mail/virtual/%d/%n -# } -} diff --git a/roles/IMAP/files/etc/dovecot/conf.d/20-lmtp.conf b/roles/IMAP/files/etc/dovecot/conf.d/20-lmtp.conf deleted file mode 100644 index cd48ab8..0000000 --- a/roles/IMAP/files/etc/dovecot/conf.d/20-lmtp.conf +++ /dev/null @@ -1,20 +0,0 @@ -## -## LMTP specific settings -## - -# Support proxying to other LMTP/SMTP servers by performing passdb lookups. -#lmtp_proxy = no - -# When recipient address includes the detail (e.g. user+detail), try to save -# the mail to the detail mailbox. See also recipient_delimiter and -# lda_mailbox_autocreate settings. -#lmtp_save_to_detail_mailbox = no - -# Verify quota before replying to RCPT TO. This adds a small overhead. -#lmtp_rcpt_check_quota = no - -protocol lmtp { - postmaster_address = postmaster@fripost.org - # Space separated list of plugins to load (default is global mail_plugins). - mail_plugins = $mail_plugins sieve -} diff --git a/roles/IMAP/files/etc/dovecot/conf.d/90-plugin.conf b/roles/IMAP/files/etc/dovecot/conf.d/90-plugin.conf deleted file mode 100644 index b6fcd3b..0000000 --- a/roles/IMAP/files/etc/dovecot/conf.d/90-plugin.conf +++ /dev/null @@ -1,31 +0,0 @@ -## -## Plugin settings -## - -# All wanted plugins must be listed in mail_plugins setting before any of the -# settings take effect. See <doc/wiki/Plugins.txt> for list of plugins and -# their configuration. Note that %variable expansion is done for all values. - -plugin { - antispam_backend = spool2dir - - antispam_trash = Trash - antispam_unsure_pattern_ignorecase = MailTrain;MailTrain/* - antispam_spam = Junk - - # The first %%lu is replaced by the current time. - # The second %%lu is replaced by a counter to generate unique names. - # These two tokens MUST be present in the template! - antispam_spool2dir_spam = /home/mail/spamspool/%u-%%10lu-%%06lu.spam - antispam_spool2dir_notspam = /home/mail/spamspool/%u-%%10lu-%%06lu.ham - - - zlib_save = gz - zlib_save_level = 6 - - - # how often to session statistics - stats_refresh = 30 secs - # track per-IMAP command statistics - stats_track_cmds = yes -} diff --git a/roles/IMAP/files/etc/dovecot/conf.d/90-sieve.conf b/roles/IMAP/files/etc/dovecot/conf.d/90-sieve.conf deleted file mode 100644 index 8308adc..0000000 --- a/roles/IMAP/files/etc/dovecot/conf.d/90-sieve.conf +++ /dev/null @@ -1,105 +0,0 @@ -## -## Settings for the Sieve interpreter -## - -# Do not forget to enable the Sieve plugin in 15-lda.conf and 20-lmtp.conf -# by adding it to the respective mail_plugins= settings. - -plugin { - # The path to the user's main active script. If ManageSieve is used, this the - # location of the symbolic link controlled by ManageSieve. - sieve = ~/dovecot.sieve - - # The default Sieve script when the user has none. This is a path to a global - # sieve script file, which gets executed ONLY if user's private Sieve script - # doesn't exist. Be sure to pre-compile this script manually using the sievec - # command line tool. - # --> See sieve_before fore executing scripts before the user's personal - # script. - #sieve_default = /var/lib/dovecot/sieve/default.sieve - - # Directory for :personal include scripts for the include extension. This - # is also where the ManageSieve service stores the user's scripts. - sieve_dir = ~/sieve - - # Directory for :global include scripts for the include extension. - #sieve_global_dir = - - # Path to a script file or a directory containing script files that need to be - # executed before the user's script. If the path points to a directory, all - # the Sieve scripts contained therein (with the proper .sieve extension) are - # executed. The order of execution within a directory is determined by the - # file names, using a normal 8bit per-character comparison. Multiple script - # file or directory paths can be specified by appending an increasing number. - #sieve_before = - #sieve_before2 = - #sieve_before3 = (etc...) - - # Identical to sieve_before, only the specified scripts are executed after the - # user's script (only when keep is still in effect!). Multiple script file or - # directory paths can be specified by appending an increasing number. - #sieve_after = - #sieve_after2 = - #sieve_after2 = (etc...) - - # Which Sieve language extensions are available to users. By default, all - # supported extensions are available, except for deprecated extensions or - # those that are still under development. Some system administrators may want - # to disable certain Sieve extensions or enable those that are not available - # by default. This setting can use '+' and '-' to specify differences relative - # to the default. For example `sieve_extensions = +imapflags' will enable the - # deprecated imapflags extension in addition to all extensions were already - # enabled by default. - #sieve_extensions = +notify +imapflags - - # Which Sieve language extensions are ONLY available in global scripts. This - # can be used to restrict the use of certain Sieve extensions to administrator - # control, for instance when these extensions can cause security concerns. - # This setting has higher precedence than the `sieve_extensions' setting - # (above), meaning that the extensions enabled with this setting are never - # available to the user's personal script no matter what is specified for the - # `sieve_extensions' setting. The syntax of this setting is similar to the - # `sieve_extensions' setting, with the difference that extensions are - # enabled or disabled for exclusive use in global scripts. Currently, no - # extensions are marked as such by default. - #sieve_global_extensions = - - # The Pigeonhole Sieve interpreter can have plugins of its own. Using this - # setting, the used plugins can be specified. Check the Dovecot wiki - # (wiki2.dovecot.org) or the pigeonhole website - # (http://pigeonhole.dovecot.org) for available plugins. - # The sieve_extprograms plugin is included in this release. - #sieve_plugins = - - # The separator that is expected between the :user and :detail - # address parts introduced by the subaddress extension. This may - # also be a sequence of characters (e.g. '--'). The current - # implementation looks for the separator from the left of the - # localpart and uses the first one encountered. The :user part is - # left of the separator and the :detail part is right. This setting - # is also used by Dovecot's LMTP service. - recipient_delimiter = + - - # The maximum size of a Sieve script. The compiler will refuse to compile any - # script larger than this limit. If set to 0, no limit on the script size is - # enforced. - #sieve_max_script_size = 1M - - # The maximum number of actions that can be performed during a single script - # execution. If set to 0, no limit on the total number of actions is enforced. - #sieve_max_actions = 32 - - # The maximum number of redirect actions that can be performed during a single - # script execution. If set to 0, no redirect actions are allowed. - #sieve_max_redirects = 4 - - # The maximum number of personal Sieve scripts a single user can have. If set - # to 0, no limit on the number of scripts is enforced. - # (Currently only relevant for ManageSieve) - #sieve_quota_max_scripts = 0 - - # The maximum amount of disk storage a single user's scripts may occupy. If - # set to 0, no limit on the used amount of disk storage is enforced. - # (Currently only relevant for ManageSieve) - #sieve_quota_max_storage = 0 -} diff --git a/roles/IMAP/files/etc/dovecot/conf.d/auth-ldap.conf.ext b/roles/IMAP/files/etc/dovecot/conf.d/auth-ldap.conf.ext index 360727e..8c33c6d 100644 --- a/roles/IMAP/files/etc/dovecot/conf.d/auth-ldap.conf.ext +++ b/roles/IMAP/files/etc/dovecot/conf.d/auth-ldap.conf.ext @@ -1,39 +1,44 @@ # Authentication for LDAP users. Included from 10-auth.conf. # # <doc/wiki/AuthDatabase.LDAP.txt> passdb { driver = ldap # Path for LDAP configuration file, see example-config/dovecot-ldap.conf.ext args = /etc/dovecot/dovecot-ldap.conf.ext } # "prefetch" user database means that the passdb already provided the # needed information and there's no need to do a separate userdb lookup. # <doc/wiki/UserDatabase.Prefetch.txt> #userdb { # driver = prefetch #} #userdb { # driver = ldap -# # This should be a different file from the passdb's, in order to perform -# # asynchronous requests. -# # args = /etc/dovecot/dovecot-ldap-userdb.conf.ext # # # Default fields can be used to specify defaults that LDAP may override # default_fields = home=/home/mail/virtual/%d/%n #} # If you don't have any user-specific settings, you can avoid the userdb LDAP # lookup by using userdb static instead of userdb ldap, for example: # <doc/wiki/UserDatabase.Static.txt> userdb { driver = static # The MTA has already verified the existence of users when doing alias resolution, # so we can skip the passdb lookup here. args = home=/home/mail/virtual/%d/%n allow_all_users=yes } + +# Used only for iteration as the static userdb above always succeeds +userdb { + driver = dict + skip = found + result_internalfail = return-fail + args = /etc/dovecot/dovecot-dict-auth.conf.ext +} diff --git a/roles/IMAP/files/etc/dovecot/dovecot-dict-auth.conf.ext b/roles/IMAP/files/etc/dovecot/dovecot-dict-auth.conf.ext new file mode 100644 index 0000000..a054ffe --- /dev/null +++ b/roles/IMAP/files/etc/dovecot/dovecot-dict-auth.conf.ext @@ -0,0 +1,12 @@ +# This file is commonly accessed via passdb {} or userdb {} section in +# conf.d/auth-dict.conf.ext + +# Dictionary URI +uri = proxy:/run/dovecot/auth-proxy: + +# Username iteration prefix. Keys under this are assumed to contain usernames. +iterate_prefix = userdb/ + +# Should iteration be disabled for this userdb? If this userdb acts only as a +# cache there's no reason to try to iterate the (partial & duplicate) users. +iterate_disable = no diff --git a/roles/IMAP/files/etc/dovecot/dovecot-ldap.conf.ext b/roles/IMAP/files/etc/dovecot/dovecot-ldap.conf.ext index 72f4604..a455616 100644 --- a/roles/IMAP/files/etc/dovecot/dovecot-ldap.conf.ext +++ b/roles/IMAP/files/etc/dovecot/dovecot-ldap.conf.ext @@ -14,42 +14,41 @@ # by dn="<dovecot's dn>" read # add this # by anonymous auth # by self write # by * none # Space separated list of LDAP hosts to use. host:port is allowed too. #hosts = # LDAP URIs to use. You can use this instead of hosts list. Note that this # setting isn't supported by all LDAP libraries. uris = ldapi:// # Distinguished Name - the username used to login to the LDAP server. # Leave it commented out to bind anonymously (useful with auth_bind=yes). #dn = # Password for LDAP server, if dn is specified. #dnpass = # Use SASL binding instead of the simple binding. Note that this changes -# ldap_version automatically to be 3 if it's lower. Also note that SASL binds -# and auth_bind=yes don't work together. +# ldap_version automatically to be 3 if it's lower. #sasl_bind = no # SASL mechanism name to use. #sasl_mech = # SASL realm to use. #sasl_realm = # SASL authorization ID, ie. the dnpass is for this "master user", but the # dn is still the logged in user. Normally you want to keep this empty. #sasl_authz_id = # Use TLS to connect to the LDAP server. #tls = no # TLS options, currently supported only with OpenLDAP: #tls_ca_cert_file = #tls_ca_cert_dir = #tls_cipher_suite = # TLS cert/key is used only if LDAP server requires a client certificate. #tls_cert_file = #tls_key_file = # Valid values: never, hard, demand, allow, try #tls_require_cert = @@ -114,30 +113,39 @@ user_attrs = # %u - username # %n - user part in user@domain, same as %u if there's no domain # %d - domain part in user@domain, empty if user there's no domain user_filter = # Password checking attributes: # user: Virtual user name (user@domain), if you wish to change the # user-given username to something else # password: Password, may optionally start with {type}, eg. {crypt} # There are also other special fields which can be returned, see # http://wiki2.dovecot.org/PasswordDatabase/ExtraFields pass_attrs = # If you wish to avoid two LDAP lookups (passdb + userdb), you can use # userdb prefetch instead of userdb ldap in dovecot.conf. In that case you'll # also have to include user_attrs in pass_attrs field prefixed with "userdb_" # string. For example: #pass_attrs = uid=user,userPassword=password,\ # homeDirectory=userdb_home,uidNumber=userdb_uid,gidNumber=userdb_gid -# Filter for password lookups (ignored for auth binds) +# Filter for password lookups pass_filter = (&(objectClass=FripostVirtualUser)(fvl=%n)(fripostIsStatusActive=TRUE)) # Attributes and filter to get a list of all users #iterate_attrs = uid=user #iterate_filter = (objectClass=posixAccount) # Default password scheme. "{scheme}" before password overrides this. # List of supported schemes is in: http://wiki2.dovecot.org/Authentication #default_pass_scheme = CRYPT + +# By default all LDAP lookups are performed by the auth master process. +# If blocking=yes, auth worker processes are used to perform the lookups. +# Each auth worker process creates its own LDAP connection so this can +# increase parallelism. With blocking=no the auth master process can +# keep 8 requests pipelined for the LDAP connection, while with blocking=yes +# each connection has a maximum of 1 request running. For small systems the +# blocking=no is sufficient and uses less resources. +#blocking = no diff --git a/roles/IMAP/files/etc/dovecot/ssl/config b/roles/IMAP/files/etc/dovecot/ssl/config new file mode 100644 index 0000000..e5359de --- /dev/null +++ b/roles/IMAP/files/etc/dovecot/ssl/config @@ -0,0 +1,2 @@ +ssl_cert = </etc/dovecot/ssl/imap.fripost.org.pem +ssl_key = </etc/dovecot/ssl/imap.fripost.org.key diff --git a/roles/IMAP/files/etc/dovecot/virtual/all/dovecot-virtual b/roles/IMAP/files/etc/dovecot/virtual/all/dovecot-virtual index 31d3e06..a106be4 100644 --- a/roles/IMAP/files/etc/dovecot/virtual/all/dovecot-virtual +++ b/roles/IMAP/files/etc/dovecot/virtual/all/dovecot-virtual @@ -1,6 +1,6 @@ !INBOX -Junk -Junk/* -Trash -* ++* ALL diff --git a/roles/IMAP/files/etc/dovecot/virtual/flagged/dovecot-virtual b/roles/IMAP/files/etc/dovecot/virtual/flagged/dovecot-virtual index 18845c6..cf24b79 100644 --- a/roles/IMAP/files/etc/dovecot/virtual/flagged/dovecot-virtual +++ b/roles/IMAP/files/etc/dovecot/virtual/flagged/dovecot-virtual @@ -1,2 +1,2 @@ -* ++* FLAGGED diff --git a/roles/IMAP/files/etc/dovecot/virtual/recent/dovecot-virtual b/roles/IMAP/files/etc/dovecot/virtual/recent/dovecot-virtual index 601ce28..4df62b0 100644 --- a/roles/IMAP/files/etc/dovecot/virtual/recent/dovecot-virtual +++ b/roles/IMAP/files/etc/dovecot/virtual/recent/dovecot-virtual @@ -1,5 +1,5 @@ -Junk -Junk/* -Trash -* ++* YOUNGER 2592000 diff --git a/roles/IMAP/files/etc/dovecot/virtual/unseen/dovecot-virtual b/roles/IMAP/files/etc/dovecot/virtual/unseen/dovecot-virtual index 3dc4a3b..09b818d 100644 --- a/roles/IMAP/files/etc/dovecot/virtual/unseen/dovecot-virtual +++ b/roles/IMAP/files/etc/dovecot/virtual/unseen/dovecot-virtual @@ -1,6 +1,6 @@ -Drafts -Junk -Junk/* -Trash -* ++* UNSEEN diff --git a/roles/IMAP/files/etc/systemd/system/dovecot-auth-proxy.service b/roles/IMAP/files/etc/systemd/system/dovecot-auth-proxy.service new file mode 100644 index 0000000..3ac0b31 --- /dev/null +++ b/roles/IMAP/files/etc/systemd/system/dovecot-auth-proxy.service @@ -0,0 +1,25 @@ +[Unit] +Description=Dovecot authentication proxy +After=dovecot.target +Requires=dovecot-auth-proxy.socket + +[Service] +User=_dovecot-auth-proxy +StandardInput=null +SyslogFacility=mail +ExecStart=/usr/local/bin/dovecot-auth-proxy.pl + +# Hardening +NoNewPrivileges=yes +PrivateDevices=yes +PrivateNetwork=yes +ProtectHome=yes +ProtectSystem=strict +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +RestrictAddressFamilies=AF_UNIX + +[Install] +WantedBy=multi-user.target +Also=postfix-sender-login.socket diff --git a/roles/IMAP/files/etc/systemd/system/dovecot-auth-proxy.socket b/roles/IMAP/files/etc/systemd/system/dovecot-auth-proxy.socket new file mode 100644 index 0000000..6dee91a --- /dev/null +++ b/roles/IMAP/files/etc/systemd/system/dovecot-auth-proxy.socket @@ -0,0 +1,8 @@ +[Socket] +SocketUser=dovecot +SocketGroup=dovecot +SocketMode=0600 +ListenStream=/run/dovecot/auth-proxy + +[Install] +WantedBy=sockets.target diff --git a/roles/IMAP/files/usr/local/bin/dovecot-auth-proxy.pl b/roles/IMAP/files/usr/local/bin/dovecot-auth-proxy.pl new file mode 100755 index 0000000..39d3762 --- /dev/null +++ b/roles/IMAP/files/usr/local/bin/dovecot-auth-proxy.pl @@ -0,0 +1,171 @@ +#!/usr/bin/perl -T + +#---------------------------------------------------------------------- +# Dovecot userdb lookup proxy table for user iteration +# Copyright © 2017,2020 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/>. +#---------------------------------------------------------------------- + +use warnings; +use strict; + +use Errno qw/EINTR/; +use Net::LDAPI; +use Net::LDAP::Constant qw/LDAP_CONTROL_PAGED LDAP_SUCCESS/; +use Net::LDAP::Control::Paged (); +use Net::LDAP::Util qw/ldap_explode_dn/; +use Authen::SASL; + +my $BASE = "ou=virtual,dc=fripost,dc=org"; + +# clean up PATH +$ENV{PATH} = join ':', qw{/usr/bin /bin}; +delete @ENV{qw/IFS CDPATH ENV BASH_ENV/}; + +# number of pre-forked servers and maximum requests per worker +my $nProc = 1; +my $maxRequests = 1; +sub server(); + +# fdopen(3) the file descriptor FD +die "This service must be socket-activated.\n" + unless defined $ENV{LISTEN_PID} and $ENV{LISTEN_PID} == $$ + and defined $ENV{LISTEN_FDS} and $ENV{LISTEN_FDS} == 1; +open my $S, '+<&=', 3 or die "fdopen: $!"; + +my @CHILDREN; +for (my $i = 0; $i < $nProc-1; $i++) { + my $pid = fork() // die "fork: $!"; + if ($pid) { + push @CHILDREN, $pid; + } else { + server(); # child, never return + exit; + } +} +server(); +waitpid $_ => 0 foreach @CHILDREN; +exit $?; + + +############################################################################# + +sub server() { + for (my $n = 0; $n < $maxRequests; $n++) { + accept(my $conn, $S) or do { + next if $! == EINTR; + die "accept: $!"; + }; + + my $hello = $conn->getline() // ''; + unless ($hello =~ /\AH(\d+)\t(\d+)\t(\d+)(?:\t.*)?\n\z/) { + warn "Invalid greeting line: $hello\n"; + close $conn or warn "Can't close: $!"; + next; + } + # <major-version> <minor-version> <value type> + unless ($1 == 2 and $2 == 2 and $3 == 0) { + warn "Unsupported protocol version $1.$2 (or value type $3)\n"; + close $conn or warn "Can't close: $!"; + next; + } + + my $cmd = $conn->getline() // ''; + if ($cmd =~ /\AI(\d+)\t(\d+)\t(.*)\n\z/) { + iterate($conn, $1, $2, $3); + } + else { + fail($conn => "Unknown command line: $cmd"); + } + close $conn or warn "Can't close: $!"; + } +} + +sub fail($;$) { + my ($fh, $msg) = @_; + $fh->printflush("F\n"); + print STDERR $msg, "\n" if defined $msg; +} + +sub dn2user($) { + my $dn = shift; + $dn = ldap_explode_dn($dn, casefold => "lower"); + if (defined $dn and $#$dn == 4 + and defined (my $l = $dn->[0]->{fvl}) + and defined (my $d = $dn->[1]->{fvd})) { + return $l ."@". $d; + } +} + +# list all users (even the inactive ones) +sub iterate($$$$) { + my ($fh, $flags, $max_rows, $prefix) = @_; + unless ($flags == 0) { + fail($fh => "Unsupported iterate flags $flags"); + return; + } + + my $ldap = Net::LDAPI::->new(); + $ldap->bind( undef, sasl => Authen::SASL::->new(mechanism => "EXTERNAL") ) + or do { fail($fh => "Error: Couldn't bind"); return; }; + my $page = Net::LDAP::Control::Paged::->new(size => 100); + + my $callback = sub($$) { + my ($mesg, $entry) = @_; + return unless defined $entry; + + my $dn = $entry->dn(); + if (defined (my $user = dn2user($dn))) { + $fh->printf("O%s%s\t\n", $prefix, $user); + } else { + print STDERR "Couldn't extract username from dn: ", $dn, "\n"; + } + $mesg->pop_entry; + }; + + my @search_args = ( + base => $BASE, + , scope => "children" + , deref => "never" + , filter => "(objectClass=FripostVirtualUser)" + , sizelimit => $max_rows + , control => [$page] + , callback => $callback + , attrs => ["1.1"] + ); + + my $cookie; + while (1) { + my $mesg = $ldap->search(@search_args); + last unless $mesg->code == LDAP_SUCCESS; + + my ($resp) = $mesg->control(LDAP_CONTROL_PAGED) or last; + $cookie = $resp->cookie(); + goto SEARCH_DONE unless defined $cookie and length($cookie) > 0; + + $page->cookie($cookie); + } + + if (defined $cookie and length($cookie) > 0) { + fail($fh => "Abnormal exit from LDAP search, aborting"); + $page->cookie($cookie); + $page->size(0); + $ldap->search(@search_args); + } + + SEARCH_DONE: + $ldap->unbind(); + $fh->printflush("\n"); +} diff --git a/roles/IMAP/handlers/main.yml b/roles/IMAP/handlers/main.yml index 10a717d..d8bbdc9 100644 --- a/roles/IMAP/handlers/main.yml +++ b/roles/IMAP/handlers/main.yml @@ -1,29 +1,32 @@ --- +- name: systemctl daemon-reload + command: /bin/systemctl daemon-reload + - name: Restart Dovecot service: name=dovecot state=restarted - name: Reload Postfix service: name=postfix state=reloaded - name: Compile Spamassassin rules - sudo_user: debian-spamd + become_user: debian-spamd # it might take a while... command: /usr/bin/sa-compile --quiet chdir=/var/lib/spamassassin/ - name: Restart Amavis service: name=amavis state=restarted - name: Copy SQL tables for spamassassin copy: src=tmp/spamassassin.sql dest=/tmp/spamassassin.sql owner=root group=root mode=0600 - name: Create SQL tables for spamassassin # see https://svn.apache.org/repos/asf/spamassassin/trunk/sql/ # for the original mysql_db: name=spamassassin state=import target=/tmp/spamassassin.sql encoding=latin1 collation=latin1_unicode_ci diff --git a/roles/IMAP/tasks/imap.yml b/roles/IMAP/tasks/imap.yml index 0c55535..c2bdca9 100644 --- a/roles/IMAP/tasks/imap.yml +++ b/roles/IMAP/tasks/imap.yml @@ -1,156 +1,197 @@ - name: Install Dovecot - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - dovecot-core - dovecot-ldap - dovecot-imapd - dovecot-lmtpd - dovecot-antispam - dovecot-managesieved - dovecot-sieve - name: Create a user 'vmail' user: name=vmail system=yes createhome=no home=/home/mail shell=/usr/sbin/nologin password=! state=present -## TODO: make a LDAP query listing all users using iterate_attrs and -## iterate_filter. (Alternatively, use a dict, see -## https://www.opensource.apple.com/source/dovecot/dovecot-293/dovecot.Config/dovecot-dict-auth.conf.ext) -## Required for dbox, see -## http://wiki2.dovecot.org/MailboxFormat/dbox#Multi-dbox -#- name: Create a nightly cron job to purge expunged messages -# cron: name="Purge expunged messages" -# minute=7 hour=5 -# user=vmail cron_file=doveadm-purge -# job="/usr/bin/doveadm purge -A" +- name: Install Net::LDAP and Authen::SASL + apt: pkg={{ packages }} + vars: + packages: + - libnet-ldap-perl + - libauthen-sasl-perl + +- name: Copy dovecot auth proxy + copy: src=usr/local/bin/dovecot-auth-proxy.pl + dest=/usr/local/bin/dovecot-auth-proxy.pl + owner=root group=staff + mode=0755 + +# Required for IDLE as all imap processes have the same UID (vmail). +- name: Set per user maximum number of inotify instances to 512 + sysctl: name=fs.inotify.max_user_instances value=512 sysctl_set=yes + tags: + - sysctl + +- name: Create '_dovecot-auth-proxy' user + user: name=_dovecot-auth-proxy system=yes + group=nogroup + createhome=no + home=/nonexistent + shell=/usr/sbin/nologin + password=! + state=present + +- name: Copy dovecot auth proxy systemd unit files + copy: src=etc/systemd/system/{{ item }} + dest=/etc/systemd/system/{{ item }} + owner=root group=root + mode=0644 + with_items: + - dovecot-auth-proxy.service + - dovecot-auth-proxy.socket + notify: + - systemctl daemon-reload + +- meta: flush_handlers + +- name: Enable dovecot auth proxy + service: name=dovecot-auth-proxy.socket state=started enabled=yes # The ownership and permissions ensure that dovecot won't try to # deliver mails under an umounted mountpoint. - name: Create a home directory for user 'vmail' file: path=/home/mail state=directory owner=root group=root mode=0755 -- name: Create /home/mail/virtual - file: path=/home/mail/virtual +- name: Mount /home/mail + mount: src=/dev/mapper/luksMail + path=/home/mail + fstype=ext4 + opts=noauto + state=mounted + +- name: Create /home/mail/{virtual,attachments,spamspool} + file: path=/home/mail/{{ item }} state=directory owner=vmail group=vmail mode=0700 + with_items: + - virtual + - attachments + - spamspool + +- name: Create a cronjob for purging and SIS deduplication + copy: src=etc/cron.d/doveadm + dest=/etc/cron.d/doveadm + owner=root group=root + mode=0644 - name: Create virtual mailbox directories file: path=/etc/dovecot/virtual/{{ item }} state=directory owner=root group=root mode=0755 with_items: - all - flagged - recent - unseen - 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 - 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 - --ou=IMAP --cn=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: Fetch Dovecot's X.509 certificate # Ensure we don't fetch private data - sudo: False - fetch: src=/etc/dovecot/ssl/imap.fripost.org.pem - dest=certs/dovecot/ - fail_on_missing=yes - flat=yes + become: False + fetch_cmd: cmd="openssl x509 -noout -pubkey" + stdin=/etc/dovecot/ssl/imap.fripost.org.pem + dest=certs/public/imap.fripost.org.pub tags: - genkey - name: Configure Dovecot copy: src=etc/dovecot/{{ item }} dest=/etc/dovecot/{{ item }} owner=root group=root mode=0644 - register: r2 + register: r1 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 - dovecot-ldap-userdb.conf.ext notify: - Restart Dovecot +- name: Configure Dovecot (2) + template: src=etc/dovecot/{{ item }}.j2 + dest=/etc/dovecot/{{ item }} + owner=root group=root + mode=0644 + register: r2 + with_items: + - conf.d/99-local.conf + notify: + - Restart Dovecot + +# TODO bookworm remove the below and inline the !include_try +- name: Copy /etc/dovecot/ssl/config workaround + copy: src=etc/dovecot/ssl/config + dest=/etc/dovecot/ssl/config + owner=root group=root + mode=0600 + notify: + - Restart Dovecot + - name: Tell Dovecot we have a remote IMAP proxy - # XXX: we should have an automatic lookup here lineinfile: dest=/etc/dovecot/dovecot.conf regexp='^(\s*#)?\s*login_trusted_networks\s*=' - line='login_trusted_networks = 171.25.193.76/32' + line="login_trusted_networks = {{ ipsec_subnet }}" state=present create=yes owner=root group=root mode=0644 register: r3 - when: "'IMAP' in group_names and 'webmail' not in group_names" + when: "groups.all | length > 1" notify: - Restart Dovecot - name: Start Dovecot service: name=dovecot state=started when: not (r1.changed or r2.changed or r3.changed) - meta: flush_handlers - name: Install 'dovecot_stats_' Munin wildcard plugin file: src=/usr/local/share/munin/plugins/dovecot_stats_ dest=/etc/munin/plugins/dovecot_stats_fripost.org owner=root group=root state=link force=yes tags: - munin - munin-node notify: - Restart munin-node diff --git a/roles/IMAP/tasks/main.yml b/roles/IMAP/tasks/main.yml index 9ed2ea6..694fa69 100644 --- a/roles/IMAP/tasks/main.yml +++ b/roles/IMAP/tasks/main.yml @@ -1,4 +1,16 @@ --- -- include: imap.yml tags=imap,dovecot -- include: mda.yml tags=mda,mail,postfix -#- include: spam.yml tags=spam,spamassassin # TODO spam filter +- import_tasks: imap.yml + tags: + - imap + - dovecot +- import_tasks: mda.yml + tags: + - mda + - mail + - postfix +## TODO spam filter +#- import_tasks: spam.yml +# tags: +# - spam +# - amavis +# - spamassassin diff --git a/roles/IMAP/tasks/mda.yml b/roles/IMAP/tasks/mda.yml index ac4b733..0e8690d 100644 --- a/roles/IMAP/tasks/mda.yml +++ b/roles/IMAP/tasks/mda.yml @@ -1,62 +1,51 @@ - name: Install Postfix - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - postfix - - postfix-ldap + - postfix-lmdb - name: Configure Postfix - template: src=etc/postfix/main.cf.j2 - dest=/etc/postfix-{{ postfix_instance[inst].name }}/main.cf + template: src=etc/postfix/{{ item }}.j2 + dest=/etc/postfix-{{ postfix_instance[inst].name }}/{{ item }} owner=root group=root mode=0644 + with_items: + - main.cf + - master.cf notify: - Reload Postfix - name: Copy the transport and recipient canonical maps copy: src=etc/postfix/{{ item }} dest=/etc/postfix-{{ postfix_instance[inst].name }}/{{ item }} owner=root group=root mode=0644 with_items: # no need to reload upon change, as cleanup(8) is short-running - recipient_canonical.pcre - transport -- 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 - tags: - - tls_policy - -- 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 - tags: - - tls_policy - - name: Compile the Postfix transport maps # trivial-rewrite(8) is a long-running process, so it's safer to reload - postmap: cmd=postmap src=/etc/postfix-{{ postfix_instance[inst].name }}/transport db=cdb + postmap: cmd=postmap src=/etc/postfix-{{ postfix_instance[inst].name }}/transport db=lmdb owner=root group=root mode=0644 notify: - Reload Postfix - meta: flush_handlers - name: Start Postfix service: name=postfix state=started - name: Install 'postfix_mailqueue_' Munin wildcard plugin file: src=/usr/local/share/munin/plugins/postfix_mailqueue_ dest=/etc/munin/plugins/postfix_mailqueue_postfix-{{ postfix_instance[inst].name }} owner=root group=root state=link force=yes tags: - munin - munin-node notify: diff --git a/roles/IMAP/tasks/spam.yml b/roles/IMAP/tasks/spam.yml index 06624dd..d70ccc9 100644 --- a/roles/IMAP/tasks/spam.yml +++ b/roles/IMAP/tasks/spam.yml @@ -1,63 +1,83 @@ - name: Install spamassassin - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: # The following two lines are for the policy lookup (made by amavis) - libnet-ldap-perl - libauthen-sasl-perl - razor - spamassassin - spamc - libdbi-perl - re2c - libc6-dev - gcc - make notify: - Compile Spamassassin rules - Restart Amavis - name: Create a 'spamassassin' database mysql_db: name=spamassassin state=present encoding=latin1 collation=latin1_general_ci notify: - Copy SQL tables for spamassassin - Create SQL tables for spamassassin - meta: flush_handlers + - name: Copy SpamAssassin's configuration copy: src=etc/{{ item }} dest=/etc/{{ item }} owner=root group=root mode=0644 with_items: - - spamassassin/local.cf - spamassassin/v310.pre - spamassassin/v320.pre + register: r1 + notify: + - Restart Amavis + +- name: Copy SpamAssassin's configuration (2) + template: src=etc/{{ item }}.j2 + dest=/etc/{{ item }} + owner=root group=root + mode=0644 + with_items: + - spamassassin/local.cf + register: r2 notify: - Restart Amavis - name: Provision /etc/default/spamassassin lineinfile: dest=/etc/default/spamassassin - regexp='^(\s*#)?\s*{{ item.var }}=' - "line={{ item.var }}={{ item.value }}" + regexp='^(\\s*#)?\\s*{{ item.var }}\\s*=' + line='{{ item.var }}={{ item.value }}' owner=root group=root mode=0644 with_items: - { var: ENABLED, value: 0 } - { var: CRON, value: 1 } - name: Create a 'amavis' SQL user # This *must* be the user we run spamd as # See https://svn.apache.org/repos/asf/spamassassin/trunk/sql/README.bayes - mysql_user2: > - name=amavis password= auth_plugin=auth_socket + mysql_user: > + name=amavis password= plugin=auth_socket priv="spamassassin.awl: SELECT,INSERT,UPDATE,DELETE /spamassassin.bayes_seen: SELECT,INSERT, DELETE /spamassassin.bayes_token: SELECT,INSERT,UPDATE,DELETE /spamassassin.bayes_global_vars: SELECT /spamassassin.bayes_vars: SELECT,INSERT,UPDATE,DELETE /spamassassin.bayes_expire: SELECT,INSERT, DELETE" state=present + register: r3 notify: - Restart Amavis + +- name: Start Amavis + service: name=amavis state=started + when: not (r1.changed or r2.changed or r3.changed) + +- meta: flush_handlers diff --git a/roles/IMAP/files/etc/dovecot/conf.d/10-master.conf b/roles/IMAP/templates/etc/dovecot/conf.d/10-master.conf.j2 index 9fcc549..d61c11b 100644 --- a/roles/IMAP/files/etc/dovecot/conf.d/10-master.conf +++ b/roles/IMAP/templates/etc/dovecot/conf.d/10-master.conf.j2 @@ -1,138 +1,166 @@ #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 +default_vsz_limit = 1024M # 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 { +{% if groups.all | length > 1 %} + address = {{ ipsec[inventory_hostname_short] }} + port = 143 +{% else %} port = 0 +{% endif %} } 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 # Max. number of IMAP processes (logins) process_limit = 256 # Number of processes to always keep waiting for more connections. process_min_avail = 4 # 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 } inet_listener pop3s { #port = 995 #ssl = yes } } +service stats { + unix_listener stats-writer { + user = vmail + mode = 0600 + } +} + +service submission-login { + inet_listener submission { + port = 0 + } +} + service lmtp { user = vmail - unix_listener /var/spool/postfix-mda/private/dovecot-lmtpd { - group = postfix + unix_listener /var/spool/postfix-{{ postfix_instance.IMAP.name }}/private/dovecot-lmtpd { user = postfix mode = 0600 } # Create inet listener only if you can't use the above UNIX socket #inet_listener lmtp { # Avoid making LMTP visible for the entire internet #address = #port = #} # Number of processes to always keep waiting for more connections. process_min_avail = 4 } service imap { # Most of the memory goes to mmap()ing files. You may need to increase this # limit if you have huge mailboxes. #vsz_limit = $default_vsz_limit # Max. number of IMAP processes (connections) #process_limit = 1024 + + unix_listener imap-master { + user = $default_internal_user + mode = 0600 + } +} + +service imap-hibernate { + unix_listener imap-hibernate { + # Match user running imap processes, cf. + # https://dovecot.org/pipermail/dovecot/2015-August/101783.html + user = vmail + mode = 0600 + } } service pop3 { # Max. number of POP3 processes (connections) #process_limit = 1024 } +service submission { + # Max. number of SMTP Submission processes (connections) + #process_limit = 1024 +} + service auth { # auth_socket_path points to this userdb socket by default. It's typically # used by dovecot-lda, doveadm, possibly imap process, etc. Users that have # full permissions to this socket are able to get a list of all usernames and # get the results of everyone's userdb lookups. # # The default 0666 mode allows anyone to connect to the socket, but the # userdb lookups will succeed only if the userdb returns an "uid" field that # matches the caller process's UID. Also if caller's uid or gid matches the # socket's uid or gid the lookup succeeds. Anything else causes a failure. # # To give the caller full permissions to lookup all users, set the mode to # something else than 0666 and Dovecot lets the kernel enforce the # permissions (e.g. 0777 allows everyone full permissions). unix_listener auth-userdb { - mode = 0600 user = vmail - group = root + mode = 0600 } # Postfix smtp-auth - unix_listener /var/spool/postfix-msa/private/dovecot-auth { - group = postfix + unix_listener /var/spool/postfix-{{ postfix_instance.MSA.name }}/private/dovecot-auth { user = postfix + group = postfix mode = 0600 } # Auth process is run as this user. #user = $default_internal_user } service auth-worker { # Auth worker process is run as root by default, so that it can access # /etc/shadow. If this isn't necessary, the user should be changed to # $default_internal_user. user = $default_internal_user } service dict { # If dict proxy is used, mail processes should have access to its socket. # For example: mode=0660, group=vmail and global mail_access_groups=vmail unix_listener dict { #mode = 0600 #user = #group = } } - -service stats { - fifo_listener stats-mail { - user = vmail - mode = 0600 - } -} diff --git a/roles/IMAP/templates/etc/dovecot/conf.d/99-local.conf.j2 b/roles/IMAP/templates/etc/dovecot/conf.d/99-local.conf.j2 new file mode 100644 index 0000000..3560193 --- /dev/null +++ b/roles/IMAP/templates/etc/dovecot/conf.d/99-local.conf.j2 @@ -0,0 +1,204 @@ +auth_default_realm = fripost.org +auth_username_format = %Lu +auth_mechanisms = plain login + +mail_uid = vmail +mail_gid = vmail +mail_privileged_group = + +first_valid_uid = 1 +last_valid_uid = 0 + +default_vsz_limit = 1024M + +service imap-login { + inet_listener imap { +{% if groups.all | length > 1 %} + address = {{ ipsec[inventory_hostname_short] }} + port = 143 +{% else %} + port = 0 +{% endif %} + } + process_limit = 256 + process_min_avail = 4 +} + +service stats { + unix_listener stats-writer { + user = vmail + mode = 0600 + } +} + +service submission-login { + inet_listener submission { + port = 0 + } +} + +service lmtp { + user = vmail + unix_listener lmtp { + mode = 0 + } + unix_listener /var/spool/postfix-mda/private/dovecot-lmtpd { + user = postfix + mode = 0600 + } + process_min_avail = 4 +} + +service imap { + unix_listener imap-master { + user = $default_internal_user + mode = 0600 + } +} +service imap-hibernate { + unix_listener imap-hibernate { + # Match user running imap processes, cf. + # https://dovecot.org/pipermail/dovecot/2015-August/101783.html + user = vmail + mode = 0600 + } +} + +service auth { + unix_listener auth-userdb { + user = vmail + mode = 0600 + } + + # Postfix smtp-auth + unix_listener /var/spool/postfix-msa/private/dovecot-auth { + user = postfix + group = postfix + mode = 0600 + } +} + +service auth-worker { + user = $default_internal_user +} + + +mail_server_comment = "fripost - demokratisk e-post" +mail_server_admin = mailto:postmaster@fripost.org + +mail_plugins = quota virtual zlib + +mail_location = mdbox:~/mail +mdbox_preallocate_space = yes + +mail_attachment_dir = /home/mail/attachments +mail_attachment_fs = sis-queue /home/mail/attachments/queue:posix +mail_attachment_hash = %{sha256} + +sendmail_path = /usr/sbin/postmulti -i msa -x /usr/sbin/sendmail + +ssl = required +# XXX `doveadm exec imap` fails with "ssl_key: Can't open file +# /etc/dovecot/ssl/imap.fripost.org.key" +# https://dovecot.org/pipermail/dovecot/2020-August/119642.html +# TODO bookworm inline the include_try +!include_try ../ssl/config +ssl_dh = </etc/ssl/dhparams.pem +ssl_min_protocol = TLSv1.2 +ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 + +namespace inbox { + inbox = yes + separator = / + + mailbox Drafts { + auto = create + special_use = \Drafts + } + mailbox Junk { + auto = create + special_use = \Junk + } + mailbox "Sent Messages" { + auto = no + special_use = \Sent + } + mailbox Sent { + auto = subscribe + special_use = \Sent + } + mailbox Trash { + auto = create + special_use = \Trash + } + mailbox virtual/All { + comment = All messages + special_use = \All + } + mailbox virtual/Flagged { + comment = All flagged messages + special_use = \Flagged + } +} + +namespace virtual { + prefix = virtual/ + separator = / + location = virtual:/etc/dovecot/virtual:INDEX=MEMORY + list = no + hidden = no + subscriptions = no +} + +imap_hibernate_timeout = 15s +protocol imap { + mail_plugins = $mail_plugins imap_zlib + mail_max_userip_connections = 16 + + ## TODO Load the 'antispam' plugin for people using the content filter. + ## (Otherwise fallback to the static userdb.) + #userdb { + # driver = ldap + # args = /etc/dovecot/dovecot-ldap-userdb.conf.ext + # + # # Default fields can be used to specify defaults that LDAP may override + # default_fields = home=/home/mail/virtual/%d/%n + #} +} + +protocol lmtp { + postmaster_address = postmaster@fripost.org + # Space separated list of plugins to load (default is global mail_plugins). + mail_plugins = $mail_plugins sieve +} + +plugin { + antispam_backend = spool2dir + + antispam_trash = Trash + antispam_unsure_pattern_ignorecase = MailTrain;MailTrain/* + antispam_spam = Junk + + # The first %%lu is replaced by the current time. + # The second %%lu is replaced by a counter to generate unique names. + # These two tokens MUST be present in the template! + antispam_spool2dir_spam = /home/mail/spamspool/%u-%%10lu-%%06lu.spam + antispam_spool2dir_notspam = /home/mail/spamspool/%u-%%10lu-%%06lu.ham +} + +plugin { + quota_rule = *:storage=0 + quota = count:User quota + quota_vsizes = yes +} + +plugin { + sieve = file:~/sieve;active=~/dovecot.sieve + sieve_extensions = +editheader + recipient_delimiter = + +} + +plugin { + zlib_save = gz + zlib_save_level = 6 +} diff --git a/roles/IMAP/templates/etc/postfix/main.cf.j2 b/roles/IMAP/templates/etc/postfix/main.cf.j2 index 1d71131..64a2a40 100644 --- a/roles/IMAP/templates/etc/postfix/main.cf.j2 +++ b/roles/IMAP/templates/etc/postfix/main.cf.j2 @@ -1,105 +1,93 @@ ######################################################################## -# MDA configuration +# Mail Delivery Agent (MDA) 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 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 +mynetworks = 127.0.0.0/8, [::1]/128 +{%- if groups.all | length > 1 -%} +{%- for mx in groups.MX | sort -%} + , {{ ipsec[ hostvars[mx].inventory_hostname_short ] | ansible.utils.ipaddr }} +{%- endfor %} +{% endif %} 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 = all - # No local delivery mydestination = local_transport = error:5.1.1 Mailbox unavailable alias_maps = alias_database = local_recipient_maps = -message_size_limit = 67108864 +message_size_limit = 0 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 and address validation is # performed on the MX:es only) virtual_transport = lmtp:unix:private/dovecot-lmtpd lmtp_bind_address = 127.0.0.1 virtual_mailbox_domains = static:all virtual_mailbox_maps = static:all -#transport_maps = cdb:$config_directory/transport +#transport_maps = lmdb:$config_directory/transport # Restore the original envelope recipient relay_domains = recipient_canonical_classes = envelope_recipient recipient_canonical_maps = pcre:$config_directory/recipient_canonical.pcre # Don't rewrite remote headers local_header_rewrite_clients = - -relay_clientcerts = cdb:$config_directory/relay_clientcerts -smtpd_tls_security_level = may -smtpd_tls_exclude_ciphers = EXPORT, LOW, MEDIUM, aNULL, eNULL, DES, RC4, MD5 -smtpd_tls_cert_file = /etc/postfix/ssl/{{ ansible_fqdn }}.pem -smtpd_tls_key_file = /etc/postfix/ssl/{{ ansible_fqdn }}.key -smtpd_tls_dh1024_param_file = /etc/ssl/private/dhparams.pem -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 - +smtp_tls_security_level = none +smtpd_tls_security_level = none 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 smtpd_relay_restrictions = reject_non_fqdn_recipient permit_mynetworks - permit_tls_clientcerts reject smtpd_data_restrictions = reject_unauth_pipelining # vim: set filetype=pfmain : diff --git a/roles/IMAP/templates/etc/postfix/master.cf.j2 b/roles/IMAP/templates/etc/postfix/master.cf.j2 new file mode 120000 index 0000000..011f8e0 --- /dev/null +++ b/roles/IMAP/templates/etc/postfix/master.cf.j2 @@ -0,0 +1 @@ +../../../../common/templates/etc/postfix/master.cf.j2
\ No newline at end of file diff --git a/roles/IMAP/templates/etc/postfix/relay_clientcerts.j2 b/roles/IMAP/templates/etc/postfix/relay_clientcerts.j2 deleted file mode 100644 index 42a83b5..0000000 --- a/roles/IMAP/templates/etc/postfix/relay_clientcerts.j2 +++ /dev/null @@ -1,6 +0,0 @@ -# {{ ansible_managed }} -# /!\ WARNING: smtp_tls_fingerprint_digest MUST be sha256! - -{% for h in groups.MX | difference([inventory_hostname]) | sort %} -{{ lookup('pipe', 'openssl x509 -in certs/postfix/'+h+'.pem -noout -fingerprint -sha256 | cut -d= -f2') }} {{ h }} -{% endfor %} diff --git a/roles/IMAP/files/etc/spamassassin/local.cf b/roles/IMAP/templates/etc/spamassassin/local.cf.j2 index 8ae4a4b..edef554 100644 --- a/roles/IMAP/files/etc/spamassassin/local.cf +++ b/roles/IMAP/templates/etc/spamassassin/local.cf.j2 @@ -4,46 +4,48 @@ # tweaked. # # Only a small subset of options are listed below # ########################################################################### # Add *****SPAM***** to the Subject header of spam e-mails # rewrite_header Subject [*****SPAM*****] # Save spam messages as a message/rfc822 MIME attachment instead of # modifying the original message (0: off, 2: use text/plain instead) # report_safe 0 # Set which networks or hosts are considered 'trusted' by your mail # server (i.e. not spammers) # -# TODO: Unclear how to do with IPSec and dynamic IPs. clear_trusted_networks -trusted_networks 192.168.122.2 192.168.122.3 +trusted_networks 127.0.0.1/8 {{ ipsec_subnet }} {{ groups.MX | join(' ') }} +# MXes and internal relays should be listed in bouth trusted_networks +# and clear_internal_networks, cf. +# https://spamassassin.apache.org/full/3.4.x/doc/Mail_SpamAssassin_Conf.html clear_internal_networks -internal_networks 192.168.122.2 192.168.122.3 +internal_networks {{ groups.MX | join(' ') }} # Set file-locking method (flock is not safe over NFS, but is faster) # lock_method flock # Set the threshold at which a message is considered spam (default: 5.0) # required_score 5.0 # Use Bayesian classifier (default: 1) # use_bayes 1 # Bayesian classifier auto-learning (default: 1) # bayes_auto_learn 1 diff --git a/roles/LDAP-provider/tasks/main.yml b/roles/LDAP-provider/tasks/main.yml index 3f7f29f..9bc227e 100644 --- a/roles/LDAP-provider/tasks/main.yml +++ b/roles/LDAP-provider/tasks/main.yml @@ -1,15 +1,21 @@ - name: Load and configure the syncprov overlay openldap: module=syncprov suffix=dc=fripost,dc=org target=etc/ldap/syncprov.ldif local=file +## XXX should be /etc/sasl2/slapd.conf ideally, but it doesn't work with +## Stretch, cf #211156 and #798462: +## ldapsearch -LLLx -H ldapi:// -b "" -s base supportedSASLMechanisms - name: Enable the EXTERNAL SASL mechanism lineinfile: dest=/usr/lib/sasl2/slapd.conf - regexp='^mech_list'':' - line=mech_list':'' EXTERNAL' + regexp='^mech_list{{':'}}' + line='mech_list{{':'}} EXTERNAL' create=yes owner=root group=root mode=0644 +#- name: Load dyngroup schema +# openldap: target=/etc/ldap/schema/dyngroup.ldif + # TODO: authz constraint diff --git a/roles/MSA/files/etc/postfix/anonymize_sender.pcre b/roles/MSA/files/etc/postfix/anonymize_sender.pcre index bd3d5f1..b91b981 100644 --- a/roles/MSA/files/etc/postfix/anonymize_sender.pcre +++ b/roles/MSA/files/etc/postfix/anonymize_sender.pcre @@ -1,7 +1,8 @@ -/^Received:\s+from\s+(?:\S+\s+\(\S+\s+\[[[:xdigit:].:]{3,39}\]\)) - (\s+\(using\s+(?:TLS|SSL)(?:v\S+)?\s+with\s+cipher\s+\S+\s+\(\S+\s+bits\)\)\s+).* +/^Received:\s+from\s+(?:\S+\s+\(\S+\s+\[(?:IPv6:)?[[:xdigit:].:]{3,39}\]\)) + (\s+\(using\s+(?:TLS|SSL)(?:v\S+)?\s+with\s+cipher\s+\S+\s+\(\S+\s+bits\) + (?:\s+key-exchange\s+\S+\s+(?:\([^)]+\)\s+)?server-signature\s+\S+\s+\(\d+\s+bits\)(?:\s+server-[[:alnum:]]+\s+\S+)*)?\)\s+).* (\bby\s+(?:\S+\.)?fripost\.org\s+\([^)]+\) \s+with\s+E?SMTPS?A\s+id\s+[[:xdigit:]]+;?\s.*)/x REPLACE Received: from [127.0.0.1] (localhost [127.0.0.1])${1}${2} /^X-Originating-IP:/ IGNORE diff --git a/roles/MSA/files/etc/postfix/check_sender_access b/roles/MSA/files/etc/postfix/check_sender_access new file mode 100644 index 0000000..07d2874 --- /dev/null +++ b/roles/MSA/files/etc/postfix/check_sender_access @@ -0,0 +1 @@ +<> REJECT Null sender not allowed diff --git a/roles/MSA/files/etc/systemd/system/postfix-sender-login.service b/roles/MSA/files/etc/systemd/system/postfix-sender-login.service new file mode 100644 index 0000000..d652f75 --- /dev/null +++ b/roles/MSA/files/etc/systemd/system/postfix-sender-login.service @@ -0,0 +1,25 @@ +[Unit] +Description=Postfix sender login socketmap +After=mail-transport-agent.target +Requires=postfix-sender-login.socket + +[Service] +User=_postfix-sender-login +StandardInput=null +SyslogFacility=mail +ExecStart=/usr/local/bin/postfix-sender-login.pl + +# Hardening +NoNewPrivileges=yes +PrivateDevices=yes +PrivateNetwork=yes +ProtectHome=yes +ProtectSystem=strict +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +RestrictAddressFamilies=AF_UNIX + +[Install] +WantedBy=multi-user.target +Also=postfix-sender-login.socket diff --git a/roles/MSA/files/etc/systemd/system/postfix-sender-login.socket b/roles/MSA/files/etc/systemd/system/postfix-sender-login.socket new file mode 100644 index 0000000..e8d99b5 --- /dev/null +++ b/roles/MSA/files/etc/systemd/system/postfix-sender-login.socket @@ -0,0 +1,8 @@ +[Socket] +SocketUser=postfix +SocketGroup=postfix +SocketMode=0600 +ListenStream=/var/spool/postfix-msa/private/sender-login + +[Install] +WantedBy=sockets.target diff --git a/roles/MSA/files/usr/local/bin/postfix-sender-login.pl b/roles/MSA/files/usr/local/bin/postfix-sender-login.pl new file mode 100755 index 0000000..a37f872 --- /dev/null +++ b/roles/MSA/files/usr/local/bin/postfix-sender-login.pl @@ -0,0 +1,196 @@ +#!/usr/bin/perl -T + +#---------------------------------------------------------------------- +# socketmap lookup table returning the SASL login name(s) owning a given +# sender address +# Copyright © 2017,2020 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/>. +#---------------------------------------------------------------------- + +use warnings; +use strict; + +use Errno 'EINTR'; + +use Net::LDAPI (); +use Net::LDAP::Util qw/ldap_explode_dn escape_dn_value escape_filter_value/; +use Net::LDAP::Constant qw/LDAP_NO_SUCH_OBJECT/; +use Authen::SASL (); + +# clean up PATH +$ENV{PATH} = join ':', qw{/usr/bin /bin}; +delete @ENV{qw/IFS CDPATH ENV BASH_ENV/}; + +my $nProc = 2; # number of pre-forked servers +my $maxRequests = 32; # maximum number of requests per worker +my $POSTMASTER = 'postmaster@fripost.org'; # returned for forbidden envelope sender addresses + +my $BASEDN = "ou=virtual,dc=fripost,dc=org"; +my $BUFSIZE = 65536; # try to read that many bytes at the time +my $LDAPI = "ldapi://"; +sub server(); + + +# fdopen(3) the file descriptor FD +die "This service must be socket-activated.\n" + unless defined $ENV{LISTEN_PID} and $ENV{LISTEN_PID} == $$ + and defined $ENV{LISTEN_FDS} and $ENV{LISTEN_FDS} == 1; +open my $S, '+<&=', 3 or die "fdopen: $!"; + +my @CHILDREN; +for (my $i = 0; $i < $nProc-1; $i++) { + my $pid = fork() // die "fork: $!"; + if ($pid) { + push @CHILDREN, $pid; + } else { + server(); # child, never return + exit; + } +} +server(); +waitpid $_ => 0 foreach @CHILDREN; +exit $?; + + +############################################################################# + +sub server() { + for (my $n = 0; $n < $maxRequests; $n++) { + accept(my $conn, $S) or do { + next if $! == EINTR; + die "accept: $!"; + }; + my $reply = process_request($conn); + + # encode the reply as a netstring and send it back + # https://cr.yp.to/proto/netstrings.txt + $reply = length($reply).':'.$reply.','; + my $len = length($reply); + + for (my $i = 0; $i < $len;) { + my $n = syswrite($conn, $reply, $len-$i, $i) // do { + next if $! == EINTR; + warn "Can't write: $!"; + last; + }; + $i += $n; + } + close $conn or warn "Can't close: $!"; + } +} + +sub process_request($) { + my $conn = shift; + my ($buf, $offset) = (undef, 0); + + # keep reading until the request length is determined + do { + my $n = sysread($conn, $buf, $BUFSIZE, $offset) // do { + next if $! == EINTR; + return "TEMP can't read: $!"; + }; + return "TEMP EOF" if $n == 0; + $offset += $n; + } until ($buf =~ /\A(0|[1-9][0-9]*):/); + + # keep reading until the whole request is buffered + my $strlen = length("$1") + 1; # [len]":" + my $len = $strlen + $1 + 1; # [len]":"[string]"," + while ($offset < $len) { + my $n = sysread($conn, $buf, $BUFSIZE, $offset) // do { + next if $! == EINTR; + return "TEMP can't read: $!"; + }; + return "TEMP EOF" if $n == 0; + $offset += $n; + } + + # requests are of the form $name <space> $key, cf. socketmap_table(5) + my $i = index($buf, ' ', $strlen); + return "TEMP invalid input: $buf" unless $i > $strlen and substr($buf,-1) eq ','; + my $name = substr($buf, $strlen, $i-$strlen); + my $key = substr($buf, $i, -1); + return "TEMP invalid name: $name" unless $name eq 'sender_login'; + + $key =~ /\A(.+)@([^\@]+)\z/ or return "NOTFOUND "; # invalid sender address + my ($localpart, $domainpart) = ($1, $2); + $localpart =~ s/\+.*//; # strip extension, cf. postconf(5)'s $recipient_delimiter + + my $ldap = Net::LDAPI::->new( $LDAPI ) // + return "TEMP couldn't create Net::LDAPI object"; + $ldap->bind( undef, sasl => Authen::SASL::->new(mechanism => 'EXTERNAL') ) or + return "TEMP LDAP: couldn't bind"; + + my $reply = lookup_sender($ldap, $localpart, $domainpart); + $ldap->unbind(); + return $reply; +} + +sub lookup_sender($$$) { + my ($ldap, $l, $d) = @_; + + my $filter = '(&(objectClass=FripostVirtualDomain)(fvd='.escape_filter_value($d).'))'; + my $mesg = $ldap->search( base => $BASEDN, scope => 'one', deref => 'never' + , filter => $filter + , attrs => [qw/objectClass fripostOwner fripostPostmaster/] + ); + return "TEMP LDAP error: ".$mesg->error() if $mesg->code; + my $entry = $mesg->pop_entry() // return "NOTFOUND "; # not a domain we know + return "TEMP LDAP error: multiple entry founds" if defined $mesg->pop_entry(); # sanity check + + # domain postmasters are allowed to use any sender address + my @logins = $entry->get_value('fripostPostmaster', asref => 0); + my @owners = $entry->get_value('fripostOwner', asref => 0); + + if (grep { $_ eq 'FripostVirtualAliasDomain' } $entry->get_value('objectClass', asref => 0)) { + # so are alias domain owners + push @logins, @owners; + } else { + my $dn = 'fvd='.escape_dn_value($d).','.$BASEDN; + my $filter = '(&(|(objectClass=FripostVirtualAlias)(objectClass=FripostVirtualList)(objectClass=FripostVirtualUser))(fvl='.escape_filter_value($l).'))'; + my $mesg = $ldap->search( base => $dn, scope => 'one', deref => 'never' + , filter => $filter + , attrs => [qw/objectClass fripostOwner/] + ); + unless ($mesg->code == 0 and defined ($entry = $mesg->pop_entry())) { + # domains owners are allowed to use any unkwown localpart as sender address + push @logins, @owners; + } else { + return "TEMP LDAP error: multiple entry founds" if defined $mesg->pop_entry(); # sanity check + if (grep { $_ eq 'FripostVirtualUser' } $entry->get_value('objectClass', asref => 0)) { + push @logins, $entry->dn(); + } else { + # alias/list owners can use the address as sender, and so are the domains owners + push @logins, @owners, $entry->get_value('fripostOwner', asref => 0); + } + } + } + + # convert DNs to SASL login names + my %logins; + foreach my $dn (@logins) { + next unless defined $dn; + $dn = ldap_explode_dn($dn, casefold => 'lower'); + next unless defined $dn and $#$dn == 4; + my $l = $dn->[0]->{fvl} // next; + my $d = $dn->[1]->{fvd} // next; + $logins{$l.'@'.$d} = 1; + } + + # if the entry is found in LDAP but doesn't have an owner, only + # $POSTMASTER is allowed to use it as sender address + my $reply = %logins ? join(',', keys %logins) : $POSTMASTER; + return "OK $reply"; +} diff --git a/roles/MSA/handlers/main.yml b/roles/MSA/handlers/main.yml index 9edf610..a3db0f8 100644 --- a/roles/MSA/handlers/main.yml +++ b/roles/MSA/handlers/main.yml @@ -1,6 +1,9 @@ --- +- name: systemctl daemon-reload + command: /bin/systemctl daemon-reload + - name: Reload Postfix service: name=postfix state=reloaded - name: Restart munin-node service: name=munin-node state=restarted diff --git a/roles/MSA/tasks/main.yml b/roles/MSA/tasks/main.yml index 6b1551f..bf17702 100644 --- a/roles/MSA/tasks/main.yml +++ b/roles/MSA/tasks/main.yml @@ -1,46 +1,144 @@ - name: Install Postfix - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - postfix + - postfix-lmdb - postfix-pcre + - postfix-policyd-spf-python + +- name: Install Net::LDAP and Authen::SASL + apt: pkg={{ packages }} + vars: + packages: + - libnet-ldap-perl + - libauthen-sasl-perl + +- name: Copy Postfix sender login socketmap + copy: src=usr/local/bin/postfix-sender-login.pl + dest=/usr/local/bin/postfix-sender-login.pl + owner=root group=staff + mode=0755 + +- name: Create '_postfix-sender-login' user + user: name=_postfix-sender-login system=yes + group=nogroup + createhome=no + home=/nonexistent + shell=/usr/sbin/nologin + password=! + state=present + +- name: Copy Postfix sender login socketmap systemd unit files + copy: src=etc/systemd/system/{{ item }} + dest=/etc/systemd/system/{{ item }} + owner=root group=root + mode=0644 + with_items: + - postfix-sender-login.service + - postfix-sender-login.socket + notify: + - systemctl daemon-reload + +- name: Copy the SMTP TLS policy maps + template: src=etc/postfix/smtp_tls_policy.j2 + dest=/etc/postfix-{{ postfix_instance[inst].name }}/smtp_tls_policy + owner=root group=root + mode=0644 + +- name: Compile the SMTP TLS policy maps + postmap: cmd=postmap src=/etc/postfix-{{ postfix_instance[inst].name }}/smtp_tls_policy db=lmdb + owner=root group=root + mode=0644 + notify: + - Reload Postfix + +- meta: flush_handlers + +- name: Enable Postfix sender login socketmap + service: name=postfix-sender-login.socket state=started enabled=yes - name: Configure Postfix - template: src=etc/postfix/main.cf.j2 - dest=/etc/postfix-{{ postfix_instance[inst].name }}/main.cf + template: src=etc/postfix/{{ item }}.j2 + dest=/etc/postfix-{{ postfix_instance[inst].name }}/{{ item }} owner=root group=root mode=0644 + with_items: + - main.cf + - master.cf notify: - Reload Postfix - name: Copy the Regex to anonymize senders # no need to reload upon change, as cleanup(8) is short-running copy: src=etc/postfix/anonymize_sender.pcre dest=/etc/postfix-{{ postfix_instance[inst].name }}/anonymize_sender.pcre owner=root group=root mode=0644 +- name: Copy the check_sender_access map + copy: src=etc/postfix/check_sender_access + dest=/etc/postfix-{{ postfix_instance[inst].name }}/check_sender_access + owner=root group=root + mode=0644 + +- name: Compile the check_sender_access map + # no need to reload upon change, as cleanup(8) is short-running + postmap: cmd=postmap src=/etc/postfix-{{ postfix_instance[inst].name }}/check_sender_access db=lmdb + owner=root group=root + mode=0644 + notify: + - Reload Postfix + +- name: Configure policyd-spf + template: src=etc/postfix-policyd-spf-python/policyd-spf.conf.j2 + dest=/etc/postfix-policyd-spf-python/policyd-spf.conf + owner=root group=root + mode=0644 + # Reload Postifx to terminate spawn(8) daemon children + notify: + - Reload Postfix + +- name: Create directory /etc/postfix/ssl + file: path=/etc/postfix-{{ postfix_instance[inst].name }}/ssl + state=directory + owner=root group=root + mode=0755 + tags: + - genkey + - meta: flush_handlers - name: Start Postfix service: name=postfix state=started +- name: Fetch Postfix's X.509 certificate + # Ensure we don't fetch private data + become: False + # `/usr/sbin/postmulti -i msa -x /usr/sbin/postconf -xh smtpd_tls_cert_file` + fetch_cmd: cmd="openssl x509 -noout -pubkey" + stdin=/etc/postfix-{{ postfix_instance[inst].name }}/ssl/smtp.fripost.org.pem + dest=certs/public/smtp.fripost.org.pub + tags: + - genkey + - name: Install 'postfix_mailqueue_' Munin wildcard plugin file: src=/usr/local/share/munin/plugins/postfix_mailqueue_ dest=/etc/munin/plugins/postfix_mailqueue_postfix-{{ postfix_instance[inst].name }} owner=root group=root state=link force=yes tags: - munin - munin-node notify: - Restart munin-node - name: Install 'postfix_stats_' Munin wildcard plugin file: src=/usr/local/share/munin/plugins/postfix_stats_ dest=/etc/munin/plugins/postfix_stats_{{ item }}_postfix-{{ postfix_instance[inst].name }} owner=root group=root state=link force=yes with_items: - smtpd - qmgr diff --git a/roles/MSA/templates/etc/postfix-policyd-spf-python/policyd-spf.conf.j2 b/roles/MSA/templates/etc/postfix-policyd-spf-python/policyd-spf.conf.j2 new file mode 100644 index 0000000..2cc1074 --- /dev/null +++ b/roles/MSA/templates/etc/postfix-policyd-spf-python/policyd-spf.conf.j2 @@ -0,0 +1,18 @@ +# {{ ansible_managed }} +# Do NOT edit this file directly! + +debugLevel = 1 +TestOnly = 1 + +HELO_reject = Softfail +Mail_From_reject = Softfail + +PermError_reject = False +TempError_Defer = False + +# We're just trying to keep our outgoing IPs clean of SPF violations, +# not seeking 100% accurate reports. While it's possible that the +# message is routed through a different IP (eg, IPv4 vs v6), giving a +# potentially inaccurate prospective report, it's quite unlikely in +# practice. +Prospective = {{ lookup('pipe', 'dig outgoing.fripost.org A +short | sort | head -n1') }} diff --git a/roles/MSA/templates/etc/postfix/main.cf.j2 b/roles/MSA/templates/etc/postfix/main.cf.j2 index efcebef..6a544ac 100644 --- a/roles/MSA/templates/etc/postfix/main.cf.j2 +++ b/roles/MSA/templates/etc/postfix/main.cf.j2 @@ -1,128 +1,127 @@ ######################################################################## -# MSA configuration +# Mail Submission Agent (MSA) 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 delay_warning_time = 4h maximal_queue_lifetime = 5d myorigin = /etc/mailname myhostname = smtp{{ msano | default('') }}.$mydomain mydomain = fripost.org append_dot_mydomain = no -# Turn off all TCP/IP listener ports except that necessary for the MSA. -master_service_disable = !submission.inet inet +mynetworks = 127.0.0.0/8, [::1]/128 +{%- for h in groups.webmail | difference([inventory_hostname]) | sort -%} + , {{ ipsec[ hostvars[h].inventory_hostname_short ] | ansible.utils.ipaddr }} +{% endfor %} 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 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 = # 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 +policyd-spf_time_limit = $ipc_timeout # 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 # TLS -{% 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 = /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 = encrypt -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_dh1024_param_file = /etc/ssl/private/dhparams.pem -smtpd_tls_session_cache_database= btree:$data_directory/smtpd_tls_session_cache +smtpd_tls_mandatory_ciphers = high +smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 +smtpd_tls_cert_file = $config_directory/ssl/smtp.fripost.org.pem +smtpd_tls_key_file = $config_directory/ssl/smtp.fripost.org.key +smtpd_tls_dh1024_param_file = /etc/ssl/dhparams.pem +smtpd_tls_session_cache_database= smtpd_tls_received_header = yes -smtpd_tls_ask_ccert = yes +tls_high_cipherlist = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 # 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 -address_verify_sender = $double_bounce_sender@$mydomain -unverified_recipient_defer_code = 250 -unverified_recipient_reject_code = 550 +address_verify_sender = $double_bounce_sender@noreply.$mydomain +address_verify_poll_count = 3 +address_verify_relayhost = +address_verify_sender_ttl = 8069m +address_verify_negative_refresh_time = 5m +unverified_recipient_defer_code = 250 +unverified_recipient_reject_code = 550 +address_verify_map = lmdb:$data_directory/verify_cache +address_verify_default_transport = smtp_verify smtpd_client_restrictions = permit_sasl_authenticated reject smtpd_helo_required = yes smtpd_helo_restrictions = reject_invalid_helo_hostname +smtpd_sender_login_maps = socketmap:unix:private/sender-login:sender_login smtpd_sender_restrictions = reject_non_fqdn_sender reject_unknown_sender_domain + check_sender_access lmdb:$config_directory/check_sender_access + check_policy_service unix:private/policyd-spf + reject_known_sender_login_mismatch smtpd_relay_restrictions = reject_non_fqdn_recipient reject_unknown_recipient_domain reject_unverified_recipient - permit_mynetworks permit_sasl_authenticated reject smtpd_data_restrictions = reject_unauth_pipelining +smtpd_forbid_bare_newline = normalize +smtpd_forbid_bare_newline_exclusions = $mynetworks + # vim: set filetype=pfmain : diff --git a/roles/MSA/templates/etc/postfix/master.cf.j2 b/roles/MSA/templates/etc/postfix/master.cf.j2 new file mode 120000 index 0000000..011f8e0 --- /dev/null +++ b/roles/MSA/templates/etc/postfix/master.cf.j2 @@ -0,0 +1 @@ +../../../../common/templates/etc/postfix/master.cf.j2
\ No newline at end of file diff --git a/roles/MSA/templates/etc/postfix/smtp_tls_policy.j2 b/roles/MSA/templates/etc/postfix/smtp_tls_policy.j2 new file mode 120000 index 0000000..b40876f --- /dev/null +++ b/roles/MSA/templates/etc/postfix/smtp_tls_policy.j2 @@ -0,0 +1 @@ +../../../../out/templates/etc/postfix/smtp_tls_policy.j2
\ No newline at end of file diff --git a/roles/MX/files/etc/opendmarc.conf b/roles/MX/files/etc/opendmarc.conf new file mode 100644 index 0000000..ebbc850 --- /dev/null +++ b/roles/MX/files/etc/opendmarc.conf @@ -0,0 +1,116 @@ +# This is a basic configuration that can easily be adapted to suit a standard +# installation. For more advanced options, see openmarc.conf(5) and/or +# /usr/share/doc/opendmarc/examples/opendmarc.conf.sample. + +## AuthservID (string) +## defaults to MTA name +## +## Sets the "authserv-id" to use when generating the Authentication-Results: +## header field after verifying a message. If the string "HOSTNAME" is +## provided, the name of the host running the filter (as returned by the +## gethostname(3) function) will be used. +# +# AuthservID name + +## FailureReports { true | false } +## default "false" +## +## Enables generation of failure reports when the DMARC test fails and the +## purported sender of the message has requested such reports. Reports are +## formatted per RFC6591. +# +# FailureReports false + +## PublicSuffixList path +## default (none) +## +## Specifies the path to a file that contains top-level domains (TLDs) that +## will be used to compute the Organizational Domain for a given domain name, +## as described in the DMARC specification. If not provided, the filter will +## not be able to determine the Organizational Domain and only the presented +## domain will be evaluated. +# +PublicSuffixList /usr/share/publicsuffix/public_suffix_list.dat + +## RejectFailures { true | false } +## default "false" +## +## If set, messages will be rejected if they fail the DMARC evaluation, or +## temp-failed if evaluation could not be completed. By default, no message +## will be rejected or temp-failed regardless of the outcome of the DMARC +## evaluation of the message. Instead, an Authentication-Results header +## field will be added. +# +RejectFailures false + +## Socket socketspec +## default (none) +## +## Specifies the socket that should be established by the filter to receive +## connections from sendmail(8) in order to provide service. socketspec is +## in one of two forms: local:path, which creates a UNIX domain socket at +## the specified path, or inet:port[@host] or inet6:port[@host] which creates +## a TCP socket on the specified port for the appropriate protocol family. +## If the host is not given as either a hostname or an IP address, the +## socket will be listening on all interfaces. This option is mandatory +## either in the configuration file or on the command line. If an IP +## address is used, it must be enclosed in square brackets. +# +Socket local:/run/opendmarc/opendmarc.sock + +## Syslog { true | false } +## default "false" +## +## Log via calls to syslog(3) any interesting activity. +# +Syslog true + +## SyslogFacility facility-name +## default "mail" +## +## Log via calls to syslog(3) using the named facility. The facility names +## are the same as the ones allowed in syslog.conf(5). +# +# SyslogFacility mail + +## TrustedAuthservIDs string +## default HOSTNAME +## +## Specifies one or more "authserv-id" values to trust as relaying true +## upstream DKIM and SPF results. The default is to use the name of +## the MTA processing the message. To specify a list, separate each entry +## with a comma. The key word "HOSTNAME" will be replaced by the name of +## the host running the filter as reported by the gethostname(3) function. +# +# TrustedAuthservIDs HOSTNAME + +## SPFIgnoreResults { true | false } +## default "false" +## +## Causes the filter to ignore any SPF results in the header of the message. +## This is useful if you want the filter to perfrom SPF checks itself, or +## because you don't trust the arriving header. +# +SPFIgnoreResults true + +## SPFSelfValidate { true | false } +## default "false" +## +## Causes the filter to perform a fallback SPF check itself when it can +## find no SPF results in the message header. If SPFIgnoreResults is also +## set, it never looks for SPF results in headers and always performs the +## SPF check itself when this is set. +# +SPFSelfValidate true + +## UMask mask +## default (none) +## +## Requests a specific permissions mask to be used for file creation. This +## only really applies to creation of the socket when Socket specifies a +## UNIX domain socket, and to the HistoryFile and PidFile (if any); temporary +## files are normally created by the mkstemp(3) function that enforces a +## specific file mode on creation regardless of the process umask. See +## umask(2) for more information. +# +UMask 0007 diff --git a/roles/MX/files/etc/postfix/reject-unknown-client-hostname.cf b/roles/MX/files/etc/postfix/reject-unknown-client-hostname.cf new file mode 100644 index 0000000..1f61f4b --- /dev/null +++ b/roles/MX/files/etc/postfix/reject-unknown-client-hostname.cf @@ -0,0 +1,10 @@ +server_host = ldapi://%2Fprivate%2Fldapi/ +version = 3 +search_base = fvd=%d,ou=virtual,dc=fripost,dc=org +domain = static:all +scope = one +bind = sasl +sasl_mechs = EXTERNAL +query_filter = (&(objectClass=FripostVirtualList)(!(objectClass=FripostPendingEntry))(fvl=%u)(fripostIsStatusActive=TRUE)) +result_attribute = fvl +result_format = reject_unknown_client_hostname diff --git a/roles/MX/files/etc/postfix/virtual/alias.cf b/roles/MX/files/etc/postfix/virtual/alias.cf index 1c104a9..2e846ca 100644 --- a/roles/MX/files/etc/postfix/virtual/alias.cf +++ b/roles/MX/files/etc/postfix/virtual/alias.cf @@ -1,9 +1,9 @@ -server_host = ldapi://%2Fvar%2Fspool%2Fpostfix-mx%2Fprivate%2Fldapi/ +server_host = ldapi://%2Fprivate%2Fldapi/ version = 3 search_base = fvd=%d,ou=virtual,dc=fripost,dc=org domain = static:all scope = one bind = sasl sasl_mechs = EXTERNAL query_filter = (&(objectClass=FripostVirtualAlias)(fvl=%u)(fripostIsStatusActive=TRUE)) result_attribute = fripostMaildrop diff --git a/roles/MX/files/etc/postfix/virtual/alias_domains.cf b/roles/MX/files/etc/postfix/virtual/alias_domains.cf index 907166f..1108ea1 100644 --- a/roles/MX/files/etc/postfix/virtual/alias_domains.cf +++ b/roles/MX/files/etc/postfix/virtual/alias_domains.cf @@ -1,11 +1,11 @@ -server_host = ldapi://%2Fvar%2Fspool%2Fpostfix-mx%2Fprivate%2Fldapi/ +server_host = ldapi://%2Fprivate%2Fldapi/ version = 3 search_base = ou=virtual,dc=fripost,dc=org domain = static:all scope = one bind = sasl sasl_mechs = EXTERNAL # The domain has already been validated (it's active and not pending) query_filter = (&(objectClass=FripostVirtualAliasDomain)(fvd=%d)) result_attribute = fripostMaildrop result_format = %U@%s diff --git a/roles/MX/files/etc/postfix/virtual/catchall.cf b/roles/MX/files/etc/postfix/virtual/catchall.cf index e0e6350..a67d39c 100644 --- a/roles/MX/files/etc/postfix/virtual/catchall.cf +++ b/roles/MX/files/etc/postfix/virtual/catchall.cf @@ -1,10 +1,10 @@ -server_host = ldapi://%2Fvar%2Fspool%2Fpostfix-mx%2Fprivate%2Fldapi/ +server_host = ldapi://%2Fprivate%2Fldapi/ version = 3 search_base = ou=virtual,dc=fripost,dc=org domain = static:all scope = one bind = sasl sasl_mechs = EXTERNAL # The domain has already been validated (it's active and not pending) query_filter = (&(objectClass=FripostVirtualDomain)(!(objectClass=FripostVirtualAliasDomain))(fvd=%d)(fripostOptionalMaildrop=*)) result_attribute = fripostOptionalMaildrop diff --git a/roles/MX/files/etc/postfix/virtual/domains.cf b/roles/MX/files/etc/postfix/virtual/domains.cf index f5a7f25..88e17e2 100644 --- a/roles/MX/files/etc/postfix/virtual/domains.cf +++ b/roles/MX/files/etc/postfix/virtual/domains.cf @@ -1,11 +1,9 @@ -# XXX: How come we use a socked relative to the chroot here? smtpd(8) is -# not (can't be) chrooted... server_host = ldapi://%2Fprivate%2Fldapi/ version = 3 search_base = ou=virtual,dc=fripost,dc=org scope = one bind = sasl sasl_mechs = EXTERNAL query_filter = (&(objectClass=FripostVirtualDomain)(!(objectClass=FripostPendingEntry))(fvd=%s)(fripostIsStatusActive=TRUE)) result_attribute = fvd result_format = OK diff --git a/roles/MX/files/etc/postfix/virtual/list.cf b/roles/MX/files/etc/postfix/virtual/list.cf index 99e2147..e2df119 100644 --- a/roles/MX/files/etc/postfix/virtual/list.cf +++ b/roles/MX/files/etc/postfix/virtual/list.cf @@ -1,12 +1,12 @@ -server_host = ldapi://%2Fvar%2Fspool%2Fpostfix-mx%2Fprivate%2Fldapi/ +server_host = ldapi://%2Fprivate%2Fldapi/ version = 3 search_base = fvd=%d,ou=virtual,dc=fripost,dc=org domain = static:all scope = one bind = sasl sasl_mechs = EXTERNAL query_filter = (&(objectClass=FripostVirtualList)(!(objectClass=FripostPendingEntry))(fvl=%u)(fripostIsStatusActive=TRUE)) result_attribute = fripostListManager # Use a dedicated "virtual" domain to decongestion potential bottlenecks # on trivial_rewrite(8) due to slow LDAP lookups in tranport_maps. result_format = %D/%U@%s.fripost.org diff --git a/roles/MX/files/etc/postfix/virtual/mailbox.cf b/roles/MX/files/etc/postfix/virtual/mailbox.cf index 7289670..36862db 100644 --- a/roles/MX/files/etc/postfix/virtual/mailbox.cf +++ b/roles/MX/files/etc/postfix/virtual/mailbox.cf @@ -1,12 +1,12 @@ -server_host = ldapi://%2Fvar%2Fspool%2Fpostfix-mx%2Fprivate%2Fldapi/ +server_host = ldapi://%2Fprivate%2Fldapi/ version = 3 search_base = fvd=%d,ou=virtual,dc=fripost,dc=org domain = static:all scope = one bind = sasl sasl_mechs = EXTERNAL query_filter = (&(objectClass=FripostVirtualUser)(fvl=%u)(fripostIsStatusActive=TRUE)) result_attribute = fvl # Use a dedicated "virtual" domain to decongestion potential bottlenecks # on trivial_rewrite(8) due to slow LDAP lookups in tranport_maps. result_format = %D/%U@mda.fripost.org diff --git a/roles/MX/files/etc/postfix/virtual/reserved_alias.pcre b/roles/MX/files/etc/postfix/virtual/reserved_alias.pcre index 9fe60c8..eb17d65 100644 --- a/roles/MX/files/etc/postfix/virtual/reserved_alias.pcre +++ b/roles/MX/files/etc/postfix/virtual/reserved_alias.pcre @@ -1,6 +1,5 @@ /^(?:postmaster|abuse)(?:\+.*)?@fripost\.org$/ admin@fripost.org # For other domains, RFC 822 section 6.3 and RFC 2142 section 4 # mandatory aliases are forwarded to OUR admin team and to the domain # owner or postmaster, if there are any. -/^(postmaster|abuse)(?:\+.*)?@(.*)/ $2/$1@reserved.fripost.org -/^(double-bounce)(?:\+.*)?@(.*)/ $2/$1@discard.fripost.org +/^(postmaster|abuse)(?:\+.*)?@(.*)/ $2/$1@reserved.fripost.org diff --git a/roles/MX/files/etc/systemd/system/opendmarc.service.d/override.conf b/roles/MX/files/etc/systemd/system/opendmarc.service.d/override.conf new file mode 100644 index 0000000..1fb5567 --- /dev/null +++ b/roles/MX/files/etc/systemd/system/opendmarc.service.d/override.conf @@ -0,0 +1,17 @@ +[Service] +Type=simple +User=opendmarc +ExecStart= +ExecStart=/usr/sbin/opendmarc -f -p fd:3 +StandardOutput=journal +SyslogFacility=mail +RuntimeDirectory=opendmarc + +# Hardening +NoNewPrivileges=yes +PrivateDevices=yes +ProtectHome=yes +ProtectSystem=strict +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes diff --git a/roles/MX/files/etc/systemd/system/opendmarc.socket b/roles/MX/files/etc/systemd/system/opendmarc.socket new file mode 100644 index 0000000..483ef60 --- /dev/null +++ b/roles/MX/files/etc/systemd/system/opendmarc.socket @@ -0,0 +1,10 @@ +[Unit] +Description=OpenDMARC Milter activation socket + +[Socket] +ListenStream=/var/spool/postfix-mx/public/opendmarc +SocketUser=postfix +SocketMode=0666 + +[Install] +WantedBy=sockets.target diff --git a/roles/MX/handlers/main.yml b/roles/MX/handlers/main.yml index 9edf610..00223a5 100644 --- a/roles/MX/handlers/main.yml +++ b/roles/MX/handlers/main.yml @@ -1,6 +1,15 @@ --- +- name: systemctl daemon-reload + command: /bin/systemctl daemon-reload + - name: Reload Postfix service: name=postfix state=reloaded - name: Restart munin-node service: name=munin-node state=restarted + +- name: Stop OpenDMARC + service: name=opendmarc.service state=stopped + +- name: Restart OpenDMARC + service: name=opendmarc.socket state=restarted diff --git a/roles/MX/tasks/main.yml b/roles/MX/tasks/main.yml index 4302502..57f17f8 100644 --- a/roles/MX/tasks/main.yml +++ b/roles/MX/tasks/main.yml @@ -1,36 +1,41 @@ - name: Install Postfix - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - postfix - postfix-pcre - postfix-ldap - - postfix-cdb + - postfix-lmdb # The following is for reserved-alias.pl - libnet-ldap-perl - libauthen-sasl-perl - name: Configure Postfix - template: src=etc/postfix/main.cf.j2 - dest=/etc/postfix-{{ postfix_instance[inst].name }}/main.cf + template: src=etc/postfix/{{ item }}.j2 + dest=/etc/postfix-{{ postfix_instance[inst].name }}/{{ item }} owner=root group=root mode=0644 + with_items: + - main.cf + - master.cf + - access-list.cidr notify: - Reload Postfix - name: Create directory /etc/postfix-.../virtual file: path=/etc/postfix-{{ postfix_instance[inst].name }}/virtual state=directory owner=root group=root mode=0755 # trivial-rewrite(8) runs in a chroot. We create an empty # /usr/lib/sasl2 to avoid "No such file or directory" warnings. # Cf. also #738989. - name: Create directory /usr/lib/sasl2 file: path=/var/spool/postfix-{{ postfix_instance[inst].name }}/{{ item }} state=directory owner=root group=root mode=0755 with_items: - /usr/lib/sasl2 - /usr/lib/{{ ansible_architecture }}-linux-gnu/sasl2 @@ -41,68 +46,139 @@ copy: src=etc/postfix/virtual/{{ item }} dest=/etc/postfix-{{ postfix_instance[inst].name }}/virtual/{{ item }} owner=root group=root mode=0644 with_items: - domains.cf # no need to reload upon change, as cleanup(8) is short-running - reserved_alias.pcre - alias.cf - mailbox.cf - list.cf - alias_domains.cf - catchall.cf - name: Copy lookup tables (2) template: src=etc/postfix/virtual/transport.j2 dest=/etc/postfix-{{ postfix_instance[inst].name }}/virtual/transport owner=root group=root mode=0644 +- name: Copy recipient access(5) map + copy: src=etc/postfix/reject-unknown-client-hostname.cf + dest=/etc/postfix-{{ postfix_instance[inst].name }}/reject-unknown-client-hostname.cf + owner=root group=root + mode=0644 + notify: + - Reload Postfix + - name: Compile the Postfix transport maps # trivial-rewrite(8) is a long-running process, so it's safer to reload postmap: instance={{ postfix_instance[inst].name }} - src=/etc/postfix-{{ postfix_instance[inst].name }}/virtual/transport db=cdb + src=/etc/postfix-{{ postfix_instance[inst].name }}/virtual/transport db=lmdb owner=root group=root mode=0644 notify: - Reload Postfix - name: Copy reserved-alias.pl copy: src=usr/local/bin/reserved-alias.pl dest=/usr/local/bin/reserved-alias.pl + owner=root group=staff + mode=0755 + +- name: Create directory /etc/postfix/ssl + file: path=/etc/postfix-{{ postfix_instance[inst].name }}/ssl + state=directory owner=root group=root mode=0755 + tags: + - genkey - meta: flush_handlers - name: Start Postfix service: name=postfix state=started +- name: Fetch Postfix's X.509 certificate + # Ensure we don't fetch private data + become: False + # `/usr/sbin/postmulti -i mx -x /usr/sbin/postconf -xh smtpd_tls_cert_file` + fetch_cmd: cmd="openssl x509 -noout -pubkey" + stdin=/etc/postfix-{{ postfix_instance[inst].name }}/ssl/mx.fripost.org.pem + dest=certs/public/mx{{ mxno | default('') }}.fripost.org.pub + tags: + - genkey + - name: Install 'postfix_mailqueue_' Munin wildcard plugin file: src=/usr/local/share/munin/plugins/postfix_mailqueue_ dest=/etc/munin/plugins/postfix_mailqueue_postfix-{{ postfix_instance[inst].name }} owner=root group=root state=link force=yes tags: - munin - munin-node notify: - Restart munin-node - name: Install 'postfix_stats_' Munin wildcard plugin file: src=/usr/local/share/munin/plugins/postfix_stats_ dest=/etc/munin/plugins/postfix_stats_{{ item }}_postfix-{{ postfix_instance[inst].name }} owner=root group=root state=link force=yes with_items: - postscreen - smtpd - qmgr - smtp - pipe tags: - munin - munin-node notify: - Restart munin-node + +# XXX we probaly want SPF verification for domains without DMARC +# policies +- name: Install OpenDMARC + apt: pkg=opendmarc + +- name: Copy OpenDMARC configuration + copy: src=etc/opendmarc.conf + dest=/etc/opendmarc.conf + owner=root group=root + mode=0644 + notify: + - Stop OpenDMARC + +- name: Create directory /etc/systemd/system/opendmarc.service.d + file: path=/etc/systemd/system/opendmarc.service.d + state=directory + owner=root group=root + mode=0755 + +- name: Harden OpenDMARC service unit + copy: src=etc/systemd/system/opendmarc.service.d/override.conf + dest=/etc/systemd/system/opendmarc.service.d/override.conf + owner=root group=root + mode=0644 + notify: + - systemctl daemon-reload + - Stop OpenDMARC + +- meta: flush_handlers + +- name: Copy OpenDMARC socket unit + copy: src=etc/systemd/system/opendmarc.socket + dest=/etc/systemd/system/opendmarc.socket + owner=root group=root + mode=0644 + notify: + - systemctl daemon-reload + - Restart OpenDMARC + +- name: Disable OpenDMARC service + service: name=opendmarc.service enabled=false + +- name: Start OpenDMARC socket + service: name=opendmarc.socket state=started enabled=true diff --git a/roles/MX/templates/etc/postfix/access-list.cidr.j2 b/roles/MX/templates/etc/postfix/access-list.cidr.j2 new file mode 100644 index 0000000..bd6e3d8 --- /dev/null +++ b/roles/MX/templates/etc/postfix/access-list.cidr.j2 @@ -0,0 +1,16 @@ +######################################################################## +# Access list, see cidr_table(5) +# +# {{ ansible_managed }} +# Do NOT edit this file directly! + +{% if ipsec_subnet is defined %} +{{ ipsec_subnet }} permit +{% endif %} + +{% for ip in lookup('pipe', 'dig +short outgoing.fripost.org A').splitlines() | sort -%} +{{ ip }}/32 permit +{% endfor %} +{% for ip in lookup('pipe', 'dig +short outgoing.fripost.org AAAA').splitlines() | sort -%} +{{ ip }}/128 permit +{% endfor %} diff --git a/roles/MX/templates/etc/postfix/main.cf.j2 b/roles/MX/templates/etc/postfix/main.cf.j2 index b9f7c09..d10f901 100644 --- a/roles/MX/templates/etc/postfix/main.cf.j2 +++ b/roles/MX/templates/etc/postfix/main.cf.j2 @@ -1,163 +1,156 @@ ######################################################################## -# MX configuration +# Mail eXchange (MX) 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 delay_warning_time = 4h maximal_queue_lifetime = 5d myorigin = /etc/mailname myhostname = mx{{ mxno | default('') }}.$mydomain mydomain = fripost.org append_dot_mydomain = no -# Turn off all TCP/IP listener ports except that necessary for the mail -# exchange. -master_service_disable = !smtp.inet inet +mynetworks_style = host 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 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 = # 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_alias_domains = !cdb:$config_directory/virtual/transport +virtual_alias_domains = !lmdb:$config_directory/virtual/transport ldap:$config_directory/virtual/domains.cf virtual_alias_maps = pcre:$config_directory/virtual/reserved_alias.pcre # unless there is a matching user/alias/list... ldap:$config_directory/virtual/mailbox.cf ldap:$config_directory/virtual/alias.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 -transport_maps = cdb:$config_directory/virtual/transport +transport_maps = lmdb:$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_destination_recipient_limit = 1 # Tolerate occasional high latency smtp_data_done_timeout = 1200s -{% 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 = /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 = may -smtpd_tls_exclude_ciphers = EXPORT, LOW, MEDIUM, aNULL, eNULL, DES, RC4, MD5 -smtpd_tls_cert_file = /etc/ssl/certs/ssl-cert-snakeoil.pem -smtpd_tls_key_file = /etc/ssl/private/ssl-cert-snakeoil.key -smtpd_tls_dh1024_param_file = /etc/ssl/private/dhparams.pem +smtpd_tls_ciphers = medium +smtpd_tls_protocols = !SSLv2, !SSLv3 +smtpd_tls_cert_file = $config_directory/ssl/mx.fripost.org.pem +smtpd_tls_key_file = $config_directory/ssl/mx.fripost.org.key +smtpd_tls_dh1024_param_file = /etc/ssl/dhparams.pem smtpd_tls_CApath = /etc/ssl/certs/ -smtpd_tls_session_cache_database= btree:$data_directory/smtpd_tls_session_cache +smtpd_tls_session_cache_database= smtpd_tls_received_header = yes -smtpd_tls_ask_ccert = yes # 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 -unknown_virtual_mailbox_reject_code = 554 +postscreen_access_list = + permit_mynetworks + cidr:$config_directory/access-list.cidr +postscreen_dnsbl_whitelist_threshold = -1 +postscreen_cache_map = lmdb:$data_directory/postscreen_cache postscreen_blacklist_action = drop -postscreen_dnsbl_threshold = 3 +postscreen_dnsbl_threshold = 8 postscreen_dnsbl_action = enforce postscreen_dnsbl_sites = - zen.spamhaus.org*3 - swl.spamhaus.org*-4 - b.barracudacentral.org*2 - bl.spameatingmonkey.net*2 - bl.spamcop.net - dnsbl.sorbs.net - list.dnswl.org=127.[0..255].[0..255].0*-2 - list.dnswl.org=127.[0..255].[0..255].1*-3 - list.dnswl.org=127.[0..255].[0..255].[2..255]*-4 - -postscreen_greet_action = enforce -postscreen_whitelist_interfaces = !88.80.11.28 ![2a00:16b0:242:13::de30] static:all + zen.spamhaus.org=127.0.0.[10;11]*8 + zen.spamhaus.org=127.0.0.[4..7]*6 + zen.spamhaus.org=127.0.0.3*4 + zen.spamhaus.org=127.0.0.2*3 + #swl.spamhaus.org*-4 + b.barracudacentral.org=127.0.0.2*7 + bl.mailspike.net=127.0.0.2*5 + bl.mailspike.net=127.0.0.[10..12]*4 + wl.mailspike.net=127.0.0.[18..20]*-2 + bl.spameatingmonkey.net=127.0.0.2*4 + bl.spamcop.net=127.0.0.2*2 + dnsbl.sorbs.net=127.0.0.10*8 + dnsbl.sorbs.net=127.0.0.5*6 + dnsbl.sorbs.net=127.0.0.7*3 + dnsbl.sorbs.net=127.0.0.8*2 + dnsbl.sorbs.net=127.0.0.6*2 + dnsbl.sorbs.net=127.0.0.9*2 + list.dnswl.org=127.0.[0..255].0*-2 + list.dnswl.org=127.0.[0..255].1*-3 + list.dnswl.org=127.0.[0..255].[2..3]*-4 + +postscreen_greet_action = enforce +postscreen_whitelist_interfaces = static:all + +smtpd_milters = { unix:public/opendmarc, protocol=6, default_action=accept } smtpd_client_restrictions = permit_mynetworks smtpd_helo_required = yes smtpd_helo_restrictions = permit_mynetworks reject_non_fqdn_helo_hostname reject_invalid_helo_hostname smtpd_sender_restrictions = reject_non_fqdn_sender + reject_unknown_sender_domain smtpd_relay_restrictions = reject_non_fqdn_recipient permit_mynetworks reject_unauth_destination reject_unlisted_recipient +smtpd_recipient_restrictions = + check_client_access cidr:$config_directory/access-list.cidr + check_recipient_access ldap:$config_directory/reject-unknown-client-hostname.cf + reject_rhsbl_reverse_client dbl.spamhaus.org=127.0.1.[2..99] + reject_rhsbl_sender dbl.spamhaus.org=127.0.1.[2..99] + smtpd_data_restrictions = reject_unauth_pipelining # vim: set filetype=pfmain : diff --git a/roles/MX/templates/etc/postfix/master.cf.j2 b/roles/MX/templates/etc/postfix/master.cf.j2 new file mode 120000 index 0000000..011f8e0 --- /dev/null +++ b/roles/MX/templates/etc/postfix/master.cf.j2 @@ -0,0 +1 @@ +../../../../common/templates/etc/postfix/master.cf.j2
\ No newline at end of file diff --git a/roles/MX/templates/etc/postfix/virtual/transport.j2 b/roles/MX/templates/etc/postfix/virtual/transport.j2 index 49f3696..536748a 100644 --- a/roles/MX/templates/etc/postfix/virtual/transport.j2 +++ b/roles/MX/templates/etc/postfix/virtual/transport.j2 @@ -1,30 +1,21 @@ # Each valid address user@example.org is aliased (on the MX) into some # example.org/user@xxx.fripost.org, and non-defaults next-hop:port are # chosen here in that table, depending on 'xxx'. The reason for such # indirection is that there is only one qmgr(8) daemon, which delegate # the routing strategy to the trivial-rewrite(8), which in turns queries # transport_maps. Hence high latency maps such as LDAP or SQL would # congestion the queue manager. On the other hand, virtual aliasing is # performed by cleanup(8), multiples instances of which can run in # parallel. See http://www.postfix.org/ADDRESS_REWRITING_README.html . # # /!\ WARNING: xxx.fripost.org should NOT be in the list of valid # domains ($virtual_alias_domains)! Otherwise at the next iteration of # the alias resolution loop the domain will be validated but not the # address, and the MTA will reply with "Recipient address rejected: User # unknown in virtual alias table". reserved.fripost.org reserved-alias: discard.fripost.org discard: -{% if 'LDA' in group_names %} -mda.fripost.org smtpl:[127.0.0.1]:{{ postfix_instance.IMAP.port }} -{% else %} -mda.fripost.org smtp:[mda.fripost.org]:{{ postfix_instance.IMAP.port }} -{% endif %} - -{% if 'lists' in group_names %} -sympa.fripost.org smtpl:[127.0.0.1]:{{ postfix_instance.lists.port }} -{% else %} -sympa.fripost.org smtp:[lists.fripost.org]:{{ postfix_instance.lists.port }} -{% endif %} +mda.fripost.org smtp:[{{ postfix_instance.IMAP.addr | ansible.utils.ipaddr }}]:{{ postfix_instance.IMAP.port }} +sympa.fripost.org smtp:[{{ postfix_instance.lists.addr | ansible.utils.ipaddr }}]:{{ postfix_instance.lists.port }} diff --git a/roles/amavis/handlers/main.yml b/roles/amavis/handlers/main.yml index 62cc6fc..684be0f 100644 --- a/roles/amavis/handlers/main.yml +++ b/roles/amavis/handlers/main.yml @@ -1,13 +1,9 @@ --- - name: Restart ClamAV service: name=clamav-daemon state=restarted -- name: Publish the public key to the DNS zone - # See the output of 'genkeypair.sh dkim --privkey=/path/to/key' - fail: "msg={{ dkim.stdout }}" - - name: Restart Amavis service: name=amavis state=restarted - name: Restart munin-node service: name=munin-node state=restarted diff --git a/roles/amavis/tasks/main.yml b/roles/amavis/tasks/main.yml index 4009c05..7fc44c7 100644 --- a/roles/amavis/tasks/main.yml +++ b/roles/amavis/tasks/main.yml @@ -1,79 +1,90 @@ - name: Install amavis and its decoders - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - amavisd-new - libnet-ldap-perl # Mail::DKIM - libmail-dkim-perl - gzip - bzip2 - xz-utils - lzop - rpm2cpio - pax - binutils - p7zip-full - unrar-free - arj - nomarch - - zoo - - ripole - cabextract - unar - tnef notify: - Restart Amavis - name: Add 'clamav' to the group 'amavis' user: name=clamav groups=amavis append=yes notify: - Restart ClamAV - Restart Amavis -- name: Set AllowSupplementaryGroups=true - lineinfile: "dest=/etc/clamav/clamd.conf - regexp='^AllowSupplementaryGroups\\s' - line='AllowSupplementaryGroups true'" - notify: - - Restart ClamAV +- name: Add an 'amavis' alias + lineinfile: dest=/etc/aliases create=yes + regexp="^amavis{{':'}} " + line="amavis{{':'}} root" -- name: Create directory /var/lib/dkim - file: path=/var/lib/dkim +- name: Compile the static local Postfix database + postmap: cmd=postalias src=/etc/aliases db=lmdb + owner=root group=root + mode=0644 + +- name: Create directory /etc/amavis/dkim + file: path=/etc/amavis/dkim state=directory owner=root group=root mode=0755 when: "'out' in group_names" tags: - genkey + - dkim - name: Generate a private key for DKIM signing - command: genkeypair.sh dkim --privkey=/var/lib/dkim/20140703.fripost.org.key -t rsa -b 1024 + command: genkeypair.sh dkim --owner=amavis --group=root --privkey="/etc/amavis/dkim/{{ item.s }}:{{ item.d }}.pem" -t rsa -b 2048 + with_items: "{{ (dkim_keys[inventory_hostname_short] | default({})).values() | list }}" register: dkim changed_when: dkim.rc == 0 failed_when: dkim.rc > 1 when: "'out' in group_names" - notify: - - Restart Amavis - - Publish the public key to the DNS zone tags: - genkey + - dkim + +- name: Fetch DKIM keys + fetch_cmd: cmd="openssl pkey -pubout -outform PEM" + stdin="/etc/amavis/dkim/{{ item.s }}:{{ item.d }}.pem" + dest="certs/dkim/{{ item.s }}:{{ item.d }}.pub" + with_items: "{{ (dkim_keys[inventory_hostname_short] | default({})).values() | list }}" + tags: + - genkey + - dkim - name: Configure Amavis template: src=etc/amavis/conf.d/50-user.j2 dest=/etc/amavis/conf.d/50-user owner=root group=root mode=0644 register: r3 notify: - Restart Amavis - meta: flush_handlers - name: Start Amavis service: name=amavis state=started - name: Install 'amavis' Munin plugin file: src=/usr/share/munin/plugins/amavis dest=/etc/munin/plugins/amavis owner=root group=root diff --git a/roles/amavis/templates/etc/amavis/conf.d/50-user.j2 b/roles/amavis/templates/etc/amavis/conf.d/50-user.j2 index 92805b8..8563a8c 100644 --- a/roles/amavis/templates/etc/amavis/conf.d/50-user.j2 +++ b/roles/amavis/templates/etc/amavis/conf.d/50-user.j2 @@ -15,46 +15,55 @@ use strict; # the amavisfeed service. $max_servers = 5; $recipient_delimiter = '+'; $mydomain = 'fripost.org'; $X_HEADER_LINE = "Debian $myproduct_name at $mydomain"; @mynetworks_maps = (); @remove_existing_spam_headers_maps = (); @bypass_virus_checks_maps = (); # load virus checking code $enable_dkim_verification = 1; # load DKIM signing/verifying code {% if 'out' not in group_names %} undef $enable_dkim_signing; @bypass_spam_checks_maps = (); # load spam checking code {% else %} $enable_dkim_signing = 1; # Sign *all* outgoing mails with *our* key (yes, amavis complains, but this is # safe as we force our domain with the 'd' tag). -dkim_key(qr/./, '20140703', '/var/lib/dkim/20140703.'.$mydomain.'.key'); +{% for x,k in dkim_keys[inventory_hostname_short] | default({}) | dictsort() -%} +dkim_key({{ (x == "~") | ternary('qr/./', "'"+(x | regex_replace('^.*@',''))+"'") }}, '{{ k.s }}', '/etc/amavis/dkim/{{ k.s }}:{{ k.d }}.pem'); +{% endfor -%} @dkim_signature_options_bysender_maps = ( - { '.' => { d => $mydomain - , a => 'rsa-sha256' - , ttl => 21*24*3600 - , c => 'relaxed/simple' } } ); +{% for x,k in dkim_keys[inventory_hostname_short] | default({}) | dictsort() %} + { '{{ (x == "~") | ternary('.', x) }}' => { + d => '{{ k.d }}' + , s => '{{ k.s }}' + , a => 'rsa-sha256' + , ttl => 21*24*3600 + , c => 'relaxed/simple' } + }{% if not loop.last %}, +{% endif %} +{% endfor %} +); # Conform to RFC 4871 and don't sign Received: headers. $signed_header_fields{received} = 0; {% endif %} # Defang viruses and nothing else %defang_maps_by_ccat = ( &CC_VIRUS => 1 , &CC_CATCHALL => undef ); # Don't change the subject for unchecked messages (not by-recip) delete $subject_tag_maps_by_ccat{+CC_UNCHECKED}; # Never BCC / DSN; don't forget to disallow setting amavisSpamDsnCutoffLevel # and amavis*Admin, also %always_bcc_by_ccat = ( &CC_CATCHALL => undef ); %dsn_bcc_by_ccat = ( &CC_CATCHALL => undef ); # Never warn sender or recipient; don't forget to disallow setting @@ -76,40 +85,41 @@ delete $subject_tag_maps_by_ccat{+CC_UNCHECKED}; $enable_ldap = 1; # Load Net::LDAP $default_ldap = { hostname => 'ldapi://', sasl => 1, sasl_mech => 'EXTERNAL', deref => 'never', timeout => 5, scope => 'one', base => 'fvd=%d,ou=virtual,dc=fripost,dc=org', # XXX: ideally we would use %u in the base and the query_filter, but # it's not supported as of amavis 2.7 (see the 'lookup_ldap' # subroutine in /usr/sbin/amavisd-new) query_filter => '(&(objectClass=amavisAccount)(ObjectClass=FripostVirtualUser)(fvl=%m))' }; {% endif %} # http://www.ijs.si/software/amavisd/amavisd-new-docs.html#pbanks-ex $protocol = 'LMTP'; +$inet_socket_bind = ['127.0.0.1']; $inet_socket_port = []; {% if 'out' in group_names %} push @$inet_socket_port, 10040; $interface_policy{'10040'} = 'OUTGOING'; {% endif %} {% if 'MDA' in group_names %} push @$inet_socket_port, 10041; $interface_policy{'10041'} = 'INCOMING'; {% endif %} $QUARANTINEDIR = "$MYHOME/virusmails"; $notify_method = 'smtp:[127.0.0.1]:16132'; # notifications $forward_method = 'smtp:[127.0.0.1]:10025'; # reinject $requeue_method = $notify_method; # requeue after quarantine # some defaults for spam checking $sa_tag_level_deflt = undef; $sa_tag2_level_deflt = 5; $sa_kill_level_deflt = 5; @@ -119,66 +129,68 @@ $sa_quarantine_cutoff_level = undef; # Here is an overall picture (sequence of events) of how pieces fit together # # bypass_virus_checks set for all recipients? ==> PASS # no viruses? ==> PASS # log virus if $log_templ is nonempty # quarantine if $virus_quarantine_to is nonempty # notify admin if $virus_admin (lookup) nonempty # notify recips if $warnvirusrecip and (recipient is local or $warn_offsite) # add address extensions for local recipients (when enabled) # send (non-)delivery notifications # to sender if DSN needed (BOUNCE or ($warnvirussender and D_PASS)) # virus_lovers or final_destiny==D_PASS ==> PASS # DISCARD (2xx) or REJECT (5xx) (depending on final_*_destiny) # Mandatory DKIM signing and virus checking only $policy_bank{'OUTGOING'} = { originating => 1, enable_dkim_verification => 0, + protocol => 'LMTP', smtpd_greeting_banner => '${helo-name} ${protocol} ${product} OUTGOING service ready', forward_method => $forward_method, # No black or white lists message_size_limit_maps => [], whitelist_sender_maps => [], blacklist_sender_maps => [], # Check for viruses (regardless of the recipient), but bypass all other checks bypass_virus_checks_maps => undef, bypass_banned_checks_maps => 1, bypass_header_checks_maps => 1, bypass_spam_checks_maps => 1, # If a virus is found, notify postmaster, quarantine, then discard. # Treat unchecked mails (eg, encrypted) as clean. quarantine_to_maps_by_ccat => { &CC_VIRUS => [$virus_quarantine_to], &CC_UNCHECKED => undef, &CC_CLEAN => undef }, quarantine_method_by_ccat => { &CC_VIRUS => [$virus_quarantine_method], &CC_UNCHECKED => undef, &CC_CLEAN => undef }, admin_maps_by_ccat => { &CC_VIRUS => ["postmaster\@$mydomain"], &CC_UNCHECKED => undef }, lovers_maps_by_ccat => { &CC_VIRUS => undef, &CC_UNCHECKED => 1 }, final_destiny_maps_by_ccat => { &CC_VIRUS => D_DISCARD, &CC_UNCHECKED => D_PASS, &CC_OVERSIZED => D_PASS }, }; $policy_bank{'INCOMING'} = { originating => 0, enable_dkim_verification => 1, + protocol => 'LMTP', smtpd_greeting_banner => '${helo-name} ${protocol} ${product} INCOMING service ready', forward_method => $forward_method, message_size_limit_maps => [], # Per-recipient Bayes Database sa_username_maps => [ new_RE ( [ qr/^(.+\@.+)$/ => '$1' ] ) , 'amavis' # catch-all ], # Never quarantine, and never notify. # (Remember to disallow setting amavisSpamQuarantineCutoffLevel and # amavisVirusQuarantine*To in the LDAP schema.) # XXX: users might want to quarantine messages and get a notification instead quarantine_method_by_ccat => { map {$_ => undef} (CC_VIRUS, CC_BANNED, CC_UNCHECKED, CC_SPAM, CC_BADH, CC_CLEAN) }, admin_maps_by_ccat => { map {$_ => undef} (CC_VIRUS, CC_BANNED, CC_UNCHECKED, CC_SPAM, CC_BADH ) }, # Always deliver messages final_destiny_maps_by_ccat => { map {$_ => D_PASS} (CC_VIRUS, CC_BANNED, CC_UNCHECKED, CC_SPAM, CC_BADH) }, lovers_maps_by_ccat => { map {$_ => 1 } (CC_VIRUS, CC_BANNED, CC_UNCHECKED, CC_SPAM, CC_SPAMMY, CC_BADH) }, }; diff --git a/roles/bacula-dir/files/etc/systemd/system/bacula-director.service.d/override.conf b/roles/bacula-dir/files/etc/systemd/system/bacula-director.service.d/override.conf new file mode 100644 index 0000000..f0d36c4 --- /dev/null +++ b/roles/bacula-dir/files/etc/systemd/system/bacula-director.service.d/override.conf @@ -0,0 +1,13 @@ +[Service] +# Hardening +NoNewPrivileges=yes +PrivateDevices=yes +ProtectHome=yes +ProtectSystem=strict +ReadWriteDirectories=-/var/lib/bacula +ReadWriteDirectories=-/var/log/bacula +PrivateDevices=yes +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 diff --git a/roles/bacula-dir/files/lib/systemd/system/bacula-director.service b/roles/bacula-dir/files/lib/systemd/system/bacula-director.service deleted file mode 100644 index 7b34c8b..0000000 --- a/roles/bacula-dir/files/lib/systemd/system/bacula-director.service +++ /dev/null @@ -1,14 +0,0 @@ -[Unit] -Description=Bacula Director service -After=network.target - -[Service] -Type=forking -PIDFile=/var/run/bacula/bacula-dir.9101.pid -StandardOutput=syslog -User=bacula -Group=tape -ExecStart=/usr/sbin/bacula-dir -c /etc/bacula/bacula-dir.conf - -[Install] -WantedBy=multi-user.target diff --git a/roles/bacula-dir/handlers/main.yml b/roles/bacula-dir/handlers/main.yml index 175dfb2..3f3c1bc 100644 --- a/roles/bacula-dir/handlers/main.yml +++ b/roles/bacula-dir/handlers/main.yml @@ -1,9 +1,6 @@ --- - name: systemctl daemon-reload command: /bin/systemctl daemon-reload -- name: Restart stunnel - service: name=stunnel4 pattern=/usr/bin/stunnel4 state=restarted - - name: Restart bacula-director service: name=bacula-director state=restarted diff --git a/roles/bacula-dir/tasks/main.yml b/roles/bacula-dir/tasks/main.yml index cee6fc2..53d44ee 100644 --- a/roles/bacula-dir/tasks/main.yml +++ b/roles/bacula-dir/tasks/main.yml @@ -1,134 +1,65 @@ -- name: Install stunnel - apt: pkg=stunnel4 - -- name: Auto-enable stunnel - lineinfile: dest=/etc/default/stunnel4 - regexp='^(\s*#)?\s*ENABLED=' - line='ENABLED=1' - owner=root group=root - mode=0644 - -- name: Create /etc/stunnel/certs - file: path=/etc/stunnel/certs - state=directory - owner=root group=root - mode=0755 - -- name: Generate a private key and a X.509 certificate for Bacula Dir - command: genkeypair.sh x509 - --pubkey=/etc/stunnel/certs/{{ inventory_hostname_short }}-dir.pem - --privkey=/etc/stunnel/certs/{{ inventory_hostname_short }}-dir.key - --ou=BaculaDir --cn={{ inventory_hostname }} --dns={{ inventory_hostname }} - -t rsa -b 4096 -h sha512 - register: r1 - changed_when: r1.rc == 0 - failed_when: r1.rc > 1 - notify: - - Restart stunnel - tags: - - genkey - -- name: Fetch Bacula Dir X.509 certificate - # Ensure we don't fetch private data - sudo: False - fetch: src=/etc/stunnel/certs/{{ inventory_hostname_short }}-dir.pem - dest=certs/bacula/ - fail_on_missing=yes - flat=yes - tags: - - genkey - -- name: Copy Bacula SD X.509 certificates - copy: src=certs/bacula/{{ hostvars[item].inventory_hostname_short }}-sd.pem - dest=/etc/stunnel/certs/ - owner=root group=root - mode=0644 - with_items: groups['bacula-sd'] | difference([inventory_hostname]) | sort - register: r2 - notify: - - Restart stunnel - -- name: Copy Bacula FD X.509 certificates - copy: src=certs/bacula/{{ hostvars[item].inventory_hostname_short }}-fd.pem - dest=/etc/stunnel/certs/ - owner=root group=root - mode=0644 - with_items: groups.all | difference([inventory_hostname]) | sort - register: r3 - notify: - - Restart stunnel - -- name: Configure stunnel - template: src=etc/stunnel/bacula-dir.conf.j2 - dest=/etc/stunnel/bacula-dir.conf - owner=root group=root - mode=0644 - register: r4 - notify: - - Restart stunnel - -- name: Start stunnel - service: name=stunnel4 pattern=/usr/bin/stunnel4 state=started - when: not (r1.changed or r2.changed or r3.changed or r4.changed) - -- meta: flush_handlers - - - - name: Install bacula-director - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - bacula-console + - bacula-director - bacula-director-mysql - name: Create a 'bacula' SQL user - mysql_user2: name=bacula password= auth_plugin=auth_socket - state=present + mysql_user: name=bacula password= plugin=unix_socket + state=present notify: - Restart bacula-director -# Create with: +# Populate with: # echo bconsole $(pwgen -sn 64 1) | sudo tee -a /etc/bacula/passwords-dir # echo $sd-sd $(pwgen -sn 64 1) | sudo tee -a /etc/bacula/passwords-dir # echo $fd-fd $(pwgen -sn 64 1) | sudo tee -a /etc/bacula/passwords-dir # # then add the password for each FD / SD: # echo $director-dir $password | sudo tee /etc/bacula/passwords-sd # echo $director-dir $password | sudo tee /etc/bacula/passwords-fd - name: Ensure /etc/bacula/passwords-dir exists file: path=/etc/bacula/passwords-dir state=file owner=bacula group=bacula mode=0600 - name: Configure bconsole template: src=etc/bacula/bconsole.conf.j2 dest=/etc/bacula/bconsole.conf owner=root group=root mode=0644 - name: Configure bacula template: src=etc/bacula/bacula-dir.conf.j2 dest=/etc/bacula/bacula-dir.conf owner=root group=root mode=0644 register: r notify: - Restart bacula-director -- name: Copy bacula-director.service - copy: src=lib/systemd/system/bacula-director.service - dest=/lib/systemd/system/bacula-director.service +- name: Create /etc/systemd/system/bacula-director.service.d + file: path=/etc/systemd/system/bacula-director.service.d + state=directory + owner=root group=root + mode=0755 + +- name: Copy bacula-director.service override + copy: src=etc/systemd/system/bacula-director.service.d/override.conf + dest=/etc/systemd/system/bacula-director.service.d/override.conf owner=root group=root mode=0644 notify: - systemctl daemon-reload - Restart bacula-director - meta: flush_handlers - name: Enable bacula-director service: name=bacula-director enabled=yes - name: Start bacula-director service: name=bacula-director state=started diff --git a/roles/bacula-dir/templates/etc/bacula/bacula-dir.conf.j2 b/roles/bacula-dir/templates/etc/bacula/bacula-dir.conf.j2 index bfae4c0..ab22375 100644 --- a/roles/bacula-dir/templates/etc/bacula/bacula-dir.conf.j2 +++ b/roles/bacula-dir/templates/etc/bacula/bacula-dir.conf.j2 @@ -1,221 +1,277 @@ # # Default Bacula Director Configuration file -# For Bacula release 5.2.6 (21 February 2012) -- debian jessie/sid +# For Bacula release 9.4.2 (04 February 2019) -- debian buster/sid # Director { # define myself Name = {{ inventory_hostname_short }}-dir @|"sed -n '/^bconsole\\s/ {s//Password = /p; q}' /etc/bacula/passwords-dir" Messages = Daemon Working Directory = /var/lib/bacula - Pid Directory = /var/run/bacula + Pid Directory = /run/bacula QueryFile = "/etc/bacula/scripts/query.sql" Maximum Concurrent Jobs = 1 DirAddress = 127.0.0.1 - DirSourceAddress = 127.0.0.1 DirPort = 9101 + FDConnectTimeout = 5 min + SDConnectTimeout = 5 min } JobDefs { Name = DefaultJob Type = Backup Level = Incremental - Storage = {{ hostvars[ groups['bacula-sd'][0] ].inventory_hostname_short }}-sd + Storage = {{ hostvars[ groups['bacula_sd'][0] ].inventory_hostname_short }}-sd Messages = Standard Accurate = yes + #Rerun Failed Levels = yes Reschedule On Error = yes - Reschedule Interval = 17m + Reschedule Interval = 17 min Reschedule Times = 3 Pool = Default Priority = 10 Write Bootstrap = "/var/lib/bacula/%n.bsr" } JobDefs { Name = DefaultMySQLJob Type = Backup Level = Full - Storage = {{ hostvars[ groups['bacula-sd'][0] ].inventory_hostname_short }}-sd + Storage = {{ hostvars[ groups['bacula_sd'][0] ].inventory_hostname_short }}-sd Messages = Standard FileSet = SQL Schedule = WeeklyCycleAfterBackup Reschedule On Error = yes - Reschedule Interval = 17m + Reschedule Interval = 17 min Reschedule Times = 3 # This creates an ASCII copy of the databases Client Run Before Job = "/usr/bin/mysqldump -r /var/lib/bacula/tmp/dump.sql --events --all-databases" # This deletes the copy of the catalog RunScript { Runs On Client = yes Runs On Success = yes Runs On Failure = yes Runs When = after Command = "/bin/rm -f /var/lib/bacula/tmp/dump.sql" } Pool = database Priority = 20 Write Bootstrap = "/var/lib/bacula/%n.bsr" } JobDefs { Name = DefaultSlapdJob Type = Backup Level = Full - Storage = {{ hostvars[ groups['bacula-sd'][0] ].inventory_hostname_short }}-sd + Storage = {{ hostvars[ groups['bacula_sd'][0] ].inventory_hostname_short }}-sd Messages = Standard FileSet = LDAP Schedule = WeeklyCycleAfterBackup Reschedule On Error = yes - Reschedule Interval = 17m + Reschedule Interval = 17 min Reschedule Times = 3 # This creates an ASCII copy of the databases Client Run Before Job = "/usr/local/sbin/slapcat-all.sh /var/lib/bacula/tmp" # This deletes the copy of the catalog RunScript { Runs On Client = yes Runs On Success = yes Runs On Failure = yes Runs When = after - Command = "/usr/bin/find /var/lib/bacula/tmp -type f -name '*.ldif' -delete" + Command = "/usr/bin/find /var/lib/bacula/tmp -type f \( -name \"*.ldif\" -o -name \"slapd-*\" \) -delete" } Pool = database Priority = 20 Write Bootstrap = "/var/lib/bacula/%n.bsr" } # Backup the director Job { Name = {{ inventory_hostname_short }}-dir Client = {{ inventory_hostname_short }}-fd JobDefs = DefaultJob FileSet = BaculaHome Schedule = WeeklyCycle } # Backup the mailboxes {% for h in groups.IMAP | sort %} Job { Name = {{ hostvars[h].inventory_hostname_short }}-mailboxes Client = {{ hostvars[h].inventory_hostname_short }}-fd JobDefs = DefaultJob + Accurate = no FileSet = Mailboxes Pool = mailboxes-inc Full Backup Pool = mailboxes-full Schedule = Mailboxes13WeeksCycle Max Start Delay = 50 min # To avoid too many overlaps Max Full Interval = 15 weeks } {% endfor %} +# Backup the Nextcloud data +{% for h in groups.nextcloud | sort %} +Job { + Name = {{ hostvars[h].inventory_hostname_short }}-nextcloud + Client = {{ hostvars[h].inventory_hostname_short }}-fd + JobDefs = DefaultJob + FileSet = NextcloudData + Pool = nextcloud-inc + Full Backup Pool = nextcloud-full + Schedule = Nextcloud13WeeksCycle + Max Start Delay = 50 min # To avoid too many overlaps + Max Full Interval = 15 weeks +} +{% endfor %} + # Backup each machine {% for fd in groups.all | sort %} Job { Name = {{ hostvars[fd].inventory_hostname_short }} Client = {{ hostvars[fd].inventory_hostname_short }}-fd JobDefs = DefaultJob FileSet = FileSetRoot Pool = {{ hostvars[fd].inventory_hostname_short }} Priority = 15 Schedule = WeeklyCycle } {% endfor %} -{% for fd in groups['MDA'] | union(groups['webmail']) | union(groups['lists']) | union(groups['bacula-dir']) | sort %} +{% for fd in groups['MDA'] | union(groups['webmail']) | union(groups['lists']) | union(groups['bacula_dir']) | union(groups['nextcloud']) | sort %} Job { Name = {{ hostvars[fd].inventory_hostname_short }}-mysql Client = {{ hostvars[fd].inventory_hostname_short }}-fd JobDefs = DefaultMySQLJob } {% endfor %} -{% for fd in groups['MDA'] | union(groups['MSA']) | union(groups['LDAP-provider']) | union(groups['MX']) | sort %} +{% for fd in groups['MDA'] | union(groups['MSA']) | union(groups['LDAP_provider']) | union(groups['MX']) | sort %} Job { Name = {{ hostvars[fd].inventory_hostname_short }}-slapd Client = {{ hostvars[fd].inventory_hostname_short }}-fd JobDefs = DefaultSlapdJob } {% endfor %} # # Standard Restore template, to be changed by Console program # Only one such job is needed for all Jobs/Clients/Storage ... Job { Name = RestoreFiles Type = Restore Client= {{ inventory_hostname_short }}-fd FileSet = FileSetRoot - Storage = {{ hostvars[ groups['bacula-sd'][0] ].inventory_hostname_short }}-sd + Storage = {{ hostvars[ groups['bacula_sd'][0] ].inventory_hostname_short }}-sd Pool = Default Messages = Standard # NOTE: Files are put on the client (FD) that is being restored. Where = /tmp/bacula-restores } # When to do the backups, full backup on first sunday of the month, # differential (i.e. incremental since full) every other sunday, # and incremental backups other days Schedule { Name = WeeklyCycle Run = Level=Full Messages=Quiet 1st sun at 01:05 Run = Level=Differential Messages=Quiet 2nd-5th sun at 01:05 Run = Level=Incremental Messages=Quiet mon-sat at 01:05 } # Backup mailboxes: full backup every 3 months, hourly incremental backup Schedule { Name = Mailboxes13WeeksCycle Run = Level=Full Pool=mailboxes-full w04 mon at 02:00 Run = Level=Full Pool=mailboxes-full w17 mon at 02:00 Run = Level=Full Pool=mailboxes-full w30 mon at 02:00 Run = Level=Full Pool=mailboxes-full w43 mon at 02:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 01:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet w05-w16 mon-sun at 02:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet w18-w29 mon-sun at 02:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet w31-w42 mon-sun at 02:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet w44-w03 mon-sun at 02:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 03:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 04:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 05:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 06:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 07:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 08:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 09:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 10:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 11:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 12:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 13:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 14:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 15:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 16:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 17:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 18:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 19:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 20:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 21:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 22:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 23:00 - Run = Level=Incremental Pool=mailboxes-inc FullPool=mailboxes-full Messages=Quiet mon-sun at 00:00 + Run = Level=Differential Pool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet w05-w16 mon at 02:00 + Run = Level=Differential Pool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet w18-w29 mon at 02:00 + Run = Level=Differential Pool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet w31-w42 mon at 02:00 + Run = Level=Differential Pool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet w44-w03 mon at 02:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 00:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 01:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet tue-sun at 02:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 03:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 04:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 05:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 06:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 07:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 08:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 09:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 10:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 11:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 12:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 13:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 14:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 15:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 16:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 17:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 18:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 19:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 20:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 21:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 22:00 + Run = Level=Incremental Pool=mailboxes-inc DifferentialPool=mailboxes-diff FullPool=mailboxes-full Messages=Quiet mon-sun at 23:00 +} + +# Backup Nextcloud data: full backup every 3 months, hourly incremental backup +Schedule { + Name = Nextcloud13WeeksCycle + Run = Level=Full Pool=nextcloud-full w05 mon at 02:30 + Run = Level=Full Pool=nextcloud-full w18 mon at 02:30 + Run = Level=Full Pool=nextcloud-full w31 mon at 02:30 + Run = Level=Full Pool=nextcloud-full w44 mon at 02:30 + Run = Level=Differential Pool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet w06-w17 mon at 02:30 + Run = Level=Differential Pool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet w19-w30 mon at 02:30 + Run = Level=Differential Pool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet w32-w43 mon at 02:30 + Run = Level=Differential Pool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet w45-w04 mon at 02:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 00:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 01:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet tue-sun at 02:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 03:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 04:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 05:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 06:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 07:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 08:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 09:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 10:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 11:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 12:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 13:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 14:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 15:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 16:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 17:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 18:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 19:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 20:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 21:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 22:30 + Run = Level=Incremental Pool=nextcloud-inc DifferentialPool=nextcloud-diff FullPool=nextcloud-full Messages=Quiet mon-sun at 23:30 } # This schedule does the databases. It starts after the WeeklyCycle Schedule { Name = WeeklyCycleAfterBackup Run = Level=Full Messages=Quiet sun-sat at 01:10 } # List of files to be backed up FileSet { Name = BaculaHome Include { Options { signature = SHA1 compression = GZIP verify = pins1 noatime = yes } File = /var/lib/bacula @@ -298,170 +354,270 @@ FileSet { File = /root File = /sbin File = /srv File = /usr File = /var Exclude Dir Containing = .no-backup } Exclude { File = /proc File = /sys File = /run File = /tmp File = /.journal File = /.fsck File = /.autofsck File = /net File = /mnt File = /exports File = /misc File = /media + File = /lost+found } } FileSet { Name = SQL Include { Options { signature = SHA1 compression = GZIP verify = s1 } File = /var/lib/bacula/tmp/dump.sql } } FileSet { Name = LDAP Include { Options { signature = SHA1 compression = GZIP verify = s1 WildFile = "*.ldif" } Options { Wild = "*" Exclude = yes } File = /var/lib/bacula/tmp } } FileSet { Name = Mailboxes Include { + # NOTE: debug FileSet with: + # `sudo -u bacula bconsole <<<"estimate job=mistral-mailboxes level=Full listing" | grep -F -e.{log,cache}` + # we use RegexFile here since bacula's doesn't set FNM_PATHNAME so the `*' and `?' metacharacters match `/' + Options { + Exclude = yes + + # cached mailbox data: $mail_location/mailboxes/INBOX/dbox-Mails/dovecot.index.cache + RegexFile = "^/home/mail/virtual/[^/]+/[^/]+/mail/mailboxes/([^/]+/)+dbox-Mails/dovecot\\.index\\.cache$" + # transaction log file: $mail_location/mailboxes/INBOX/dbox-Mails/dovecot.index.log + RegexFile = "^/home/mail/virtual/[^/]+/[^/]+/mail/mailboxes/([^/]+/)+dbox-Mails/dovecot\\.index\\.log(\\.[0-9])?$" + RegexFile = "^/home/mail/virtual/[^/]+/[^/]+/mail/storage/dovecot\\.map\\.index\\.log(\\.[0-9])?$" + # mailbox list index files: $mail_location/dovecot.list.index.log + RegexFile = "^/home/mail/virtual/[^/]+/[^/]+/mail/dovecot\\.list\\.index\\.log(\\.[0-9])?$" + # mailbox changelog: $mail_location/dovecot.mailbox.log + RegexFile = "^/home/mail/virtual/[^/]+/[^/]+/mail/dovecot\\.mailbox\\.log(\\.[0-9])?$" + # sieve logfile: ~/dovecot.sieve + RegexFile = "^/home/mail/virtual/[^/]+/[^/]+/dovecot\\.sieve\\.log(\\.[0-9])?$" + + # exclude queued files for SiS deduplication + Wild = "/home/mail/attachments/queue/*" + } Options { signature = SHA1 verify = pins1 } File = /home/mail/virtual + File = /home/mail/attachments File = /home/mail/spamspool } + Exclude { + File = "/home/mail/lost+found" + } +} + +FileSet { + Name = NextcloudData + Include { + Options { + Exclude = yes + RegexFile = "^/mnt/nextcloud-data/nextcloud\\.log(\\.[0-9])?$" + RegexFile = "^/mnt/nextcloud-data/updater\\.log(\\.[0-9])?$" + RegexDir = "^/mnt/nextcloud-data/[a-z0-9\\-]+/files_trashbin$" + RegexDir = "^/mnt/nextcloud-data/[a-z0-9\\-]+/files_versions$" + RegexDir = "^/mnt/nextcloud-data/[a-z0-9\\-]+/cache$" + RegexDir = "^/mnt/nextcloud-data/[a-z0-9\\-]+/uploads$" + RegexDir = "^/mnt/nextcloud-data/__groupfolders/trash$" + RegexDir = "^/mnt/nextcloud-data/__groupfolders/versions$" + RegexDir = "^/mnt/nextcloud-data/updater-[[:alnum:]]+$" + RegexDir = "^/mnt/nextcloud-data/appdata_[[:alnum:]]+/preview$" + RegexDir = "^/mnt/nextcloud-data/appdata_[[:alnum:]]+/[^/]+/cache$" + } + Options { + signature = SHA1 + verify = pins1 + } + File = /mnt/nextcloud-data + } + Exclude { + File = "/mnt/nextcloud-data/lost+found" + } } # Client (File Services) to backup -{% set n = 0 %} {% for fd in groups.all | sort %} -{% set n = n + 1 %} Client { Name = {{ hostvars[fd].inventory_hostname_short }}-fd -{% if fd == inventory_hostname %} - Address = 127.0.0.1 -{% else %} - Address = 127.0.{{ n }}.1 -{% endif %} - FDPort = 9112 + Address = {{ ipsec[ hostvars[fd].inventory_hostname_short ] }} + FDPort = 9102 Catalog = MyCatalog @|"sed -n '/^{{ hostvars[fd].inventory_hostname_short }}-fd\\s/ {s//Password = /p; q}' /etc/bacula/passwords-dir" File Retention = 4 months Job Retention = 5 months AutoPrune = yes #Maximum Bandwidth = 1mb/s } {% endfor %} # Definition of file storage device +{% for sd in groups['bacula_sd'] | sort %} Storage { - Name = {{ hostvars[ groups['bacula-sd'][0] ].inventory_hostname_short }}-sd - Address = 127.0.0.1 - SDPort = 9113 - @|"sed -n '/^{{ hostvars[ groups['bacula-sd'][0] ].inventory_hostname_short }}-sd\\s/ {s//Password = /p; q}' /etc/bacula/passwords-dir" + Name = {{ hostvars[sd].inventory_hostname_short }}-sd + Address = {{ ipsec[ hostvars[sd].inventory_hostname_short ] }} + SDPort = 9103 + @|"sed -n '/^{{ hostvars[sd].inventory_hostname_short }}-sd\\s/ {s//Password = /p; q}' /etc/bacula/passwords-dir" Device = FileStorage Media Type = File } +{% endfor %} # Default pool definition Pool { Name = Default Pool Type = Backup Recycle = yes AutoPrune = yes Volume Retention = 3 months Maximum Volume Bytes = 5GB Label Format = "Default-${NumVols:p/4/0/r}" } # Scratch pool definition Pool { Name = Scratch Pool Type = Backup Maximum Volume Bytes = 5GB Label Format = "Scratch-${NumVols:p/4/0/r}" } # System pools definition {% for h in groups.all | sort %} Pool { Name = {{ hostvars[h].inventory_hostname_short }} Pool Type = Backup Recycle = yes AutoPrune = yes Volume Retention = 3 months Maximum Volume Bytes = 5GB Label Format = "{{ hostvars[h].inventory_hostname_short }}-${NumVols:p/4/0/r}" } {% endfor %} # Mailbox pool definition (full backup) Pool { Name = mailboxes-full Pool Type = Backup Recycle = yes AutoPrune = yes - Volume Retention = 26 weeks - Maximum Volume Bytes = 5GB - Label Format = "mailboxes-full-${NumVols:p/4/0/r}" + Volume Retention = 26 weeks # >13 weeks cycle + Maximum Volume Jobs = 1 + Label Format = "mailboxes-full-" + Maximum Volumes = 3 # >2 volumes used at the end of retention period +} + +# Mailbox pool definition (diff backup) +Pool { + Name = mailboxes-diff + Pool Type = Backup + Recycle = yes + AutoPrune = yes + Volume Retention = 15 weeks # >13 weeks cycle + Maximum Volume Jobs = 1 + Label Format = "mailboxes-diff-" + Maximum Volumes = 20 # >15 volumes used at the end of retention period } # Mailbox pool definition (inc backup) Pool { Name = mailboxes-inc Pool Type = Backup Recycle = yes AutoPrune = yes - Volume Retention = 26 weeks - Maximum Volume Bytes = 5GB - Label Format = "mailboxes-inc-${NumVols:p/4/0/r}" + Volume Retention = 8 days # >1 week cycle + Maximum Volume Jobs = 24 # group by day + Label Format = "mailboxes-inc-" + Maximum Volumes = 10 # >8 volumes used at the end of retention period +} + +# Nextcloud pool definition (full backup) +Pool { + Name = nextcloud-full + Pool Type = Backup + Recycle = yes + AutoPrune = yes + Volume Retention = 26 weeks # >13 weeks cycle + Maximum Volume Jobs = 1 + Label Format = "nextcloud-full-" + Maximum Volumes = 3 # >2 volumes used at the end of retention period +} + +# Nextcloud pool definition (diff backup) +Pool { + Name = nextcloud-diff + Pool Type = Backup + Recycle = yes + AutoPrune = yes + Volume Retention = 15 weeks # >13 weeks cycle + Maximum Volume Jobs = 1 + Label Format = "nextcloud-diff-" + Maximum Volumes = 20 # >15 volumes used at the end of retention period +} + +# Nextcloud pool definition (inc backup) +Pool { + Name = nextcloud-inc + Pool Type = Backup + Recycle = yes + AutoPrune = yes + Volume Retention = 8 days # >1 week cycle + Maximum Volume Jobs = 24 # group by day + Label Format = "nextcloud-inc-" + Maximum Volumes = 10 # >8 volumes used at the end of retention period } # Database pool definition Pool { Name = database Pool Type = Backup Recycle = yes AutoPrune = yes Volume Retention = 3 months Maximum Volume Bytes = 5GB Label Format = "database-${NumVols:p/4/0/r}" } # Generic catalog service Catalog { Name = MyCatalog Password = "" DB Name = bacula User = bacula diff --git a/roles/bacula-dir/templates/etc/stunnel/bacula-dir.conf.j2 b/roles/bacula-dir/templates/etc/stunnel/bacula-dir.conf.j2 deleted file mode 100644 index aae49bc..0000000 --- a/roles/bacula-dir/templates/etc/stunnel/bacula-dir.conf.j2 +++ /dev/null @@ -1,70 +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-dir.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 }}-dir.pem -key = /etc/stunnel/certs/{{ inventory_hostname_short }}-dir.key -client = yes -socket = a:SO_BINDTODEVICE=lo - -; 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) * -; ************************************************************************** - -{% if 'bacula-sd' not in group_names %} -[{{ hostvars[ groups['bacula-sd'][0] ].inventory_hostname_short }}-sd] -accept = 127.0.{{ n }}.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 %} - -{% set n = 0 %} -{% for fd in groups.all | sort %} -{% set n = n + 1 %} -{% if fd != inventory_hostname %} -[{{ hostvars[fd].inventory_hostname_short }}-fd] -accept = 127.0.{{ n }}.1:9112 -connect = {{ fd }}:9102 -delay = yes -CAfile = /etc/stunnel/certs/{{ hostvars[fd].inventory_hostname_short }}-fd.pem -{% endif %} - -{% endfor %} - -; vim:ft=dosini diff --git a/roles/bacula-sd/files/etc/systemd/system/bacula-sd.service.d/override.conf b/roles/bacula-sd/files/etc/systemd/system/bacula-sd.service.d/override.conf new file mode 100644 index 0000000..b228078 --- /dev/null +++ b/roles/bacula-sd/files/etc/systemd/system/bacula-sd.service.d/override.conf @@ -0,0 +1,13 @@ +[Service] +# Hardening +NoNewPrivileges=yes +PrivateDevices=yes +ProtectHome=yes +ProtectSystem=strict +ReadWriteDirectories=-/var/lib/bacula +ReadWriteDirectories=/mnt/backup/bacula +PrivateDevices=yes +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +RestrictAddressFamilies=AF_INET AF_INET6 diff --git a/roles/bacula-sd/files/lib/systemd/system/bacula-sd.service b/roles/bacula-sd/files/lib/systemd/system/bacula-sd.service deleted file mode 100644 index 4c3f81d..0000000 --- a/roles/bacula-sd/files/lib/systemd/system/bacula-sd.service +++ /dev/null @@ -1,14 +0,0 @@ -[Unit] -Description=Bacula Storage Daemon service -After=network.target - -[Service] -Type=forking -PIDFile=/var/run/bacula/bacula-sd.9113.pid -StandardOutput=syslog -User=bacula -Group=tape -ExecStart=/usr/sbin/bacula-sd -c /etc/bacula/bacula-sd.conf - -[Install] -WantedBy=multi-user.target diff --git a/roles/bacula-sd/handlers/main.yml b/roles/bacula-sd/handlers/main.yml index ce391d2..3434333 100644 --- a/roles/bacula-sd/handlers/main.yml +++ b/roles/bacula-sd/handlers/main.yml @@ -1,9 +1,6 @@ --- - name: systemctl daemon-reload command: /bin/systemctl daemon-reload -- name: Restart stunnel - service: name=stunnel4 pattern=/usr/bin/stunnel4 state=restarted - - name: Restart bacula-sd service: name=bacula-sd state=restarted diff --git a/roles/bacula-sd/tasks/main.yml b/roles/bacula-sd/tasks/main.yml index 7a6c8c3..f30fe7f 100644 --- a/roles/bacula-sd/tasks/main.yml +++ b/roles/bacula-sd/tasks/main.yml @@ -1,110 +1,62 @@ -- name: Install stunnel - apt: pkg=stunnel4 - -- name: Auto-enable stunnel - lineinfile: dest=/etc/default/stunnel4 - regexp='^(\s*#)?\s*ENABLED=' - line='ENABLED=1' - owner=root group=root - mode=0644 - -- name: Create /etc/stunnel/certs - file: path=/etc/stunnel/certs - state=directory - owner=root group=root - mode=0755 - -- name: Generate a private key and a X.509 certificate for Bacula SD - command: genkeypair.sh x509 - --pubkey=/etc/stunnel/certs/{{ inventory_hostname_short }}-sd.pem - --privkey=/etc/stunnel/certs/{{ inventory_hostname_short }}-sd.key - --ou=BaculaSD --cn={{ inventory_hostname }} --dns={{ inventory_hostname }} - -t rsa -b 4096 -h sha512 - register: r1 - changed_when: r1.rc == 0 - failed_when: r1.rc > 1 - notify: - - Restart stunnel - tags: - - genkey - -- name: Fetch Bacula SD X.509 certificate - # Ensure we don't fetch private data - sudo: False - fetch: src=/etc/stunnel/certs/{{ inventory_hostname_short }}-sd.pem - dest=certs/bacula/ - fail_on_missing=yes - flat=yes - tags: - - genkey - -- name: Copy Bacula Dir/FD X.509 certificates - assemble: src=certs/bacula regexp="-(dir|fd)\.pem$" remote_src=no - dest=/etc/stunnel/certs/bacula-dir+fds.pem - owner=root group=root - mode=0644 - register: r2 - notify: - - Restart stunnel - -- name: Configure stunnel - template: src=etc/stunnel/bacula-sd.conf.j2 - dest=/etc/stunnel/bacula-sd.conf - owner=root group=root - mode=0644 - register: r3 - notify: - - Restart stunnel - -- name: Start stunnel - service: name=stunnel4 pattern=/usr/bin/stunnel4 state=started - when: not (r1.changed or r2.changed or r3.changed) - -- meta: flush_handlers - - - - name: Install bacula-sd apt: pkg=bacula-sd -# Create with: +# Populate with: # echo $director-dir $(pwgen -sn 64 1) | sudo tee -a /etc/bacula/passwords-sd - name: Ensure /etc/bacula/passwords-sd exists file: path=/etc/bacula/passwords-sd state=file owner=bacula group=bacula mode=0600 - name: Configure bacula template: src=etc/bacula/bacula-sd.conf.j2 dest=/etc/bacula/bacula-sd.conf owner=root group=root mode=0644 notify: - Restart bacula-sd -- name: Copy bacula-sd.service - copy: src=lib/systemd/system/bacula-sd.service - dest=/lib/systemd/system/bacula-sd.service +- name: Create /etc/systemd/system/bacula-sd.service.d + file: path=/etc/systemd/system/bacula-sd.service.d + state=directory + owner=root group=root + mode=0755 + +- name: Copy bacula-sd.service override + copy: src=etc/systemd/system/bacula-sd.service.d/override.conf + dest=/etc/systemd/system/bacula-sd.service.d/override.conf owner=root group=root mode=0644 notify: - systemctl daemon-reload - Restart bacula-sd +# Avoid bacula creating archives under /mnt/backup/bacula when it's +# not mounted +- name: Create directory /mnt/backup + file: path=/mnt/backup + state=directory + owner=root group=root + mode=0755 + +- name: Mount /mnt/backup + mount: src=/dev/mapper/fripost-backup + path=/mnt/backup + fstype=ext4 + opts=noauto + state=mounted + - meta: flush_handlers - name: Enable bacula-sd service: name=bacula-sd enabled=yes - name: Start bacula-sd service: name=bacula-sd state=started -# To avoid bacula creating archives under /mnt/backup/bacula when it's -# not a mountpoint, use `chmod 0700 /mnt/backup; chown root:root /mnt/backup` -# before mounting the disk. - name: Create /mnt/backup/bacula file: path=/mnt/backup/bacula state=directory owner=bacula group=tape mode=0750 diff --git a/roles/bacula-sd/templates/etc/bacula/bacula-sd.conf.j2 b/roles/bacula-sd/templates/etc/bacula/bacula-sd.conf.j2 index 7be783b..a898e0d 100644 --- a/roles/bacula-sd/templates/etc/bacula/bacula-sd.conf.j2 +++ b/roles/bacula-sd/templates/etc/bacula/bacula-sd.conf.j2 @@ -1,56 +1,56 @@ # # Default Bacula Storage Daemon Configuration file # -# For Bacula release 5.2.6 (21 February 2012) -- debian jessie/sid +# For Bacula release 9.4.2 (04 February 2019) -- debian buster/sid # # You may need to change the name of your tape drive # on the "Archive Device" directive in the Device # resource. If you change the Name and/or the # "Media Type" in the Device resource, please ensure # that dird.conf has corresponding changes. # Storage { # define myself Name = {{ inventory_hostname_short }}-sd Working Directory = /var/lib/bacula - Pid Directory = /var/run/bacula + Pid Directory = /run/bacula Maximum Concurrent Jobs = 20 - SDAddress = 127.0.0.1 - SDPort = 9113 + SDAddress = {{ ipsec[inventory_hostname_short] }} + SDPort = 9103 } # # List Directors who are permitted to contact Storage 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-sd" } # # Send all messages to the Director, # mount messages also are sent to the email address # Messages { Name = Standard director = {{ hostvars[dir].inventory_hostname_short }}-dir = all } {% endfor %} # # Devices supported by this Storage daemon # To connect, the Director's bacula-dir.conf must have the # same Name and MediaType. # Device { Name = FileStorage Media Type = File Archive Device = /mnt/backup/bacula - LabelMedia = yes; # lets Bacula label unlabeled media - Random Access = Yes; - AutomaticMount = yes; # when device opened, read it - RemovableMedia = no; - AlwaysOpen = no; + LabelMedia = Yes # lets Bacula label unlabeled media + Random Access = Yes + AutomaticMount = Yes # when device opened, read it + RemovableMedia = No + AlwaysOpen = No } diff --git a/roles/bacula-sd/templates/etc/stunnel/bacula-sd.conf.j2 b/roles/bacula-sd/templates/etc/stunnel/bacula-sd.conf.j2 deleted file mode 100644 index b193826..0000000 --- a/roles/bacula-sd/templates/etc/stunnel/bacula-sd.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/bacula-sd.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 }}-sd.pem -key = /etc/stunnel/certs/{{ inventory_hostname_short }}-sd.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 }}-sd] -client = no -accept = 9103 -connect = 127.0.0.1:9113 -CAfile = /etc/stunnel/certs/bacula-dir+fds.pem - -; vim:ft=dosini diff --git a/roles/common-LDAP/files/usr/local/sbin/slapcat-all.sh b/roles/common-LDAP/files/usr/local/sbin/slapcat-all.sh index cd5abd9..db128c9 100755 --- a/roles/common-LDAP/files/usr/local/sbin/slapcat-all.sh +++ b/roles/common-LDAP/files/usr/local/sbin/slapcat-all.sh @@ -1,20 +1,31 @@ #!/bin/sh # Usage: slapcat-all.sh DIR -# Save all LDAP databases in DIR: DIR/0.ldif, DIR/1.ldif, ... +# Save all LDAP databases in DIR: DIR/SUFFIX0.ldif, DIR/SUFFIX1.ldif, ... set -ue -PATH=/usr/sbin:/sbin:/usr/bin:/bin +PATH="/usr/bin:/bin" +export PATH -target="$1" +TARGET="$1" umask 0077 -prefix=slapcat- -slapcat -n0 -l"$target/${prefix}0.ldif" -n=$(grep -Ec '^dn:\s+olcDatabase={[1-9][0-9]*}' "$target/${prefix}0.ldif") +ldapsearch() { + command ldapsearch -H "ldapi://" -QY EXTERNAL "$@" +} -while [ $n -gt 0 ]; do - # the Monitor backend can't be slapcat(8)'ed - grep -qE "^dn:\s+olcDatabase=\{$n\}monitor,cn=config$" "$target/${prefix}0.ldif" || slapcat -n$n -l"$target/${prefix}$n.ldif" - n=$(( $n - 1 )) -done +backup_database() { + local base="$1" + ldapsearch -b "$base" \+ \* >"$TARGET/$base.ldif" +} + +backup_database "cn=config" + +SUFFIXES="$TARGET/slapd-suffixes" +ldapsearch -LLL -oldif-wrap="no" -b "cn=config" "(&(objectClass=olcDatabaseConfig)(objectClass=olcMdbConfig))" "olcSuffix" >"$SUFFIXES" +sed -n -i "s/^olcSuffix:\\s*//p" "$SUFFIXES" + +while IFS= read -r b; do + [ "${b%,dc=fripost-test,dc=org}" = "$b" ] || continue + backup_database "$b" +done <"$SUFFIXES" diff --git a/roles/common-LDAP/tasks/main.yml b/roles/common-LDAP/tasks/main.yml index aff0e58..e17bc3a 100644 --- a/roles/common-LDAP/tasks/main.yml +++ b/roles/common-LDAP/tasks/main.yml @@ -1,112 +1,120 @@ # XXX If #742056 gets fixed, we should preseed slapd to use peercreds as # RootDN once the fix enters stable. - name: Install OpenLDAP - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - slapd - ldap-utils - ldapvi - db-util - - python-ldap + - python3-ldap # for the 'slapd2' munin plugin - libnet-ldap-perl - libauthen-sasl-perl - name: Configure slapd template: src=etc/default/slapd.j2 dest=/etc/default/slapd owner=root group=root mode=0644 register: r1 notify: - Restart slapd - name: Create directory /etc/ldap/ssl file: path=/etc/ldap/ssl state=directory owner=root group=root mode=0755 tags: - genkey -# XXX: It's ugly to list all roles here, and to prunes them with a -# conditional... - name: Generate a private key and a X.509 certificate for slapd - # XXX: GnuTLS (libgnutls26 2.12.20-8+deb7u2, found in Wheezy) doesn't - # support ECDSA; and slapd doesn't seem to support DHE (!?) so - # we're stuck with "plain RSA" Key-Exchange. Also, there is a bug with - # SHA-512. command: genkeypair.sh x509 --pubkey=/etc/ldap/ssl/{{ item.name }}.pem --privkey=/etc/ldap/ssl/{{ item.name }}.key --ou=LDAP {{ item.ou }} --cn={{ item.name }} - --usage=digitalSignature,keyEncipherment,keyCertSign - -t rsa -b 4096 -h sha256 - --chown="root:openldap" --chmod=0640 + --usage=digitalSignature,keyEncipherment + -t ed25519 + --owner=root --group=openldap --mode=0640 register: r2 changed_when: r2.rc == 0 failed_when: r2.rc > 1 with_items: - - { group: 'LDAP-provider', name: ldap.fripost.org, ou: } + - { group: 'LDAP_provider', name: ldap.fripost.org, ou: } - { group: 'MX', name: mx, ou: --ou=SyncRepl } - { group: 'lists', name: lists, ou: --ou=SyncRepl } when: "item.group in group_names" + notify: + - Restart slapd + tags: + - genkey + +- name: Fetch the SyncProv's X.509 certificate + # Ensure we don't fetch private data + become: False + fetch_cmd: cmd="openssl x509" + stdin=/etc/ldap/ssl/ldap.fripost.org.pem + dest=certs/ldap/ldap.fripost.org.pem + when: "'LDAP_provider' in group_names" tags: - genkey - name: Fetch slapd's X.509 certificate # Ensure we don't fetch private data - sudo: False - fetch: src=/etc/ldap/ssl/{{ item.name }}.pem - dest=certs/ldap/ - fail_on_missing=yes - flat=yes + become: False + fetch_cmd: cmd="openssl x509" + stdin=/etc/ldap/ssl/{{ item.name }}.pem + dest=certs/ldap/syncrepl/{{ item.name }}@{{ inventory_hostname_short }}.pem with_items: - - { group: 'LDAP-provider', name: ldap.fripost.org } - { group: 'MX', name: mx } - { group: 'lists', name: lists } when: "item.group in group_names" tags: - genkey - name: Copy the SyncProv's server certificate copy: src=certs/ldap/ldap.fripost.org.pem dest=/etc/ldap/ssl/ldap.fripost.org.pem owner=root group=root mode=0644 - when: "'LDAP-provider' not in group_names" + when: "'LDAP_provider' not in group_names" tags: - genkey - name: Copy the SyncRepls's client certificates - assemble: src=certs/ldap remote_src=no - dest=/etc/ldap/ssl/clients.pem + assemble: src=certs/ldap/syncrepl remote_src=no + dest=/etc/ldap/ssl/syncrepl.pem owner=root group=root mode=0644 - when: "'LDAP-provider' in group_names" + when: "'LDAP_provider' in group_names" tags: - genkey + register: r3 + notify: + - Restart slapd - name: Start slapd service: name=slapd state=started - when: not (r1.changed or r2.changed) + when: not (r1.changed or r2.changed or r3.changed) - meta: flush_handlers - name: Copy fripost & amavis' schema copy: src=etc/ldap/schema/{{ item }} dest=/etc/ldap/schema/{{ item }} owner=root group=root mode=0644 # It'd certainly be nicer if we didn't have to deploy amavis' schema # everywhere, but we need the 'objectClass' in our replicates, hence # they need to be aware of the 'amavisAccount' class. with_items: - fripost.ldif - amavis.schema tags: - amavis - name: Load amavis' schema openldap: target=/etc/ldap/schema/amavis.schema format=slapd.conf name=amavis @@ -116,36 +124,36 @@ - name: Load the back_monitor overlay openldap: module=back_monitor # We assume a clean (=stock) cn=config - name: Configure the LDAP database openldap: target=etc/ldap/database.ldif.j2 local=template # On read-only replicates, you might have to temporarily switch back to # read-write, delete the SyncRepl, and delete the DN manually: # sudo ldapdelete -Y EXTERNAL -H ldapi:// cn=admin,dc=fripost,dc=org - name: Remove cn=admin,dc=fripost,dc=org openldap: name="cn=admin,dc=fripost,dc=org" delete=entry - name: Remove the rootDN under the 'config' database openldap: name="olcDatabase={0}config,cn=config" delete=olcRootDN,olcRootPW - name: Copy /usr/local/sbin/slapcat-all.sh copy: src=usr/local/sbin/slapcat-all.sh dest=/usr/local/sbin/slapcat-all.sh - owner=root group=root + owner=root group=staff mode=0755 - name: Install 'slapd2' Munin plugin # we don't install 'slapd_' because it doesn't support SASL binds and # ours is more parcimonious with LDAP connections file: src=/usr/local/share/munin/plugins/slapd2 dest=/etc/munin/plugins/slapd2 owner=root group=root state=link force=yes tags: - munin - munin-node notify: - Restart munin-node diff --git a/roles/common-LDAP/templates/etc/default/slapd.j2 b/roles/common-LDAP/templates/etc/default/slapd.j2 index 80c1be1..dd3f87e 100644 --- a/roles/common-LDAP/templates/etc/default/slapd.j2 +++ b/roles/common-LDAP/templates/etc/default/slapd.j2 @@ -3,44 +3,44 @@ # /etc/ldap/slapd.conf). SLAPD_CONF= # System account to run the slapd server under. If empty the server # will run as root. SLAPD_USER="openldap" # System group to run the slapd server under. If empty the server will # run in the primary group of its user. SLAPD_GROUP="openldap" # Path to the pid file of the slapd server. If not set the init.d script # will try to figure it out from $SLAPD_CONF (/etc/ldap/slapd.conf by # default) SLAPD_PIDFILE= # slapd normally serves ldap only on all TCP-ports 389. slapd can also # service requests on TCP-port 636 (ldaps) and requests via unix # sockets. SLAPD_SERVICES="ldapi:///" -{% for i in group_names | intersect(['MX','lists']) | sort %} +{% for i in group_names | intersect(['MX','lists','MSA']) | sort %} SLAPD_SERVICES="$SLAPD_SERVICES ldapi://%2Fvar%2Fspool%2Fpostfix-{{ postfix_instance[i].name }}%2Fprivate%2Fldapi/" {% endfor %} -{% if 'LDAP-provider' in group_names %} +{% if 'LDAP_provider' in group_names %} SLAPD_SERVICES="$SLAPD_SERVICES ldaps:///" {% endif %} # If SLAPD_NO_START is set, the init script will not start or restart # slapd (but stop will still work). Uncomment this if you are # starting slapd via some other means or if you don't want slapd normally # started at boot. #SLAPD_NO_START=1 # If SLAPD_SENTINEL_FILE is set to path to a file and that file exists, # the init script will not start or restart slapd (but stop will still # work). Use this for temporarily disabling startup of slapd (when doing # maintenance, for example, or through a configuration management system) # when you don't want to edit a configuration file. SLAPD_SENTINEL_FILE=/etc/ldap/noslapd # For Kerberos authentication (via SASL), slapd by default uses the system # keytab file (/etc/krb5.keytab). To use a different keytab file, # uncomment this line and change the path. #export KRB5_KTNAME=/etc/krb5.keytab diff --git a/roles/common-LDAP/templates/etc/ldap/database.ldif.j2 b/roles/common-LDAP/templates/etc/ldap/database.ldif.j2 index 8310818..a0ac705 100644 --- a/roles/common-LDAP/templates/etc/ldap/database.ldif.j2 +++ b/roles/common-LDAP/templates/etc/ldap/database.ldif.j2 @@ -1,157 +1,159 @@ # Fripost's LDAP database definition # Copyright (c) 2013-2014 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/>. dn: cn=config objectClass: olcGlobal cn: config -olcArgsFile: /var/run/slapd/slapd.args -olcPidFile: /var/run/slapd/slapd.pid +olcArgsFile: /run/slapd/slapd.args +olcPidFile: /run/slapd/slapd.pid olcLogLevel: none olcToolThreads: 1 {% if ansible_processor_vcpus > 4 %} olcThreads: {{ 2 * ansible_processor_vcpus }} {% else %} olcThreads: 8 {% endif %} -{% if 'LDAP-provider' in group_names %} +{% if 'LDAP_provider' in group_names %} olcTLSCertificateFile: /etc/ldap/ssl/ldap.fripost.org.pem olcTLSCertificateKeyFile: /etc/ldap/ssl/ldap.fripost.org.key # If we are being offered a client cert, it has to be trusted (in which # case we map the X.509 subject to a DN in our namespace), or we # terminate the connection. Not providing a certificate is fine for # TLS-protected simple binds, though. olcTLSVerifyClient: try -olcTLSCACertificateFile: /etc/ldap/ssl/clients.pem +olcTLSCACertificateFile: /etc/ldap/ssl/syncrepl.pem olcAuthzRegexp: "^(cn=[^,]+,ou=syncRepl),ou=LDAP,ou=SSLcerts,o=Fripost$" - "$1,dc=fripost,dc=org" + "dn.exact:$1,dc=fripost,dc=org" olcSaslSecProps: minssf=128,noanonymous,noplain,nodict olcTLSCipherSuite: PFS:%LATEST_RECORD_VERSION:!CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:!VERS-SSL3.0:!VERS-TLS1.0:!VERS-TLS1.1 +olcTLSDHParamFile: /etc/ssl/dhparams.pem {% endif %} olcLocalSSF: 128 # /!\ This is not portable! But we only use glibc's crypt(3), which # supports (salted, streched) SHA512 olcPasswordHash: {CRYPT} olcPasswordCryptSaltFormat: $6$%s dn: olcDatabase=monitor,cn=config objectClass: olcDatabaseConfig objectClass: olcMonitorConfig olcRootDN: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth olcAccess: to dn.subtree="cn=monitor" by dn.exact="username=munin,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" read by * =0 dn: olcDatabase=mdb,cn=config objectClass: olcDatabaseConfig objectClass: olcMdbConfig olcDbDirectory: /var/lib/ldap olcSuffix: dc=fripost,dc=org -{% if 'LDAP-provider' not in group_names and 'MX' in group_names %} +{% if 'LDAP_provider' not in group_names and 'MX' in group_names %} olcReadOnly: TRUE {% endif %} -{% if 'LDAP-provider' in group_names %} +{% if 'LDAP_provider' in group_names %} olcLastMod: TRUE olcDbCheckpoint: 512 15 {% else %} olcLastMod: FALSE {% endif %} # The root user has all rights on the whole database (when SASL-binding # on a UNIX socket). olcRootDN: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth # Ensure that all DIT access is made according to the LDAPv3 protocol, # and must use 1/ authentication, and 2/ SASL or TLS. (Local clients # should use ldapi:// and SASL/EXERNAL, while remote clients should use # TLS.) olcRequires: none LDAPv3 authc strong olcSecurity: simple_bind=128 ssf=128 update_ssf=128 # # ######################################################################## # Performance considerations # # To reindex an existing database, you have to # * Stop slapd sudo service slapd stop # * Reindex sudo -u openldap slapindex -b 'dc=fripost,dc=org' # * Restart slapd sudo service slapd start # olcDbIndex: objectClass eq # Let us make Postfix's life easier. -{% if 'LDAP-provider' in group_names %} +{% if 'LDAP_provider' in group_names %} olcDbIndex: fvd,fvl eq,sub olcDbIndex: fripostIsStatusActive eq {% elif 'MX' in group_names or 'MDA' in group_names %} olcDbIndex: fripostIsStatusActive,fvd,fvl eq {% endif %} -{% if 'LDAP-provider' in group_names %} +{% if 'LDAP_provider' in group_names %} olcDbIndex: fripostOptionalMaildrop,fripostMaildrop eq,sub olcDbIndex: fripostCanAddDomain,fripostCanAddAlias,fripostCanAddList,fripostOwner,fripostPostmaster,fripostListManager eq {% elif 'MX' in group_names %} olcDbIndex: fripostOptionalMaildrop pres {% endif %} -{% if 'LDAP-provider' in group_names %} +{% if 'LDAP_provider' in group_names %} +olcDbIndex: member,cn eq {% endif %} -{% if ('LDAP-provider' not in group_names and 'MX' in group_names) or - ('LDAP-provider' in group_names and groups.MX | difference([inventory_hostname])) %} +{% if ('LDAP_provider' not in group_names and 'MX' in group_names) or + ('LDAP_provider' in group_names and groups.MX | difference([inventory_hostname])) %} # SyncProv/SyncRepl specific indexing. olcDbIndex: entryCSN,entryUUID eq {% endif%} # # # References # - https://wiki.zimbra.com/wiki/OpenLDAP_Performance_Tuning_5.0 # - http://www.openldap.org/doc/admin24/tuning.html # - http://www.openldap.org/faq/data/cache/42.html # - http://www.openldap.org/faq/data/cache/136.html # - http://www.zytrax.com/books/ldap/apa/indeces.html # # ######################################################################## # Sync Replication # # References: # - http://www.openldap.org/doc/admin24/replication.html#Syncrepl # - http://www.zytrax.com/books/ldap/ch7/#ol-syncrepl-rap # -{% if 'LDAP-provider' in group_names %} +{% if 'LDAP_provider' in group_names %} olcLimits: dn.onelevel="ou=syncRepl,dc=fripost,dc=org" time.soft=unlimited time.hard=unlimited size.soft=unlimited size.hard=unlimited {% endif %} -{% if 'MX' in group_names and 'LDAP-provider' not in group_names %} +{% if 'MX' in group_names and 'LDAP_provider' not in group_names %} # Test it: # LDAPSASL_MECH=external LDAPTLS_CACERT=/etc/ldap/ssl/ldap.fripost.org.pem LDAPTLS_CERT=/etc/ldap/ssl/mx.pem LDAPTLS_KEY=/etc/ldap/ssl/mx.key sudo -u openldap ldapwhoami -H ldaps://ldap.fripost.org/ # LDAPSASL_MECH=external LDAPTLS_CACERT=/etc/ldap/ssl/ldap.fripost.org.pem LDAPTLS_CERT=/etc/ldap/ssl/mx.pem LDAPTLS_KEY=/etc/ldap/ssl/mx.key sudo -u openldap ldapsearch -H ldaps://ldap.fripost.org/ -b ou=virtual,dc=fripost,dc=org olcSyncrepl: rid=000 provider=ldaps://ldap.fripost.org type=refreshAndPersist retry="10 30 300 +" searchbase="ou=virtual,dc=fripost,dc=org" attrs=objectClass,fvd,fvl,fripostIsStatusActive,fripostMaildrop,fripostOptionalMaildrop,fripostPostmaster,fripostOwner,fripostUseContentFilter,fripostListManager scope=sub sizelimit=unlimited schemachecking=off bindmethod=sasl saslmech=external tls_cert=/etc/ldap/ssl/mx.pem tls_key=/etc/ldap/ssl/mx.key tls_cacert=/etc/ldap/ssl/ldap.fripost.org.pem tls_reqcert=hard {% endif %} # @@ -196,300 +198,363 @@ olcAddContentAcl: TRUE # local ldapi:// socket (when using auth_binds, Dovecot delegates # authentication to the LDAP server). # * Authenticated users are allowed to change (ie replace) their # password through TLS-protected connections, but read access is not # granted. # * Domain postmasters are allowed to change (ie replace) their users' # password through TLS-protected connections, but read access is not # granted. # * The same goes for general admins. # * The same goes for local admins. olcAccess: to dn.regex="^fvl=[^,]+,(fvd=[^,]+,ou=virtual,dc=fripost,dc=org)$" filter=(objectClass=FripostVirtualUser) attrs=userPassword by realanonymous tls_ssf=128 =xd by realanonymous sockurl.regex="^ldapi://" =xd by realself tls_ssf=128 =w by group/FripostVirtualDomain/fripostPostmaster.expand="$1" tls_ssf=128 =w by dn.onelevel="ou=admins,dc=fripost,dc=org" tls_ssf=128 =w by group.exact="cn=admin,ou=groups,dc=fripost,dc=org" =w # -# TODO: are there other services which need to be able to simple bind? +# * Services can authenticate +{% if 'LDAP_provider' in group_names -%} +olcAccess: to dn.onelevel="ou=services,dc=fripost,dc=org" + filter=(objectClass=simpleSecurityObject) + attrs=userPassword + by realanonymous tls_ssf=128 =xd +{% endif -%} # # * Catch-all: no one else may access the passwords (including for # simple bind). olcAccess: to dn.subtree="dc=fripost,dc=org" attrs=userPassword by * =0 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Base # # * Only SyncRepl replicates may access operational attributes in the # subtree, when using a TLS-protected connection. -{% if 'LDAP-provider' in group_names -%} +{% if 'LDAP_provider' in group_names -%} olcAccess: to dn.subtree="ou=virtual,dc=fripost,dc=org" - attrs=entryDN,entryCSN,entryUUID,structuralObjectClass,hasSubordinates,subschemaSubentry + attrs=entryCSN,structuralObjectClass,hasSubordinates,subschemaSubentry by dn.onelevel="ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd by * =0 # # * They may also read entries (ie, the attributes they have access to # as per the ACL below) in that subtree, when using a TLS-protected # connection. Listing entries (their DN) is required to replicate # deletions properly. olcAccess: to dn.subtree="ou=virtual,dc=fripost,dc=org" attrs=entry,objectClass by dn.onelevel="ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd by group.exact="cn=admin,ou=groups,dc=fripost,dc=org" =wrsd by users =0 break olcAccess: to dn.children="ou=virtual,dc=fripost,dc=org" by group.exact="cn=admin,ou=groups,dc=fripost,dc=org" =wrsd by users =0 break {% endif -%} # # * Postfix may use the base as a searchBase on the MX:es, when # connecting a local ldapi:// socket from the 'private' directory in # one of the non-default instance's chroot. -# * So may Dovecot on the MDA (needed for the iterate filter), when -# SASL-binding using the EXTERNAL mechanism and connecting to a local -# ldapi:// socket. +# * So may _dovecot-auth-proxy on the MDA (needed for the iterate +# logic), when SASL-binding using the EXTERNAL mechanism and +# connecting to a local ldapi:// socket. +# * So may Nextcloud on the LDAP provider olcAccess: to dn.exact="ou=virtual,dc=fripost,dc=org" attrs=entry,objectClass filter=(objectClass=FripostVirtual) {% if 'MDA' in group_names -%} - by dn.exact="username=dovecot,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =sd + by dn.exact="username=_dovecot-auth-proxy,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =sd {% endif -%} {% if 'MX' in group_names -%} by dn.exact="username=postfix,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =sd {% endif -%} + {% if 'MSA' in group_names -%} + by dn.exact="username=_postfix-sender-login,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =sd + {% endif -%} + {% if 'LDAP_provider' in group_names -%} + by dn.exact="cn=nextcloud,ou=services,dc=fripost,dc=org" tls_ssf=128 =sd + {% endif -%} by users =0 break # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Domain entries # # * The SyncRepl replicates have read access to the entry itself, when # using a TLS-protected connection. # * So has Postfix, when connecting a local ldapi:// socket from the # 'private' directory in one of the non-default instance's chroot. -# * So has Dovecot on the MDA (for the iterate filter), when -# SASL-binding using the EXTERNAL mechanism and connecting to a local -# ldapi:// socket. -# * Amavis may use the entry as searchBase (required to look for the -# per-user preferences) but doesn't have read access to the entry. # * The 'nobody' UNIX user has read access on the MX:es, when using # SASL-binding using the EXTERNAL mechanism and connecting to a local # ldapi:// socket. This is required for the 'reserved-alias.pl' # script. +# * Amavis may use the entry as searchBase (required to look for the +# per-user preferences) but doesn't have read access to the entry. +# * So has _dovecot-auth-proxy on the MDA (for the iterate logic), when +# SASL-binding using the EXTERNAL mechanism and connecting to a local +# ldapi:// socket. +# * So has _postfix-sender-login on the submission service to verify +# envelope sender ownership olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=entry,objectClass,fvd filter=(&(objectClass=FripostVirtualDomain)(!(objectClass=FripostPendingEntry))) - {% if 'LDAP-provider' in group_names -%} - {% if groups.MX | difference([inventory_hostname]) -%} + {% if 'LDAP_provider' in group_names and groups.MX | difference([inventory_hostname]) -%} by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} - {% endif -%} + {% if 'MX' in group_names -%} by dn.exact="username=postfix,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd + by dn.exact="username=nobody,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd + {% endif -%} {% if 'MDA' in group_names -%} - by dn.exact="username=dovecot,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd by dn.exact="username=amavis,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =sd {% endif -%} - {% if 'MX' in group_names -%} - by dn.exact="username=nobody,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd + {% if 'IMAP' in group_names -%} + by dn.exact="username=_dovecot-auth-proxy,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd + {% endif -%} + {% if 'MSA' in group_names -%} + by dn.exact="username=_postfix-sender-login,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd {% endif -%} by users =0 break # # * The SyncRepl MX replicates can check whether a virtual domain is # active, and read the destination address for catch-alls, when using # a TLS-protected connection. # * So can Postfix on the MX:es, when connecting a local ldapi:// socket # from the 'private' directory in one of the non-default instance's # chroot. -{% if 'MX' in group_names or ('LDAP-provider' in group_names and groups.MX | difference([inventory_hostname])) %} +{% if 'MX' in group_names or ('LDAP_provider' in group_names and groups.MX | difference([inventory_hostname])) %} olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=fripostIsStatusActive,fripostOptionalMaildrop filter=(&(objectClass=FripostVirtualDomain)(!(objectClass=FripostPendingEntry))) - {% if 'LDAP-provider' in group_names and groups.MX | difference([inventory_hostname]) -%} + {% if 'LDAP_provider' in group_names and groups.MX | difference([inventory_hostname]) -%} by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% if 'MX' in group_names -%} by dn.exact="username=postfix,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd {% endif -%} by users =0 break {% endif %} # # * The 'nobody' UNIX user can list the domain owners and postmasters on # the MX:es, when SASL-binding using the EXTERNAL mechanism and # connecting to a local ldapi:// socket. This is required for the # 'reserved-alias.pl' script. olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=fripostOwner,fripostPostmaster filter=(&(objectClass=FripostVirtualDomain)(!(objectClass=FripostPendingEntry))) - {% if 'LDAP-provider' in group_names and groups.MX | difference([inventory_hostname]) -%} + {% if 'LDAP_provider' in group_names and groups.MX | difference([inventory_hostname]) -%} by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% if 'MX' in group_names %} by dn.exact="username=nobody,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd {% endif -%} by users =0 break # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Alias domain entries # # * The SyncRepl MX replicates have read access to the entry itself and # the destination domain it aliases to, when using a TLS-protected # connection. # * So has Postfix on the MX:es, when connecting a local ldapi:// socket # from the 'private' directory in one of the non-default instance's # chroot. -{% if 'MX' in group_names or ('LDAP-provider' in group_names and groups.MX | difference([inventory_hostname])) %} +{% if 'MX' in group_names or ('LDAP_provider' in group_names and groups.MX | difference([inventory_hostname])) %} olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=entry,fripostMaildrop filter=(&(objectClass=FripostVirtualAliasDomain)(!(objectClass=FripostPendingEntry))) - {% if 'LDAP-provider' in group_names and groups.MX | difference([inventory_hostname]) -%} + {% if 'LDAP_provider' in group_names and groups.MX | difference([inventory_hostname]) -%} by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% if 'MX' in group_names -%} by dn.exact="username=postfix,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd {% endif -%} by users =0 break {% endif %} # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # User entries # # * The SyncRepl replicates have read access to the entry itself, when # using a TLS-protected connection. # * So has Postfix, when connecting a local ldapi:// socket from the # 'private' directory in one of the non-default instance's chroot. -# * So has Dovecot on the MDA (for the iterate filter), when +# * So has _dovecot-auth-proxy on the MDA (for the iterate logic), when # SASL-binding using the EXTERNAL mechanism and connecting to a local # ldapi:// socket. # * So has Amavis on the MDA, when SASL-binding using the EXTERNAL # mechanism and connecting to a local ldapi:// socket. olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=entry,objectClass,fvl filter=(objectClass=FripostVirtualUser) - {% if 'LDAP-provider' in group_names and groups.MX | difference([inventory_hostname]) -%} + {% if 'LDAP_provider' in group_names and groups.MX | difference([inventory_hostname]) -%} by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} + {% if 'MX' in group_names -%} by dn.exact="username=postfix,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd + {% endif -%} {% if 'MDA' in group_names -%} - by dn.exact="username=dovecot,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd by dn.exact="username=amavis,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd {% endif -%} + {% if 'IMAP' in group_names -%} + by dn.exact="username=_dovecot-auth-proxy,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd + {% endif -%} + {% if 'MSA' in group_names -%} + by dn.exact="username=_postfix-sender-login,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd + {% endif -%} by users =0 break # # * The SyncRepl MX replicates can check whether a virtual user is # active, when using a TLS-protected connection. # * So can Postfix on the MX:es, when connecting a local ldapi:// socket # from the 'private' directory in one of the non-default instance's # chroot. -{% if 'MX' in group_names or ('LDAP-provider' in group_names and groups.MX | difference([inventory_hostname])) %} +{% if 'MX' in group_names or ('LDAP_provider' in group_names and groups.MX | difference([inventory_hostname])) %} olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=fripostIsStatusActive,fripostUseContentFilter filter=(objectClass=FripostVirtualUser) - {% if 'LDAP-provider' in group_names and groups.MX | difference([inventory_hostname]) -%} + {% if 'LDAP_provider' in group_names and groups.MX | difference([inventory_hostname]) -%} by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% if 'MX' in group_names -%} by dn.exact="username=postfix,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd {% endif -%} by users =0 break {% endif %} {% if 'MDA' in group_names %} # # * Amavis can look for per-user configuration options, when # SASL-binding using the EXTERNAL mechanism and connecting to a local # ldapi:// socket. # TODO: only allow it to read the configuration options users are allowed # to set and modify. olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=@AmavisAccount filter=(&(objectClass=FripostVirtualUser)(objectClass=AmavisAccount)(fripostIsStatusActive=TRUE)(fripostUseContentFilter=TRUE)) by dn.exact="username=amavis,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd by users =0 break # # * Dovecot can look for user quotas, when SASL-binding using the # EXTERNAL mechanism and connecting to a local ldapi:// socket. olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=fripostUserQuota filter=(objectClass=FripostVirtualUser) by dn.exact="username=dovecot,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd by users =0 break {% endif %} # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Alias entries # # * The SyncRepl MX replicates can read the entry itelf, whether it # is active, and the address(es) it aliases to, when using a # TLS-protected connection. # * So can Postfix on the MX:es, when connecting a local ldapi:// socket # from the 'private' directory in one of the non-default instance's # chroot. -{% if 'MX' in group_names or ('LDAP-provider' in group_names and groups.MX | difference([inventory_hostname])) %} +{% if 'MX' in group_names or ('LDAP_provider' in group_names and groups.MX | difference([inventory_hostname])) %} olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=entry,objectClass,fvl,fripostMaildrop,fripostIsStatusActive filter=(objectClass=FripostVirtualAlias) - {% if 'LDAP-provider' in group_names and groups.MX | difference([inventory_hostname]) -%} + {% if 'LDAP_provider' in group_names and groups.MX | difference([inventory_hostname]) -%} by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% if 'MX' in group_names -%} by dn.exact="username=postfix,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd {% endif -%} by users =0 break {% endif %} # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # List entries # # * The SyncRepl replicates can read the entry itelf and the list manager, when # using a TLS-protected connection. # * So can Postfix on the MX:es, when connecting a local ldapi:// socket # from the 'private' directory in one of the non-default instance's chroot. -{% if 'MX' in group_names or ('LDAP-provider' in group_names and groups.MX | difference([inventory_hostname])) %} +{% if 'MX' in group_names or ('LDAP_provider' in group_names and groups.MX | difference([inventory_hostname])) %} olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=entry,objectClass,fvl,fripostListManager filter=(&(objectClass=FripostVirtualList)(!(objectClass=FripostPendingEntry))) - {% if 'LDAP-provider' in group_names and groups.MX | difference([inventory_hostname]) -%} + {% if 'LDAP_provider' in group_names and groups.MX | difference([inventory_hostname]) -%} by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% if 'MX' in group_names -%} by dn.exact="username=postfix,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd {% endif -%} by users =0 break {% endif %} # # * The SyncRepl MX replicates can check whether a virtual list is # active when using a TLS-protected connection. # * So can Postfix on the MX:es, when connecting a local ldapi:// socket # from the 'private' directory in one of the non-default instance's # chroot. -{% if 'MX' in group_names or ('LDAP-provider' in group_names and groups.MX | difference([inventory_hostname])) %} +{% if 'MX' in group_names or ('LDAP_provider' in group_names and groups.MX | difference([inventory_hostname])) %} olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" attrs=fripostIsStatusActive filter=(&(objectClass=FripostVirtualList)(!(objectClass=FripostPendingEntry))) - {% if 'LDAP-provider' in group_names and groups.MX | difference([inventory_hostname]) -%} + {% if 'LDAP_provider' in group_names and groups.MX | difference([inventory_hostname]) -%} by dn.exact="cn=mX,ou=syncRepl,dc=fripost,dc=org" tls_ssf=128 =rsd {% endif -%} {% if 'MX' in group_names -%} by dn.exact="username=postfix,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://%2Fvar%2Fspool%2Fpostfix-[-[:alnum:]]+%2Fprivate%2F" =rsd {% endif -%} by users =0 break {% endif %} -{% if 'LDAP-provider' in group_names %} +# +# * The MSA's _postfix-sender-login user can read entry ownership to +# dermine the SASL login name(s) owning a given sender address +{% if 'MSA' in group_names %} +olcAccess: to dn.regex="^fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" + attrs=fripostOwner,fripostPostmaster + filter=(|(objectClass=FripostVirtualAliasDomain)(objectClass=FripostVirtualDomain)) + by dn.exact="username=_postfix-sender-login,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd + by users =0 break +olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$" + attrs=entry,objectClass,fvl,fripostOwner + filter=(|(objectClass=FripostVirtualAlias)(objectClass=FripostVirtualList)(objectClass=FripostVirtualUser)) + by dn.exact="username=_postfix-sender-login,cn=peercred,cn=external,cn=auth" sockurl.regex="^ldapi://" =rsd + by users =0 break +{% endif %} +{% if 'LDAP_provider' in group_names %} # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# +# Export Fripost members to Nextcloud +olcAccess: to dn.exact="fvd=fripost.org,ou=virtual,dc=fripost,dc=org" + attrs=entry,objectClass,fvd + filter=(&(objectClass=FripostVirtualDomain)(!(objectClass=FripostPendingEntry))) + by dn.exact="cn=nextcloud,ou=services,dc=fripost,dc=org" tls_ssf=128 =rsd + by users =0 break +olcAccess: to dn.regex="^fvl=[^,]+,fvd=fripost.org,ou=virtual,dc=fripost,dc=org$" + attrs=entry,entryDN,entryUUID,objectClass,fvl,fripostIsStatusActive + filter=(&(objectClass=FripostVirtualUser)(!(objectClass=FripostPendingEntry))(fripostIsStatusActive=TRUE)) + by dn.exact="cn=nextcloud,ou=services,dc=fripost,dc=org" tls_ssf=128 =rsd + by users =0 break +olcAccess: to dn.exact="ou=groups,dc=fripost,dc=org" + attrs=entry,objectClass + by dn.exact="cn=nextcloud,ou=services,dc=fripost,dc=org" tls_ssf=128 =rsd + by users =0 break +olcAccess: to dn.exact="cn=medlemmar,ou=groups,dc=fripost,dc=org" + by dn.exact="cn=nextcloud,ou=services,dc=fripost,dc=org" tls_ssf=128 =rsd + by users =0 break +olcAccess: to dn.exact="cn=styrelse,ou=groups,dc=fripost,dc=org" + by dn.exact="cn=nextcloud,ou=services,dc=fripost,dc=org" tls_ssf=128 =rsd + by users =0 break +# # TODO: allow users to edit their entry, etc # {% endif %} # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Catch-all # # * Catch all the breaks above. # * Deny any access to everyone else. olcAccess: to dn.subtree="dc=fripost,dc=org" by dn.children="ou=virtual,dc=fripost,dc=org" +0 by * =0 # vim: set filetype=ldif : diff --git a/roles/common-SQL/files/etc/mysql/mariadb.conf.d/99-user.cnf b/roles/common-SQL/files/etc/mysql/mariadb.conf.d/99-user.cnf new file mode 100644 index 0000000..f3323f9 --- /dev/null +++ b/roles/common-SQL/files/etc/mysql/mariadb.conf.d/99-user.cnf @@ -0,0 +1,4 @@ +[mysqld] +skip-networking +innodb_file_per_table +innodb_flush_method = O_DIRECT diff --git a/roles/common-SQL/files/etc/mysql/my.cnf b/roles/common-SQL/files/etc/mysql/my.cnf deleted file mode 100644 index 909236d..0000000 --- a/roles/common-SQL/files/etc/mysql/my.cnf +++ /dev/null @@ -1,129 +0,0 @@ -# -# The MySQL database server configuration file. -# -# You can copy this to one of: -# - "/etc/mysql/my.cnf" to set global options, -# - "~/.my.cnf" to set user-specific options. -# -# One can use all long options that the program supports. -# Run program with --help to get a list of available options and with -# --print-defaults to see which it would actually understand and use. -# -# For explanations see -# http://dev.mysql.com/doc/mysql/en/server-system-variables.html - -# This will be passed to all mysql clients -# It has been reported that passwords should be enclosed with ticks/quotes -# escpecially if they contain "#" chars... -# Remember to edit /etc/mysql/debian.cnf when changing the socket location. -[client] -port = 3306 -socket = /var/run/mysqld/mysqld.sock - -# Here is entries for some specific programs -# The following values assume you have at least 32M ram - -# This was formally known as [safe_mysqld]. Both versions are currently parsed. -[mysqld_safe] -socket = /var/run/mysqld/mysqld.sock -nice = 0 - -[mysqld] -# -# * Basic Settings -# -user = mysql -pid-file = /var/run/mysqld/mysqld.pid -socket = /var/run/mysqld/mysqld.sock -plugin-load = auth_socket=auth_socket.so -port = 3306 -basedir = /usr -datadir = /var/lib/mysql -tmpdir = /tmp -lc-messages-dir = /usr/share/mysql -character_set_server = utf8 -collation_server = utf8_unicode_ci -skip-external-locking -# -# Instead of skip-networking the default is now to listen only on -# localhost which is more compatible and is not less secure. -#bind-address = 127.0.0.1 -skip-networking -# -# * Fine Tuning -# -key_buffer_size = 16M -max_allowed_packet = 16M -thread_stack = 192K -thread_cache_size = 8 -# This replaces the startup script and checks MyISAM tables if needed -# the first time they are touched -myisam-recover = BACKUP -#max_connections = 100 -#table_cache = 64 -#thread_concurrency = 10 -# -# * Query Cache Configuration -# -query_cache_limit = 1M -query_cache_size = 16M -# -# * Logging and Replication -# -# Both location gets rotated by the cronjob. -# Be aware that this log type is a performance killer. -# As of 5.1 you can enable the log at runtime! -#general_log_file = /var/log/mysql/mysql.log -#general_log = 1 -# -# Error logging goes to syslog due to /etc/mysql/conf.d/mysqld_safe_syslog.cnf. -# -# Here you can see queries with especially long duration -#log_slow_queries = /var/log/mysql/mysql-slow.log -#long_query_time = 2 -#log-queries-not-using-indexes -# -# The following can be used as easy to replay backup logs or for replication. -# note: if you are setting up a replication slave, see README.Debian about -# other settings you may need to change. -#server-id = 1 -#log_bin = /var/log/mysql/mysql-bin.log -expire_logs_days = 10 -max_binlog_size = 100M -#binlog_do_db = include_database_name -#binlog_ignore_db = include_database_name -# -# * InnoDB -# -# InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/. -# Read the manual for more InnoDB related options. There are many! -# -# * Security Features -# -# Read the manual, too, if you want chroot! -# chroot = /var/lib/mysql/ -# -# For generating SSL certificates I recommend the OpenSSL GUI "tinyca". -# -# ssl-ca=/etc/mysql/cacert.pem -# ssl-cert=/etc/mysql/server-cert.pem -# ssl-key=/etc/mysql/server-key.pem - - - -[mysqldump] -quick -quote-names -max_allowed_packet = 16M - -[mysql] -#no-auto-rehash # faster start of mysql but no tab completition - -[isamchk] -key_buffer_size = 16M - -# -# * IMPORTANT: Additional settings that can override those from this file! -# The files must end with '.cnf', otherwise they'll be ignored. -# -!includedir /etc/mysql/conf.d/ diff --git a/roles/common-SQL/handlers/main.yml b/roles/common-SQL/handlers/main.yml index d1d355f..eae5efd 100644 --- a/roles/common-SQL/handlers/main.yml +++ b/roles/common-SQL/handlers/main.yml @@ -1,6 +1,6 @@ --- -- name: Restart MySQL - service: name=mysql state=restarted +- name: Restart MariaDB + service: name=mariadb state=restarted - name: Restart munin-node service: name=munin-node state=restarted diff --git a/roles/common-SQL/tasks/main.yml b/roles/common-SQL/tasks/main.yml index 9064a68..7e59f60 100644 --- a/roles/common-SQL/tasks/main.yml +++ b/roles/common-SQL/tasks/main.yml @@ -1,67 +1,62 @@ -# XXX If #742046 gets fixed, we should preseed mysql-server to use -# auth_socket as auth_plugin once the fix enters stable. -- name: Install MySQL - apt: pkg={{ item }} - with_items: - # XXX: In non-interactive mode apt-get doesn't put a password on - # MySQL's root user; we fix that on the next task, but an intruder - # could exploit the race condition and for instance create dummy - # users. - - mysql-common - - mysql-server - - python-mysqldb +- name: Install MariaDB + apt: pkg={{ packages }} + vars: + packages: + - mariadb-common + - mariadb-server + - python3-mysqldb # for the 'mysql_' munin plugin - libcache-cache-perl -- name: Copy MySQL's configuration - copy: src=etc/mysql/my.cnf - dest=/etc/mysql/my.cnf +- name: Copy MySQL/MariaDB configuration + copy: src=etc/mysql/mariadb.conf.d/99-user.cnf + dest=/etc/mysql/mariadb.conf.d/99-user.cnf owner=root group=root mode=0644 register: r notify: - - Restart MySQL + - Restart MariaDB # We need to restart now and load the relevant authplugin before we # connect to the database. - meta: flush_handlers # XXX Dirty fix for #742046 - name: Force root to use UNIX permissions - mysql_user2: name=root password= auth_plugin=auth_socket - state=present + mysql_user: name=root password="" plugin=unix_socket + state=present - name: Disallow anonymous and TCP/IP root login - mysql_user2: name={{ item.name|default('') }} host={{ item.host }} - state=absent + mysql_user: name={{ item.name|default('') }} host={{ item.host }} + state=absent with_items: - { host: '{{ inventory_hostname_short }}' } - { host: 'localhost' } - { host: '127.0.0.1'} - { host: '::1'} - { name: root, host: '{{ inventory_hostname_short }}' } - { name: root, host: '127.0.0.1'} - { name: root, host: '::1'} -- name: Start MySQL - service: name=mysql state=started +- name: Start MariaDB + service: name=mariadb state=started - name: Install 'mysql_' Munin wildcard plugin file: src=/usr/share/munin/plugins/mysql_ dest=/etc/munin/plugins/mysql_{{ item }} owner=root group=root state=link force=yes with_items: # sudo /usr/share/munin/plugins/mysql_ suggest - bin_relay_log - commands - connections - files_tables - innodb_bpool - innodb_bpool_act - innodb_io - innodb_log - innodb_rows - innodb_semaphores - innodb_tnx diff --git a/roles/common-web/files/etc/nginx/fastcgi/php b/roles/common-web/files/etc/nginx/fastcgi/php deleted file mode 100644 index 1ba3937..0000000 --- a/roles/common-web/files/etc/nginx/fastcgi/php +++ /dev/null @@ -1,10 +0,0 @@ -# cf. http://wiki.nginx.org/Pitfalls#Passing_Uncontrolled_Requests_to_PHP -try_files $uri $uri/ =404; - -include fastcgi/params; -# required if PHP was built with --enable-force-cgi-redirect -fastcgi_param REDIRECT_STATUS 200; - -fastcgi_intercept_errors on; -fastcgi_read_timeout 14400; -fastcgi_pass unix:/var/run/php5-fpm.sock; diff --git a/roles/common-web/files/etc/nginx/fastcgi/php-ssl b/roles/common-web/files/etc/nginx/fastcgi/php-ssl deleted file mode 100644 index b2a419c..0000000 --- a/roles/common-web/files/etc/nginx/fastcgi/php-ssl +++ /dev/null @@ -1,8 +0,0 @@ -# PHP only. -# Credits to http://claylo.com/post/7617674014/ssl-php-fpm-and-nginx - -fastcgi_param HTTPS on; -fastcgi_param SSL_PROTOCOL $ssl_protocol; -fastcgi_param SSL_CIPHER $ssl_cipher; -fastcgi_param SSL_SESSION_ID $ssl_session_id; -fastcgi_param SSL_CLIENT_VERIFY $ssl_client_verify; diff --git a/roles/common-web/files/etc/nginx/sites-available/default b/roles/common-web/files/etc/nginx/sites-available/default new file mode 100644 index 0000000..cae8fc0 --- /dev/null +++ b/roles/common-web/files/etc/nginx/sites-available/default @@ -0,0 +1,12 @@ +server { + listen 80 default_server; + listen [::]:80 default_server; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log info; + + # serve ACME challenges on all virtual hosts + # /!\ need to be served individually for each explicit virtual host as well! + include /etc/lacme/nginx.conf; + include snippets/headers.conf; +} diff --git a/roles/common-web/files/etc/nginx/snippets/fastcgi-php-ssl.conf b/roles/common-web/files/etc/nginx/snippets/fastcgi-php-ssl.conf new file mode 100644 index 0000000..aa82ca6 --- /dev/null +++ b/roles/common-web/files/etc/nginx/snippets/fastcgi-php-ssl.conf @@ -0,0 +1,10 @@ +# PHP only. +# Credits to http://claylo.com/post/7617674014/ssl-php-fpm-and-nginx + +include snippets/fastcgi-php.conf; + +fastcgi_param HTTPS on; +fastcgi_param SSL_PROTOCOL $ssl_protocol; +fastcgi_param SSL_CIPHER $ssl_cipher; +fastcgi_param SSL_SESSION_ID $ssl_session_id; +fastcgi_param SSL_CLIENT_VERIFY $ssl_client_verify; diff --git a/roles/common-web/files/etc/nginx/snippets/fastcgi-php.conf b/roles/common-web/files/etc/nginx/snippets/fastcgi-php.conf new file mode 100644 index 0000000..f82bc5d --- /dev/null +++ b/roles/common-web/files/etc/nginx/snippets/fastcgi-php.conf @@ -0,0 +1,13 @@ +# regex to split $uri to $fastcgi_script_name and $fastcgi_path +fastcgi_split_path_info ^(.+?\.php)(/.*)$; + +# Check that the PHP script exists before passing it +try_files $fastcgi_script_name =404; + +# Bypass the fact that try_files resets $fastcgi_path_info +# see: http://trac.nginx.org/nginx/ticket/321 +set $path_info $fastcgi_path_info; +fastcgi_param PATH_INFO $path_info; + +fastcgi_index index.php; +include snippets/fastcgi.conf; diff --git a/roles/common-web/files/etc/nginx/fastcgi/params b/roles/common-web/files/etc/nginx/snippets/fastcgi.conf index 80132ec..9a0a029 100644 --- a/roles/common-web/files/etc/nginx/fastcgi/params +++ b/roles/common-web/files/etc/nginx/snippets/fastcgi.conf @@ -1,23 +1,25 @@ +fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; -fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_NAME $fastcgi_script_name; -fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; +fastcgi_param REQUEST_SCHEME $scheme; +fastcgi_param HTTPS $https if_not_empty; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; -fastcgi_param HTTPS $https; +# PHP only, required if PHP was built with --enable-force-cgi-redirect +fastcgi_param REDIRECT_STATUS 200; diff --git a/roles/common-web/files/etc/nginx/snippets/headers.conf b/roles/common-web/files/etc/nginx/snippets/headers.conf new file mode 100644 index 0000000..798a151 --- /dev/null +++ b/roles/common-web/files/etc/nginx/snippets/headers.conf @@ -0,0 +1,5 @@ +# https://securityheaders.io/ +add_header Referrer-Policy no-referrer; +add_header X-Frame-Options "SAMEORIGIN"; +add_header X-Content-Type-Options nosniff; +add_header X-XSS-Protection "1; mode=block"; diff --git a/roles/common-web/files/etc/nginx/snippets/ssl.conf b/roles/common-web/files/etc/nginx/snippets/ssl.conf new file mode 100644 index 0000000..0284b0a --- /dev/null +++ b/roles/common-web/files/etc/nginx/snippets/ssl.conf @@ -0,0 +1,26 @@ +# https://wiki.mozilla.org/Security/Server_Side_TLS +# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.14.2&openssl=1.1.1c&hsts=yes&profile=intermediate + +ssl on; + +ssl_session_timeout 1d; +ssl_session_cache shared:SSL:50m; +ssl_session_tickets off; + +# Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits +ssl_dhparam /etc/ssl/dhparams.pem; + +ssl_protocols TLSv1.2 TLSv1.3; +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; +ssl_prefer_server_ciphers off; + +# HSTS (ngx_http_headers_module is required) (31557600 seconds = 1 year) +add_header Strict-Transport-Security "max-age=31557600; includeSubDomains" always; + +# OCSP Stapling: fetch OCSP records from URL in ssl_certificate and cache them +# https://github.com/jsha/ocsp-stapling-examples/blob/master/nginx.conf +ssl_stapling on; +ssl_stapling_verify on; + +# verify chain of trust of OCSP response using Root CA and Intermediate certs +ssl_trusted_certificate /usr/share/lacme/ca-certificates.crt; diff --git a/roles/common-web/files/etc/nginx/ssl/config b/roles/common-web/files/etc/nginx/ssl/config deleted file mode 100644 index 26a64f4..0000000 --- a/roles/common-web/files/etc/nginx/ssl/config +++ /dev/null @@ -1,20 +0,0 @@ -ssl on; - -# See http://nginx.org/en/docs/http/configuring_https_servers.html#optimization -keepalive_timeout 75 75; -ssl_session_timeout 5m; -ssl_session_cache shared:SSL:5m; - -# XXX: Ideally we want to get rid of TLSv1, to be immune to the BEAST -# attack. Sadly as of 2013 many clients don't support TLSv1.2, though. -# The alternative would be to reject BEAST-vulnerable ciphers from TLSv1 -# in favor of RC4, but that's not satisfactory either since RC4 has -# other weaknesses. -ssl_protocols TLSv1 TLSv1.1 TLSv1.2; -ssl_ciphers HIGH:!SSLv2:!aNULL:!eNULL:!3DES:!MD5:@STRENGTH; -ssl_dhparam /etc/ssl/private/dhparams.pem; -ssl_prefer_server_ciphers on; - -# Strict Transport Security header for enhanced security. See -# http://www.chromium.org/sts. -add_header Strict-Transport-Security "max-age=15552000"; diff --git a/roles/common-web/tasks/main.yml b/roles/common-web/tasks/main.yml index d2b2acd..f059bfc 100644 --- a/roles/common-web/tasks/main.yml +++ b/roles/common-web/tasks/main.yml @@ -1,61 +1,68 @@ - name: Install Nginx - apt: pkg=nginx + apt: pkg=nginx-light - name: Limit Nginx logging lineinfile: "dest=/etc/logrotate.d/nginx create=yes regexp='^\\s*rotate\\s' - line='\trotate 1'" + line='\trotate 7'" tags: - logrotate -- name: Delete /etc/nginx/sites-{available,enabled}/default - file: path=/etc/nginx/sites-{{ item }}/default state=absent - with_items: - - enabled - - available - -- name: Create directory /etc/nginx/{fastcgi,ssl} - file: path=/etc/nginx/{{ item }} - state=directory - owner=root group=root - mode=0755 - with_items: - - fastcgi - - ssl - -- name: Copy fastcgi parameters - copy: src=etc/nginx/fastcgi/{{ item }} - dest=/etc/nginx/fastcgi/{{ item }} +- name: Copy fastcgi parameters and SSL configuration snippets + copy: src=etc/nginx/snippets/{{ item }} + dest=/etc/nginx/snippets/{{ item }} owner=root group=root mode=0644 register: r1 with_items: - - params - - php - - php-ssl + - fastcgi.conf + - fastcgi-php.conf + - fastcgi-php-ssl.conf + - ssl.conf + - headers.conf notify: - Restart Nginx -- name: Copy SSL configuration - copy: src=etc/nginx/ssl/config - dest=/etc/nginx/ssl/config +- name: Copy /etc/nginx/sites-available/default + copy: src=etc/nginx/sites-available/default + dest=/etc/nginx/sites-available/default owner=root group=root mode=0644 register: r2 notify: - Restart Nginx +- name: Create /etc/nginx/sites-enabled/default + file: src=../sites-available/default + dest=/etc/nginx/sites-enabled/default + owner=root group=root + state=link force=yes + register: r3 + notify: + - Restart Nginx + - name: Add .asc to text/plain MIME types lineinfile: dest=/etc/nginx/mime.types regexp='^(\s*text/plain\s+)' backrefs=yes line='\1txt asc;' - register: r3 + register: r4 + notify: + - Restart Nginx + +# WARN Bullseye: nginx >=1.15.1 uses font/woff and font/woff2 (cf. https://trac.nginx.org/nginx/ticket/1243) +# however Bootstrap(?) appears to query resources with "Accept: application/font-woff" resp. application/font-woff2. +# Unfortunately it also uses "Accept-Encoding: identity" so the resource isn't compressed... +- name: Fix MIME type for woff + lineinfile: dest=/etc/nginx/mime.types + insertafter='^\s*\S+\s\s+woff;' + line=' application/font-woff2 woff2;' + register: r5 notify: - Restart Nginx - name: Start Nginx service: name=nginx state=started - when: not (r1.changed or r2.changed or r3.changed) + when: not (r1.changed or r2.changed or r3.changed or r4.changed or r5.changed) - meta: flush_handlers diff --git a/roles/common/files/etc/apt/apt.conf.d/50unattended-upgrades b/roles/common/files/etc/apt/apt.conf.d/50unattended-upgrades index c9adc5f..fd7cf1d 100644 --- a/roles/common/files/etc/apt/apt.conf.d/50unattended-upgrades +++ b/roles/common/files/etc/apt/apt.conf.d/50unattended-upgrades @@ -1,93 +1,164 @@ // Unattended-Upgrade::Origins-Pattern controls which packages are // upgraded. // -// Lines below have the format format is "keyword=value,...". A +// Lines below have the format "keyword=value,...". A // package will be upgraded only if the values in its metadata match // all the supplied keywords in a line. (In other words, omitted // keywords are wild cards.) The keywords originate from the Release // file, but several aliases are accepted. The accepted keywords are: // a,archive,suite (eg, "stable") -// c,component (eg, "main", "crontrib", "non-free") +// c,component (eg, "main", "contrib", "non-free") // l,label (eg, "Debian", "Debian-Security") // o,origin (eg, "Debian", "Unofficial Multimedia Packages") // n,codename (eg, "jessie", "jessie-updates") // site (eg, "http.debian.net") // The available values on the system are printed by the command // "apt-cache policy", and can be debugged by running // "unattended-upgrades -d" and looking at the log file. // // Within lines unattended-upgrades allows 2 macros whose values are // derived from /etc/debian_version: // ${distro_id} Installed origin. -// ${distro_codename} Installed codename (eg, "jessie") +// ${distro_codename} Installed codename (eg, "buster") Unattended-Upgrade::Origins-Pattern { // Codename based matching: // This will follow the migration of a release through different // archives (e.g. from testing to stable and later oldstable). -// "o=Debian,n=jessie"; -// "o=Debian,n=jessie-updates"; -// "o=Debian,n=jessie-proposed-updates"; -// "o=Debian,n=jessie,l=Debian-Security"; + // Software will be the latest available for the named release, + // but the Debian release itself will not be automatically upgraded. +// "origin=Debian,codename=${distro_codename}-updates"; +// "origin=Debian,codename=${distro_codename}-proposed-updates"; + "origin=Debian,codename=${distro_codename},label=Debian"; + "origin=Debian,codename=${distro_codename},label=Debian-Security"; + "origin=Debian,codename=${distro_codename}-security,label=Debian-Security"; // Archive or Suite based matching: // Note that this will silently match a different release after // migration to the specified archive (e.g. testing becomes the // new stable). // "o=Debian,a=stable"; // "o=Debian,a=stable-updates"; // "o=Debian,a=proposed-updates"; - "origin=Debian,codename=${distro_codename}"; - "origin=Debian,codename=${distro_codename},label=Debian-Security"; +// "o=Debian Backports,a=${distro_codename}-backports,l=Debian Backports"; }; -// List of packages to not update (regexp are supported) +// Python regular expressions, matching packages to exclude from upgrading Unattended-Upgrade::Package-Blacklist { -// "vim"; -// "libc6"; -// "libc6-dev"; -// "libc6-i686"; + // The following matches all packages starting with linux- +// "linux-"; + + // Use $ to explicitely define the end of a package name. Without + // the $, "libc6" would match all of them. +// "libc6$"; +// "libc6-dev$"; +// "libc6-i686$"; + + // Special characters need escaping +// "libstdc\+\+6$"; + + // The following matches packages like xen-system-amd64, xen-utils-4.1, + // xenstore-utils and libxenstore3.0 +// "(lib)?xen(store)?"; + + // For more information about Python regular expressions, see + // https://docs.python.org/3/howto/regex.html }; // This option allows you to control if on a unclean dpkg exit // unattended-upgrades will automatically run // dpkg --force-confold --configure -a // The default is true, to ensure updates keep getting installed -//Unattended-Upgrade::AutoFixInterruptedDpkg "false"; +//Unattended-Upgrade::AutoFixInterruptedDpkg "true"; // Split the upgrade into the smallest possible chunks so that -// they can be interrupted with SIGUSR1. This makes the upgrade +// they can be interrupted with SIGTERM. This makes the upgrade // a bit slower but it has the benefit that shutdown while a upgrade // is running is possible (with a small delay) //Unattended-Upgrade::MinimalSteps "true"; -// Install all unattended-upgrades when the machine is shuting down -// instead of doing it in the background while the machine is running -// This will (obviously) make shutdown slower -//Unattended-Upgrade::InstallOnShutdown "true"; +// Install all updates when the machine is shutting down +// instead of doing it in the background while the machine is running. +// This will (obviously) make shutdown slower. +// Unattended-upgrades increases logind's InhibitDelayMaxSec to 30s. +// This allows more time for unattended-upgrades to shut down gracefully +// or even install a few packages in InstallOnShutdown mode, but is still a +// big step back from the 30 minutes allowed for InstallOnShutdown previously. +// Users enabling InstallOnShutdown mode are advised to increase +// InhibitDelayMaxSec even further, possibly to 30 minutes. +//Unattended-Upgrade::InstallOnShutdown "false"; // Send email to this address for problems or packages upgrades // If empty or unset then no email is sent, make sure that you // have a working mail setup on your system. A package that provides // 'mailx' must be installed. E.g. "user@example.com" Unattended-Upgrade::Mail "admin@fripost.org"; -// Set this value to "true" to get emails only on errors. Default -// is to always send a mail if Unattended-Upgrade::Mail is set -//Unattended-Upgrade::MailOnlyOnError "true"; +// Set this value to one of: +// "always", "only-on-error" or "on-change" +// If this is not set, then any legacy MailOnlyOnError (boolean) value +// is used to chose between "only-on-error" and "on-change" +//Unattended-Upgrade::MailReport "on-change"; + +// Remove unused automatically installed kernel-related packages +// (kernel images, kernel headers and kernel version locked tools). +//Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; -// Do automatic removal of new unused dependencies after the upgrade +// Do automatic removal of newly unused dependencies after the upgrade +//Unattended-Upgrade::Remove-New-Unused-Dependencies "true"; + +// Do automatic removal of unused packages after the upgrade // (equivalent to apt-get autoremove) //Unattended-Upgrade::Remove-Unused-Dependencies "false"; // Automatically reboot *WITHOUT CONFIRMATION* if -// the file /var/run/reboot-required is found after the upgrade +// the file /var/run/reboot-required is found after the upgrade //Unattended-Upgrade::Automatic-Reboot "false"; +// Automatically reboot even if there are users currently logged in +// when Unattended-Upgrade::Automatic-Reboot is set to true +//Unattended-Upgrade::Automatic-Reboot-WithUsers "true"; + // If automatic reboot is enabled and needed, reboot at the specific // time instead of immediately // Default: "now" //Unattended-Upgrade::Automatic-Reboot-Time "02:00"; // Use apt bandwidth limit feature, this example limits the download // speed to 256kb/sec Acquire::http::Dl-Limit "256"; + +// Enable logging to syslog. Default is False +// Unattended-Upgrade::SyslogEnable "false"; + +// Specify syslog facility. Default is daemon +// Unattended-Upgrade::SyslogFacility "daemon"; + +// Download and install upgrades only on AC power +// (i.e. skip or gracefully stop updates on battery) +// Unattended-Upgrade::OnlyOnACPower "true"; + +// Download and install upgrades only on non-metered connection +// (i.e. skip or gracefully stop updates on a metered connection) +// Unattended-Upgrade::Skip-Updates-On-Metered-Connections "true"; + +// Verbose logging +// Unattended-Upgrade::Verbose "false"; + +// Print debugging information both in unattended-upgrades and +// in unattended-upgrade-shutdown +// Unattended-Upgrade::Debug "false"; + +// Allow package downgrade if Pin-Priority exceeds 1000 +// Unattended-Upgrade::Allow-downgrade "false"; + +// When APT fails to mark a package to be upgraded or installed try adjusting +// candidates of related packages to help APT's resolver in finding a solution +// where the package can be upgraded or installed. +// This is a workaround until APT's resolver is fixed to always find a +// solution if it exists. (See Debian bug #711128.) +// The fallback is enabled by default, except on Debian's sid release because +// uninstallable packages are frequent there. +// Disabling the fallback speeds up unattended-upgrades when there are +// uninstallable packages at the expense of rarely keeping back packages which +// could be upgraded or installed. +// Unattended-Upgrade::Allow-APT-Mark-Fallback "true"; diff --git a/roles/common/files/etc/apt/listchanges.conf b/roles/common/files/etc/apt/listchanges.conf index dc31f5e..cee0648 100644 --- a/roles/common/files/etc/apt/listchanges.conf +++ b/roles/common/files/etc/apt/listchanges.conf @@ -1,6 +1,9 @@ [apt] frontend=mail email_address=admin@fripost.org confirm=0 save_seen=/var/lib/apt/listchanges.db which=news +email_format=text +headers=false +reverse=false diff --git a/roles/common/files/etc/default/rkhunter b/roles/common/files/etc/default/rkhunter index da59a73..2e7fae7 100644 --- a/roles/common/files/etc/default/rkhunter +++ b/roles/common/files/etc/default/rkhunter @@ -1,34 +1,34 @@ # Defaults for rkhunter automatic tasks # sourced by /etc/cron.*/rkhunter and /etc/apt/apt.conf.d/90rkhunter # # This is a POSIX shell fragment # # Set this to yes to enable rkhunter daily runs -# (default: true) +# (default: false) CRON_DAILY_RUN="yes" # Set this to yes to enable rkhunter weekly database updates -# (default: true) +# (default: false) CRON_DB_UPDATE="yes" # Set this to yes to enable reports of weekly database updates # (default: false) DB_UPDATE_EMAIL="false" # Set this to the email address where reports and run output should be sent # (default: root) REPORT_EMAIL="admin@fripost.org" # Set this to yes to enable automatic database updates # (default: false) APT_AUTOGEN="false" # Nicenesses range from -20 (most favorable scheduling) to 19 (least favorable) # (default: 0) NICE="10" # Should daily check be run when running on battery # powermgmt-base is required to detect if running on battery or on AC power # (default: false) -RUN_CHECK_ON_BATTERY="false" +RUN_CHECK_ON_BATTERY="false" diff --git a/roles/common/files/etc/fail2ban/action.d/nftables-allports.local b/roles/common/files/etc/fail2ban/action.d/nftables-allports.local new file mode 100644 index 0000000..3b9ebc8 --- /dev/null +++ b/roles/common/files/etc/fail2ban/action.d/nftables-allports.local @@ -0,0 +1,16 @@ +[Definition] +# No need to create sets and rules, these are defined globally in nftables.conf +actionstart = +actionstop = +actioncheck = + +# unbanning is taken care of by setting a timeout on the nft set already +actionunban = + +[Init] +# With banaction = *-allports there is no need for separate rule names +table = filter +addr_set = fail2ban + +[Init?family=inet6] +addr_set = fail2ban6 diff --git a/roles/common/files/etc/fail2ban/fail2ban.local b/roles/common/files/etc/fail2ban/fail2ban.local new file mode 100644 index 0000000..23a92e9 --- /dev/null +++ b/roles/common/files/etc/fail2ban/fail2ban.local @@ -0,0 +1,9 @@ +[Definition] + +# Options: dbfile +# Notes.: Set the file for the fail2ban persistent data to be stored. +# A value of ":memory:" means database is only stored in memory +# and data is lost when fail2ban is stopped. +# A value of "None" disables the database. +# Values: [ None :memory: FILE ] Default: /var/lib/fail2ban/fail2ban.sqlite3 +dbfile = None diff --git a/roles/common/files/etc/fail2ban/filter.d/nextcloud.conf b/roles/common/files/etc/fail2ban/filter.d/nextcloud.conf new file mode 100644 index 0000000..22305d6 --- /dev/null +++ b/roles/common/files/etc/fail2ban/filter.d/nextcloud.conf @@ -0,0 +1,6 @@ +# Source: https://github.com/nextcloud/vm/blob/master/apps/fail2ban.sh + +[Definition] +failregex=(?:^{|,)\"message\":\"Login failed: <F-USER>.*?</F-USER> \(Remote IP: '<HOST>'\)\"(:?,|}$) + (?:^{|,)\"message\":\"Login failed: <F-USER>.*?</F-USER> \(Remote IP: <HOST>\)\"(:?,|}$) + (?:^{|,)\"remoteAddr\":\"<HOST>\",.*Trusted domain error diff --git a/roles/common/files/etc/fail2ban/filter.d/roundcube.conf b/roles/common/files/etc/fail2ban/filter.d/roundcube.conf deleted file mode 100644 index c8cb5d3..0000000 --- a/roles/common/files/etc/fail2ban/filter.d/roundcube.conf +++ /dev/null @@ -1,16 +0,0 @@ -[Definition] - -# Option: failregex -# Notes.: regex to match the password failures messages in the logfile. The -# host must be matched by a group named "host". The tag "<HOST>" can -# be used for standard IP/hostname matching and is only an alias for -# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+) -# Values: TEXT -# -failregex = IMAP Error: Login failed for \S+ from <HOST>\. AUTHENTICATE \S+: Authentication failed\. - -# Option: ignoreregex -# Notes.: regex to ignore. If this regex matches, the line is ignored. -# Values: TEXT -# -ignoreregex = diff --git a/roles/common/files/etc/initramfs-tools/conf.d/resume b/roles/common/files/etc/initramfs-tools/conf.d/resume new file mode 100644 index 0000000..213a0e2 --- /dev/null +++ b/roles/common/files/etc/initramfs-tools/conf.d/resume @@ -0,0 +1 @@ +RESUME=none diff --git a/roles/common/files/etc/logcheck/ignore.d.server/common-local b/roles/common/files/etc/logcheck/ignore.d.server/common-local index 769e326..3a4cb36 100644 --- a/roles/common/files/etc/logcheck/ignore.d.server/common-local +++ b/roles/common/files/etc/logcheck/ignore.d.server/common-local @@ -1,40 +1,138 @@ # Ansible Managed # Do NOT edit this file directly! # -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: subsystem request for sftp by user [-_.[:alnum:]]+$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: User [-_.[:alnum:]]+ not allowed because account is locked$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: fatal: Read from socket failed: (Connection reset by peer|Connection timed out) \[preauth\]$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: (error: )?Received disconnect from [:.[:xdigit:]]+: (3|11|14): .* \[preauth\]$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: Protocol major versions differ for [:.[:xdigit:]]+: SSH-2\.0-OpenSSH_ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: Disconnecting: Change of username or service not allowed: \(\S+\) -> \(\S+\) \[preauth\]$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: Disconnecting: Too many authentication failures for invalid user [-_.[:alnum:]]+ from [:.[:xdigit:]]+ port [[:digit:]]+ ssh2? \[preauth\]$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: userauth_pubkey: unsupported public key algorithm: [[:alnum:]-]+ \[preauth\]$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: fatal: Write failed: (Connection (timed out|reset by peer)|Broken pipe) \[preauth\]$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: fatal: (no hostkey alg|Unable to negotiate a key exchange method) \[preauth\]$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: fatal: no matching cipher found: client [.@[:alnum:]-]+(,[.@[:alnum:]-]+)* server [.@[:alnum:]-]+(,[.@[:alnum:]-]+)* \[preauth\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: (error: )?Received disconnect from [:.[:xdigit:]]+ port [0-9]+:(3|11|14): .* \[preauth\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: error: maximum authentication attempts exceeded for invalid user .* from [:.[:xdigit:]]+ port [0-9]+ ssh2 \[preauth\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: (Disconnected from|Connection (closed|reset) by)( invalid user .*)? [[:xdigit:].:]{3,39} port [0-9]+( \[preauth\])?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: (Did not receive identification string|Invalid user .*) from [[:xdigit:].:]{3,39} port [0-9]+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: (Disconnected from|Connection closed by) (invalid|authenticating) user .* \[preauth\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: Disconnecting (authenticating|invalid) user .* [[:xdigit:].:]{3,39} port [0-9]+: (Change of username or service not allowed: .* -> .*|Too many authentication failures) \[preauth\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: ssh_dispatch_run_fatal: Connection from (invalid user .* )?[[:xdigit:].:]{3,39} port [0-9]+: (message authentication code incorrect|Connection corrupted|Broken pipe) \[preauth\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: Disconnected from user [a-z0-9]+ [[:xdigit:].:]{3,39} port [0-9]+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: Unable to negotiate with [:.[:xdigit:]]+ port [[:digit:]]+: no matching (host key type|key exchange method|cipher) found\. Their offer: [@.[:alnum:],-]+ \[preauth\]$ +no matching cipher found: client [.@[:alnum:]-]+(,[.@[:alnum:]-]+)* server [.@[:alnum:]-]+(,[.@[:alnum:]-]+)* \[preauth\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: Bad packet length [0-9]+\. \[preauth\]$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: Bad protocol version identification '.*' from [:.[:xdigit:]]+ port [[:digit:]]+$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: Accepted publickey for [^[:space:]]+ from [^[:space:]]+ port [[:digit:]]+( (ssh|ssh2))?(: (RSA|ECDSA|ED25519) ([[:xdigit:]]{2}:){15}[[:xdigit:]]{2})?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: WARNING: no suitable primes in /etc/ssh/primes$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: error: Bad remote protocol version identification: '.*'$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: Corrupted MAC on input\. \[preauth\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: Protocol major versions differ for [[:xdigit:].:]{3,39} port [0-9]+: .+ vs\. .+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: error: Protocol major versions differ: .+ vs\. .+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: banner exchange: Connection from [[:xdigit:].:]{3,39} port [0-9]+: could not read protocol version$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: userauth_pubkey: key type [-[:alnum:]]+ not in PubkeyAcceptedKeyTypes \[preauth\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: error: kex_exchange_identification: Connection closed by remote host$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: error: kex_exchange_identification: read: Connection reset by peer$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: error: kex_exchange_identification: client sent invalid protocol identifier " +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: error: kex_exchange_identification: banner line contains invalid characters$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: error: kex_protocol_error: type [0-9]+ seq [0-9]+ \[preauth\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: fatal: ssh_packet_send_debug: Broken pipe$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: error: send_error: write: Connection reset by peer$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: fatal: userauth_pubkey: parse request failed: incomplete message \[preauth\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: banner exchange: Connection from [[:xdigit:].:]{3,39} port [0-9]+: invalid format$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: error: beginning MaxStartups throttling$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: exited MaxStartups throttling after [0-9]{2}:[0-9]{2}:[0-9]{2}, [0-9]+ connections dropped$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: drop connection #[0-9]+ from \[[[:xdigit:].:]{3,39}\]:[0-9]+ on \[[[:xdigit:].:]{3,39}\]:[0-9]+ past MaxStartups$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sshd\[[[:digit:]]+\]: User .+ from [[:xdigit:].:]{3,39} not allowed because none of user's groups are listed in AllowGroups$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[1\]: Start(ing|ed) Cleanup of Temporary Directories\.(\.\.)?$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ slapd\[[[:digit:]]+\]: connection_input: conn=[[:digit:]]+ deferring operation: binding$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (slapd\[[[:digit:]]+\]|slap(acl|add|auth|cat|dn|index)|ldap(add|compare|delete|exop|modify|modrdn|passwd|search|url|whoami)): DIGEST-MD5 common mech free$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sudo:[[:space:]]+[_[:alnum:].-]+ : TTY=(unknown|(pts/|tty|vc/)[[:digit:]]+) ; PWD=[^;]+ ; USER=[._[:alnum:]-]+ (; ENV=([_a-zA-Z]+=\S* )+)?; COMMAND=(/(usr|etc|bin|sbin)/|sudoedit ) -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: bytecode\.(cld|cvd) (is up to date|updated) \(version: [[:digit:]]+, sigs: [[:digit:]]+, f-level: [[:digit:]]+, builder: [._[:alnum:]-]+\)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: WARNING: Your ClamAV installation is OUTDATED!$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: WARNING: Local version: [[:digit:]]+(\.[[:digit:]]+)* Recommended version: [[:digit:]]+(\.[[:digit:]]+)*$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[1\]: (Created|Removed) slice User Slice of UID [-[:alnum:]]+\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[[0-9]+\]: (Listening on|Closed) .*\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[1\]: Starting (Cleanup of Temporary Directories|Daily man-db regeneration)\.\.\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[1\]: Started (Cleanup of Temporary Directories|Daily man-db regeneration)\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[1\]: Start(ing|ed) Session [0-9]+ of user [-[:alnum:]]+\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[[0-9]+\]: Startup finished in \S+\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd: pam_unix\(systemd-user:session\): session (opened|closed) for user [-[:alnum:]]+( by \(uid=0\))?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[[0-9]+\]: [-[:alnum:]\\_@]+\.(mount|scope|service|socket): Succeeded\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd(-[a-z]+)?\[[0-9]+\]: \[/[^]]+:[0-9]+\] Line references path below legacy directory /var/run/, updating /var/run/ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[1\]: /lib/systemd/system/[-[:alnum:]\\_@]+\.service:[0-9]+: PIDFile= references path below legacy directory /var/run/, updating /var/run/ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ runuser: pam_unix\(runuser:session\): session (opened|closed) for user [-[:alnum:]]+( by \(uid=0\))?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ CRON\[[0-9]+\]: pam_unix\(cron:session\): session (opened|closed) for user _?[-[:alnum:]]+(\(uid=[0-9]+\))?( by \(uid=0\))?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[1\]: Start(ing|ed) Session [0-9]+ of user [-[:alnum:]]+\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd-logind\[[0-9]+\]: New session c?[0-9]+ of user [-[:alnum:]]+\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd-logind\[[0-9]+\]: Removed session c?[0-9]+\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd-logind\[[0-9]+\]: Session [0-9]+ logged out\. Waiting for processes to exit\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[1\]: apt-daily(-upgrade)?\.timer: Adding .* random time\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[1\]: Starting Daily apt-listbugs preferences cleanup\.\.\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[1\]: Finished (Daily apt-listbugs preferences cleanup|Cleanup of Temporary Directories|Autocommit of changes in /etc directory|Clean php session files|Launch apticron to notify of packages pending an update|Online ext4 Metadata Check for All Filesystems|Autocommit of changes in /etc directory)\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[1\]: [[:alnum:]@-]+\.(service|scope): Consumed ([0-9]+min )?[0-9.]+s CPU time\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[1\]: rsyslog\.service: Sent signal SIGHUP to main process [0-9]+ \(rsyslogd\) on client request\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sudo:[[:space:]]+[_[:alnum:].-]+ : (TTY=(unknown|(pts/|tty|vc/)[[:digit:]]+) ; )?PWD=[^;]+ ; USER=[._[:alnum:]-]+ (; ENV=([_a-zA-Z]+=\S* )+)?; COMMAND=(/(usr|etc|bin|sbin)/|sudoedit ) +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> daily database available for update \(local version: [[:digit:]]+, remote version: [[:digit:]]+\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> (bytecode|main|daily)\.(cld|cvd) (database )?(is up to date|is up-to-date|updated) \(version: [[:digit:]]+, sigs: [[:digit:]]+, f-level: [[:digit:]]+, builder: [._[:alnum:]-]+\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> Testing database: '/var/lib/clamav/tmp[^/]*/clamav-.*' \.\.\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> Database test passed\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> Received signal: wake up$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> ClamAV update process started at \w{3} \w{3} [ :[:digit:]]{16}$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: (\w{3} \w{3} [ :[:digit:]]{16} -> \^|WARNING: )Your ClamAV installation is OUTDATED!$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: (\w{3} \w{3} [ :[:digit:]]{16} -> \^|WARNING: )Local version: [[:digit:]]+(\.[[:digit:]]+)* Recommended version: [[:digit:]]+(\.[[:digit:]]+)*$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: WARNING: getfile: [._[:alnum:]-]+ not found on remote server \(IP: [.[:digit:]]+\)$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: WARNING: Incremental update failed, trying to download daily\.cvd$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: (WARNING|ERROR): (getpatch: )?Can't download [._[:alnum:]-]+ from [.[:alnum:]-]+$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: Trying host [.[:alnum:]-]+ \([.[:digit:]]+\)\.\.\.$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: Trying again in [[:digit:]]+ secs\.\.\.$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: Giving up on [.[:alnum:]-]+\.\.\.$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: Downloading [._[:alnum:]-]+ \[[[:digit:]]+%\]$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: DON'T PANIC! Read http://www\.clamav\.net/support/faq$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> \*Can't query [._[:alnum:]-]+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> Downloading [._[:alnum:]-]+ \[[[:digit:]]+%\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> DON'T PANIC! Read https?://www\.clamav\.net/(support/faq|documents/upgrading-clamav)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> DON'T PANIC! Read https://docs\.clamav\.net/manual/Installing\.html$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[0-9]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> Database updated \([0-9]+ signatures\) from .* \(IP: [[:xdigit:].:]{3,39}\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[0-9]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> Clamd successfully notified about the update\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ freshclam\[[[:digit:]]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> Received signal: re-opening log file$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ clamd\[[[:digit:]]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> Reading databases from /var/lib/clamav$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ clamd\[[[:digit:]]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> SelfCheck: (Database status OK|Database modification detected\. Forcing reload)\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ clamd\[[[:digit:]]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> Database correctly reloaded \([0-9]+ signatures\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ clamd\[[[:digit:]]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> SIGHUP caught: re-opening log file\.$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ clamd\[[[:digit:]]+\]: \w{3} \w{3} [ :[:digit:]]{16} -> Activating the newly loaded database\.\.\.$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ kernel: \[ *[[:digit:]]+\.[[:digit:]]+ *\] Peer [.[:digit:]]+:[[:digit:]]+/[[:digit:]]+ unexpectedly shrunk window [[:digit:]]+:[[:digit:]]+ \(repaired\)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ rsyslogd: \[origin software="rsyslogd" swVersion="[.[:digit:]]+" x-pid="[[:digit:]]+" x-info="http://www.rsyslog.com"\] rsyslogd was HUPed$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ rsyslogd-?([[:digit:]]+): action '[^']+' (resumed \(module '[.[:alnum:]-]+:[.[:alnum:]-]+'\)|suspended, next retry is \w{3} \w{3} [ :[:digit:]]{16}) \[try http://www\.rsyslog\.com/e/\1 \]$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ ansible-([_a-z]+|<stdin>): Invoked with -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (sympa\((command|distribute)\)|wwsympa|archived|bounced|bulk|task_manager)\[[[:digit:]]+\]: (info|notice)\s -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[[:digit:]]+\]: err .* main::check_action_parameters\(\) user not logged in$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ rsyslogd: \[origin software="rsyslogd" swVersion="[0-9.]+" x-pid="[0-9]+" x-info="https://www\.rsyslog\.com"\] rsyslogd was HUPed$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ smartd\[[0-9]+\]: Device: /dev/sd[a-z] \[SAT\], CHECK POWER STATUS spins up disk \(0x[0-9a-f]{2} -> 0xff\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ ansible-([_a-z0-9.]+|<stdin>): Invoked with +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ python3(\.[0-9]+)?\[[0-9]+\]: ansible-[_a-z0-9.]+ Invoked with +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ fail2ban-server\[[0-9]+\]: fail2ban\.filter\s*\[[0-9]+\]: INFO\s+\[[._[:alnum:]-]+\] (Found [[:xdigit:].:]{3,39} - |Ignore [[:xdigit:].:]{3,39} by ip$) +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ fail2ban-server\[[0-9]+\]: fail2ban\.actions\s*\[[0-9]+\]: NOTICE\s+\[[._[:alnum:]-]+\] (Ban|Unban) [[:xdigit:].:]{3,39} +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sympa_msg\[[0-9]+\]: notice Sympa::Request::Message:: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sympa_msg\[[0-9]+\]: notice Sympa::(Bulk|Spool)::store\(\) +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sympa_msg\[[0-9]+\]: info Sympa::Spool::_create\(\) Creating directory /var/spool/sympa/auth +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sympa_msg\[[0-9]+\]: (info|notice) Sympa::Spindle::Process(Incoming|Message|Template|Digest):: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sympa_msg\[[0-9]+\]: info Sympa::Spindle::Do(Command|Message):: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sympa_msg\[[0-9]+\]: info Sympa::Request::Handler::(subscribe|confirm|reject|signoff|distribute)::_twist\(\) +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sympa_msg\[[0-9]+\]: (info|notice) Sympa::Spindle::To(List|Moderation|Auth|Held):: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sympa_msg\[[0-9]+\]: notice Sympa::Spindle::AuthorizeMessage::_twist\(\) Message Sympa::Message .* rejected\(\) because sender not allowed$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sympa_msg\[[0-9]+\]: err main::#[0-9]+ > Sympa::Spindle::spin#[0-9]+ > +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ task_manager\[[0-9]+\]: (info|notice) main:: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ task_manager\[[0-9]+\]: (info|notice) Sympa::(Spindle::ProcessTask|Spool):: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: info main:: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: info Sympa::Ticket::load\(\) Ticket: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: notice main:: Redirecting to\s +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: notice main::do_login\(\) Authentication failed$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: err main::#[0-9]+ > main::do_login#[0-9]+ > Sympa::WWW::Auth::check_auth#[0-9]+ > Sympa::WWW::Auth::authentication#[0-9]+ Incorrect password for user\s +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: notice Sympa::WWW::Tools::_get_css_url\(\) Template file /usr/share/sympa/default/web_tt2/css\.tt2 or configuration has changed; updating CSS file /var/lib/sympa/css/[.[:alnum:]-]+/style\.css$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: info Sympa::WWW::Session::new\(\) Ignoring unknown session cookie +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: err main::#[0-9]+ > main::get_parameters#[0-9]+ \[robot [.[:alnum:]-]+\] \[client [[:xdigit:].:]{3,39}\] Syntax error for parameter action value "(lists|search_list_request)#[^"]+" not conform to regexp: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: err main::#[0-9]+ > main::check_action_parameters#[0-9]+ \[robot [.[:alnum:]-]+\] \[session [[:xdigit:]]+\] \[client [[:xdigit:].:]{3,39}\] \[list [.[:alnum:]-]+\] User not logged in$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: err main::#[0-9]+ > main::do_arc#[0-9]+ \[robot [.[:alnum:]-]+\] \[session [[:xdigit:]]+\] \[client [[:xdigit:].:]{3,39}\] \[list [.[:alnum:]-]+\] Empty archive Sympa::Archive < +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: notice Sympa::Spindle::Process(Request|Template)::_twist\(\) Processing Sympa:: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: info Sympa::Spindle::ToAuth:: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: notice Sympa::Bulk::store\(\) Message Sympa::Message::Template < +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (wwsympa|sympa_msg)\[[0-9]+\]: notice Sympa::Spool::Outgoing::store\(\) Message Sympa::Message::Template < +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: notice Sympa::Spool::store\(\) Sympa::Request < +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: info Sympa::Request::Handler::(add|signoff|subscribe)::_twist\(\) +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: info Sympa::Scenario::authz\(\) Sympa::Scenario <create_list\.[.[:alnum:]-]+;ERROR>: No rule match, reject$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ wwsympa\[[0-9]+\]: err main::#[0-9]+ > main::check_param_in#[0-9]+ > Sympa::Scenario::new#[0-9]+ Unable to find scenario file " +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sympa_msg\[[0-9]+\]: notice Sympa::Spool::Outgoing::store\(\) Message Sympa::Message <[^>]+> is stored into bulk spool as <[^>]+>$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ mysqld\[[0-9]+\]: [0-9: -]{19} [0-9]+ \[Warning\] Aborted connection [0-9]+ to db: '[^']+' user: '[^']+' host: 'localhost' \(Got (timeout|an error) reading communication packets\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ bulk\[[0-9]+\]: notice main:: Bulk exited normally due to signal$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ bulk\[[0-9]+\]: notice Sympa::Mailer::store\(\) +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ bulk\[[0-9]+\]: (info|notice) Sympa::Spindle::ProcessOutgoing:: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ bounced\[[0-9]+\]: notice Sympa::Tracking::store\(\) Sympa::Message < +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ bounced\[[0-9]+\]: notice Sympa::Spindle::ProcessBounce::_twist\(\) +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ bounced\[[0-9]+\]: info Sympa::Spindle::ProcessBounce::_twist\(\) No address found in message Sympa::Message < +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ archived\[[0-9]+\]: notice Sympa::Spindle::ProcessArchive:: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ archived\[[0-9]+\]: (info|notice) Sympa::Archive:: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ ikiwiki\[[[:digit:]]+\]: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ ikiwiki: \[Fripost wiki\] (invalid page|unknown do) parameter$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ unbound: \[[0-9]+:[0-9]+\] info: generate keytag query _ta-[[:xdigit:]]+\. NULL IN$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ rrdcached\[[[:digit:]]+\]: (flushing old values|rotating journals|started new journal /\S+$|removing old journal /\S+$) ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ rrdcached\[[[:digit:]]+\]: queue_thread_main: rrd_update_r \(([^)]+)\) failed with status -1. \(opening '\1': No such file or directory\) -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ stunnel: LOG3\[[[:digit:]]+\]: SSL_accept: (Peer suddenly disconnected|[[:xdigit:]]+: error:[[:xdigit:]]+:SSL routines:SSL2?3_GET_CLIENT_HELLO:(unknown protocol|http request))$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ auditd\[[[:digit:]]+\]: Audit daemon rotating log files$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dhclient\[[[:digit:]]+\]: DHCPREQUEST for [[:digit:].]{3,15} on [[:alnum:]]+ to [[:digit:].]{3,15} port 67$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ bacula-sd\[[[:digit:]]+\]: [._[:alnum:]-]+: askdir\.c:[0-9]+-[0-9]+ Discard: JobMedia Vol=[._[:alnum:]-]+ wrote=[0-9]+ MediaId=[0-9]+ FI=[0-9]+ LI=[0-9]+ StartAddr=[0-9]+ EndAddr=[0-9]+$ +### +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ slapd\[[[:digit:]]+\]: connection_read\([[:digit:]]+\): no connection!$ diff --git a/roles/common/files/etc/logcheck/ignore.d.server/dovecot-local b/roles/common/files/etc/logcheck/ignore.d.server/dovecot-local index 5f474ed..532a2a0 100644 --- a/roles/common/files/etc/logcheck/ignore.d.server/dovecot-local +++ b/roles/common/files/etc/logcheck/ignore.d.server/dovecot-local @@ -1,26 +1,23 @@ # Ansible Managed # Do NOT edit this file directly! # -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: imap\([-_.@[:alnum:]]+\): Disconnected(: Logged out| for inactivity( in reading our output)?|: Disconnected( in [[:upper:]]+)?| in [[:upper:]]+( \([[:digit:]]+ msgs, [[:digit:]]+ secs, [[:digit:]]+/[[:digit:]]+ bytes\))?|: Too many invalid IMAP commands\.|: IMAP session state is inconsistent, please relogin\.)? in=[[:digit:]]+ out=[[:digit:]]+$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: managesieve\([-_.@[:alnum:]]+\): Disconnected for inactivity bytes=[[:digit:]]+/[[:digit:]]+$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: imap\([-_.@[:alnum:]]+\): Connection closed(: Connection reset by peer)?( in=[[:digit:]]+ out=[[:digit:]]+)?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: (pop3|imap)-login: Login: user=<[^>]*>, method=[[:alnum:]-]+, rip=[.:[:xdigit:]]+, lip=[.:[:xdigit:]]+, mpid=[0-9]+(, (TLS|secured)(: Disconnected)?, session=<[^>]+>)?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: (pop3|imap)-login: Aborted login( \(auth failed, [[:digit:]]+ attempts in [[:digit:]]+ secs\))?: (user=<[^>]*>, method=[[:alnum:]-]+, )?rip=[.:[:xdigit:]]+, lip=[.:[:xdigit:]]+, TLS, session=<[^>]+>$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: (pop3|imap|managesieve)-login: (Disconnected|Aborted login)(: Inactivity)? (\(no auth attempts in [[:digit:]]+ secs\):( user=<>,)?|\(auth failed, [[:digit:]]+ attempts in [[:digit:]]+ secs\): user=<[^>]*>, method=PLAIN,|\(aborted authentication\): method=PLAIN,) rip=[.:[:xdigit:]]+, lip=[.:[:xdigit:]]+(, (TLS|SSL)( handshaking)?(: SSL_(accept|read)\(\) (syscall failed: Connection reset by peer|failed: error:140940E5:SSL routines:SSL3_READ_BYTES:ssl handshake failure: SSL alert number 10|failed: error:14094412:SSL routines:SSL3_READ_BYTES:sslv3 alert bad certificate: SSL alert number 42|failed: error:14094416:SSL routines:SSL3_READ_BYTES:sslv3 alert certificate unknown: SSL alert number 46|failed: error:14094418:SSL routines:SSL3_READ_BYTES:tlsv1 alert unknown ca: SSL alert number 48|failed: error:[[:xdigit:]]+:SSL routines:SSL2?3_GET_CLIENT_HELLO:(unknown protocol|unsupported protocol|http request|no shared cipher|wrong version number)|failed: error:[[:xdigit:]]+:SSL routines:SSL_BYTES_TO_CIPHER_LIST:inappropriate fallback)|: Disconnected)?|, secured)?, session=<[^>]+>$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: auth: Warning: auth client [[:digit:]]+ disconnected with [[:digit:]]+ pending requests: EOF$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: (pop3|imap)-login: Warning: Auth connection closed with [[:digit:]]+ pending requests \(max [[:digit:]]+ secs, pid=[[:digit:]]+, EOF\)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: (pop3|imap)-login: Disconnected \((auth process communication failure|client didn't finish SASL auth, waited [[:digit:]]+ secs)\): user=<>, method=PLAIN, rip=[.:[:xdigit:]]+, lip=[.:[:xdigit:]]+(, (TLS|secured)(: SSL_read\(\) syscall failed: Connection reset by peer|: Disconnected)?, session=<[^>]+>)?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: (pop3|imap)-login: Error: SSL: Stacked error: error:140943F2:SSL routines:SSL3_READ_BYTES:sslv3 alert unexpected message: SSL alert number 10$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: imap-login: (Disconnected: Inactivity during authentication|Aborted login) \(client didn't finish SASL auth, waited [[:digit:]]+ secs\): user=<[^>]*>, method=[[:alnum:]-]+, rip=[.:[:xdigit:]]+, lip=[.:[:xdigit:]]+(, (TLS|secured)(: Disconnected)?, session=<[^>]+>)?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: (pop3|imap|managesieve)-login: Disconnected \(tried to use unsupported auth mechanism\): user=<>, method=[[:alnum:]-]+, rip=[.:[:xdigit:]]+, lip=[.:[:xdigit:]]+(, (TLS|secured), session=<[^>]+>)?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: lmtp\([0-9]+, [-_.@[:alnum:]]+\): [^:]{22}: msgid=<?.*>?( \((added by [^[:space:]]+|sfid-[_[:xdigit:]]+)\)?)?[[:space:]]*: (saved mail to [-_.[:alnum:]]+|(forwarded|discarded duplicate forward) to <[^[:space:]]+>)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: (pop3|imap|managesieve)-login: Maximum number of connections from user\+IP exceeded \(mail_max_userip_connections=[[:digit:]]+\): user=<[^>]*>, method=[[:alnum:]-]+, rip=[.:[:xdigit:]]+, lip=[.:[:xdigit:]]+(, (TLS|secured), session=<[^>]+>)?$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: lmtp\([-_.@[:alnum:]]+\): Connect from local$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: lmtp\([-_.@[:alnum:]]+\): Disconnect from local: (Client quit|Connection closed) \(in reset\)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: lmtp\([-_.@[:alnum:]]+, [^@]+@[^@]+\): [+/[:alnum:]]{22}: sieve: msgid=(\S+ )?|<[^>]*>( \(added by \S+\))?: (stored mail into mailbox '|marked message to be discarded if not explicitly delivered \(discard action\)$|forwarded to ) -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: lmtp\([-_.@[:alnum:]]+, [^@]+@[^@]+\): Error: [+/[:alnum:]]{22}: sieve: execution of script \S+ failed, but implicit keep was successful \(user logfile \S+ may reveal additional details\)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: lmtp\([0-9]+\): Disconnect from local: Successful quit$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: (pop3|imap|managesieve)-login: Login: user=<[^>]*>, method=[[:alnum:]-]+, rip=[.:[:xdigit:]]+, lip=[.:[:xdigit:]]+, mpid=[0-9]+(, TLS, session=<[^>]+>)?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: managesieve\([-_.@[:alnum:]]+\): Disconnected: Logged out bytes=[[:digit:]]+/[[:digit:]]+?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: ssl-params: (Generating SSL parameters|SSL parameters regeneration completed)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: stats: Error: Mail server input error: UPDATE-SESSION [-_.@[:alnum:]]+ imap: stats shrank: (mcache|mrcount|mrbytes) [[:digit:]]+ < [[:digit:]]+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: imap\([-_.@[:alnum:]]+\)<[0-9]+><[+/[:alnum:]]+>: Connection closed(: read\(size=[0-9]+\) failed: Connection reset by peer)? \((((UID )?[[:alpha:]\-]* finished [0-9.]+ secs ago|IDLE running for [^\)]+, state=wait-input|No commands sent)\))?( in=[0-9]+ out=[0-9]+ deleted=[0-9]+ expunged=[0-9]+ trashed=[0-9]+ hdr_count=[0-9]+ hdr_bytes=[0-9]+ body_count=[0-9]+ body_bytes=[0-9]+)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: imap\(.+\)<[0-9]+><[+/[:alnum:]]+>(: Disconnected)?(: Logged out| for inactivity( in reading our output)?|: Disconnected in [[:upper:]]+ \([0-9]+ msgs, [0-9]+ secs, [0-9]+/[0-9]+ bytes\)|: Too many invalid IMAP commands\.)?( in=[0-9]+ out=[0-9]+ deleted=[0-9]+ expunged=[0-9]+ trashed=[0-9]+ hdr_count=[0-9]+ hdr_bytes=[0-9]+ body_count=[0-9]+ body_bytes=[0-9]+)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: imap: Logged out in=[0-9]+ out=[0-9]+ deleted=0 expunged=0 trashed=0 hdr_count=0 hdr_bytes=0 body_count=0 body_bytes=0$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: imap\([-_.@[:alnum:]]+\)<[0-9]+><[+/[:alnum:]]+>: Connection closed(: (read\(size=[0-9]+\) failed: )?Connection reset by peer)?( \((LOGOUT,)?(UID )?[[:alpha:]\-]+ (running for|finished) [^\)]+\))? in=[0-9]+ out=[0-9]+ deleted=[0-9]+ expunged=[0-9]+ trashed=[0-9]+ hdr_count=[0-9]+ hdr_bytes=[0-9]+ body_count=[0-9]+ body_bytes=[0-9]+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: imap-hibernate\([-_.@[:alnum:]]+\)<[0-9]+><[+/[:alnum:]]+>: Connection closed in=[0-9]+ out=[0-9]+ deleted=[0-9]+ expunged=[0-9]+ trashed=[0-9]+ hdr_count=[0-9]+ hdr_bytes=[0-9]+ body_count=[0-9]+ body_bytes=[0-9]+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: (pop3|imap|managesieve)-login: Login: user=<[^>]*>, method=[[:alnum:]-]+, rip=[[:xdigit:].:]{3,39}, lip=[[:xdigit:].:]{3,39}, mpid=[0-9]+, (TLS|secured)(: (read\(size=[0-9]+\) failed: )?Connection (closed|reset by peer))?, session=<[^>]+>$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: managesieve\([-_.@[:alnum:]]+\)<[0-9]+><[+/[:alnum:]]+>: Disconnected: Logged out bytes=[[:digit:]]+/[[:digit:]]+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: (imap|managesieve)-login: (Disconnected(: Inactivity)?|Aborted login) \(auth failed, [[:digit:]]+ attempts in [[:digit:]]+ secs\): user=<[^>]*>, method=[A-Z\-]+, rip=[[:xdigit:].:]{3,39}, lip=[[:xdigit:].:]{3,39}, (TLS|SSL|secured)[:,] +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: (imap|managesieve)-login: Disconnected \((no auth attempts in|disconnected before auth was ready, waited) [[:digit:]]+ secs\):( user=<>,)? rip=[[:xdigit:].:]{3,39}, lip=[[:xdigit:].:]{3,39}, (TLS|SSL)( handshaking)?: (SSL_accept\(\)( syscall)? failed:|(read\(size=[0-9]+\) failed: )?Connection (closed|reset by peer), session=<[+/[:alnum:]]+>$|SSL_read failed: error:[[:xdigit:]]+:SSL routines:(ssl3_get_record:decryption failed or bad record mac|ssl3_read_bytes:unexpected record), session=<[+/[:alnum:]]+>$) +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: (imap|managesieve)-login: (Disconnected(: (Inactivity|Too many invalid commands\.?))?|Aborted login) \(no auth attempts in [[:digit:]]+ secs\):( user=<>,)? rip=[[:xdigit:].:]{3,39}, lip=[[:xdigit:].:]{3,39}(, (TLS|SSL)( handshaking)?)?, session=<[+/[:alnum:]]+>$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: (pop3|imap)-login: Disconnected(: Inactivity during authentication)? \(client didn't finish SASL auth, waited [[:digit:]]+ secs\): user=<>, method=[[:alnum:]-]+, rip=[[:xdigit:].:]{3,39}, lip=[[:xdigit:].:]{3,39}, TLS(: (read\(size=[0-9]+\) failed: Connection reset by peer|Disconnected|Connection closed))?, session=<[^>]+>$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: lmtp\([-_.@[:alnum:]]+\)<[0-9]+><[+/[:alnum:]]{22}(:[0-9]+)?>: msgid=(\? )?(<[^>]*>|[^[:blank:]]*|[^,()]+@[.[:alnum:]-]+)( \(added by \S+@[.[:alnum:]-]+\))?: saved mail to\s +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: lmtp\([-_.@[:alnum:]]+\)<[0-9]+><[+/[:alnum:]]{22}(:[0-9]+)?>: sieve: msgid=(\? )?(<[^>]*>\s*|[^[:blank:]]*|[^,()]+@[.[:alnum:]-]+)( \(added by \S+@[.[:alnum:]-]+\)| [[:alnum:]]+ action)?: (stored mail into mailbox '|(forwarded|discarded duplicate forward) to <[^[:space:]]+>$|Marked message to be discarded if not explicitly delivered \(discard action\)$) +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: lmtp\([^@]+@[^@]+\)<[0-9]+><[+/[:alnum:]]{22}(:[0-9]+)?>: sieve: Execution of script \S+ failed, but implicit keep was successful \(user logfile \S+ may reveal additional details\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: (pop3|imap|managesieve)-login: Maximum number of connections from user\+IP exceeded \(mail_max_userip_connections=[[:digit:]]+\): user=<[^>]*>, method=[[:alnum:]-]+, rip=[[:xdigit:].:]{3,39}, lip=[[:xdigit:].:]{3,39}(, TLS, session=<[^>]+>)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: lmtp\([0-9]+\): Disconnect from local: (Client has quit the connection|Remote closed connection) \(state=[[:upper:]]+\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: auth: Warning: auth client [0-9]+ disconnected with [0-9]+ pending requests: (Connection reset by peer|EOF)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ dovecot: stats: Warning: UPDATE-CMD: Already expired$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[1\]: Start(ing|ed) Dovecot authentication proxy\.(\.\.)?$ diff --git a/roles/common/files/etc/logcheck/ignore.d.server/postfix-local b/roles/common/files/etc/logcheck/ignore.d.server/postfix-local index 4364197..dcc1198 100644 --- a/roles/common/files/etc/logcheck/ignore.d.server/postfix-local +++ b/roles/common/files/etc/logcheck/ignore.d.server/postfix-local @@ -1,97 +1,133 @@ # Ansible Managed # Do NOT edit this file directly! # -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix/local\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^[:space:]]+>,( orig_to=<[^[:space:]]+>,)? relay=local, delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=2(\.[[:digit:]]+){2})?, status=sent \(forwarded as [[:xdigit:]]{10}\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix/local\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^>]+>,( orig_to=<[^>]+>,)? relay=local, delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=2(\.[[:digit:]]+){2})?, status=sent \(forwarded as [[:xdigit:]]{10}\)$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/postfix-script\[[[:digit:]]+\]: refreshing the Postfix mail system$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/master[[[:digit:]]+]: reload -- version [.[:digit:]]+, configuration /etc/postfix$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/smtpd\[[[:digit:]]+\]: (dis)?connect from [^[:space:]]+$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/smtpd\[[[:digit:]]+\]: (NOQUEUE|[[:xdigit:]]+): reject: RCPT from [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]: [45][[:digit:]][[:digit:]] [45](\.[[:digit:]]+){2} <[^>]+>: Recipient address rejected: ((unverified|undeliverable) address:|Domain not found;) -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/[ls]mtp\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^[:space:]]+>,( orig_to=<[^[:space:]]+>,)? relay=[._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\](:[[:digit:]]{1,5})?, (conn_use=[[:digit:]]+, )?delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=2(\.[[:digit:]]+){2})?, status=(sent|deliverable) \(2[[:digit:]][[:digit:]] .+\)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/anvil\[[[:digit:]]+\]: statistics: max (message|recipient|connection) (count|rate) [/[:digit:]s]+ for \(([.:[:xdigit:]]+)?(smtp[sd]?|25|submission|587)?:([.:[:xdigit:]]+|unknown)\) at \w{3} [ :[:digit:]]{11}$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/[ls]mtp\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^>]+>,( orig_to=<[^>]+>,)? relay=[._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\](:[[:digit:]]{1,5})?, (conn_use=[[:digit:]]+, )?delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=2(\.[[:digit:]]+){2})?, status=(sent|deliverable) \(2[[:digit:]][[:digit:]] .+\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/anvil\[[[:digit:]]+\]: statistics: max (message|recipient|connection) (count|rate) [/[:digit:]s]+ for \((\[[.:[:xdigit:]]+\]:)?(smtp[sd]?|25|submissions?|587|465)?:([.:[:xdigit:]]+|unknown)\) at \w{3} [ :[:digit:]]{11}$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/anvil\[[[:digit:]]+\]: statistics: max cache size [[:digit:]]+ at \w{3} [ :[:digit:]]{11}$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/scache\[[[:digit:]]+\]: statistics: start interval \w{3} [ :[:digit:]]{11}$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/scache\[[[:digit:]]+\]: statistics: (domain|address) lookup hits=[[:digit:]]+ miss=[[:digit:]]+ success=[[:digit:]]+%$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/scache\[[[:digit:]]+\]: statistics: max simultaneous domains=[[:digit:]]+ addresses=[[:digit:]]+ connection=[[:digit:]]+$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/smtpd\[[[:digit:]]+\]: [[:xdigit:]]+: client=[._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/cleanup\[[[:digit:]]+\]: [[:xdigit:]]+: (resent-|)message-id=(<[^>]*>|[[:alnum:]_/+.@-]+)( \(added by [^[:space:]]+\))?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/qmgr\[[[:digit:]]+\]: [[:xdigit:]]+: from=<[^[:space:]]*>, size=[[:digit:]]+, nrcpt=[[:digit:]]+ \(queue active\)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/n?qmgr\[[[:digit:]]+\]: [[:xdigit:]]+: from=<.*>, status=expired, returned to sender$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/n?qmgr\[[[:digit:]]+\]: [[:xdigit:]]+: message-id=(<[^>]*>|[[:alnum:]_/+.@-]+)( \(added by [^[:space:]]+\))? +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/cleanup\[[[:digit:]]+\]: [[:xdigit:]]+: (resent-)?message-id=([^[:blank:]]*|(mid:)?[[:alnum:]_/+.$@-]+|[^,()]+@[.[:alnum:]-]+)( \(added by [^[:space:]]+\))?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/cleanup\[[[:digit:]]+\]: warning: unix_trigger_event: read timeout for service public/qmgr$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/cleanup\[[[:digit:]]+\]: warning: milter unix:public/opendmarc: can't read SMFIC_BODYEOB reply packet header: Success$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/qmgr\[[[:digit:]]+\]: [[:xdigit:]]+: from=<[^>]*>, size=[[:digit:]]+, nrcpt=[[:digit:]]+ \(queue active\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/n?qmgr\[[[:digit:]]+\]: [[:xdigit:]]+: from=<[^>]*>, status=expired, returned to sender$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/n?qmgr\[[[:digit:]]+\]: [[:xdigit:]]+: message-id=(<[^>]*>|[[:alnum:]_/+.$@-]+)( \(added by [^[:space:]]+\))? ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/n?qmgr\[[[:digit:]]+\]: [[:xdigit:]]+: removed$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/n?qmgr\[[[:digit:]]+\]: [[:xdigit:]]+: skipped, still being delivered$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/verify\[[[:digit:]]+\]: close database /var/lib/postfix\1/verify_cache\.db: No such file or directory \(possible Berkeley DB bug\)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/verify\[[[:digit:]]+\]: cache btree:/var/lib/postfix\1/verify_cache full cleanup: retained=[[:digit:]]+ dropped=[[:digit:]]+ entries$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/smtpd\[[[:digit:]]+\]: lost connection after (AUTH|DATA \([[:digit:]]+ bytes\)) from [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-\w+/smtpd\[[[:digit:]]+\]: lost connection after (CONNECT|STARTTLS) from [._[:alnum:]-]+\[([[:xdigit:].:]{3,39}|unknown)\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/verify\[[[:digit:]]+\]: cache [a-z]+:\S+ (partial|full) cleanup: retained=[[:digit:]]+ dropped=[[:digit:]]+ entries$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/smtpd\[[[:digit:]]+\]: disconnect from [._[:alnum:]-]+\[([[:xdigit:].:]{3,39}|unknown)\]( (ehlo|helo|xforward|starttls|auth|mail|rcpt|data|bdat|noop|rset|quit|commands|unknown)=[0-9]+(/[0-9]+)?)+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/smtpd\[[[:digit:]]+\]: warning: non-SMTP command from [-._[:alnum:]]+\[[[:xdigit:].:]{3,39}\]:\s +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/pickup\[[[:digit:]]+\]: [[:xdigit:]]+: uid=[[:digit:]]+ from=<[^>]*>$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/cleanup\[[[:digit:]]+\]: [[:xdigit:]]+: replace: header\s ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/smtpd\[[[:digit:]]+\]: [[:xdigit:]]+: client=[^[:space:]]+, sasl_method=[-[:alnum:]]+, sasl_username=[-_.@[:alnum:]]+(, sasl_sender=[-_.@[:alnum:]]+)?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/smtpd\[[[:digit:]]+\]: warning: [-._[:alnum:]]+\[[.[:digit:]]+\]: SASL (PLAIN|LOGIN) authentication failed(:[ [:alnum:]]*)?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(msa|mx)/smtpd\[[[:digit:]]+\]: improper command pipelining after (EHLO|HELO|MAIL) from [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/smtpd\[[[:digit:]]+\]: warning: [-._[:alnum:]]+\[[[:xdigit:].:]{3,39}\]: SASL [[:alpha:]]+ authentication (failed|aborted)(:|$) +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(msa|mx)/smtpd\[[[:digit:]]+\]: improper command pipelining after (CONNECT|EHLO|HELO|AUTH|MAIL|QUIT) from [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]: ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(msa|mx)/smtpd\[[[:digit:]]+\]: warning: hostname [._[:alnum:]-]+ does not resolve to address [[:xdigit:].:]{3,39}(: Name or service not known)?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: warning: Connection concurrency limit exceeded: [0-9]+ from [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\] for service smtpd$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: NOQUEUE: reject: RCPT from [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]: 5[[:digit:]]{2} 5(\.[[:digit:]]){2} <[^>]+>: Helo command rejected: need fully-qualified hostname;( from=<[^>]*> to=<[^>]+>)? proto=E?SMTP( helo=<[^>]+>)? +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(msa|mx)/smtpd\[[[:digit:]]+\]: warning: Connection concurrency limit exceeded: [0-9]+ from [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\] for service (submissions?|smtpd)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: NOQUEUE: reject: RCPT from [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]: 5[[:digit:]]{2} 5(\.[[:digit:]]+){2} <[^>]+>: Helo command rejected: need fully-qualified hostname;( from=<[^>]*> to=<[^>]+>)? proto=E?SMTP( helo=<[^>]+>)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: NOQUEUE: reject: RCPT from [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]: 4[[:digit:]]{2} 4(\.[[:digit:]]+){2} <[^>]+>: Sender address rejected: (Domain not found|Malformed DNS server reply);( from=<[^>]*> to=<[^>]+>)? proto=E?SMTP( helo=<[^>]+>)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: NOQUEUE: reject: RCPT from [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]: 5[[:digit:]]{2} 5(\.[[:digit:]]+){2} <[^>]+>: Sender address rejected: Domain [.[:alnum:]-]+ does not accept mail \(nullMX\);( from=<[^>]*> to=<[^>]+>)? proto=E?SMTP( helo=<[^>]+>)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: NOQUEUE: reject: RCPT from [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]: 5[[:digit:]]{2} 5(\.[[:digit:]]+){2} Service unavailable; (Unverified Client host|Sender address) \[\S+\] blocked using [._[:alnum:]-]+(; Listed by DBL, see https?://[^[:blank:];]+)?; from=<[^>]*> to=<[^>]+> proto=E?SMTP( helo=<[^>]+>)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: NOQUEUE: reject: RCPT from [._[:alnum:]-]+\[([[:xdigit:].:]{3,39})\]: 4[[:digit:]]{2} 4(\.[[:digit:]]+){2} Client host rejected: cannot find your hostname, \[\1\]; from=<[^>]*> to=<[^>]+> proto=E?SMTP( helo=<[^>]+>)?$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(msa|mx)/smtpd\[[[:digit:]]+\]: timeout after [-[:upper:]]+( \([[:digit:]]+ bytes\))? from [^[:space:]]+$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-((msa|mx)/smtpd|out/smtp)\[[[:digit:]]+\]: warning: (tls_text_name: [-._[:alnum:]]+\[[[:xdigit:].:]{3,39}\]: )?peer certificate has no (subject CN|issuer Organization)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/smtpd\[[[:digit:]]+\]: (NOQUEUE|[[:xdigit:]]+): reject: [[:upper:]]+ from [^[:space:]]+: 450( 4\.1\.2)? <[^>]*>: Recipient address rejected: Domain not found;( from=<[^>]*> to=<[^[:space:]]+>)? proto=E?SMTP( helo=<[^[:space:]]+>)?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/smtpd\[[[:digit:]]+\]: (NOQUEUE|[[:xdigit:]]+): reject: [[:upper:]]+ from [^[:space:]]+: 450( 4\.1\.8)? <[^>]*>: Sender address rejected: Domain not found;( from=<[^>]*> to=<[^[:space:]]+>)? proto=E?SMTP( helo=<[^[:space:]]+>)?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/smtpd\[[[:digit:]]+\]: (NOQUEUE|[[:xdigit:]]+): reject: [[:upper:]]+ from [^[:space:]]+: 504( 5\.5\.2)? <[^>]*>: (Recipient|Sender) address rejected: need fully-qualified address;( from=<[^>]*> to=<[^[:space:]]+>)? proto=E?SMTP( helo=<[^[:space:]]+>)?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/smtpd\[[[:digit:]]+\]: NOQUEUE: reject: RCPT from [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]: 554 5\.7\.1 <[._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]>: Client host rejected: Access denied;( from=<[^>]*> to=<[^[:space:]]+>)? proto=E?SMTP( helo=<[^[:space:]]+>)?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mda/lmtp\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^[:space:]]+>,( orig_to=<[^[:space:]]+>,)? relay=[._[:alnum:]-]+\[private/dovecot-lmtpd\],( conn_use=[[:digit:]]+,)? delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=2(\.[[:digit:]]+){2})?, status=sent \(2[[:digit:]][[:digit:]] .+\)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-\w+/(error|n?qmgr|smtp)\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^[:space:]]+>,( orig_to=<[^[:space:]]+>,)? relay=(none|[^[:space:]]+\[[[:xdigit:].:]{3,39}\]:[[:digit:]]+),( conn_use=[[:digit:]]+,)? delay=[[:digit:].]+,( delays=[[:digit:]./]+,)?( dsn=[45]\.[[:digit:]]\.[[:digit:]],)? status=(deferred|undeliverable|bounced) \((delivery temporarily suspended: )?((lost connection with [^[:space:]]+|conversation with [^[:space:]]+ timed out) while (sending [[:alnum:]]+( [[:alnum:]]+)?|performing the (HELO|EHLO) handshake|receiving the initial server greeting|sending [[:alnum:]]+( [/[:alnum:]]+)?|sending end of data -- message may be sent more than once)|connect to [^[:space:]]+: (Connection timed out|read timeout|Connection refused|Network is unreachable)|host [^[:space:]]+ refused to talk to me: [45][[:digit:]][[:digit:]].*|Host or domain name not found. Name service error for name=[^[:space:]]+ type=MX: Host not found, try again|User unknown in virtual alias table|host [^[:space:]]+\[[[:xdigit:].:]{3,39}\] said: [45][[:digit:]][[:digit:]] [45](\.[[:digit:]]+){2} <[^>]+>: (Temporarily rejected\. Try again later\.|Recipient address rejected: ((undeliverable|unverified) address:|Domain not found)) .*)\)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: lost connection with [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\] while (receiving the initial server greeting|sending [[:upper:] ]+)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/smtpd\[[[:digit:]]+\]: (NOQUEUE|[[:xdigit:]]+): reject: [[:upper:]]+ from [^[:space:]]+: 450( 4\.1\.2)? <[^>]*>: Recipient address rejected: Domain not found;( from=<[^>]*> to=<[^>]+>)? proto=E?SMTP( helo=<[^[:space:]]+>)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/smtpd\[[[:digit:]]+\]: (NOQUEUE|[[:xdigit:]]+): reject: [[:upper:]]+ from [^[:space:]]+: 450( 4\.1\.8)? <[^>]*>: Sender address rejected: Domain not found;( from=<[^>]*> to=<[^>]+>)? proto=E?SMTP( helo=<[^[:space:]]+>)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/smtpd\[[[:digit:]]+\]: NOQUEUE: reject: [[:upper:]]+ from [^[:space:]]+: 554( 5\.7\.1)? <>: Sender address rejected: Null sender not allowed; from=<> to=<[^>]+> proto=E?SMTP( helo=<[^[:space:]]+>)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/smtpd\[[[:digit:]]+\]: NOQUEUE: reject: [[:upper:]]+ from [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]: 550( 5\.7\.23)? <[^>]+>: Sender address rejected: Message rejected due to: (domain owner discourages use of this host|SPF fail - not authorized)\. Please see https?://www\.openspf\.net/Why\?\S+; from=<[^>]+> to=<[^>]+> proto=E?SMTP( helo=<[^[:space:]]+>)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ policyd-spf\[[[:digit:]]+\]: 550( 5\.7\.23)? Message rejected due to: (domain owner discourages use of this host|SPF fail - not authorized)\. Please see https?://www\.openspf\.net/Why\?\S+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(msa|mx)/smtpd\[[[:digit:]]+\]: (NOQUEUE|[[:xdigit:]]+): reject: [[:upper:]]+ from [^[:space:]]+: 5[[:digit:]]{2} 5(\.[[:digit:]]+){2} <[^>]*>: (Recipient|Sender) address rejected: need fully-qualified address;( from=<[^>]*> to=<[^>]+>)? proto=E?SMTP( helo=<[^[:space:]]+>)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/smtpd\[[[:digit:]]+\]: NOQUEUE: reject: RCPT from [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]: 554 5\.7\.1 <[._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]>: Client host rejected: Access denied;( from=<[^>]*> to=<[^>]+>)? proto=E?SMTP( helo=<[^[:space:]]+>)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mda/lmtp\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^>]+>,( orig_to=<[^>]+>,)? relay=[._[:alnum:]-]+\[private/dovecot-lmtpd\],( conn_use=[[:digit:]]+,)? delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=2(\.[[:digit:]]+){2})?, status=sent \(2[[:digit:]][[:digit:]] .+\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-\w+/(error|n?qmgr|smtp)\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^>]+>,( orig_to=<[^>]+>,)? relay=(none|[^[:space:]]+\[[[:xdigit:].:]{3,39}\]:[[:digit:]]+),( conn_use=[[:digit:]]+,)? delay=[[:digit:].]+,( delays=[[:digit:]./]+,)?( dsn=[45]\.[[:digit:]]\.[[:digit:]],)? status=(deferred|undeliverable|bounced) \((delivery temporarily suspended: )?((lost connection with [^[:space:]]+|conversation with [^[:space:]]+ timed out) while (sending [[:alnum:]]+( [[:alnum:]]+)?|performing the (HELO|EHLO) handshake|receiving the initial server greeting|sending [[:alnum:]]+( [/[:alnum:]]+)?|sending end of data -- message may be sent more than once)|connect to [^[:space:]]+: (Connection timed out|read timeout|Connection refused|Network is unreachable|No route to host)|host [^[:space:]]+ refused to talk to me: [45][[:digit:]][[:digit:]].*|Host or domain name not found. Name service error for name=[^[:space:]]+ type=(MX|A|AAAA): Host (not found, try again|found but no data record of requested type)|User unknown in virtual alias table|host [^[:space:]]+\[[[:xdigit:].:]{3,39}\] said: [45][[:digit:]][[:digit:]] [45](\.[[:digit:]]+){2} <[^>]+>: (Temporarily rejected\. Try again later\.|Recipient address rejected: ((undeliverable|unverified) address:|Domain not found)) .*)\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^>]+>,( orig_to=<[^>]+>,)? relay=[^[:space:]]+\[[[:xdigit:].:]{3,39}\]:[[:digit:]]+,( conn_use=[[:digit:]]+,)? delay=[[:digit:].]+,( delays=[[:digit:]./]+,)?( dsn=[45]\.[[:digit:]]\.[[:digit:]],)? status=undeliverable \(host +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-msa/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: host [^[:space:]]+\[[[:xdigit:].:]{3,39}\]( said: 45[01][ -].* \(in reply to (MAIL FROM|RCPT TO) command\)| refused to talk to me: (421|450) ) +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(out|msa)/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: lost connection with [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\] while (receiving the initial server greeting|sending [[:upper:] ]+|performing the HELO handshake|sending end of data -- message may be sent more than once)$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: conversation with [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\] timed out while (sending message body|receiving the initial server greeting)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^[:space:]]+>(, orig_to=<[^[:space:]]+>)?, relay=[._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\](:[[:digit:]]{1,5})?,( conn_use=[[:digit:]]+,)? delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=[45](\.[[:digit:]]+){2})?, status=(deferred|bounced|undeliverable|SOFTBOUNCE) \(host [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\] said: [45][[:digit:]][[:digit:]][- ]+.* \(in reply to (HELO|EHLO|MAIL FROM|RCPT TO|DATA|end of DATA) command\)\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^>]+>(, orig_to=<[^>]+>)?, relay=[._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\](:[[:digit:]]{1,5})?,( conn_use=[[:digit:]]+,)? delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=[45](\.[[:digit:]]+){2})?, status=(deferred|bounced|undeliverable|SOFTBOUNCE) \(host [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\] said: [45][[:digit:]][[:digit:]][- ]+.* \(in reply to (HELO|EHLO|MAIL FROM|RCPT TO|DATA|end of DATA) command\)\)$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: connect to [^[:space:]]+: (read timeout|Connection (refused|timed out)|Network is unreachable|No route to host)( \(port [[:digit:]]+\))?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/[ls]mtp\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^[:space:]]+>, relay=(none|[._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\](:[[:digit:]]{1,5})?), (conn_use=[[:digit:]]+, )?delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=[45](\.[[:digit:]]+){2})?, status=(deferred|bounced|undeliverable) \((host [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\] said: [45][[:digit:]][[:digit:]] .+ \(in reply to (HELO|EHLO|MAIL FROM|RCPT TO|end of DATA) command\)|connect to [^[:space:]]+: (read timeout|Connection (refused|timed out)|Network is unreachable|No route to host)( \(port [[:digit:]]+\))?)\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/[ls]mtp\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^>]+>, relay=(none|[._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\](:[[:digit:]]{1,5})?), (conn_use=[[:digit:]]+, )?delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=[45](\.[[:digit:]]+){2})?, status=(deferred|bounced|undeliverable) \((host [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\] said: [45][[:digit:]][[:digit:]] .+ \(in reply to (HELO|EHLO|MAIL FROM|RCPT TO|end of DATA) command\)|connect to [^[:space:]]+: (read timeout|Connection (refused|timed out)|Network is unreachable|No route to host)( \(port [[:digit:]]+\))?|mail for [._[:alnum:]-]+ loops back to myself)\)$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: host [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\] said: [45][[:digit:]][[:digit:]][- ]+.* \(in reply to (HELO|EHLO|MAIL FROM|RCPT TO|(end of )?DATA) command\)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(mda|out)/smtpd\[[[:digit:]]+\]: [[:xdigit:]]+: client=[._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\], orig_queue_id=[[:xdigit:]]+, orig_client=[._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: [A-Z[:digit:]]+: to=<[^[:space:]]+>,( orig_to=<[^[:space:]]+>,) relay=[^[:space:]]+, delay=[[:digit:]]+, status=deferred \(host [^[:space:]]+ said: [45][[:digit:]]{2} <[^[:space:]]*>: Recipient address rejected: Greylisted for [[:digit:]]+ (seconds|minutes)(\(see http://isg.ee.ethz.ch/tools/postgrey/help/[.[:alnum:]-]+.html\))? \(in reply to (HELO|EHLO|MAIL FROM|RCPT TO|DATA|end of DATA) command\)\)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: to=<.*>,( orig_to=<[^[:space:]]+>,)? relay=[^[:space:]]+\](:[[:digit:]]+)?,( conn_use=[[:digit:]]+,)? delay=[[:digit:].]+,( delays=[[:digit:]./]+,)?( dsn=4\.[[:digit:]]\.[[:digit:]],)? status=deferred \(host [^[:space:]]+\] said: .*$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(mda|out)/smtpd?\[[[:digit:]]+\]: warning: numeric domain name in resource data of MX record for [._[:alnum:]-]+: [[:xdigit:].:]{3,39}$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/smtpd\[[[:digit:]]+\]: SSL_accept error from [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]: (lost connection|Connection reset by peer|-?[[:digit:]]+|Connection timed out)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(msa|mx)/smtpd\[[[:digit:]]+\]: warning: TLS library problem: error:[[:xdigit:]]+:SSL routines:SSL3_READ_BYTES:(sslv3|tlsv1) alert (unknown ca|certificate unknown):s3_pkt.c:[0-9]+:SSL alert number [[:digit:]]+:$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(mda|out|lists)/smtpd\[[[:digit:]]+\]: [[:xdigit:]]+: client=[._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\], orig_queue_id=[[:xdigit:]]+, orig_client=[._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: [A-Z[:digit:]]+: to=<[^>]+>,( orig_to=<[^>]+>,) relay=[^[:space:]]+, delay=[[:digit:]]+, status=deferred \(host [^[:space:]]+ said: [45][[:digit:]]{2} <[^[:space:]]*>: Recipient address rejected: Greylisted for [[:digit:]]+ (seconds|minutes)(\(see http://isg.ee.ethz.ch/tools/postgrey/help/[.[:alnum:]-]+.html\))? \(in reply to (HELO|EHLO|MAIL FROM|RCPT TO|DATA|end of DATA) command\)\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: to=<.*>,( orig_to=<[^>]+>,)? relay=[^[:space:]]+\](:[[:digit:]]+)?,( conn_use=[[:digit:]]+,)? delay=[[:digit:].]+,( delays=[[:digit:]./]+,)?( dsn=4\.[[:digit:]]\.[[:digit:]],)? status=deferred \(host [^[:space:]]+\] said: .*$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(mda|out|mx)/smtpd?\[[[:digit:]]+\]: warning: valid_hostname: numeric hostname: [0-9]+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(mda|out|mx)/smtpd?\[[[:digit:]]+\]: warning: (malformed|numeric) domain name in resource data of MX record for [._[:alnum:]-]+:\s +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: warning: no MX host for [._[:alnum:]-]+ has a valid address record$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix(-\w+)?/smtpd\[[[:digit:]]+\]: SSL_accept error from [._[:alnum:]-]+\[([[:xdigit:].:]{3,39}|unknown)\]: (lost connection|Connection reset by peer|-?[[:digit:]]+|Connection timed out|Broken pipe)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(msa|mx)/smtpd\[[[:digit:]]+\]: warning: TLS library problem: error:[[:xdigit:]]+:SSL routines:SSL3_READ_BYTES:(sslv3|tlsv1) alert (unknown ca|certificate unknown):s3_pkt\.c:[0-9]+:SSL alert number [[:digit:]]+:$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(out|mx)/bounce\[[[:digit:]]+\]: [[:xdigit:]]+: sender (delay|non-delivery|delivery status) notification: [[:xdigit:]]+$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: host [^[:space:]]+ refused to talk to me: [45][[:digit:]][[:digit:]].*$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: enabling PIX <CRLF>\.<CRLF> workaround for [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\](:[[:digit:]]{1,5})?$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: enabling PIX workarounds:( (disable_esmtp|delay_dotcrlf))+ for [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\](:[[:digit:]]{1,5})?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^>]+>, relay=[-_.[:alnum:]]+, delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=4(\.[[:digit:]]+){2})?, status=deferred \(connect to [._[:alnum:]-]+\[(unknown|[[:xdigit:].:]{3,39})\]:[[:digit:]]+: (Network is unreachable|No route to host)\)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: [[:xdigit:]]+: reject: RCPT from [^[:space:]]+: 4[[:digit:]][[:digit:]]( 4(\.[[:digit:]]){2}) <[^[:space:]]*>: Recipient address rejected: Greylisted( for [[:digit:]]+ (second|minute)s)?, see https?://[-_.:/[:alnum:]]+\.html?; from=<[^[:space:]]*> to=<[^[:space:]]+> proto=E?SMTP helo=<[^[:space:]]+>$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(mx|msa)/smtpd\[[[:digit:]]+\]: lost connection after [[:upper:]]+( \([[:digit:]]+ bytes\))? from [._[:alnum:]-]+\[(unknown|[[:xdigit:].:]{3,39})\]$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: [[:xdigit:]]+: reject: (CONNECT|RCPT) from [^[:space:]]+: [45][[:digit:]][[:digit:]]( [45](\.[[:digit:]]){2})? Service unavailable; Client host \[([[:xdigit:].:]{3,39}|[-._[:alnum:]]+)\] blocked using [._[:alnum:]-]+;( .+;)? (from=<[^[:space:]]*> to=<[^[:space:]]+> )?proto=E?SMTP( helo=<[^[:space:]]+>)?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: (NOQUEUE|[[:xdigit:]]+): reject: RCPT from [^[:space:]]+: [[:digit:]]{3}( [45](\.[[:digit:]]){2})? <[^[:space:]]*>: Relay access denied; from=<[^[:space:]]*> to=<[^[:space:]]+> proto=E?SMTP helo=<[^[:space:]]+>$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: warning: ([-._[:alnum:]]+): RBL lookup error: Host or domain name not found\. Name service error for name=\1 type=A: Host not found, try again$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(mx|msa)/(smtpd|tlsproxy)\[[[:digit:]]+\]: warning: TLS library problem: error:[[:xdigit:]]+:SSL routines:SSL2?3_(GET_RECORD:(decryption failed or bad record mac|wrong version number):s3_pkt\.c:[0-9]+:|READ_BYTES:(reason\([[:digit:]]+\)|sslv3 alert (unexpected message|bad certificate)):s3_pkt\.c:[[:digit:]]+:SSL alert number (0|10|42):|GET_CLIENT_HELLO:(unsupported protocol|no shared cipher|unknown protocol):s2?3_srvr\.c:[0-9]+:)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: (NOQUEUE|[[:xdigit:]]+): reject: [[:upper:]]+ from [^[:space:]]+: 554( 5\.1\.[01])? <[^[:space:]]*>: Recipient address rejected: User unknown in virtual alias table;( from=<[^[:space:]]*> to=<[^[:space:]]+>)? proto=E?SMTP( helo=<[^[:space:]]+>)?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: [[:xdigit:]]+: reject: RCPT from [^[:space:]]+: [45][[:digit:]][[:digit:]]( [45](\.[[:digit:]]){2})? <[^[:space:]]*>: Helo command rejected: .+; from=<[^[:space:]]*> to=<[^[:space:]]+> proto=E?SMTP helo=<[^[:space:]]+>$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^>]+>, relay=[-_.[:alnum:]]+, delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=4(\.[[:digit:]]+){2})?, status=deferred \(connect to [._[:alnum:]-]+\[(unknown|[[:xdigit:].:]{3,39})\]:[[:digit:]]+: (Network is unreachable|No route to host|Connection refused)\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: SSL_connect error to [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\]:[[:digit:]]+: -?[[:digit:]]+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: warning: TLS library problem: error:[[:xdigit:]]+:SSL routines:SSL2?3_CHECK_CERT_AND_ALGORITHM:dh key too small:s2?3_clnt\.c:[0-9]+:$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: Cannot start TLS: handshake failure$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/tlsproxy\[[[:digit:]]+\]: TLS handshake failed for service=smtpd peer=\[[[:xdigit:].:]{3,39}\]:[0-9]+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: Host offered STARTTLS: \[[._[:alnum:]-]+\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: [[:xdigit:]]+: reject: RCPT from [^[:space:]]+: 4[[:digit:]][[:digit:]]( 4(\.[[:digit:]]+){2}) <[^[:space:]]*>: Recipient address rejected: Greylisted( for [[:digit:]]+ (second|minute)s)?, see https?://[-_.:/[:alnum:]]+\.html?; from=<[^>]*> to=<[^>]+> proto=E?SMTP helo=<[^[:space:]]+>$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-\w+/smtpd\[[[:digit:]]+\]: lost connection after [[:upper:]]+( \([[:digit:]]+ bytes\))? from [._[:alnum:]-]+\[(unknown|[[:xdigit:].:]{3,39})\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: [[:xdigit:]]+: reject: (CONNECT|RCPT) from [^[:space:]]+: [45][[:digit:]][[:digit:]]( [45](\.[[:digit:]]+){2})? Service unavailable; Client host \[([[:xdigit:].:]{3,39}|[-._[:alnum:]]+)\] blocked using [._[:alnum:]-]+;( .+;)? (from=<[^>]*> to=<[^>]+> )?proto=E?SMTP( helo=<[^[:space:]]+>)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: (NOQUEUE|[[:xdigit:]]+): reject: RCPT from [^[:space:]]+: [[:digit:]]{3}( [45](\.[[:digit:]]+){2})? <[^[:space:]]*>: Relay access denied; from=<[^>]*> to=<[^>]+> proto=E?SMTP helo=<[^[:space:]]+>$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: warning: ([-._[:alnum:]]+): RBL lookup error: Host or domain name not found\. Name service error for name=\1 type=A(AAA)?: Host not found, try again$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(mx|msa)/(smtpd|tlsproxy)\[[[:digit:]]+\]: warning: TLS library problem: error:[[:xdigit:]]+:SSL routines:SSL2?3_(GET_RECORD:(decryption failed or bad record mac|wrong version number):s3_pkt\.c:[0-9]+:|READ_BYTES:(reason\([[:digit:]]+\)|sslv3 alert (unexpected message|bad certificate)):s3_pkt\.c:[[:digit:]]+:SSL alert number (0|10|42):|GET_CLIENT_HELLO:(unsupported protocol|no shared cipher|unknown protocol|wrong version number):s2?3_srvr\.c:[0-9]+:)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(mx|msa)/(smtpd|tlsproxy)\[[[:digit:]]+\]: warning: TLS library problem: error:[[:xdigit:]]+:SSL routines:(ssl3_get_record:(wrong version number|http request|packet length too long|bad record type)|tls_post_process_client_hello:no shared cipher): +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(mx|msa)/(smtpd|tlsproxy)\[[[:digit:]]+\]: warning: TLS library problem: error:[[:xdigit:]]+:SSL routines:ssl3_read_bytes:(sslv3 alert bad certificate|unexpected record|tlsv1 alert user cancelled): +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(mx|msa)/(smtpd|tlsproxy)\[[[:digit:]]+\]: warning: TLS library problem: error:[[:xdigit:]]+:SSL routines:tls_parse_ctos_key_share:bad key share: +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(msa|mx)/(smtpd|tlsproxy)\[[[:digit:]]+\]: warning: TLS library problem: error:[[:xdigit:]]+:SSL routines:tls_((early_)?post_)?process_client_hello:(no shared cipher|unknown protocol|unsupported protocol|version too low):\.\./ssl/statem/statem_srvr\.c:[0-9]+:$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: (NOQUEUE|[[:xdigit:]]+): reject: [[:upper:]]+ from [^[:space:]]+: 55[0-9]( 5\.1\.[01])? <[^[:space:]]*>: Recipient address rejected: User unknown in virtual alias table;( from=<[^>]*> to=<[^>]+>)? proto=E?SMTP( helo=<[^[:space:]]+>)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: [[:xdigit:]]+: reject: RCPT from [^[:space:]]+: [45][[:digit:]][[:digit:]]( [45](\.[[:digit:]]+){2})? <[^[:space:]]*>: Helo command rejected: .+; from=<[^>]*> to=<[^>]+> proto=E?SMTP helo=<[^[:space:]]+>$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: too many errors after ([[:upper:]]{4}|END-OF-MESSAGE|UNKNOWN|DATA \(0 bytes\)) from [._[:alnum:]-]+\[[.[:digit:]]+\]$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(mx|msa)/smtpd\[[[:digit:]]+\]: warning: hostname [^[:space:]]+ does not resolve to address [[:xdigit:].:]{3,39}: (No address associated with hostname|Temporary failure in name resolution)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/smtpd\[[[:digit:]]+\]: warning: (numeric hostname: [[:xdigit:].:]{3,39}|valid_hostname: misplaced delimiter: \S)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(mx|lists)/pipe\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^[:space:]]+>,( orig_to=<[^[:space:]]+>,)* relay=([-_.[:alnum:]]+), delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=2(\.[[:digit:]]+){2})?, status=sent \(delivered via \3 service\)$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/discard\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^[:space:]]+>,( orig_to=<[^[:space:]]+>,)* relay=none, delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=2(\.[[:digit:]]+){2})?, status=sent \(discard\.fripost\.org\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(mx|msa)/smtpd\[[[:digit:]]+\]: warning: (numeric hostname: [[:xdigit:].:]{3,39}|valid_hostname: misplaced delimiter: \S)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(mx|lists)/pipe\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^>]+>,( orig_to=<[^>]+>,)* relay=([-_.[:alnum:]]+), delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=2(\.[[:digit:]]+){2})?, status=sent \(delivered via \3 service\)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/discard\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^>]+>,( orig_to=<[^>]+>,)* relay=none, delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=2(\.[[:digit:]]+){2})?, status=sent \(discard\.fripost\.org\)$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-(mx|msa)/smtpd\[[[:digit:]]+\]: warning: Illegal address syntax from [._[:alnum:]-]+\[[[:xdigit:].:]{3,39}\] in (MAIL|RCPT) command:\s ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: (PASS (OLD|NEW)|WHITELISTED) \[[[:xdigit:].:]{3,39}\]:[[:digit:]]+$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: CONNECT from \[[[:xdigit:].:]{3,39}\]:[[:digit:]]+ to \[[[:xdigit:].:]{3,39}\]:25$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: DISCONNECT \[[[:xdigit:].:]{3,39}\]:[[:digit:]]+$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: PREGREET [[:digit:]]+ after [.[:digit:]]+ from \[[[:xdigit:].:]{3,39}\]:[[:digit:]]+:\s ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: HANGUP after [.[:digit:]]+ from \[[[:xdigit:].:]{3,39}\]:[[:digit:]]+ in tests (before|after) SMTP handshake$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: BARE NEWLINE from \[[[:xdigit:].:]{3,39}\]:[[:digit:]]+( after\s.*)?$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: NON-SMTP COMMAND from \[[[:xdigit:].:]{3,39}\]:[[:digit:]]+\s ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: COMMAND PIPELINING from \[[[:xdigit:].:]{3,39}\]:[[:digit:]]+ after\s ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: DNSBL rank [[:digit:]]+ for \[[[:xdigit:].:]{3,39}\]:[[:digit:]]+$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: NOQUEUE: reject: RCPT from (\[[[:xdigit:].:]{3,39}\]):[[:digit:]]+: [45][[:digit:]][[:digit:]]( [45](\.[[:digit:]]){2})? (Service unavailable; client \1 blocked using [._[:alnum:]-]+|Protocol error);( .+;)? (from=<[^>]*>, to=<[^>]+>, )?proto=E?SMTP(, helo=<[^[:space:]]+>)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: [[:upper:]]+ without valid RCPT from \[[[:xdigit:].:]{3,39}\]:[[:digit:]]+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: NOQUEUE: reject: RCPT from (\[[[:xdigit:].:]{3,39}\]):[[:digit:]]+: [45][[:digit:]][[:digit:]]( [45](\.[[:digit:]]+){2})? (Service unavailable; client \1 blocked using [._[:alnum:]-]+|Protocol error|Service currently unavailable);( .+;)? (from=<[^>]*>, to=<[^>]+>, )?proto=E?SMTP(, helo=<[^[:space:]]+>)?$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: NOQUEUE: reject: CONNECT from \[[[:xdigit:].:]{3,39}\]:[[:digit:]]+: too many connections$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: COMMAND (COUNT|TIME) LIMIT from \[[[:xdigit:].:]{3,39}\]:[[:digit:]]+( after [[:upper:]]+)?$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: cache [a-z]+:\S+ full cleanup: retained=[[:digit:]]+ dropped=[[:digit:]]+ entries$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: cache [a-z]+:\S+ (partial|full) cleanup: retained=[[:digit:]]+ dropped=[[:digit:]]+ entries$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: warning: getpeername: Transport endpoint is not connected -- dropping this connection$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: warning: psc_cache_update: btree:/var/lib/postfix-mx/postscreen_cache update average delay is [[:digit:]]+ ms$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: warning: psc_cache_update: lmdb:/var/lib/postfix-mx/postscreen_cache update average delay is [[:digit:]]+ ms$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/postscreen\[[[:digit:]]+\]: warning: dnsblog reply timeout [[:digit:]]+s for [._[:alnum:]-]+$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/dnsblog\[[[:digit:]]+\]: addr [[:xdigit:].:]{3,39} listed by domain [._[:alnum:]-]+ as [[:xdigit:].:]{3,39}$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/dnsblog\[[[:digit:]]+\]: warning: dnsblog_query: lookup error for DNS query ([._[:alnum:]-]+): Host or domain name not found. Name service error for name=\1 type=A: Host not found, try again$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/dnsblog\[[[:digit:]]+\]: warning: dnsblog_query: lookup error for DNS query ([._[:alnum:]-]+): Host or domain name not found. Name service error for name=\1 type=A(AAA)?: Host not found, try again$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtp\[[[:digit:]]+\]: [[:xdigit:]]+: to=<[^>]+>, relay=none, delay=[.[:digit:]]+(, delays=([.[:digit:]]+/){3}[.[:digit:]]+)?(, dsn=5(\.[[:digit:]]+){2})?, status=(undeliverable|bounced) \(Host or domain name not found. Name service error for name=[^[:space:]]+ type=A(AAA)?: Host not found\)$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-mx/tlsproxy\[[[:digit:]]+\]: (CONNECT from|DISCONNECT) \[[[:xdigit:].:]{3,39}\]:[[:digit:]]+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ systemd\[1\]: Start(ing|ed) Postfix sender login socketmap\.(\.\.)?$ # # Amavis -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ amavis\[[[:digit:]]+\]: \([-[:digit:]]+\) Passed (CLEAN|SPAM|UNCHECKED(-[A-Z]+)?|BAD-HEADER-[28]) {Relayed(Tagged)?(Internal|Inbound|Outbound)}, (INCOMING|OUTGOING)( LOCAL)? \[(IPv6:)?[[:xdigit:].:]{3,39}\](:[[:digit:]]+)?( \[[[:xdigit:].:]{3,39}\])? <[^>]*> -> (<[^>]*>,)+( Queue-ID: [[:xdigit:]]+,)?( Message-ID: <[^>]+>,)?( Resent-Message-ID: <[^>]+>,)? mail_id: [_-+[:alnum:]]+, Hits: -?[[:digit:].]*, size: [[:digit:]]+, queued_as: [[:xdigit:]]+(, dkim_(new|sd)?=[-.:[:alnum:]]+)?, [[:digit:]]+ ms$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ amavis\[[[:digit:]]+\]: \([-[:digit:]]+\) Passed (CLEAN|SPAM|UNCHECKED(-[A-Z]+)?|BAD-HEADER-[28]) {Relayed(Tagged)?(Internal|Inbound|Outbound)}, (INCOMING|OUTGOING)( LOCAL)? \[(IPv6:)?[[:xdigit:].:]{3,39}\](:[[:digit:]]+)?( \[[[:xdigit:].:]{3,39}\])? <[^>]*> -> (<[^>]*>,)+( Queue-ID: [[:xdigit:]]+,)?( Message-ID: <[^>]+>,)?( Resent-Message-ID: <[^>]+>,)? mail_id: [-_+[:alnum:]]+, Hits: -?[[:digit:].]*, size: [[:digit:]]+, queued_as: [[:xdigit:]]+(, dkim_(new|sd)?=[-_.,:[:alnum:]]+)?, [[:digit:]]+ ms$ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ amavis\[[[:digit:]]+\]: \([-[:digit:]]+\) Passed (CLEAN|SPAM|UNCHECKED(-[A-Z]+)?|BAD-HEADER-[28]) {Relayed(Tagged)?(Internal|Inbound|Outbound)}, (INCOMING|OUTGOING)( LOCAL)? \[(IPv6:)?[[:xdigit:].:]{3,39}\](:[[:digit:]]+)?( \[[[:xdigit:].:]{3,39}\])? <[^>]*> -> (<[^>]*>,)*(<[^>]*)?\.\.\.$ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ amavis\[[[:digit:]]+\]: \([-[:digit:]]+\) \.\.\.([^>]*>)?(,<[^>]*>)*,( Queue-ID: [[:xdigit:]]+,)?( Message-ID: <[^>]+>,)?( Resent-Message-ID: <[^>]+>,)? mail_id: [_-+[:alnum:]]+, Hits: -?[[:digit:].]*, size: [[:digit:]]+, queued_as: [[:xdigit:]]+(, dkim_(new|sd)?=[-.:[:alnum:]]+)?, [[:digit:]]+ ms$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ amavis\[[[:digit:]]+\]: \([-[:digit:]]+\) \.\.\.([^>]*>)?(,<[^>]*>)*,( Queue-ID: [[:xdigit:]]+,)?( Message-ID: <[^>]+>,)?( Resent-Message-ID: <[^>]+>,)? mail_id: [-_+[:alnum:]]+, Hits: -?[[:digit:].]*, size: [[:digit:]]+, queued_as: [[:xdigit:]]+(, dkim_(new|sd)?=[-_.,:[:alnum:]]+)?, [[:digit:]]+ ms$ # SMTP client connection caching was introduced in 2.6.0; the SMTP session is held for the next task, and is terminated by Postfix if the next mail comes soon enough ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ postfix-out/smtpd\[[[:digit:]]+\]: timeout after END-OF-MESSAGE from [._[:alnum:]-]+\[(127.0.0.1|::1)\]$ +# +# OpenDMARC +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ opendmarc\[[0-9]+\]: implicit authentication service: [-_.[:alnum:]]+\.fripost\.org$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ opendmarc\[[0-9]+\]: [[:xdigit:]]+: RFC5322 requirement error: (not exactly one Date field|multiple Message-Id fields)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ opendmarc\[[0-9]+\]: [[:xdigit:]]+: unable to parse From header field$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ opendmarc\[[0-9]+\]: [[:xdigit:]]+:? ignoring Authentication-Results at\s +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ opendmarc\[[0-9]+\]: [[:xdigit:]]+: ignoring invalid ARC-Authentication-Results header ".*" +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ opendmarc\[[0-9]+\]: [[:xdigit:]]+: SPF\((mailfrom|helo)\): \S+ (none|tempfail|fail|pass)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ opendmarc\[[0-9]+\]: [[:xdigit:]]+: [-_.[:alnum:]]+ (none|fail|pass)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ opendmarc\[[0-9]+\]: [[:xdigit:]]+: opendmarc_policy_query_dmarc\([._[:alnum:]-]+\) returned status 4$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ opendmarc\[[0-9]+\]: opendmarc: opendmarc-arcares\.c:[0-9]+: opendmarc_arcares_strip_whitespace: Assertion `.*' failed\.$ diff --git a/roles/common/files/etc/logcheck/ignore.d.server/strongswan-local b/roles/common/files/etc/logcheck/ignore.d.server/strongswan-local new file mode 100644 index 0000000..e78e9f0 --- /dev/null +++ b/roles/common/files/etc/logcheck/ignore.d.server/strongswan-local @@ -0,0 +1,24 @@ +# Ansible Managed +# Do NOT edit this file directly! +# +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[KNL\] creating acquire job for policy [[:xdigit:].:]{3,39}/[[:digit:]]+(\[\w+(/[[:alnum:]-]+)?\])? === [[:xdigit:].:]{3,39}/[[:digit:]]+(\[\w+(/[[:alnum:]-]+)?\])? with reqid \{[[:digit:]]+\}$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[KNL\] creating rekey job for CHILD_SA ESP/0x[[:xdigit:]]{8}/[[:xdigit:].:]{3,39}$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[KNL\] creating delete job for CHILD_SA ESP/0x[[:xdigit:]]{8}/([[:xdigit:].:]{3,39}|%any)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[JOB\] CHILD_SA ESP/0x[[:xdigit:]]{8}/([[:xdigit:].:]{3,39}|%any) not found for delete$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ ipsec\[[[:digit:]]+\]: [[:digit:]]+\[JOB\] spawning [0-9]+ worker threads$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] initiating IKE_SA [[:alnum:]._-]+\[[[:digit:]]+\] to [[:xdigit:].:]{3,39}$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] [[:xdigit:].:]{3,39} is initiating an IKE_SA$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] establishing CHILD_SA [[:alnum:]._-]+(\{[[:digit:]]+\}( reqid [0-9]+)?)?$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] IKE_SA [[:alnum:]._-]+\[[[:digit:]]+\] established between [[:xdigit:].:]{3,39}\[[^]\"]+\]\.\.\.[[:xdigit:].:]{3,39}\[[^]]+\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] ((in|out)bound )?CHILD_SA [[:alnum:]._-]+\{[[:digit:]]+\} established with SPIs [[:xdigit:]]{8}_i [[:xdigit:]]{8}_o and TS [[:xdigit:].:]{3,39}/[[:digit:]]+ === [[:xdigit:].:]{3,39}/[[:digit:]]+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] closing CHILD_SA [[:alnum:]._-]+\{[[:digit:]]+\} with SPIs [[:xdigit:]]{8}_i \([[:digit:]]+ bytes\) [[:xdigit:]]{8}_o \([[:digit:]]+ bytes\) and TS [[:xdigit:].:]{3,39}/[[:digit:]]+ === [[:xdigit:].:]{3,39}/[[:digit:]]+$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] reauthenticating IKE_SA [[:alnum:]._-]+\[[[:digit:]]+\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[JOB\] deleting IKE_SA after [[:digit:]]+ seconds of CHILD_SA inactivity$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] deleting IKE_SA [[:alnum:]._-]+\[[[:digit:]]+\] between [[:xdigit:].:]{3,39}\[[^]\"]+\]\.\.\.[[:xdigit:].:]{3,39}\[[^]]+\]$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[JOB\] deleting CHILD_SA after [[:digit:]]+ seconds of inactivity$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[JOB\] CHILD_SA \{[[:digit:]]+\} not found for delete$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[JOB\] deleting half open IKE_SA with [[:xdigit:].:]{3,39} after timeout$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] IKE_SA deleted$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[MGR\] ignoring request with ID [[:digit:]]+, already processing$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[KNL\] flags changed for [[:xdigit:].:]{3,39} on e[nt][[:alnum:]]+ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[KNL\] [[:xdigit:].:]{3,39} appeared on e[nt][[:alnum:]]+$ diff --git a/roles/common/files/etc/logcheck/logcheck.conf b/roles/common/files/etc/logcheck/logcheck.conf index 9a7e7c6..4c7ff10 100644 --- a/roles/common/files/etc/logcheck/logcheck.conf +++ b/roles/common/files/etc/logcheck/logcheck.conf @@ -52,34 +52,45 @@ FQDN=1 # Controls the base directory for rules file location # This must be an absolute path #RULEDIR="/etc/logcheck" # Controls if syslog-summary is run over each section. # Alternatively, set to "1" to enable extra summary. # HINT: syslog-summary needs to be installed. #SYSLOGSUMMARY=0 # Controls Subject: lines on logcheck reports: #ATTACKSUBJECT="Security Alerts" #SECURITYSUBJECT="Security Events" #EVENTSSUBJECT="System Events" # Controls [logcheck] prefix on Subject: lines -#ADDTAG="no" +#ADDTAG="no" + +# Previous versions of logcheck always sent messages in 7bit encoding, +# even if that resulted in RFC-violating messages. For example, really +# long syslog lines would generate too-long SMTP lines, which are +# rejected at least by Debian's default exim configuration. The new +# default is to let mime-construct pick an appropriate encoding, but you +# can override it by setting the below (to any of the encodings +# supported by mime-construct). You may need to do this if you have +# tools handling logcheck emails that don't understand MIME encoding. + +#MIMEENCODING= # Set a different location for temporary files than /tmp # this is useful if your /tmp is small and you are getting # errors such as: # cp: writing `/tmp/logcheck.y12449/checked': No space left on device # /usr/sbin/logcheck: line 161: cannot create temp file for here document: No space left on device # mail: /tmp/mail.RsXXXXpc2eAx: No space left on device # Null message body; hope that's ok # # If this is happening, likely you will want to change the following to be some other # location, such as /var/tmp TMP="/tmp" diff --git a/roles/common/files/etc/logcheck/violations.ignore.d/logcheck-sudo b/roles/common/files/etc/logcheck/violations.ignore.d/logcheck-sudo index e474019..70673ae 100644 --- a/roles/common/files/etc/logcheck/violations.ignore.d/logcheck-sudo +++ b/roles/common/files/etc/logcheck/violations.ignore.d/logcheck-sudo @@ -1,7 +1,5 @@ -^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sudo: pam_krb5\(sudo:auth\): user [[:alnum:]-]+ authenticated as [[:alnum:]-]+@[.A-Z]+$ -# ignore sudo with custom ENV -#^\w{3} [ :0-9]{11} [._[:alnum:]-]+ sudo:[[:space:]]+[_[:alnum:].-]+ : TTY=(unknown|console|(pts/|tty|vc/)[[:digit:]]+) ; PWD=[^;]+ ; USER=[._[:alnum:]-]+ ; COMMAND=((/(usr|etc|bin|sbin)/|sudoedit ).*|list)$ -^\w{3} [ :0-9]{11} [._[:alnum:]-]+ sudo:[[:space:]]+[_[:alnum:].-]+ : TTY=(unknown|console|(pts/|tty|vc/)[[:digit:]]+) ; PWD=[^;]+ ; USER=[._[:alnum:]-]+ (; ENV=([_a-zA-Z]+=\S* )+)?; COMMAND=((/(usr|etc|bin|sbin)/|sudoedit ).*|list)$ +^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ sudo: pam_krb5\(sudo:auth\): user [._[:alnum:]-]+ authenticated as [._[:alnum:]-]+@[.A-Z]+$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ sudo:[[:space:]]+[_[:alnum:].-]+ : (TTY=(unknown|console|(pts/|tty|vc/)[[:digit:]]+) ; )?PWD=[^;]+ ; USER=[._[:alnum:]-]+( ; GROUP=[._[:alnum:]-]+)? ; COMMAND=((/(usr|etc|bin|sbin)/|sudoedit ).*|list)$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ sudo:[[:space:]]+[_[:alnum:].-]+ : \(command continued\).*$ -^\w{3} [ :0-9]{11} [._[:alnum:]-]+ sudo: pam_[[:alnum:]]+\(sudo:session\): session opened for user [[:alnum:]-]+ by ([[:alnum:]-]+)?\(uid=[0-9]+\)$ -^\w{3} [ :0-9]{11} [._[:alnum:]-]+ sudo: pam_[[:alnum:]]+\(sudo:session\): session closed for user [[:alnum:]-]+$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ sudo: pam_[[:alnum:]]+\(sudo:session\): session opened for user [._[:alnum:]-]+\(uid=[0-9]+\) by ([[:alnum:]-]+)?\(uid=[0-9]+\)$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ sudo: pam_[[:alnum:]]+\(sudo:session\): session closed for user [._[:alnum:]-]+$ diff --git a/roles/common/files/etc/logrotate.d/fripost-mail b/roles/common/files/etc/logrotate.d/fripost-mail index 4fc1a85..7f7ffc2 100644 --- a/roles/common/files/etc/logrotate.d/fripost-mail +++ b/roles/common/files/etc/logrotate.d/fripost-mail @@ -1,34 +1,34 @@ # Don't forget to remove these log files from other files under # /etc/logrotate.d/ ! /var/log/mail.log /var/log/mail.info { rotate 3 daily missingok notifempty compress delaycompress sharedscripts postrotate - invoke-rc.d rsyslog rotate > /dev/null + /usr/lib/rsyslog/rsyslog-rotate endscript } # Keep a mapping Postfix's message ID -> SASL username for a month, to # find authors of potential abuse emails (if we are shown the header of # such emails.) /var/log/mail.sasl { rotate 4 weekly missingok notifempty compress delaycompress sharedscripts postrotate - invoke-rc.d rsyslog rotate > /dev/null + /usr/lib/rsyslog/rsyslog-rotate endscript } diff --git a/roles/common/files/etc/network/if-post-down.d/iptables b/roles/common/files/etc/network/if-post-down.d/iptables deleted file mode 100755 index d27977d..0000000 --- a/roles/common/files/etc/network/if-post-down.d/iptables +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh - -# A post-down hook to flush ip tables and delete custom chains in the -# loaded v4 and v6 rulesets. -# Copyright © 2013 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; run the script for ifdown only. -[ "$IFACE" != lo -a "$MODE" = stop ] || exit 0 - -case "$ADDRFAM" in - inet) ipts=/sbin/iptables-save; ipt=/sbin/iptables;; - inet6) ipts=/sbin/ip6tables-save; ipt=/sbin/ip6tables;; - *) exit 0 -esac - -$ipts | sed -nr 's/^\*//p' | \ -while read table; do - $ipt -t "$table" -F - $ipt -t "$table" -X -done diff --git a/roles/common/files/etc/network/if-pre-up.d/iptables b/roles/common/files/etc/network/if-pre-up.d/iptables deleted file mode 100755 index 2b83cdc..0000000 --- a/roles/common/files/etc/network/if-pre-up.d/iptables +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash - -# A pre-up hook to auto-(re)load the iptables rulesets whenever the -# network is brought up. If the action fails, an alert message is passed -# to syslogd. -# Copyright © 2013 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 -uo pipefail -PATH=/usr/sbin:/usr/bin:/sbin:/bin - -# NOTE: syslog starts after networking during the boot process, messages -# won't be logged at boot time. -log="/usr/bin/logger -st firewall" - -# Ignore the loopback interface; run the script for ifup only. -[ "$IFACE" != lo -a "$MODE" = start ] || exit 0 - -# We support only IPv4 and IPv6. -[ "$ADDRFAM" = inet -o "$ADDRFAM" = inet6 ] || exit 0 - -$log -p user.info -- "Loading $ADDRFAM firewall on interface $IFACE." - -case "$ADDRFAM" in - inet) iptr=/sbin/iptables-restore; rules=rules.v4;; - inet6)iptr=/sbin/ip6tables-restore; rules=rules.v6;; -esac -rules="/etc/iptables/$rules" - -$iptr < $rules 2>&1 | $log -p user.err -rv=$? - -[ $rv -gt 0 ] && $log -p user.alert \ - "WARN: Failed to load iptables rulesets; the machine may be unprotected!" -exit $rv diff --git a/roles/common/files/etc/postfix/master.cf b/roles/common/files/etc/postfix/master.cf deleted file mode 100644 index 9b81c70..0000000 --- a/roles/common/files/etc/postfix/master.cf +++ /dev/null @@ -1,80 +0,0 @@ -# -# Postfix master process configuration file. For details on the format -# of the file, see the master(5) manual page (command: "man 5 master"). -# -# Do not forget to execute "postfix reload" after editing this file. -# -# ========================================================================== -# service type private unpriv chroot wakeup maxproc command + args -# (yes) (yes) (yes) (never) (100) -# ========================================================================== -smtpd pass - - n - - smtpd - -o cleanup_service_name=cleanup_nochroot -smtp inet n - n - 1 postscreen -tlsproxy unix - - n - 0 tlsproxy -dnsblog unix - - n - 0 dnsblog -submission inet n - - - - smtpd -pickup fifo n - - 60 1 pickup -cleanup unix n - - - 0 cleanup -cleanup_nochroot unix n - 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 -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 -reserved-alias unix - n n - - pipe - flags=Rhu user=nobody argv=/usr/local/bin/reserved-alias.pl ${sender} ${original_recipient} @fripost.org -sympa unix - n n - - pipe - flags=Rhu user=sympa argv=/usr/local/bin/sympa-queue ${user} - -# Client part (lmtp) - amavis -amavisfeed unix - - n - 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 (if the MDA and outgoing proxy are on the -# same machine, we need to create another entry, on another port.) -127.0.0.1:10025 inet n - n - - 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 diff --git a/roles/common/files/etc/rkhunter.conf b/roles/common/files/etc/rkhunter.conf index 31b3062..c7358d0 100644 --- a/roles/common/files/etc/rkhunter.conf +++ b/roles/common/files/etc/rkhunter.conf @@ -1,139 +1,142 @@ # # This is the main configuration file for Rootkit Hunter. # # You can modify this file directly, or you can create a local configuration # file. The local file must be named 'rkhunter.conf.local', and must reside # in the same directory as this file. Alternatively you can create a directory, # named 'rkhunter.d', which also must be in the same directory as this # configuration file. Within the 'rkhunter.d' directory you can place further # configuration files. There is no restriction on the file names used, other # than they must end in '.conf'. # # Please modify the configuration file(s) to your own requirements. It is # recommended that the command 'rkhunter -C' is run after any changes have # been made. # # Please review the documentation before posting bug reports or questions. -# To report bugs, obtain updates, or provide patches or comments, please go -# to: http://rkhunter.sourceforge.net +# To report bugs, provide patches or comments, please go to: +# http://rkhunter.sourceforge.net # # To ask questions about rkhunter, please use the 'rkhunter-users' mailing list. # Note that this is a moderated list, so please subscribe before posting. # # In the configuration files, lines beginning with a hash (#), and blank lines, # are ignored. Also, end-of-line comments are not supported. # # Any of the configuration options may appear more than once. However, several # options only take one value, and so the last one seen will be used. Some # options are allowed to appear more than once, and the text describing the # option will say if this is so. These configuration options will, in effect, # have their values concatenated together. To delete a previously specified # option list, specify the option with no value (that is, a null string). # # Some of the options are space-separated lists, others, typically those # specifying pathnames, are newline-separated lists. These must be entered # as one item per line. Quotes must not be used to surround the pathname. # # For example, to specify two pathnames, '/tmp/abc' and '/tmp/xyz', for an # option: XXX=/tmp/abc (correct) # XXX=/tmp/xyz # # XXX="/tmp/abc" (incorrect) # XXX="/tmp/xyz" # # XXX=/tmp/abc /tmp/xyz (incorrect) # or XXX="/tmp/abc /tmp/xyz" (incorrect) # or XXX="/tmp/abc" "/tmp/xyz" (incorrect) # # The last three examples are being configured as space-separated lists, # which is incorrect, generally, for options specifying pathnames. They # should be configured with one entry per line as in the first example. # # If wildcard characters (globbing) are allowed for an option, then the -# text describing the option will say so. +# text describing the option will say so. Any globbing character explicitly +# required in a pathname should be escaped. # # Space-separated lists may be enclosed by quotes, although they are not # required. If they are used, then they must only appear at the start and # end of the list, not in the middle. # # For example: XXX=abc def gh (correct) # XXX="abc def gh" (correct) # XXX="abc" "def" "gh" (incorrect) # # Space-separated lists may also be entered simply as one entry per line. # # For example: XXX=abc (correct) # XXX=def # XXX="gh" # # If a configuration option is never set, then the program will assume a # default value. The text describing the option will state the default value. # If there is no default, then rkhunter will calculate a value or pathname -# to use. +# to use. If a value is set for a configuration option, then the default +# value is ignored. If it is wished to keep the default value, as well as +# any other set value, then the default must be explicitly set. # # # If this option is set to '1', it specifies that the mirrors file # ('mirrors.dat'), which is used when the '--update' and '--versioncheck' # options are used, is to be rotated. Rotating the entries in the file allows # a basic form of load-balancing between the mirror sites whenever the above # options are used. # # If the option is set to '0', then the mirrors will be treated as if in a # priority list. That is, the first mirror listed will always be used first. # The second mirror will only be used if the first mirror fails, the third # mirror will only be used if the second mirror fails, and so on. # # If the mirrors file is read-only, then the '--versioncheck' command-line # option can only be used if this option is set to '0'. # # The default value is '1'. # #ROTATE_MIRRORS=1 # # If this option is set to '1', it specifies that when the '--update' option is # used, then the mirrors file is to be checked for updates as well. If the # current mirrors file contains any local mirrors, these will be prepended to # the updated file. If this option is set to '0', the mirrors file can only be # updated manually. This may be useful if only using local mirrors. # # The default value is '1'. # -#UPDATE_MIRRORS=1 +UPDATE_MIRRORS=0 # # The MIRRORS_MODE option tells rkhunter which mirrors are to be used when # the '--update' or '--versioncheck' command-line options are given. # Possible values are: # 0 - use any mirror # 1 - only use local mirrors # 2 - only use remote mirrors # # Local and remote mirrors can be defined in the mirrors file by using the # 'local=' and 'remote=' keywords respectively. # # The default value is '0'. # -#MIRRORS_MODE=0 +MIRRORS_MODE=1 # # Email a message to this address if a warning is found when the system is # being checked. Multiple addresses may be specified simply be separating # them with a space. To disable the option, simply set it to the null string # or comment it out. # # The option may be specified more than once. # # The default value is the null string. # # Also see the MAIL_CMD option. # MAIL-ON-WARNING=admin@fripost.org # # This option specifies the mail command to use if MAIL-ON-WARNING is set. # # NOTE: Double quotes are not required around the command, but are required # around the subject line if it contains spaces. @@ -201,41 +204,41 @@ SCRIPTDIR=/usr/share/rkhunter/scripts # rkhunter --lang en --list languages # # The default language is 'en' (English). # #LANGUAGE=en # # This option is a space-separated list of the languages that are to be updated # when the '--update' option is used. If unset, then all the languages will be # updated. If none of the languages are to be updated, then set this option to # just 'en'. # # The default language, specified by the LANGUAGE option, and the English (en) # language file will always be updated regardless of this option. # # This option may be specified more than once. # # The default value is the null string, indicating that all the language files # will be updated. # -#UPDATE_LANG="" +UPDATE_LANG="en" # # This option specifies the log file pathname. The file will be created if it # does not initially exist. If the option is unset, then the program will # display a message each time it is run saying that the default value is being # used. # # The default value is '/var/log/rkhunter.log'. # LOGFILE=/var/log/rkhunter.log # # Set this option to '1' if the log file is to be appended to whenever rkhunter # is run. A value of '0' will cause a new log file to be created whenever the # program is run. # # The default value is '0'. # #APPEND_LOG=0 @@ -247,41 +250,41 @@ LOGFILE=/var/log/rkhunter.log # If the option value is '0', then the log file will not be copied regardless # of whether any errors or warnings occurred. # # The default value is '0'. # #COPY_LOG_ON_ERROR=0 # # Set the following option to enable the rkhunter check start and finish times # to be logged by syslog. Warning messages will also be logged. The value of # the option must be a standard syslog facility and priority, separated by a # dot. For example: # # USE_SYSLOG=authpriv.warning # # Setting the value to 'NONE', or just leaving the option commented out, # disables the use of syslog. # # The default value is not to use syslog. # -#USE_SYSLOG=authpriv.notice +USE_SYSLOG=authpriv.warning # # Set the following option to '1' if the second colour set is to be used. This # can be useful if your screen uses black characters on a white background # (for example, a PC instead of a server). A value of '0' will cause the default # colour set to be used. # # The default value is '0'. # #COLOR_SET2=0 # # Set the following option to '0' if rkhunter should not detect if X is being # used. If X is detected as being used, then the second colour set will # automatically be used. If set to '1', then the use of X will be detected. # # The default value is '0'. # AUTO_X_DETECT=1 @@ -300,144 +303,147 @@ AUTO_X_DETECT=1 # 'PermitRootLogin' option. A warning will be displayed if they do not match. # However, if a value has not been set in the SSH configuration file, then a # value here of 'unset' can be used to avoid warning messages. # # The default value is 'no'. # #ALLOW_SSH_ROOT_USER=no # # Set this option to '1' to allow the use of the SSH-1 protocol, but note # that theoretically it is weaker, and therefore less secure, than the # SSH-2 protocol. Do not modify this option unless you have good reasons # to use the SSH-1 protocol (for instance for AFS token passing or Kerberos4 # authentication). If the 'Protocol' option has not been set in the SSH # configuration file, then a value of '2' may be set here in order to # suppress a warning message. A value of '0' indicates that the use of # SSH-1 is not allowed. # # The default value is '0'. # -#ALLOW_SSH_PROT_V1=0 +ALLOW_SSH_PROT_V1=2 # # This setting tells rkhunter the directory containing the SSH configuration -# file. This setting will be worked out by rkhunter, and so should not -# usually need to be set. +# file. If unset, this setting will be worked out by rkhunter, and so should +# not usually need to be set. # # This option has no default value. # #SSH_CONFIG_DIR=/etc/ssh # # These two options determine which tests are to be performed. The ENABLE_TESTS # option can use the word 'ALL' to refer to all of the available tests. The # DISABLE_TESTS option can use the word 'NONE' to mean that no tests are # disabled. The list of disabled tests is applied to the list of enabled tests. # # Both options are space-separated lists of test names, and both options may # be specified more than once. The currently available test names can be seen # by using the command 'rkhunter --list tests'. # # The supplied configuration file has some tests already disabled, and these # are tests that will be used only occasionally, can be considered 'advanced' # or that are prone to produce more than the average number of false-positives. # # Please read the README file for more details about enabling and disabling # tests, the test names, and how rkhunter behaves when these options are used. # # The default values are to enable all tests and to disable none. However, if # either of the options below are specified, then they will override the # program defaults. # -# hidden_procs test requires the unhide and/or unhide.rb commands which are -# part of the unhide respectively unhide.rb packages in Debian. -# -# apps test is disabled by default as it triggers warnings about outdated -# applications (and warns about possible security risk: we better trust -# the Debian Security Team). -# ENABLE_TESTS=ALL -DISABLE_TESTS=suspscan hidden_procs deleted_files packet_cap_apps apps +DISABLE_TESTS=suspscan hidden_ports hidden_procs deleted_files packet_cap_apps apps # # The HASH_CMD option can be used to specify the command to use for the file # properties hash value check. It can be specified as just the command name or # the full pathname. If just the command name is given, and it is one of MD5, # SHA1, SHA224, SHA256, SHA384 or SHA512, then rkhunter will first look for the # relevant command, such as 'sha256sum', and then for 'sha256'. If neither of # these are found, it will then look to see if a perl module has been installed # which will support the relevant hash function. To see which perl modules have # been installed use the command 'rkhunter --list perl'. # # Systems using prelinking are restricted to using either the SHA1 or MD5 # function. # # A value of 'NONE' (in uppercase) can be specified to indicate that no hash # function should be used. Rkhunter will detect this, and automatically disable # the file properties hash check test. # # Examples: # For Solaris 9 : HASH_CMD=gmd5sum # For Solaris 10: HASH_CMD=sha1sum # For AIX (>5.2): HASH_CMD="csum -hMD5" # For NetBSD : HASH_CMD="cksum -a sha512" # # NOTE: Whenever this option is changed 'rkhunter --propupd' must be run. # -# The default value is the SHA1 function, or MD5 if SHA1 cannot be found. +# The default value is the SHA256 function, unless prelinking is used in +# which case it defaults to the SHA1 function. # -# Also see the HASH_FLD_IDX option. +# Also see the HASH_FLD_IDX option. In addition, note the comments under +# the PKGMGR option relating to the use of HASH_CMD. # -HASH_CMD=sha512sum +HASH_CMD=SHA512 # # The HASH_FLD_IDX option specifies which field from the HASH_CMD command # output contains the hash value. The fields are assumed to be space-separated. # # The option value must be an integer greater than zero. # # The default value is '1', but for *BSD users rkhunter will, by default, use a # value of '4' if the HASH_CMD option has not been set. # #HASH_FLD_IDX=4 # # The PKGMGR option tells rkhunter to use the specified package manager to # obtain the file property information. This is used when updating the file # properties file ('rkhunter.dat'), and when running the file properties check. # For RedHat/RPM-based systems, 'RPM' can be used to get information from the # RPM database. For Debian-based systems 'DPKG' can be used, for *BSD systems -# 'BSD' can be used, and for Solaris systems 'SOLARIS' can be used. No value, -# or a value of 'NONE', indicates that no package manager is to be used. +# 'BSD' can be used, or for *BSD systems with the 'pkg' command 'BSDng' can be +# used, and for Solaris systems 'SOLARIS' can be used. No value, or a value of +# 'NONE', indicates that no package manager is to be used. # -# The current package managers, except 'SOLARIS', store the file hash values -# using an MD5 hash function. The Solaris package manager includes a checksum -# value, but this is not used by default (see USE_SUNSUM below). +# The package managers obtain each file hash value using a hash function. The +# Solaris package manager includes a 16-bit checksum value, but this is not +# used by default (see USE_SUNSUM below). The 'RPM' and 'BSDng' package managers +# currently use a SHA256 hash function. Other package managers will, typically, +# use an MD5 hash function. # -# The 'DPKG' and 'BSD' package managers only provide MD5 hash values. -# The 'RPM' package manager additionally provides values for the inode, -# file permissions, uid, gid and other values. The 'SOLARIS' also provides -# most of the values, similar to 'RPM', but not the inode number. +# The 'DPKG', 'BSD' and 'BSDng' package managers only provide a file hash value. +# The 'RPM' package manager additionally provides values for the inode, file +# permissions, uid, gid and other values. The 'SOLARIS' package manager also +# provides most of the values, similar to 'RPM', but not the inode number. # # For any file not part of a package, rkhunter will revert to using the -# HASH_CMD hash function instead. +# HASH_CMD hash function instead. This means that if the HASH_CMD option +# is set, and PKGMGR is set, then the HASH_CMD hash function is only used, +# and stored, for non-packaged files. All packaged files will use, and store, +# whatever hash function the relevant package manager uses. So, for example, +# with the 'RPM' package manager, packaged files will be stored with their +# SHA256 value regardless of the value of the HASH_CMD option. # # NOTE: Whenever this option is changed 'rkhunter --propupd' must be run. # # The default value is 'NONE'. # # Also see the PKGMGR_NO_VRFY and USE_SUNSUM options. # # NONE is the default for Debian as well, as running --propupd takes # about 4 times longer when it's set to DPKG # #PKGMGR=NONE # # It is possible that a file, which is part of a package, may have been # modified by the administrator. Typically this occurs for configuration # files. However, the package manager may list the file as being modified. # For the RPM package manager this may well depend on how the package was # built. This option specifies a pathname which is to be exempt from the # package manager verification process, and which will be treated # as a non-packaged file. As such, the file properties are still checked. @@ -482,199 +488,225 @@ HASH_CMD=sha512sum # # The default value is the null string. # #IGNORE_PRELINK_DEP_ERR=/bin/ps /usr/bin/top # # These options specify a command, directory or file pathname which will be # included or excluded in the file properties checks. # # For the USER_FILEPROP_FILES_DIRS option, simple command names - for example, # 'top' - and directory names are added to the internal list of directories to # be searched for each of the command names in the command list. Additionally, # full pathnames to files, which need not be commands, may be given. Any files # or directories which are already part of the internal lists will be silently # ignored from the configuration. # # For the USER_FILEPROP_FILES_DIRS option, wildcards are allowed, except for # simple command names. # For example, 'top*' cannot be given, but '/usr/bin/top*' is allowed. # +# To extend the use of wildcards to include recursive checking of directories, +# see the GLOBSTAR configuration option. +# # Specific files may be excluded by using the EXCLUDE_USER_FILEPROP_FILES_DIRS # option. Wildcards may be used with this option. # # By combining these two options, and using wildcards, whole directories can be # excluded. For example: # # USER_FILEPROP_FILES_DIRS=/etc/* # USER_FILEPROP_FILES_DIRS=/etc/*/* # EXCLUDE_USER_FILEPROP_FILES_DIRS=/etc/rc?.d/* # # This will look for files in the first two directory levels of '/etc'. However, # anything in '/etc/rc0.d', '/etc/rc1.d', '/etc/rc2.d' and so on, will be # excluded. # # NOTE: Only files and directories which have been added by the user, and are # not part of the internal lists, can be excluded. So, for example, it is not # possible to exclude the 'ps' command by using '/bin/ps'. These will be # silently ignored from the configuration. # # Both options can be specified more than once. # # NOTE: Whenever these options are changed 'rkhunter --propupd' must be run. # # The default value for both options is the null string. # #USER_FILEPROP_FILES_DIRS=top #USER_FILEPROP_FILES_DIRS=/usr/local/sbin #USER_FILEPROP_FILES_DIRS=/etc/rkhunter.conf #USER_FILEPROP_FILES_DIRS=/etc/rkhunter.conf.local -#USER_FILEPROP_FILES_DIRS=/var/lib/rkhunter/db/* -#USER_FILEPROP_FILES_DIRS=/var/lib/rkhunter/db/i18n/* +#USER_FILEPROP_FILES_DIRS=/etc/rkhunter.d/* #EXCLUDE_USER_FILEPROP_FILES_DIRS=/opt/ps* -#EXCLUDE_USER_FILEPROP_FILES_DIRS=/var/lib/rkhunter/db/mirrors.dat -#EXCLUDE_USER_FILEPROP_FILES_DIRS=/var/lib/rkhunter/db/rkhunter* # # This option whitelists files and directories from existing, or not existing, # on the system at the time of testing. This option is used when the # configuration file options themselves are checked, and during the file # properties check, the hidden files and directories checks, and the filesystem # check of the '/dev' directory. # # This option may be specified more than once, and may use wildcards. # Be aware though that this is probably not what you want to do as the # wildcarding will be expanded after files have been deleted. As such # deleted files won't be whitelisted if wildcarded. # # NOTE: The user must take into consideration how often the file will appear # and disappear from the system in relation to how often rkhunter is run. If # the file appears, and disappears, too often then rkhunter may not notice -# this. All it will see is that the file has changed. The inode-number and DTM +# this. All it will see is that the file has changed. The inode number and DTM # will certainly be different for each new file, and rkhunter will report this. # # The default value is the null string. # #EXISTWHITELIST="" +# work around for usr-merge, cf. https://bugs.debian.org/932594 +EXISTWHITELIST=/usr/bin/egrep +EXISTWHITELIST=/usr/bin/fgrep + # # Whitelist various attributes of the specified file. The attributes are those # of the 'attributes' test. Specifying a file name here does not include it # being whitelisted for the write permission test (see below). # # This option may be specified more than once, and may use wildcard characters. # # The default value is the null string. # #ATTRWHITELIST=/usr/bin/date # # Allow the specified file to have the 'others' (world) permission have the # write-bit set. For example, files with permissions r-xr-xrwx or rwxrwxrwx. # # This option may be specified more than once, and may use wildcard characters. # # The default value is the null string. # #WRITEWHITELIST=/usr/bin/date # # Allow the specified file to be a script. # # This option may be specified more than once, and may use wildcard characters. # # The default value is the null string. # SCRIPTWHITELIST=/bin/egrep SCRIPTWHITELIST=/bin/fgrep -SCRIPTWHITELIST=/bin/which -SCRIPTWHITELIST=/usr/bin/groups +SCRIPTWHITELIST=/usr/bin/egrep +SCRIPTWHITELIST=/usr/bin/fgrep +SCRIPTWHITELIST=/usr/bin/which SCRIPTWHITELIST=/usr/bin/ldd -#SCRIPTWHITELIST=/usr/bin/lwp-request +SCRIPTWHITELIST=/usr/bin/lwp-request SCRIPTWHITELIST=/usr/sbin/adduser #SCRIPTWHITELIST=/usr/sbin/prelink -#SCRIPTWHITELIST=/usr/bin/unhide.rb +#SCRIPTWHITELIST=/usr/sbin/unhide.rb # # Allow the specified file to have the immutable attribute set. # # This option may be specified more than once, and may use wildcard characters. # # The default value is the null string. # #IMMUTWHITELIST=/sbin/ifdown # # If this option is set to '1', then the immutable-bit test is reversed. That # is, the files are expected to have the bit set. A value of '0' means that the # immutable-bit should not be set. # # The default value is '0'. # #IMMUTABLE_SET=0 # +# If this option is set to '1', then any changed inode value is ignored in +# the file properties check. The inode test itself still runs, but it will +# always return that no inodes have changed. +# +# This option may be useful for filesystems such as Btrfs, which handle inodes +# slightly differently than other filesystems. +# +# The default value is '0'. +# +#SKIP_INODE_CHECK=0 + +# # Allow the specified hidden directory to be whitelisted. # # This option may be specified more than once, and may use wildcard characters. # # The default value is the null string. # -ALLOWHIDDENDIR=/etc/.java +#ALLOWHIDDENDIR=/etc/.java ALLOWHIDDENDIR=/etc/.git +#ALLOWHIDDENDIR=/dev/.lxc # # Allow the specified hidden file to be whitelisted. # # This option may be specified more than once, and may use wildcard characters. # # The default value is the null string. # #ALLOWHIDDENFILE=/usr/share/man/man1/..1.gz #ALLOWHIDDENFILE=/usr/bin/.fipscheck.hmac #ALLOWHIDDENFILE=/usr/bin/.ssh.hmac #ALLOWHIDDENFILE=/usr/lib/.libfipscheck.so.1.1.0.hmac #ALLOWHIDDENFILE=/usr/lib/hmaccalc/sha1hmac.hmac #ALLOWHIDDENFILE=/usr/lib/hmaccalc/sha256hmac.hmac #ALLOWHIDDENFILE=/usr/sbin/.sshd.hmac #ALLOWHIDDENFILE=/usr/share/man/man5/.k5login.5.gz -ALLOWHIDDENFILE=/etc/.etckeeper +#ALLOWHIDDENFILE=/usr/share/man/man5/.k5identity.5.gz ALLOWHIDDENFILE=/etc/.gitignore #ALLOWHIDDENFILE=/etc/.bzrignore - +ALLOWHIDDENFILE=/etc/.etckeeper # # Allow the specified process to use deleted files. The process name may be -# followed by a colon-separated list of full pathnames. The process will then -# only be whitelisted if it is using one of the given files. For example: +# followed by a colon-separated list of full pathnames (which have been +# deleted). The process will then only be whitelisted if it is using one of +# the given pathnames. For example: # # ALLOWPROCDELFILE=/usr/libexec/gconfd-2:/tmp/abc:/var/tmp/xyz # # This option may be specified more than once. It may also use wildcards, but -# only in the file names. +# only in the deleted file pathnames, not in the process name. The use of +# extended pattern matching in pathname expansion (for example, '**') is not +# supported for this option. However, the option itself extends globbing when +# the '*' character is used by matching zero or more characters in the +# pathname, including those in sub-directories. For example, the pathname +# '/tmp/abc/def/xyz' would not be matched by shell globbing using '/tmp/*/xyz' +# but is matched when used in this option. Similarly, using '/tmp/*' will +# match any file found in the '/tmp' directory or any sub-directories. # # The default value is the null string. # #ALLOWPROCDELFILE=/sbin/cardmgr #ALLOWPROCDELFILE=/usr/lib/libgconf2-4/gconfd-2 #ALLOWPROCDELFILE=/usr/sbin/mysqld:/tmp/ib* -#ALLOWPROCDELFILE=/usr/lib/iceweasel/firefox-bin +#ALLOWPROCDELFILE=/usr/lib/iceweasel/iceweasel #ALLOWPROCDELFILE=/usr/bin/file-roller # # Allow the specified process to listen on any network interface. # # This option may be specified more than once, and may use wildcard characters. # # The default value is the null string. # #ALLOWPROCLISTEN=/sbin/dhclient #ALLOWPROCLISTEN=/usr/bin/dhcpcd #ALLOWPROCLISTEN=/usr/sbin/tcpdump #ALLOWPROCLISTEN=/usr/sbin/snort-plain # # Allow the specified network interfaces to be in promiscuous mode. # # This is a space-separated list of interface names. The option may be # specified more than once. # @@ -690,40 +722,80 @@ ALLOWHIDDENFILE=/etc/.gitignore # it is highly recommended that this value is used. # # The default value is 'THOROUGH'. # # Also see the ALLOWDEVFILE option. # #SCAN_MODE_DEV=THOROUGH # # Allow the specified file to be present in the '/dev' directory, and not # regarded as suspicious. # # This option may be specified more than once, and may use wildcard characters. # # The default value is the null string. # #ALLOWDEVFILE=/dev/shm/pulse-shm-* #ALLOWDEVFILE=/dev/shm/sem.ADBE_* # +# Allow the specified process pathnames to use shared memory segments. +# +# This option may be specified more than once, and may use wildcard characters. +# +# The default value is the null string. +# +#ALLOWIPCPROC=/usr/bin/firefox +#ALLOWIPCPROC=/usr/bin/vlc + +# +# Allow the specified memory segment creator PIDs to use shared memory segments. +# +# This is a space-separated list of PID numbers (as given by the +# 'ipcs -p' command). This option may be specified more than once. +# +# The default value is the null string. +# +#ALLOWIPCPID=12345 6789 + +# +# Allow the specified account names to use shared memory segments. +# +# This is a space-separated list of account names. The option may be specified +# more than once. +# +# The default value is the null string. +# +#ALLOWIPCUSER=usera userb + +# +# This option can be used to set the maximum shared memory segment size +# (in bytes) that is not considered suspicious. Any segment above this size, +# and with 600 or 666 permissions, will be considered suspicious during the +# shared memory check. +# +# The default is 1048576 (1M) bytes. +# +#IPC_SEG_SIZE=1048576 + +# # This option is used to indicate if the Phalanx2 test is to perform a basic # check, or a more thorough check. If the option is set to '0', then a basic # check is performed. If it is set to '1', then all the directories in the # '/etc' and '/usr' directories are scanned. # # NOTE: Setting this option to '1' will cause the test to take longer # to complete. # # The default value is '0'. # #PHALANX2_DIRTEST=0 # # This option tells rkhunter where the inetd configuration file is located. # # The default value is the null string. # #INETD_CONF_PATH=/etc/inetd.conf # @@ -759,92 +831,93 @@ ALLOWHIDDENFILE=/etc/.gitignore # This option tells rkhunter where the xinetd configuration file is located. # # The default value is the null string. # #XINETD_CONF_PATH=/etc/xinetd.conf # # This option allows the specified enabled xinetd services. Whilst it would be # nice to use the service names themselves, at the time of testing we only have # the pathname available. As such, these entries are the xinetd file pathnames. # # This is a space-separated list of service names. The option may be specified # more than once. # # The default value is the null string. # #XINETD_ALLOWED_SVC=/etc/xinetd.d/echo # # This option tells rkhunter the local system startup file pathnames. The -# directories will be searched for files. By default rkhunter will try and -# determine were the startup files are located. If the option is set to 'NONE', -# then certain tests will be skipped. +# directories will be searched for files. If unset, then rkhunter will try +# and determine were the startup files are located. If the option is set to +# 'NONE' then certain tests will be skipped. # # This is a space-separated list of file and directory pathnames. The option # may be specified more than once, and may use wildcard characters. # # This option has no default value. # #STARTUP_PATHS=/etc/init.d /etc/rc.local # # This option tells rkhunter the pathname to the file containing the user -# account passwords. This setting will be worked out by rkhunter, and so -# should not usually need to be set. Users of TCB shadow files should not -# set this option. +# account passwords. If unset, this setting will be worked out by rkhunter, +# and so should not usually need to be set. Users of TCB shadow files should +# not set this option. # # This option has no default value. # #PASSWORD_FILE=/etc/shadow # # This option allows the specified accounts to be root equivalent. These # accounts will have a UID value of zero. The 'root' account does not need # to be listed as it is automatically whitelisted. # # This is a space-separated list of account names. The option may be specified # more than once. # # NOTE: For *BSD systems you will probably need to use this option for the # 'toor' account. # # The default value is the null string. # #UID0_ACCOUNTS=toor rooty sashroot # # This option allows the specified accounts to have no password. NIS/YP entries # do not need to be listed as they are automatically whitelisted. # # This is a space-separated list of account names. The option may be specified # more than once. # # The default value is the null string. # #PWDLESS_ACCOUNTS=abc # # This option tells rkhunter the pathname to the syslog configuration file. -# This setting will be worked out by rkhunter, and so should not usually need -# to be set. A value of 'NONE' can be used to indicate that there is no -# configuration file, but that the syslog daemon process may be running. +# If unset, this setting will be worked out by rkhunter, and so should not +# usually need to be set. A value of 'NONE' can be used to indicate that +# there is no configuration file, but that the syslog daemon process may +# be running. # # This is a space-separated list of pathnames. The option may be specified # more than once. # # This option has no default value. # #SYSLOG_CONFIG_FILE=/etc/syslog.conf # # If this option is set to '1', then the use of syslog remote logging is # permitted. A value of '0' disallows the use of remote logging. # # The default value is '0'. # #ALLOW_SYSLOG_REMOTE_LOGGING=0 # # This option allows the specified applications, or a specific version of an # application, to be whitelisted. If a specific version is to be whitelisted, # then the name must be followed by a colon and then the version number. @@ -879,51 +952,63 @@ ALLOWHIDDENFILE=/etc/.gitignore # #SUSPSCAN_DIRS=/tmp /var/tmp # # This option specifies the directory for temporary files used by the # 'suspscan' test. A memory-based directory, such as a tempfs filesystem, is # better (faster). Do not use a directory name that is listed in SUSPSCAN_DIRS # as that is highly likely to cause false-positive results. # # The default value is '/dev/shm'. # #SUSPSCAN_TEMP=/dev/shm # # This option specifies the 'suspscan' test maximum filesize in bytes. Files # larger than this will not be inspected. Do make sure you have enough space # available in your temporary files directory. # # The default value is '1024000'. # -#SUSPSCAN_MAXSIZE=10240000 +#SUSPSCAN_MAXSIZE=1024000 # # This option specifies the 'suspscan' test score threshold. Below this value # no hits will be reported. # # The default value is '200'. # #SUSPSCAN_THRESH=200 # +# This option may be used to whitelist file pathnames from the suspscan test. +# +# Shell globbing may be used in the pathname. Also see the GLOBSTAR configuration +# option. +# +# This option may be specified more than once. +# +# The default value is the null string. +# +#SUSPSCAN_WHITELIST="" + +# # The following options can be used to whitelist network ports which are known # to have been used by malware. # # The PORT_WHITELIST option is a space-separated list of one or more of two # types of whitelisting. These are: # # 1) a 'protocol:port' pair # 2) an asterisk ('*') # # Only the UDP or TCP protocol may be specified, and the port number must be # between 1 and 65535 inclusive. # # The asterisk can be used to indicate that any executable which rkhunter can # locate as a command, is whitelisted. (Also see BINDIR) # # The PORT_PATH_WHITELIST option specifies one of two types of whitelisting. # These are: # # 1) a pathname to an executable # 2) a combined pathname, protocol and port @@ -1059,90 +1144,105 @@ ALLOWHIDDENFILE=/etc/.gitignore #READLINK_CMD=BUILTIN # # In the file properties test any modification date/time is displayed as the # number of epoch seconds. Rkhunter will try and use the 'date' command, or # failing that the 'perl' command, to display the date and time in a # human-readable format as well. This option may be used if some other command # should be used instead. The given command must understand the '%s' and # 'seconds ago' options found in the GNU 'date' command. # # A value of 'NONE' may be used to request that only the epoch seconds be shown. # A value of 'PERL' may be used to force rkhunter to use the 'perl' command, if # it is present. # # This option has no default value. # #EPOCH_DATE_CMD="" # # This setting tells rkhunter the directory containing the available Linux -# kernel modules. This setting will be worked out by rkhunter, and so should -# not usually need to be set. +# kernel modules. If unset, this setting will be worked out by rkhunter, and +# so should not usually need to be set. # # This option has no default value. # #MODULES_DIR="" # # The following option can be set to a command which rkhunter will use when # downloading files from the Internet - that is, when the '--update' or # '--versioncheck' option is used. The command can take options. # # This allows the user to use a command other than the one automatically # selected by rkhunter, but still one which it already knows about. # For example: # # WEB_CMD=curl # # Alternatively, the user may specify a completely new command. However, note # that rkhunter expects the downloaded file to be written to stdout, and that # everything written to stderr is ignored. For example: # # WEB_CMD="/opt/bin/dlfile --timeout 5m -q" # # *BSD users may want to use the 'ftp' command, provided that it supports the # HTTP protocol: # # WEB_CMD="ftp -o -" # # This option has no default value. # -#WEB_CMD="" +WEB_CMD="/bin/false" # # Set the following option to '1' if locking is to be used when rkhunter runs. # The lock is set just before logging starts, and is removed when the program # ends. It is used to prevent items such as the log file, and the file # properties file, from becoming corrupted if rkhunter is running more than -# once. The mechanism used is to simply create a lock file in the TMPDIR +# once. The mechanism used is to simply create a lock file in the LOCKDIR # directory. If the lock file already exists, because rkhunter is already # running, then the current process simply loops around sleeping for 10 seconds # and then retrying the lock. A value of '0' means not to use locking. # # The default value is '0'. # -# Also see the LOCK_TIMEOUT and SHOW_LOCK_MSGS options. +# Also see the LOCKDIR, LOCK_TIMEOUT and SHOW_LOCK_MSGS options. # #USE_LOCKING=0 # +# This option specifies the directory to be used when locking is enabled. +# If the option is unset, then the directory to be used will be worked out +# by rkhunter. In that instance the directories '/run/lock', '/var/lock', +# '/var/run/lock', '/run' and '/var/run' will be checked in turn. If none +# of those can be found, or are not read/writeable, then the TMPDIR directory +# will be used. +# +# To avoid the lock file persisting across a server reboot, the directory +# used should be memory-resident. +# +# This option has no default value. +# +#LOCKDIR="" + +# # If locking is used, then rkhunter may have to wait to get the lock file. # This option sets the total amount of time, in seconds, that rkhunter should # wait. It will retry the lock every 10 seconds, until either it obtains the # lock or the timeout value has been reached. # # The default value is 300 seconds (5 minutes). # #LOCK_TIMEOUT=300 # # If locking is used, then rkhunter may be doing nothing for some time if it # has to wait for the lock. If this option is set to '1', then some simple # messages are echoed to the users screen to let them know that rkhunter is # waiting for the lock. Set this option to '0' if the messages are not to be # displayed. # # The default value is '1'. # #SHOW_LOCK_MSGS=1 @@ -1174,56 +1274,40 @@ ALLOWHIDDENFILE=/etc/.gitignore # list, and will be executed in the order given. # # This option may be specified more than once. # # The default value is 'sys' in order to maintain compatibility with older # versions of 'unhide'. # #UNHIDE_TESTS=sys # # The following option can be used to set options for the 'unhide-tcp' command. # The options are space-separated. # # This option may be specified more than once. # # The default value is the null string. # #UNHIDETCP_OPTS="" # -# If both the C 'unhide', and Ruby 'unhide.rb', programs exist on the system, -# then it is possible to disable the execution of one of the programs if -# desired. By default rkhunter will look for both programs, and execute each -# of them as they are found. If the value of this option is '0', then both -# programs will be executed if they are present. A value of '1' will disable -# execution of the C 'unhide' program, and a value of '2' will disable the Ruby -# 'unhide.rb' program. To disable both programs, then disable the -# 'hidden_procs' test. -# -# The default value is '0'. -# -DISABLE_UNHIDE=1 - -INSTALLDIR=/usr - -# # This option can be set to either '0' or '1'. If set to '1' then the summary, # shown after rkhunter has run, will display the actual number of warnings # found. If it is set to '0', then the summary will simply indicate that # 'One or more' warnings were found. If no warnings were found, and this option # is set to '1', then a "0" will be shown. If the option is set to '0', then # the words 'No warnings' will be shown. # # The default value is '0'. # #SHOW_SUMMARY_WARNINGS_NUMBER=0 # # This option is used to determine where, if anywhere, the summary scan time is # displayed. A value of '0' indicates that it should not be displayed anywhere. # A value of '1' indicates that the time should only appear on the screen, and a # value of '2' that it should only appear in the log file. A value of '3' # indicates that the time taken should appear both on the screen and in the log # file. # # The default value is '3'. @@ -1232,20 +1316,42 @@ INSTALLDIR=/usr # # The two options below may be used to check if a file is missing or empty # (that is, it has a size of zero). The EMPTY_LOGFILES option will also check # if the file is missing, since that can be interpreted as a file of no size. # However, the file will only be reported as missing if the MISSING_LOGFILES # option hasn't already done this. # # Both options are space-separated lists of pathnames, and may be specified # more than once. # # NOTE: Log files are usually 'rotated' by some mechanism. At that time it is # perfectly possible for the file to be either missing or empty. As such these # options may produce false-positive warnings when log files are rotated. # # For both options the default value is the null string. # #EMPTY_LOGFILES="" #MISSING_LOGFILES="" +# +# This option can be set to either '0' or '1'. If set to '1' then the globbing +# characters '**' can be used to allow the recursive checking of directories. +# This can be useful, for example, with the USER_FILEPROP_FILES_DIRS option. +# For example: +# +# USER_FILEPROP_FILES_DIRS=/etc/**/*.conf +# +# This will check all '.conf' files within the '/etc' directory, and any +# sub-directories (at any level). If GLOBSTAR is not set, then the shell will +# interpret '**' as '*' and only one level of sub-directories will be checked. +# +# NOTE: This option is only valid for those shells which support the 'globstar' +# option. Typically this will be 'bash' (version 4 and above) via the 'shopt' command, +# and 'ksh' via the 'set' command. +# +# The default value is '0'. +# +#GLOBSTAR=0 + +INSTALLDIR=/usr + diff --git a/roles/common/files/etc/rsyslog.conf b/roles/common/files/etc/rsyslog.conf index 6ebaa92..42b01c5 100644 --- a/roles/common/files/etc/rsyslog.conf +++ b/roles/common/files/etc/rsyslog.conf @@ -1,45 +1,41 @@ -# /etc/rsyslog.conf Configuration file for rsyslog. +# /etc/rsyslog.conf configuration file for rsyslog # -# For more information see -# /usr/share/doc/rsyslog-doc/html/rsyslog_conf.html +# For more information install rsyslog-doc and see +# /usr/share/doc/rsyslog-doc/html/configuration/index.html ################# #### MODULES #### ################# -$ModLoad imuxsock # provides support for local system logging -$ModLoad imklog # provides kernel logging support -#$ModLoad immark # provides --MARK-- message capability +module(load="imuxsock") # provides support for local system logging +module(load="imklog") # provides kernel logging support +#module(load="immark") # provides --MARK-- message capability # provides UDP syslog reception -#$ModLoad imudp -#$UDPServerRun 514 +#module(load="imudp") +#input(type="imudp" port="514") # provides TCP syslog reception -#$ModLoad imtcp -#$InputTCPServerRun 514 - -# Disable rate-limiting (the default for rsyslog v7, but not for rsyslog v5) -$SystemLogRateLimitInterval 0 -$SystemLogRateLimitBurst 0 +#module(load="imtcp") +#input(type="imtcp" port="514") ########################### #### GLOBAL DIRECTIVES #### ########################### # # Use traditional timestamp format. # To enable high precision timestamps, comment out the following line. # $ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat # # Set the default permissions for all log files. # $FileOwner root $FileGroup adm $FileCreateMode 0640 $DirCreateMode 0755 $Umask 0022 @@ -62,74 +58,45 @@ $IncludeConfig /etc/rsyslog.d/*.conf # # Logging for the mail system. Split it up so that # it is easy to write scripts to parse these files. # mail.* -/var/log/mail.log mail.info -/var/log/mail.info mail.warn -/var/log/mail.warn mail.err /var/log/mail.err # To preserve the privacy of our users, we stop processing relevant log # entries (eg, we don't put them into /var/log/syslog) that are of # severity info and lower. Those lines are put into mail.log and # mail.info for troubleshooting, but those files are rotated frequently. # XXX: we should improve that: we shouldn't log envelopes and IPs unless # the mail is bounced, for instance. if $programname == 'amavis' and $syslogfacility-text == 'mail' and $syslogseverity >= 5 then ~ if ($programname startswith 'postfix-' or $programname == 'dovecot') and $syslogfacility-text == 'mail' and $syslogseverity >= 6 then ~ # -# Some standard log files. Log by facility. +# First some standard log files. Log by facility. # -auth,authpriv.* /var/log/auth.log +auth,authpriv.* /var/log/auth.log *.*;auth,authpriv.none -/var/log/syslog #cron.* /var/log/cron.log daemon.* -/var/log/daemon.log kern.* -/var/log/kern.log lpr.* -/var/log/lpr.log user.* -/var/log/user.log # -# Logging for INN news system. -# -news.crit /var/log/news/news.crit -news.err /var/log/news/news.err -news.notice -/var/log/news/news.notice - -# # Some "catch-all" log files. # *.=debug;\ auth,authpriv.none;\ - news.none;mail.none -/var/log/debug + mail.none -/var/log/debug *.=info;*.=notice;*.=warn;\ auth,authpriv.none;\ cron,daemon.none;\ - mail,news.none -/var/log/messages + mail.none -/var/log/messages # # Emergencies are sent to everybody logged in. # *.emerg :omusrmsg:* - -# -# I like to have messages displayed on the console, but only on a virtual -# console I usually leave idle. -# -#daemon,mail.*;\ -# news.=crit;news.=err;news.=notice;\ -# *.=debug;*.=info;\ -# *.=notice;*.=warn /dev/tty8 - -# The named pipe /dev/xconsole is for the `xconsole' utility. To use it, -# you must invoke `xconsole' with the `-file' option: -# -# $ xconsole -file /dev/xconsole [...] -# -# NOTE: adjust the list below, or you'll go crazy if you have a reasonably -# busy site.. -# -#daemon.*;mail.*;\ -# news.err;\ -# *.=debug;*.=info;\ -# *.=notice;*.=warn |/dev/xconsole diff --git a/roles/common/files/etc/samhain/samhainrc b/roles/common/files/etc/samhain/samhainrc deleted file mode 100644 index 7f304b7..0000000 --- a/roles/common/files/etc/samhain/samhainrc +++ /dev/null @@ -1,711 +0,0 @@ -##################################################################### -# -# Configuration file template for samhain. -# -##################################################################### -# -# -- empty lines and lines starting with '#', ';' or '//' are ignored -# -- boolean options can be Yes/No or True/False or 1/0 -# -- you can PGP clearsign this file -- samhain will check (if compiled -# with support) or otherwise ignore the signature -# -- CHECK mail address -# -# To each log facility, you can assign a threshold severity. Only -# reports with at least the threshold severity will be logged -# to the respective facility (even further below). -# -##################################################################### -# -# SETUP for file system checking: -# -# (i) There are several policies, each has its own section. Put files -# into the section for the appropriate policy (see below). -# (ii) Section [EventSeverity]: -# To each policy, you can assign a severity (further below). -# (iii) Section [Log]: -# To each log facility, you can assign a threshold severity. Only -# reports with at least the threshold severity will be logged -# to the respective facility (even further below). -# -##################################################################### - -##################################################################### -# -# Files are defined with: file = /absolute/path -# -# Directories are defined with: dir = /absolute/path -# or with an optional recursion depth (N <= 99): dir = N/absolute/path -# -# Directory inodes are checked. If you only want to check files -# in a directory, but not the directory inode itself, use (e.g.): -# -# [ReadOnly] -# dir = /some/directory -# [IgnoreAll] -# file = /some/directory -# -# You can use shell-style globbing patterns, like: file = /path/foo* -# -###################################################################### - -[Misc] -## -## Add or subtract tests from the policies -## - if you want to change their definitions, -## you need to do that before using the policies -## -# RedefReadOnly = (no default) -# RedefAttributes=(no default) -# RedefLogFiles=(no default) -# RedefGrowingLogFiles=(no default) -# RedefIgnoreAll=(no default) -# RedefIgnoreNone=(no default) -# RedefUser0=(no default) -# RedefUser1=(no default) -FileNamesAreUTF8 = yes -# Switch off hardlink check for BTRFS -UseHardlinkCheck=no - -[Attributes] -## -## for these files, only changes in permissions and ownership are checked -## -file=/etc/mtab -#file=/etc/ssh_random_seed -#file=/etc/asound.conf -file=/etc/resolv.conf -file=/etc/localtime -#file=/etc/ioctl.save -#file=/etc/passwd.backup -#file=/etc/shadow.backup -#file=/etc/postfix/prng_exch -file=/etc/adjtime -file=/etc/network/run/ifstate -#file=/etc/lvm/.cache -file=/etc/ld.so.cache - -# -# There are files in /etc that might change, thus changing the directory -# timestamps. Put it here as 'file', and in the ReadOnly section as 'dir'. -# -file=/etc - -[LogFiles] -## -## for these files, changes in signature, timestamps, and size are ignored -## -file=/var/run/utmp -file=/etc/motd - - - -##################################################################### -# -# This would be the proper syntax for parts that should only be -# included for certain hosts. -# You may enclose anything in a @HOSTNAME/@end bracket, as long as the -# result still has the proper syntax for the config file. -# You may have any number of @HOSTNAME/@end brackets. -# HOSTNAME should be the fully qualified 'official' name -# (e.g. 'nixon.watergate.com', not 'nixon'), no aliases. -# No IP number - except if samhain cannot determine the -# fully qualified hostname. -# -# @HOSTNAME -# file=/foo/bar -# @end -# -# These are two examples for conditional inclusion/exclusion -# of a machine based on the output from 'uname -srm' -# -# $Linux:2.*.7:i666 -# file=/foo/bar3 -# $end -# -# !$Linux:2.*.7:i686 -# file=/foo/bar2 -# $end -# -##################################################################### - -[GrowingLogFiles] -## -## for these files, changes in signature, timestamps, and increase in size -## are ignored -## -#file=/var/log/warn -file=/var/log/messages -file=/var/log/wtmp -file=/var/log/faillog -file=/var/log/auth.log -file=/var/log/daemon.log -file=/var/log/user.log -file=/var/log/kern.log -file=/var/log/syslog - - -[IgnoreAll] -## -## for these files, no modifications are reported -## -## This file might be created or removed by the system sometimes. -## -#file=/etc/resolv.conf.pcmcia.save -#file=/etc/nologin -file=/etc/network/run -file=/etc/.etckeeper -dir=-1/etc/.git - - -[IgnoreNone] -## -## for these files, all modifications (even access time) are reported -## - you may create some interesting-looking file (like /etc/safe_passwd), -## just to watch whether someone will access it ... -## - -[Prelink] -## -## Use for prelinked files or directories holding them -## - - -[ReadOnly] -## -## for these files, only access time is ignored -## -dir=/usr/bin -dir=/bin -dir=/boot -# -# SuSE (old) has the boot init scripts in /sbin/init.d/*, -# so we go 3 levels deep -# -dir=3/sbin -dir=/usr/sbin -dir=/lib -# -# RedHat and Debian have the bootinit scripts in /etc/init.d/* or /etc/rc.d/*, -# so we go 3 levels deep there too -# -dir=3/etc - -# Various directories / files that may include / be SUID/SGID binaries -# -# -file=/usr/lib/pt_chown -# X11, in Debian X7 this is now a symlink -#dir=/usr/X11R6/bin -#dir=/usr/X11R6/lib/X11/xmcd/bin -# Apache: -#file=/usr/lib/apache/suexec -#file=/usr/lib/apache/suexec.disabled -# Extra directories: -#dir=/opt/gnome/bin -#dir=/opt/kde/bin - -[User0] -[User1] -## User0 and User1 are sections for files/dirs with user-definable checking -## (see the manual) - - -[EventSeverity] -## -## Here you can assign severities to policy violations. -## If this severity exceeds the treshold of a log facility (see below), -## a policy violation will be logged to that facility. -## -## Severity for verification failures. -## -# SeverityReadOnly=crit -# SeverityLogFiles=crit -# SeverityGrowingLogs=crit -# SeverityIgnoreNone=crit -# SeverityAttributes=crit -# SeverityUser0=crit -# SeverityUser1=crit - -# Default behaviour -SeverityReadOnly=crit -SeverityLogFiles=crit -SeverityGrowingLogs=warn -SeverityIgnoreNone=crit -SeverityAttributes=crit - - -## -## We have a file in IgnoreAll that might or might not be present. -## Setting the severity to 'info' prevents messages about deleted/new file. -## -# SeverityIgnoreAll=crit -SeverityIgnoreAll=info - -## Files : file access problems -# SeverityFiles=crit - -## Dirs : directory access problems -# SeverityDirs=crit - -## Names : suspect (non-printable) characters in a pathname -# SeverityNames=crit - -# Default behaviour -SeverityFiles=crit -SeverityDirs=crit -SeverityNames=warn - - -[Log] -## -## Switch on/OFF log facilities and set their threshold severity -## -## Values: debug, info, notice, warn, mark, err, crit, alert, none. -## 'mark' is used for timestamps. -## -## -## Use 'none' to SWITCH OFF a log facility -## -## By default, everything equal to and above the threshold is logged. -## The specifiers '*', '!', and '=' are interpreted as -## 'all', 'all but', and 'only', respectively (like syslogd(8) does, -## at least on Linux). Examples: -## MailSeverity=* -## MailSeverity=!warn -## MailSeverity==crit - -## E-mail -## -MailSeverity=crit - -## Console -## -PrintSeverity=none - -## Logfile -## -LogSeverity=warn - -## Syslog -## -SyslogSeverity=alert - -## Remote server (yule) -## -# ExportSeverity=none - -## External script or program -## -# ExternalSeverity = none - -## Logging to a database -## -# DatabaseSeverity = none - - - - - -##################################################### -# -# Optional modules -# -##################################################### - -# [SuidCheck] -## -## --- Check the filesystem for SUID/SGID binaries -## - -## Switch on -# -# SuidCheckActive = yes - -## Interval for check (seconds) -# -# SuidCheckInterval = 7200 - -## Alternative: crontab-like schedule -# -# SuidCheckSchedule = NULL - -## Directory to exclude -# -# SuidCheckExclude = NULL - -## Limit on files per second (0 == no limit) -# -# SuidCheckFps = 0 - -## Alternative: yield after every file -# -# SuidCheckYield = no - -## Severity of a detection -# -# SeveritySuidCheck = crit - -## Quarantine SUID/SGID files if found -# -# SuidCheckQuarantineFiles = yes - -## Method for Quarantining files: -# 0 - Delete or truncate the file. -# 1 - Remove SUID/SGID permissions from file. -# 2 - Move SUID/SGID file to quarantine dir. -# -# SuidCheckQuarantineMethod = 0 - -## For method 1 and 3, really delete instead of truncating -# -# SuidCheckQuarantineDelete = yes - -# [Kernel] -## -## --- Check for loadable kernel module rootkits (Linux/FreeBSD only) -## - -## Switch on/off -# -# KernelCheckActive = True - -## Check interval (seconds); btw., the check is VERY fast -# -# KernelCheckInterval = 300 - -## Severity -# -# SeverityKernel = crit - - -# [Utmp] -## -## --- Logging of login/logout events -## - -## Switch on/off -# -# LoginCheckActive = True - -## Severity for logins, multiple logins, logouts -# -# SeverityLogin=info -# SeverityLoginMulti=warn -# SeverityLogout=info - -## Interval for login/logout checks -# -# LoginCheckInterval = 300 - - -# [Database] -## -## --- Logging to a relational database -## - -## Database name -# -# SetDBName = samhain - -## Database table -# -# SetDBTable = log - -## Database user -# -# SetDBUser = samhain - -## Database password -# -# SetDBPassword = (default: none) - -## Database host -# -# SetDBHost = localhost - -## Log the server timestamp for received messages -# -# SetDBServerTstamp = True - -## Use a persistent connection -# -# UsePersistent = True - -# [External] -## -## Interface to call external scripts/programs for logging -## - -## The absolute path to the command -## - Each invocation of this directive will end the definition of the -## preceding command, and start the definition of -## an additional, new command -# -# OpenCommand = (no default) - -## Type (log or rv) -## - log for log messages, srv for messages received by the server -# -# SetType = log - -## The command (full command line) to execute -# -# SetCommandLine = (no default) - -## The environment (KEY=value; repeat for more) -# -# SetEnviron = TZ=(your timezone) - -## The TIGER192 checksum (optional) -# -# SetChecksum = (no default) - -## User who runs the command -# -# SetCredentials = (default: samhain process uid) - -## Words not allowed in message -# -# SetFilterNot = (none) - -## Words required (ALL of them) -# -# SetFilterAnd = (none) - -## Words required (at least one) -# -# SetFilterOr = (none) - -## Deadtime between consecutive calls -# -# SetDeadtime = 0 - -## Add default environment (HOME, PATH, SHELL) -# -# SetDefault = no - - -##################################################### -# -# Miscellaneous configuration options -# -##################################################### - -[Misc] - -## whether to become a daemon process -## (this is not honoured on database initialisation) -# -# Daemon = no -Daemon = yes - -## whether to test signature of files (init/check/none) -## - if 'none', then we have to decide this on the command line - -# -# ChecksumTest = none -ChecksumTest=check - -## whether to drop linux capabilities that are not required -## - will make a root process a 'mere mortal' in many respects -# -# UseCaps = yes - -## Set nice level (-19 to 19, see 'man nice'), -## and I/O limit (kilobytes per second; 0 == off) -## to reduce load on host. -# -SetNiceLevel = 19 -# SetIOLimit = 0 - -## The version string to embed in file signature databases -# -# VersionString = NULL - -## Interval between time stamp messages -# -# SetLoopTime = 60 -SetLoopTime = 21600 - -## Interval between file checks -# -# SetFileCheckTime = 600 -SetFileCheckTime = 7200 - -## Alternative: crontab-like schedule -# -# FileCheckScheduleOne = NULL - -## Alternative: crontab-like schedule(2) -# -# FileCheckScheduleTwo = NULL - -## Report only once on modified fles -## Setting this to 'FALSE' will generate a report for any policy -## violation (old and new ones) each time the daemon checks the file system. -# -# ReportOnlyOnce = True - -## Report in full detail -# -# ReportFullDetail = False - -## Report file timestamps in local time rather than GMT -# -# UseLocalTime = No - -## The console device (can also be a file or named pipe) -## - There are two console devices. Accordingly, you can use -## this directive a second time to set the second console device. -## If you have not defined the second device at compile time, -## and you don't want to use it, then: -## setting it to /dev/null is less effective than just leaving -## it alone (setting to /dev/null will waste time by opening -## /dev/null and writing to it) -# -# SetConsole = /dev/console - -## Activate the SysV IPC message queue -# -# MessageQueueActive = False - - -## If false, skip reverse lookup when connecting to a host known -## by name rather than IP address (i.e. trust the DNS) -# -# SetReverseLookup = True - -## --- E-Mail --- - -# Only highest-level (alert) reports will be mailed immediately, -# others will be queued. Here you can define, when the queue will -# be flushed (Note: the queue is automatically flushed after -# completing a file check). -# -SetMailTime = 86400 - -## Maximum number of mails to queue -# -SetMailNum = 10 - -## Recipient (max. 8) -# -SetMailAddress = admin@fripost.org - -## Mail relay (IP address) -# -# XXX: it's unfortunate that samhain cannot use the sendmail binary. We -# use a custom port here to avoid conflicts with the usual SMTP port the -# MX:es need to listen on. -# See also: /usr/share/doc/samhain/TODO.Debian -SetMailRelay = 127.0.0.1 -SetMailPort = 16132 - -## Custom subject format -# -MailSubject = [Samhain at %H] %T: %S - -## --- end E-Mail --- - -## Path to the prelink executable -# -# SetPrelinkPath = /usr/sbin/prelink - -## TIGER192 checksum of the prelink executable -# -# SetPrelinkChecksum = (no default) - - -## Path to the executable. If set, will be checksummed after startup -## and before exit. -# -# SamhainPath = (no default) - - -## The IP address of the log server -# -# SetLogServer = (default: compiled-in) - -## The IP address of the time server -# -# SetTimeServer = (default: compiled-in) - -## Trusted Users (comma delimited list of user names) -# -# TrustedUser = (no default; this adds to the compiled-in list) - -## Path to the file signature database -# -# SetDatabasePath = (default: compiled-in) - -## Path to the log file -# -# SetLogfilePath = (default: compiled-in) - -## Path to the PID file -# -# SetLockPath = (default: compiled-in) - - -## The digest/checksum/hash algorithm -# -# DigestAlgo = TIGER192 - - -## Custom format for message header. -## CAREFUL if you use XML logfile format. -## -## %S severity -## %T timestamp -## %C class -## -## %F source file -## %L source line -# -# MessageHeader="%S %T " - - -## Don't log path to config/database file on startup -# -# HideSetup = False - -## The syslog facility, if you log to syslog -# -# SyslogFacility = LOG_AUTHPRIV -SyslogFacility=LOG_LOCAL2 - -## The message authentication method -## - If you change this, you *must* change it -## on client *and* server -# -# MACType = HMAC-TIGER - - -## everything below is ignored -[EOF] - -##################################################################### -# This would be the proper syntax for parts that should only be -# included for certain hosts. -# You may enclose anything in a @HOSTNAME/@end bracket, as long as the -# result still has the proper syntax for the config file. -# You may have any number of @HOSTNAME/@end brackets. -# HOSTNAME should be the fully qualified 'official' name -# (e.g. 'nixon.watergate.com', not 'nixon'), no aliases. -# No IP number - except if samhain cannot determine the -# fully qualified hostname. -# -# @HOSTNAME -# file=/foo/bar -# @end -# -# These are two examples for conditional inclusion/exclusion -# of a machine based on the output from 'uname -srm' -# $Linux:2.*.7:i666 -# file=/foo/bar3 -# $end -# -# !$Linux:2.*.7:i686 -# file=/foo/bar2 -# $end -# -##################################################################### diff --git a/roles/common/files/etc/strongswan.d/charon.conf b/roles/common/files/etc/strongswan.d/charon.conf new file mode 100644 index 0000000..efb241c --- /dev/null +++ b/roles/common/files/etc/strongswan.d/charon.conf @@ -0,0 +1,419 @@ +# Options for the charon IKE daemon. +charon { + + # Deliberately violate the IKE standard's requirement and allow the use of + # private algorithm identifiers, even if the peer implementation is unknown. + # accept_private_algs = no + + # Accept unencrypted ID and HASH payloads in IKEv1 Main Mode. + # accept_unencrypted_mainmode_messages = no + + # Maximum number of half-open IKE_SAs (including unprocessed IKE_SA_INITs) + # for a single peer IP. + # block_threshold = 5 + + # Whether Certificate Revocation Lists (CRLs) fetched via HTTP or LDAP + # should be saved under a unique file name derived from the public key of + # the Certification Authority (CA) to /etc/ipsec.d/crls (stroke) or + # /etc/swanctl/x509crl (vici), respectively. + # cache_crls = no + + # Whether relations in validated certificate chains should be cached in + # memory. + # cert_cache = yes + + # Whether to use DPD to check if the current path still works after any + # changes to interfaces/addresses. + # check_current_path = no + + # Send the Cisco FlexVPN vendor ID payload (IKEv2 only). + # cisco_flexvpn = no + + # Send Cisco Unity vendor ID payload (IKEv1 only). + # cisco_unity = no + + # Close the IKE_SA if setup of the CHILD_SA along with IKE_AUTH failed. + # close_ike_on_child_failure = no + + # Number of half-open IKE_SAs (including unprocessed IKE_SA_INITs) that + # activate the cookie mechanism. + # cookie_threshold = 30 + + # Number of half-open IKE_SAs (including unprocessed IKE_SA_INITs) for a + # single peer IP that activate the cookie mechanism. + # cookie_threshold_ip = 3 + + # Delete CHILD_SAs right after they got successfully rekeyed (IKEv1 only). + # delete_rekeyed = no + + # Delay in seconds until inbound IPsec SAs are deleted after rekeyings + # (IKEv2 only). + # delete_rekeyed_delay = 5 + + # Use ANSI X9.42 DH exponent size or optimum size matched to cryptographic + # strength. + # dh_exponent_ansi_x9_42 = yes + + # Use RTLD_NOW with dlopen when loading plugins and IMV/IMCs to reveal + # missing symbols immediately. + # dlopen_use_rtld_now = no + + # DNS server assigned to peer via configuration payload (CP). + # dns1 = + + # DNS server assigned to peer via configuration payload (CP). + # dns2 = + + # Enable Denial of Service protection using cookies and aggressiveness + # checks. + # dos_protection = yes + + # Free objects during authentication (might conflict with plugins). + # flush_auth_cfg = no + + # Whether to follow IKEv2 redirects (RFC 5685). + # follow_redirects = yes + + # Violate RFC 5998 and use EAP-only authentication even if the peer did not + # send an EAP_ONLY_AUTHENTICATION notify during IKE_AUTH. + # force_eap_only_authentication = no + + # Maximum size (complete IP datagram size in bytes) of a sent IKE fragment + # when using proprietary IKEv1 or standardized IKEv2 fragmentation, defaults + # to 1280 (use 0 for address family specific default values, which uses a + # lower value for IPv4). If specified this limit is used for both IPv4 and + # IPv6. + # fragment_size = 1280 + + # Name of the group the daemon changes to after startup. + # group = + + # Timeout in seconds for connecting IKE_SAs (also see IKE_SA_INIT DROPPING). + # half_open_timeout = 30 + + # Enable hash and URL support. + # hash_and_url = no + + # Allow IKEv1 Aggressive Mode with pre-shared keys as responder. + # i_dont_care_about_security_and_use_aggressive_mode_psk = no + + # Whether to ignore the traffic selectors from the kernel's acquire events + # for IKEv2 connections (they are not used for IKEv1). + # ignore_acquire_ts = no + + # A space-separated list of routing tables to be excluded from route + # lookups. + # ignore_routing_tables = + + # Maximum number of IKE_SAs that can be established at the same time before + # new connection attempts are blocked. + # ikesa_limit = 0 + + # Number of exclusively locked segments in the hash table. + # ikesa_table_segments = 1 + + # Size of the IKE_SA hash table. + # ikesa_table_size = 1 + + # Whether to close IKE_SA if the only CHILD_SA closed due to inactivity. + inactivity_close_ike = yes + + # Limit new connections based on the current number of half open IKE_SAs, + # see IKE_SA_INIT DROPPING in strongswan.conf(5). + # init_limit_half_open = 0 + + # Limit new connections based on the number of queued jobs. + # init_limit_job_load = 0 + + # Causes charon daemon to ignore IKE initiation requests. + # initiator_only = no + + # Install routes into a separate routing table for established IPsec + # tunnels. + install_routes = no + + # Install virtual IP addresses. + install_virtual_ip = no + + # The name of the interface on which virtual IP addresses should be + # installed. + # install_virtual_ip_on = + + # Check daemon, libstrongswan and plugin integrity at startup. + # integrity_test = no + + # A comma-separated list of network interfaces that should be ignored, if + # interfaces_use is specified this option has no effect. + # interfaces_ignore = + + # A comma-separated list of network interfaces that should be used by + # charon. All other interfaces are ignored. + # interfaces_use = + + # NAT keep alive interval. + # keep_alive = 20s + + # Number of seconds the keep alive interval may be exceeded before a DPD is + # sent instead of a NAT keep alive (0 to disable). This is only useful if a + # clock is used that includes time spent suspended (e.g. CLOCK_BOOTTIME). + # keep_alive_dpd_margin = 0s + + # Plugins to load in the IKE daemon charon. + # load = + + # Determine plugins to load via each plugin's load option. + # load_modular = no + + # Initiate IKEv2 reauthentication with a make-before-break scheme. + # make_before_break = no + + # Maximum number of IKEv1 phase 2 exchanges per IKE_SA to keep state about + # and track concurrently. + # max_ikev1_exchanges = 3 + + # Maximum packet size accepted by charon. + # max_packet = 10000 + + # Enable multiple authentication exchanges (RFC 4739). + # multiple_authentication = yes + + # WINS servers assigned to peer via configuration payload (CP). + # nbns1 = + + # WINS servers assigned to peer via configuration payload (CP). + # nbns2 = + + # UDP port used locally. If set to 0 a random port will be allocated. + # port = 500 + + # UDP port used locally in case of NAT-T. If set to 0 a random port will be + # allocated. Has to be different from charon.port, otherwise a random port + # will be allocated. + # port_nat_t = 4500 + + # Whether to prefer updating SAs to the path with the best route. + # prefer_best_path = no + + # Prefer locally configured proposals for IKE/IPsec over supplied ones as + # responder (disabling this can avoid keying retries due to + # INVALID_KE_PAYLOAD notifies). + # prefer_configured_proposals = yes + + # Controls whether permanent or temporary IPv6 addresses are used as source, + # or announced as additional addresses if MOBIKE is used. + # prefer_temporary_addrs = no + + # Process RTM_NEWROUTE and RTM_DELROUTE events. + # process_route = yes + + # How RDNs in subject DNs of certificates are matched against configured + # identities (strict, reordered, or relaxed). + # rdn_matching = strict + + # Delay in ms for receiving packets, to simulate larger RTT. + # receive_delay = 0 + + # Delay request messages. + # receive_delay_request = yes + + # Delay response messages. + # receive_delay_response = yes + + # Specific IKEv2 message type to delay, 0 for any. + # receive_delay_type = 0 + + # Size of the AH/ESP replay window, in packets. + # replay_window = 32 + + # Base to use for calculating exponential back off, see IKEv2 RETRANSMISSION + # in strongswan.conf(5). + # retransmit_base = 1.8 + + # Maximum jitter in percent to apply randomly to calculated retransmission + # timeout (0 to disable). + # retransmit_jitter = 0 + + # Upper limit in seconds for calculated retransmission timeout (0 to + # disable). + # retransmit_limit = 0 + + # Timeout in seconds before sending first retransmit. + # retransmit_timeout = 4.0 + + # Number of times to retransmit a packet before giving up. + # retransmit_tries = 5 + + # Interval in seconds to use when retrying to initiate an IKE_SA (e.g. if + # DNS resolution failed), 0 to disable retries. + # retry_initiate_interval = 0 + + # Initiate CHILD_SA within existing IKE_SAs (always enabled for IKEv1). + # reuse_ikesa = yes + + # Numerical routing table to install routes to. + # routing_table = + + # Priority of the routing table. + # routing_table_prio = + + # Whether to use RSA with PSS padding instead of PKCS#1 padding by default. + # rsa_pss = no + + # Whether to encode an explicit trailerField value of 0x01 in the RSA-PSS + # algorithmIdentifier (CONTEXT3) or using the DEFAULT value by omitting it. + # rsa_pss_trailerfield = no + + # Delay in ms for sending packets, to simulate larger RTT. + # send_delay = 0 + + # Delay request messages. + # send_delay_request = yes + + # Delay response messages. + # send_delay_response = yes + + # Specific IKEv2 message type to delay, 0 for any. + # send_delay_type = 0 + + # Send strongSwan vendor ID payload + # send_vendor_id = no + + # Whether to enable Signature Authentication as per RFC 7427. + # signature_authentication = yes + + # Whether to enable constraints against IKEv2 signature schemes. + # signature_authentication_constraints = yes + + # Value mixed into the local IKE SPIs after applying spi_mask. + # spi_label = 0x0000000000000000 + + # Mask applied to local IKE SPIs before mixing in spi_label (bits set will + # be replaced with spi_label). + # spi_mask = 0x0000000000000000 + + # The upper limit for SPIs requested from the kernel for IPsec SAs. + # spi_max = 0xcfffffff + + # The lower limit for SPIs requested from the kernel for IPsec SAs. + # spi_min = 0xc0000000 + + # Number of worker threads in charon. + # threads = 16 + + # Name of the user the daemon changes to after startup. + # user = + + crypto_test { + + # Benchmark crypto algorithms and order them by efficiency. + # bench = no + + # Buffer size used for crypto benchmark. + # bench_size = 1024 + + # Time in ms during which crypto algorithm performance is measured. + # bench_time = 50 + + # Test crypto algorithms during registration (requires test vectors + # provided by the test-vectors plugin). + # on_add = no + + # Test crypto algorithms on each crypto primitive instantiation. + # on_create = no + + # Strictly require at least one test vector to enable an algorithm. + # required = no + + # Whether to test RNG with TRUE quality; requires a lot of entropy. + # rng_true = no + + } + + host_resolver { + + # Maximum number of concurrent resolver threads (they are terminated if + # unused). + # max_threads = 3 + + # Minimum number of resolver threads to keep around. + # min_threads = 0 + + } + + leak_detective { + + # Includes source file names and line numbers in leak detective output. + # detailed = yes + + # Threshold in bytes for allocations to be included in usage reports (0 + # to include all). + # usage_threshold = 10240 + + # Threshold in number of allocations for allocations to be included in + # usage reports (0 to include all). + # usage_threshold_count = 0 + + } + + processor { + + # Section to configure the number of reserved threads per priority class + # see JOB PRIORITY MANAGEMENT in strongswan.conf(5). + priority_threads { + + } + + } + + # Section containing a list of scripts (name = path) that are executed when + # the daemon is started. + start-scripts { + + } + + # Section containing a list of scripts (name = path) that are executed when + # the daemon is terminated. + stop-scripts { + + } + + tls { + + # List of TLS encryption ciphers. + # cipher = + + # List of TLS key exchange groups. + # ke_group = + + # List of TLS key exchange methods. + # key_exchange = + + # List of TLS MAC algorithms. + # mac = + + # Whether to include CAs in a server's CertificateRequest message. + # send_certreq_authorities = yes + + # List of TLS signature schemes. + # signature = + + # List of TLS cipher suites. + # suites = + + # Maximum TLS version to negotiate. + # version_max = 1.2 + + # Minimum TLS version to negotiate. + # version_min = 1.2 + + } + + x509 { + + # Discard certificates with unsupported or unknown critical extensions. + # enforce_critical = yes + + } + +} + diff --git a/roles/common/files/etc/strongswan.d/charon/socket-default.conf b/roles/common/files/etc/strongswan.d/charon/socket-default.conf new file mode 100644 index 0000000..abf4650 --- /dev/null +++ b/roles/common/files/etc/strongswan.d/charon/socket-default.conf @@ -0,0 +1,23 @@ +socket-default { + + # Firewall mark to set on outbound packets. + # fwmark = + + # Whether to load the plugin. Can also be an integer to increase the + # priority of this plugin. + load = yes + + # Set source address on outbound packets, if possible. + # set_source = yes + + # Force sending interface on outbound packets, if possible. + # set_sourceif = no + + # Listen on IPv4, if possible. + # use_ipv4 = yes + + # Listen on IPv6, if possible. + # use_ipv6 = yes + +} + diff --git a/roles/common/files/etc/systemd/system/bacula-fd.service.d/override.conf b/roles/common/files/etc/systemd/system/bacula-fd.service.d/override.conf new file mode 100644 index 0000000..537bf1e --- /dev/null +++ b/roles/common/files/etc/systemd/system/bacula-fd.service.d/override.conf @@ -0,0 +1,13 @@ +[Service] +# Hardening +NoNewPrivileges=yes +ProtectHome=read-only +ProtectSystem=strict +ReadWriteDirectories=/var/lib/bacula +PrivateTmp=yes +PrivateDevices=yes +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +CapabilityBoundingSet=CAP_DAC_READ_SEARCH diff --git a/roles/common/files/etc/systemd/system/fail2ban.service.d/override.conf b/roles/common/files/etc/systemd/system/fail2ban.service.d/override.conf new file mode 100644 index 0000000..b34d130 --- /dev/null +++ b/roles/common/files/etc/systemd/system/fail2ban.service.d/override.conf @@ -0,0 +1,21 @@ +[Unit] +After=nftables.service + +[Service] +ExecStartPre= +ExecStart= +ExecStart=/usr/bin/fail2ban-server -xf --logtarget=sysout start + +# Need explicit rights to read logs as we don't grant CAP_DAC_READ_SEARCH +SupplementaryGroups=adm + +# Hardening +NoNewPrivileges=yes +ProtectSystem=strict +RuntimeDirectory=fail2ban +PrivateDevices=yes +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +RestrictAddressFamilies=AF_UNIX AF_NETLINK +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW diff --git a/roles/common/files/etc/systemd/system/munin-node.service.d/override.conf b/roles/common/files/etc/systemd/system/munin-node.service.d/override.conf new file mode 100644 index 0000000..fee16b3 --- /dev/null +++ b/roles/common/files/etc/systemd/system/munin-node.service.d/override.conf @@ -0,0 +1,14 @@ +[Service] +ExecStartPre= + +# Hardening +NoNewPrivileges=yes +ProtectSystem=strict +ReadWriteDirectories=/var/lib/munin-node/plugin-state +ReadWriteDirectories=/var/log/munin +RuntimeDirectory=munin +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +CapabilityBoundingSet=CAP_SETUID CAP_SETGID diff --git a/roles/common/files/etc/systemd/system/stunnel4.service b/roles/common/files/etc/systemd/system/stunnel4.service new file mode 100644 index 0000000..990e07b --- /dev/null +++ b/roles/common/files/etc/systemd/system/stunnel4.service @@ -0,0 +1,14 @@ +# This service is actually a systemd target, +# but we are using a service since targets cannot be reloaded. + +[Unit] +Description=SSL tunnel for network daemons (multi-instance-master) + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/bin/true +ExecReload=/bin/true + +[Install] +WantedBy=multi-user.target diff --git a/roles/common/files/etc/systemd/system/stunnel4@.service b/roles/common/files/etc/systemd/system/stunnel4@.service new file mode 100644 index 0000000..4d69702 --- /dev/null +++ b/roles/common/files/etc/systemd/system/stunnel4@.service @@ -0,0 +1,32 @@ +[Unit] +Description=SSL tunnel for network daemons (instance %i) +Documentation=man:stunnel4(8) +After=network.target nss-lookup.target +PartOf=stunnel4.service +ReloadPropagatedFrom=stunnel4.service + +[Service] +DynamicUser=yes +; force dynamic user/group allocation (stunnel4 user exists already) +User=_stunnel4-%i +Group=_stunnel4-%i +ExecStart=/usr/bin/stunnel4 /etc/stunnel/%i.conf +ExecReload=/bin/kill -HUP ${MAINPID} +KillSignal=SIGINT +TimeoutStartSec=120 +TimeoutStopSec=60 +Restart=on-failure + +# Hardening +NoNewPrivileges=yes +PrivateDevices=yes +ProtectHome=yes +ProtectSystem=strict +PrivateDevices=yes +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +RestrictAddressFamilies=AF_INET AF_INET6 + +[Install] +WantedBy=multi-user.target diff --git a/roles/common/files/lib/systemd/system/bacula-fd.service b/roles/common/files/lib/systemd/system/bacula-fd.service deleted file mode 100644 index 6e2bbd4..0000000 --- a/roles/common/files/lib/systemd/system/bacula-fd.service +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=Bacula File Daemon service -After=network.target - -[Service] -Type=forking -PIDFile=/var/run/bacula/bacula-fd.9112.pid -StandardOutput=syslog -ExecStart=/usr/sbin/bacula-fd -c /etc/bacula/bacula-fd.conf - -[Install] -WantedBy=multi-user.target diff --git a/roles/common/files/usr/local/bin/gendhparam.sh b/roles/common/files/usr/local/bin/gendhparam.sh index 074986b..a94175a 100755 --- a/roles/common/files/usr/local/bin/gendhparam.sh +++ b/roles/common/files/usr/local/bin/gendhparam.sh @@ -1,13 +1,10 @@ #!/bin/sh set -ue PATH=/usr/bin:/bin -privkey="$1" +out="$1" bits="${2:-2048}" -rand= -mv -f "$(mktemp)" "$privkey" -chmod og-rwx "$privkey" - -openssl dhparam -rand "${rand:-/dev/urandom}" "$bits" >"$privkey" +install --mode=0644 /dev/null "$out" +openssl dhparam -rand /dev/urandom "$bits" >"$out" diff --git a/roles/common/files/usr/local/bin/genkeypair.sh b/roles/common/files/usr/local/bin/genkeypair.sh index 5bf67f2..72102f4 100755 --- a/roles/common/files/usr/local/bin/genkeypair.sh +++ b/roles/common/files/usr/local/bin/genkeypair.sh @@ -4,202 +4,194 @@ # certificates or Certificate Signing Requests, or DKIM private keys. # Inspired from make-ssl-cert(8) and opendkim-genkey(8). # # Copyright © 2014 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/bin:/bin +export PATH # Default values type=rsa bits= hash= force=0 config= pubkey=pubkey.pem privkey=privkey.pem dns= ou= cn= usage= -chmod= -chown= -rand= +mode= +owner= +group= usage() { cat >&2 <<- EOF Usage: $0 command [OPTIONS] Command: x509: generate a self-signed X.509 server certificate csr: generate a Certificate Signing Request dkim: generate a private key (to use for DKIM signing) + keypair: generate a key pair Options: -t type: key type (default: rsa) -b bits: key length or EC curve (default: 2048 for RSA, 1024 for DSA, secp224r1 for ECDSA) -h digest: digest algorithm --ou: organizational Unit Name; can be repeated --cn: common Name (default: \$(hostname --fqdn) --dns: hostname for AltName; can be repeated -f: force; can be repeated (0: don't overwrite, default; 1: reuse private key if it exists; 2: overwrite both keys if they exist) --config: configuration file --pubkey: public key file (default: pubkey.pem) - --privkey: private key file (default: privkey.pem; created with og-rwx) + --privkey: private key file (default: privkey.pem) --usage: key usage (default: digitalSignature,keyEncipherment,keyCertSign) - --chmod: chmod the private key - --chown: chown the private key + --mode: set privkey's permission mode (default: 0600) + --owner: set privkey's owner (default: the process' current owner) + --group: set privkey's group (default: the process' current group) Return values: 0 The key pair was successfully generated 1 The public or private key file exists, and -f is not set 2 The key generation failed EOF } -dkiminfo() { - echo "Add the following TXT record to your DNS zone:" - echo "${cn:-$(date +%Y%m%d)}._domainkey\tIN\tTXT ( " - # See https://tools.ietf.org/html/rfc4871#section-3.6.1 - # t=s: the "i=" domain in signature headers MUST NOT be a subdomain of "d=" - # s=email: limit DKIM signing to email - openssl pkey -pubout <"$privkey" | sed '/^--.*--$/d' \ - | { echo -n "v=DKIM1; k=$type; t=s; s=email; p="; tr -d '\n'; } \ - | fold -w 250 \ - | { sed 's/.*/\t"&"/'; echo ' )'; } -} - [ $# -gt 0 ] || { usage; exit 2; } cmd="$1"; shift case "$cmd" in - x509|csr|dkim) ;; + x509|csr|dkim|keypair) ;; *) echo "Unrecognized command: $cmd" >&2; exit 2 esac nou=1 while [ $# -gt 0 ]; do case "$1" in -t) shift; type="$1";; -t*) type="${1#-t}";; -b) shift; bits="$1";; -b*) bits="${1#-b}";; -h) shift; hash="$1";; -h*) hash="${1#-h}";; --dns=?*) dns="${dns:+$dns, }DNS:${1#--dns=}";; --cn=?*) cn="${1#--cn=}";; --ou=?*) ou="${ou:+$ou\n}$nou.organizationalUnitName = ${1#--ou=}" nou=$(( 1 + $nou ));; -f) force=$(( 1 + $force ));; --pubkey=?*) pubkey="${1#--pubkey=}";; --privkey=?*) privkey="${1#--privkey=}";; --usage=?*) usage="${usage:+$usage,}${1#--usage=}";; - --config=?*) dns="${1#--config=}";; + --config=?*) config="${1#--config=}";; - --chmod=?*) chmod="${1#--chmod=}";; - --chown=?*) chown="${1#--chown=}";; + --mode=?*) mode="${1#--mode=}";; + --owner=?*) owner="${1#--owner=}";; + --group=?*) group="${1#--group=}";; --help) usage; exit;; *) echo "Unrecognized argument: $1" >&2; exit 2 esac shift; done case "$type" in # XXX: genrsa and dsaparam have been deprecated in favor of genpkey. # genpkey can also create explicit EC parameters, but not named. - rsa) genkey=genrsa; genkeyargs="-f4 ${bits:-2048}";; - dsa) genkey=dsaparam; genkeyargs="-noout -genkey ${bits:-1024}";; + rsa) genkey=genrsa; genkeyargs="-rand /dev/urandom -f4 ${bits:-2048}";; + dsa) genkey=dsaparam; genkeyargs="-rand /dev/urandom -noout -genkey ${bits:-1024}";; # See 'openssl ecparam -list_curves' for the list of supported # curves. StrongSwan doesn't support explicit curve parameters # (however explicit parameters might be required to make exotic # curves work with some clients.) ecdsa) genkey=ecparam - genkeyargs="-noout -name ${bits:-secp224r1} -param_enc named_curve -genkey";; + genkeyargs="-rand /dev/urandom -noout -name ${bits:-secp224r1} -param_enc named_curve -genkey";; + x25519|x448|ed25519|ed448) genkey=genpkey + genkeyargs="-algorithm $type";; *) echo "Unrecognized key type: $type" >&2; exit 2 esac if [ "$cmd" = x509 -o "$cmd" = csr ]; then case "$hash" in md5|rmd160|sha1|sha224|sha256|sha384|sha512|'') ;; *) echo "Invalid digest algorithm: $hash" >&2; exit 2; esac [ "$cn" ] || cn="$(hostname --fqdn)" [ ${#cn} -le 64 ] || { echo "CommonName too long: $cn" >&2; exit 2; } fi if [ -z "$config" -a \( "$cmd" = x509 -o "$cmd" = csr \) ]; then config=$(mktemp) || exit 2 trap 'rm -f "$config"' EXIT # see /usr/share/ssl-cert/ssleay.cnf cat >"$config" <<- EOF [ req ] distinguished_name = req_distinguished_name prompt = no policy = policy_anything req_extensions = v3_req x509_extensions = v3_req [ req_distinguished_name ] organizationName = Fripost organizationalUnitName = SSLcerts $(echo "$ou") - commonName = $cn + commonName = ${cn:-/} [ v3_req ] subjectAltName = email:admin@fripost.org${dns:+, $dns} basicConstraints = critical, CA:FALSE # https://security.stackexchange.com/questions/24106/which-key-usages-are-required-by-each-key-exchange-method keyUsage = critical, ${usage:-digitalSignature, keyEncipherment, keyCertSign} subjectKeyIdentifier = hash EOF fi if [ -s "$privkey" -a $force -eq 0 ]; then echo "Error: private key exists: $privkey" >&2 - [ "$cmd" = dkim ] && dkiminfo exit 1 elif [ ! -s "$privkey" -o $force -ge 2 ]; then - # Ensure "$privkey" is created with umask 0077 - mv -f "$(mktemp)" "$privkey" || exit 2 - chmod "${chmod:-og-rwx}" "$privkey" || exit 2 - [ -z "$chown" ] || chown "$chown" "$privkey" || exit 2 - openssl $genkey -rand "${rand:-/dev/urandom}" $genkeyargs >"$privkey" || exit 2 - [ "$cmd" = dkim ] && { dkiminfo; exit; } + install --mode="${mode:-0600}" ${owner:+--owner="$owner"} ${group:+--group="$group"} /dev/null "$privkey" || exit 2 + openssl $genkey $genkeyargs >"$privkey" || exit 2 + [ "$cmd" = dkim ] && exit fi if [ "$cmd" = x509 -a "$pubkey" = "$privkey" ]; then pubkey=$(mktemp) openssl req -config "$config" -new -x509 ${hash:+-$hash} -days 3650 -key "$privkey" >"$pubkey" || exit 2 cat "$pubkey" >>"$privkey" || exit 2 rm -f "$pubkey" elif [ "$cmd" = x509 -o "$cmd" = csr ]; then if [ -s "$pubkey" -a $force -eq 0 ]; then echo "Error: public key exists: $pubkey" >&2 exit 1 else [ "$cmd" = x509 ] && x509=-x509 || x509= openssl req -config "$config" -new $x509 ${hash:+-$hash} -days 3650 -key "$privkey" >"$pubkey" || exit 2 fi +elif [ "$cmd" = keypair -a "$pubkey" ]; then + openssl pkey -pubout <"$privkey" >"$pubkey" fi diff --git a/roles/common/files/usr/local/sbin/update-firewall b/roles/common/files/usr/local/sbin/update-firewall new file mode 100755 index 0000000..e11e8a9 --- /dev/null +++ b/roles/common/files/usr/local/sbin/update-firewall @@ -0,0 +1,61 @@ +#!/bin/bash + +set -ue +PATH=/usr/sbin:/usr/bin:/sbin:/bin +export PATH + +NFTABLES="/etc/nftables.conf" + +script="$(mktemp --tmpdir=/dev/shm)" +oldrules="$(mktemp --tmpdir=/dev/shm)" +newrules="$(mktemp --tmpdir=/dev/shm)" +netns= +cleanup(){ + rm -f -- "$script" "$oldrules" "$newrules" + [ -z "$netns" ] || ip netns del "$netns" +} +trap cleanup EXIT INT TERM + +echo "flush ruleset" >"$script" # should be included already, but... +cat <"$NFTABLES" >>"$script" + +ip netns add "nft-dryrun" +netns="nft-dryrun" + +declare -a INTERFACES=() +for iface in /sys/class/net/*; do + idx="$(< "$iface/ifindex")" + INTERFACES[idx]="${iface#/sys/class/net/}" +done + +# create dummy interfaces so we can use iif/oif in the nft rules +# (we preserve indices to preserve canonical set representation) +for idx in "${!INTERFACES[@]}"; do + [ "${INTERFACES[idx]}" != "lo" ] || continue + ip netns exec "$netns" ip link add "${INTERFACES[idx]}" index "$idx" type dummy +done + +# clear sets in the old rules before diff'ing with the new ones +nft -sn list ruleset >"$oldrules" +ip netns exec "$netns" nft -f - <"$oldrules" +ip netns exec "$netns" nft flush set inet filter fail2ban || true +ip netns exec "$netns" nft flush set inet filter fail2ban6 || true +ip netns exec "$netns" nft -sn list ruleset >"$oldrules" + +ip netns exec "$netns" nft -f - <"$script" +ip netns exec "$netns" nft -sn list ruleset >"$newrules" +ip netns del "$netns" +netns= + +if [ ! -t 0 ] || [ ! -t 1 ]; then + diff -q -- "$oldrules" "$newrules" && exit 0 || exit 1 +elif ! diff -u --color=auto --label=a/ruleset --label=b/ruleset \ + -- "$oldrules" "$newrules" && nft -f - <"$script"; then + read -p "Ruleset applied. Revert? [Y/n] " -r -t10 r || r="y" + if [ "${r,,[a-z]}" != "n" ]; then + echo "Reverting..." + echo "flush ruleset" >"$script" + cat <"$oldrules" >>"$script" + nft -f - <"$script" + fi +fi diff --git a/roles/common/files/usr/local/sbin/update-firewall.sh b/roles/common/files/usr/local/sbin/update-firewall.sh deleted file mode 100755 index f25f507..0000000 --- a/roles/common/files/usr/local/sbin/update-firewall.sh +++ /dev/null @@ -1,475 +0,0 @@ -#!/bin/bash - -# Create iptables (v4 and v6) rules. Unless one of [-f] or [-c] is -# given, or if the ruleset is unchanged, a confirmation is asked after -# loading the new rulesets; if the user answers No or doesn't answer, -# the old ruleset is restored. If the user answer Yes (or if the flag -# [-f] is given), the new ruleset is made persistent (requires a pre-up -# hook) by moving it to /etc/iptables/rules.v[46]. -# -# The [-c] flag switch to dry-run (check) mode. The rulesets are not -# applied, but merely checked against the existing ones. The return -# value is 0 iff. they do not differ. -# -# This firewall is only targeted towards end-servers, not gateways. In -# particular, there is no NAT'ing at the moment. -# -# Dependencies: netmask(1) -# -# Copyright © 2013 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 -timeout=10 - -force=0 -check=0 -verbose=0 -addrfam= - -secmark=0xA99 # must match that in /etc/network/if-up.d/ipsec -secproto=esp # must match /etc/ipsec.conf; ESP is the default (vs AH/IPComp) - -fail2ban_re='^(\[[0-9]+:[0-9]+\]\s+)?-A fail2ban-\S' -IPSec_re=" -m policy --dir (in|out) --pol ipsec --reqid [0-9]+ --proto $secproto -j ACCEPT$" -declare -A rss=() tables=() - -usage() { - cat >&2 <<- EOF - Usage: $0 [OPTIONS] - - Options: - -f force: no confirmation asked - -c check: check (dry-run) mode - -v verbose: see the difference between old and new ruleset - -4 IPv4 only - -6 IPv6 only - EOF - exit 1 -} - -log() { - /usr/bin/logger -st firewall -p user.info -- "$@" -} -fatal() { - /usr/bin/logger -st firewall -p user.err -- "$@" - exit 1 -} - -iptables() { - # Fake iptables/ip6tables(8); use the more efficient - # iptables-restore(8) instead. - echo "$@" >> "$new"; -} -commit() { - # End a table - echo COMMIT >> "$new" -} -inet46() { - case "$1" in - 4) echo "$2";; - 6) echo "$3";; - esac -} -ipt-chains() { - # Define new (tables and) chains. - while [ $# -gt 0 ]; do - case "$1" in - ?*:*) echo ":${1%:*} ${1##*:} [0:0]";; - ?*) echo "*$1";; - esac - shift - done >> "$new" -} - -ipt-trim() { - # Remove dynamic chain/rules from the input stream, as they are - # automatically included by third-party servers (such as strongSwan - # or fail2ban). The output is ready to be made persistent. - grep -Ev -e '^:fail2ban-\S' \ - -e "$IPSec_re" \ - -e '-j fail2ban-\S+$' \ - -e "$fail2ban_re" -} - -ipt-diff() { - # Get the difference between two rulesets. - if [ $verbose -eq 1 ]; then - /usr/bin/diff -u -I '^#' "$1" "$2" - else - /usr/bin/diff -q -I '^#' "$1" "$2" >/dev/null - fi -} - -ipt-persist() { - # Make the current ruleset persistent. (Requires a pre-up hook - # script to load the rules before the network is configured.) - - log "Making ruleset persistent... " - [ -d /etc/iptables ] || mkdir /etc/iptables - - local f rs table - for f in "${!tables[@]}"; do - ipts=/sbin/$(inet46 $f iptables ip6tables)-save - rs=/etc/iptables/rules.v$f - - for table in ${tables[$f]}; do - /bin/ip netns exec $netns $ipts -t $table - done | ipt-trim > "$rs" - chmod 0600 "$rs" - done -} - -ipt-revert() { - [ $check -eq 0 ] || return - log "Reverting to old ruleset... " - - local rs - for f in "${!rss[@]}"; do - /sbin/$(inet46 $f iptables ip6tables)-restore -c < "${rss[$f]}" - rm -f "${rss[$f]}" - done - exit 1 -} - -run() { - # Build and apply the firewall for IPv4/6. - local f="$1" - local ipt=/sbin/$(inet46 $f iptables ip6tables) - tables[$f]=filter - - # The default interface associated with this address. - local if=$( /bin/ip -$f route show to default scope global \ - | sed -nr '/^default via \S+ dev (\S+).*/ {s//\1/p;q}' ) - - # The virtual interface reserved for IPSec. - local ifsec=$( /bin/ip -o -$f link show \ - | sed -nr "/^[0-9]+:\s+(sec[0-9]+)@$if:\s.*/ {s//\1/p;q}" ) - - # The (host-scoped) IP reserved for IPSec. - local ipsec= - if [ "$ifsec" -a $f = 4 ]; then - tables[$f]='mangle nat filter' - ipsec=$( /bin/ip -$f address show dev "$ifsec" scope host \ - | sed -nr '/^\s+inet\s(\S+).*/ {s//\1/p;q}' ) - fi - - # Store the old (current) ruleset - local old=$(mktemp --tmpdir current-rules.v$f.XXXXXX) \ - new=$(mktemp --tmpdir new-rules.v$f.XXXXXX) - for table in ${tables[$f]}; do - $ipt-save -ct $table - done > "$old" - rss[$f]="$old" - - local fail2ban=0 - # XXX: As of Wheezy, fail2ban is IPv4 only. See - # https://github.com/fail2ban/fail2ban/issues/39 for the current - # state of the art. - if [ "$f" = 4 ] && which /usr/bin/fail2ban-server >/dev/null; then - fail2ban=1 - fi - - # The usual chains in filter, along with the desired default policies. - ipt-chains filter INPUT:DROP FORWARD:DROP OUTPUT:DROP - - if [ ! "$if" ]; then - # If the interface is not configured, we stop here and DROP all - # packets by default. Thanks to the pre-up hook this tight - # policy will be activated whenever the interface goes up. - mv "$new" /etc/iptables/rules.v$f - return 0 - fi - - # Fail2ban-specific chains and traps - if [ $fail2ban -eq 1 ]; then - echo ":fail2ban - [0:0]" - # Don't remove existing rules & traps in the current rulest - grep -- '^:fail2ban-\S' "$old" || true - grep -E -- ' -j fail2ban-\S+$' "$old" || true - grep -E -- "$fail2ban_re" "$old" || true - fi >> "$new" - - if [ "$ipsec" ]; then - # (Host-to-host) IPSec tunnels come first. TODO: test IPSec with IPv6. - grep -E -- "$IPSec_re" "$old" >> "$new" || true - - # Allow any IPsec $secproto protocol packets to be sent and received. - iptables -A INPUT -i $if -p $secproto -j ACCEPT - iptables -A OUTPUT -o $if -p $secproto -j ACCEPT - fi - - - ######################################################################## - # DROP all RFC1918 addresses, martian networks, multicasts, ... - # Credits to http://newartisans.com/2007/09/neat-tricks-with-iptables/ - # http://baldric.net/loose-iptables-firewall-for-servers/ - - local ip - if [ "$f" = 4 ]; then - # Private-use networks (RFC 1918) and link local (RFC 3927) - local MyNetwork=$( /bin/ip -4 address show dev $if scope global \ - | sed -nr 's/^\s+inet\s(\S+).*/\1/p') - [ "$MyNetwork" ] && \ - for ip in 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 169.254.0.0/16; do - # Don't lock us out if we are behind a NAT ;-) - [ "$ip" = "$(/usr/bin/netmask -nc $ip $MyNetwork | sed 's/ //g')" ] \ - || iptables -A INPUT -i $if -s "$ip" -j DROP - done - - # Other martian packets: "This" network, multicast, broadcast (RFCs - # 1122, 3171 and 919). - for ip in 0.0.0.0/8 224.0.0.0/4 240.0.0.0/4 255.255.255.255/32; do - iptables -A INPUT -i $if -s "$ip" -j DROP - iptables -A INPUT -i $if -d "$ip" -j DROP - done - - elif [ "$f" = 6 ]; then - # Martian IPv6 packets: ULA (RFC 4193) and site local addresses - # (RFC 3879). - for ip in fc00::/7 fec0::/10; do - iptables -A INPUT -i $if -s "$ip" -j DROP - iptables -A INPUT -i $if -d "$ip" -j DROP - done - fi - - # DROP INVALID packets immediately. - iptables -A INPUT -m state --state INVALID -j DROP - iptables -A OUTPUT -m state --state INVALID -j DROP - - # DROP bogus TCP packets. - iptables -A INPUT -p tcp -m tcp --tcp-flags FIN,SYN FIN,SYN -j DROP - iptables -A INPUT -p tcp -m tcp --tcp-flags SYN,RST SYN,RST -j DROP - iptables -A INPUT -p tcp \! --syn -m state --state NEW -j DROP - - # Allow all input/output to/from the loopback interface. - local localhost=$(inet46 $f '127.0.0.1/8' '::1/128') - iptables -A INPUT -i lo -s "$localhost" -d "$localhost" -j ACCEPT - iptables -A OUTPUT -o lo -s "$localhost" -d "$localhost" -j ACCEPT - - if [ "$ipsec" ]; then - # ACCEPT any, *IPSec* traffic destinating to the non-routable - # $ipsec. Also ACCEPT all traffic originating from $ipsec, as - # it is MASQUERADE'd. - iptables -A INPUT -d "$ipsec" -i $if -m policy --dir in \ - --pol ipsec --proto $secproto -j ACCEPT - iptables -A OUTPUT -m mark --mark "$secmark" -o $if -j ACCEPT - fi - - # Prepare fail2ban. We make fail2ban insert its rules in a - # dedicated chain, so that it doesn't mess up the existing rules. - [ $fail2ban -eq 1 ] && iptables -A INPUT -i $if -j fail2ban - - if [ "$f" = 4 ]; then - # Allow only ICMP of type 0, 3 and 8. The rate-limiting is done - # directly by the kernel (net.ipv4.icmp_ratelimit and - # net.ipv4.icmp_ratemask runtime options). See icmp(7). - local t - for t in 'echo-reply' 'destination-unreachable' 'echo-request'; do - iptables -A INPUT -p icmp -m icmp --icmp-type $t -j ACCEPT - iptables -A OUTPUT -p icmp -m icmp --icmp-type $t -j ACCEPT - done - elif [ $f = 6 ]; then - iptables -A INPUT -p icmpv6 -j ACCEPT - iptables -A OUTPUT -p icmpv6 -j ACCEPT - fi - - - ######################################################################## - # ACCEPT new connections to the services we provide, or to those we want - # to connect to. - - sed -re 's/#.*//; /^\s*$/d' -e "s/^(in|out|inout)$f?(\s.*)/\1\2/" \ - /etc/iptables/services | \ - grep -Ev '^(in|out|inout)\S\s' | \ - while read dir proto dport sport; do - # We add two entries per config line: we need to accept the new - # connection, and latter the reply. - local stNew=NEW,ESTABLISHED - local stEst=ESTABLISHED - - # In-Out means full-duplex - [[ "$dir" =~ ^inout ]] && stEst="$stNew" - - local iptNew= iptEst= optsNew= optsEst= - case "$dport" in - *,*|*:*) optsNew="--match multiport --dports $dport" - optsEst="--match multiport --sports $dport";; - ?*) optsNew="--dport $dport" - optsEst="--sport $dport";; - esac - case "$sport" in - *,*|*:*) optsNew+=" --match multiport --sports $sport" - optsEst+=" --match multiport --dports $sport";; - ?*) optsNew+=" --sport $sport" - optsEst+=" --dport $sport";; - esac - case "$dir" in - in|inout) iptNew="-A INPUT -i"; iptEst="-A OUTPUT -o";; - out) iptNew="-A OUTPUT -o"; iptEst="-A INPUT -i";; - *) fatal "Error: Unknown direction: '$dir'." - esac - - iptables $iptNew $if -p $proto $optsNew -m state --state $stNew -j ACCEPT - iptables $iptEst $if -p $proto $optsEst -m state --state $stEst -j ACCEPT - done - - ######################################################################## - commit - - if [ "$ipsec" ]; then - # DNAT the IPSec paquets to $ipsec after decapsulation, and SNAT - # them before encapsulation. We need to do the NAT'ing before - # packets enter the IPSec stack because they are signed - # afterwards, and NAT'ing would mess up the signature. - ipt-chains mangle PREROUTING:ACCEPT INPUT:ACCEPT \ - FORWARD:DROP \ - OUTPUT:ACCEPT POSTROUTING:ACCEPT - - # Packets which destination is $ipsec *must* be associated with - # an IPSec policy. - iptables -A INPUT -d "$ipsec" -i $if -m policy --dir in \ - --pol ipsec --proto $secproto -j ACCEPT - iptables -A INPUT -d "$ipsec" -i $if -j DROP - - # Packets originating from our (non-routable) $ipsec are marked; - # if there is no xfrm lookup (i.e., no matching IPSec - # association), the packet will retain its mark and be null - # routed later on. Otherwise, the packet is re-queued unmarked. - iptables -A OUTPUT -o $if -j MARK --set-mark 0x0 - iptables -A OUTPUT -s "$ipsec" -o $if -m policy --dir out \ - --pol none -j MARK --set-mark $secmark - commit - - ipt-chains nat PREROUTING:ACCEPT INPUT:ACCEPT \ - OUTPUT:ACCEPT POSTROUTING:ACCEPT - - # DNAT all marked packets after decapsulation. - iptables -A PREROUTING \! -d "$ipsec" -i $if -m policy --dir in \ - --pol ipsec --proto $secproto -j DNAT --to "${ipsec%/*}" - - # Packets originating from our IPSec are SNAT'ed (MASQUERADE). - # (And null-routed later on unless there is an xfrm - # association.) - iptables -A POSTROUTING -m mark --mark $secmark -o $if -j MASQUERADE - commit - fi - - ######################################################################## - - - local rv1=0 rv2=0 persistent=/etc/iptables/rules.v$f - local oldz=$(mktemp --tmpdir current-rules.v$f.XXXXXX) - - # Reset the counters. They are not useful for comparing and/or - # storing persistent ruleset. (We don't use sed -i because we want - # to restore the counters when reverting.) - sed -r -e '/^:/ s/\[[0-9]+:[0-9]+\]$/[0:0]/' \ - -e 's/^\[[0-9]+:[0-9]+\]\s+//' \ - "$old" > "$oldz" - - /usr/bin/uniq "$new" | /bin/ip netns exec $netns $ipt-restore || ipt-revert - - for table in ${tables[$f]}; do - /bin/ip netns exec $netns $ipt-save -t $table - done > "$new" - - ipt-diff "$oldz" "$new" || rv1=$? - - if ! [ -f "$persistent" -a -x /etc/network/if-pre-up.d/iptables ]; then - rv2=1 - else - ipt-trim < "$oldz" | ipt-diff - "$persistent" || rv2=$? - fi - - local update="Please run '${0##*/}'." - if [ $check -eq 0 ]; then - /usr/bin/uniq "$new" | $ipt-restore || ipt-revert - else - if [ $rv1 -ne 0 ]; then - log "WARN: The IPv$f firewall is not up to date! $update" - fi - if [ $rv2 -ne 0 ]; then - log "WARN: The current IPv$f firewall is not persistent! $update" - fi - fi - - rm -f "$oldz" "$new" - return $(( $rv1 | $rv2 )) -} - - -# Parse options -while [ $# -gt 0 ]; do - case "$1" in - -?*) for (( k=1; k<${#1}; k++ )); do - o="${1:$k:1}" - case "$o" in - 4|6) addrfam="$o";; - c) check=1;; - f) force=1;; - v) verbose=1;; - *) usage;; - esac - done - ;; - *) usage;; - esac - shift -done - -# If we are going to apply the ruleset, we should either have a TTY, or -# use -f. -if ! /usr/bin/tty -s && [ $force -eq 0 -a $check -eq 0 ]; then - echo "Error: Not a TTY. Try with -f (at your own risks!)" >&2 - exit 1 -fi - -# Create an alternative net namespace in which we apply the ruleset, so -# we can easily get a normalized version we can compare latter. See -# http://bugzilla.netfilter.org/show_bug.cgi?id=790 -netns="ipt-firewall-test-$$" -/bin/ip netns add $netns - -trap '/bin/ip netns del $netns 2>/dev/null || true; ipt-revert' SIGINT -trap '/bin/ip netns del $netns; rm -f "${rss[@]}"' EXIT - -rv=0 -for f in ${addrfam:=4 6}; do - run $f || rv=$(( $rv | $? )) -done - -if [ $force -eq 1 ]; then - # At the user's own risks... - ipt-persist - -elif [ $check -eq 1 -o $rv -eq 0 ]; then - # Nothing to do, we're all set. - exit $rv - -else - echo "Try now to establish NEW connections to the machine." - - read -n1 -t$timeout \ - -p "Are you sure you want to use the new ruleset? (y/N) " \ - ret 2>&1 || { [ $? -gt 128 ] && echo -n "Timeout..."; } - case "${ret:-N}" in - [yY]*) echo; ipt-persist - ;; - *) echo; ipt-revert - ;; - esac -fi diff --git a/roles/common/handlers/main.yml b/roles/common/handlers/main.yml index a852c4d..18462cb 100644 --- a/roles/common/handlers/main.yml +++ b/roles/common/handlers/main.yml @@ -1,54 +1,57 @@ # 'service: name=... state=started' tasks should NOT run if there is a # corresponding state=restarted handler. (Register the task notifying # the handler, and add a conditional.) --- - name: systemctl daemon-reload command: /bin/systemctl daemon-reload - name: Refresh hostname service: name=hostname.sh state=restarted - name: apt-get update apt: update_cache=yes -- name: Reload samhain - service: name=samhain state=reloaded +- name: Restart unbound + service: name=unbound state=restarted - name: Update rkhunter's data file command: /usr/bin/rkhunter --propupd +- name: Update firewall + command: /usr/local/sbin/update-firewall -c + - name: Restart fail2ban service: name=fail2ban state=restarted -- name: Reload networking - # /etc/init.d/networking doesn't answer the status command; but since - # it should be "up" whenever ansible has access to the machine, we use - # pattern=init as a dummy assumption. - service: name=networking pattern=init state=reloaded +- name: Restart IPsec + service: name=ipsec state=restarted - name: Restart rsyslog service: name=rsyslog state=restarted +- name: Restart systemd-resolved + service: name=systemd-resolved.service state=restarted + +- name: Restart systemd-timesyncd + service: name=systemd-timesyncd state=restarted + - name: Restart ntp service: name=ntp state=restarted - name: Restart Postfix service: name=postfix state=restarted - name: Reload Postfix service: name=postfix state=reloaded -- name: Restart stunnel - service: name=stunnel4 pattern=/usr/bin/stunnel4 state=restarted - - name: Restart bacula-fd service: name=bacula-fd state=restarted -- name: Update certificate - command: update-ca-certificates - - name: Restart munin-node service: name=munin-node state=restarted - name: Restart freshclam service: name=clamav-freshclam state=restarted + +- name: Update initramfs + command: /usr/sbin/update-initramfs -u diff --git a/roles/common/tasks/apt.yml b/roles/common/tasks/apt.yml index f444315..8df3e8f 100644 --- a/roles/common/tasks/apt.yml +++ b/roles/common/tasks/apt.yml @@ -1,49 +1,43 @@ - name: Install various APT tools - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - apt - apt-listchanges - apt-show-versions - debian-archive-keyring - debian-goodies - needrestart - unattended-upgrades - debfoster - deborphan - - debsecan - debsums - name: Configure APT (1) template: src=etc/apt/{{ item }}.j2 dest=/etc/apt/{{ item }} owner=root group=root mode=0644 with_items: - sources.list - preferences notify: - apt-get update - name: Configure APT (2) copy: src=etc/apt/{{ item }} dest=/etc/apt/{{ item }} owner=root group=root mode=0644 with_items: - listchanges.conf - apt.conf.d/10periodic - apt.conf.d/50unattended-upgrades -- name: Configure the Debian Security Analyzer - template: src=etc/default/debsecan.j2 - dest=/etc/default/debsecan - owner=root group=root - mode=0644 - - name: Start cron service: name=cron state=started tags: - cron # We should run 'apt-get update' before proceeding to any other task. - meta: flush_handlers diff --git a/roles/common/tasks/bacula.yml b/roles/common/tasks/bacula.yml index 248d47d..308e358 100644 --- a/roles/common/tasks/bacula.yml +++ b/roles/common/tasks/bacula.yml @@ -1,111 +1,33 @@ -- name: Install stunnel - apt: pkg=stunnel4 - -- name: Auto-enable stunnel - lineinfile: dest=/etc/default/stunnel4 - regexp='^(\s*#)?\s*ENABLED=' - line='ENABLED=1' - owner=root group=root - mode=0644 - -- name: Create /etc/stunnel/certs - file: path=/etc/stunnel/certs - state=directory - owner=root group=root - mode=0755 - -- name: Generate a private key and a X.509 certificate for Bacula FD - command: genkeypair.sh x509 - --pubkey=/etc/stunnel/certs/{{ inventory_hostname_short }}-fd.pem - --privkey=/etc/stunnel/certs/{{ inventory_hostname_short }}-fd.key - --ou=BaculaFD --cn={{ inventory_hostname }} --dns={{ inventory_hostname }} - -t rsa -b 4096 -h sha512 - register: r1 - changed_when: r1.rc == 0 - failed_when: r1.rc > 1 - notify: - - Restart stunnel - tags: - - genkey - -- name: Fetch Bacula FD X.509 certificate - # Ensure we don't fetch private data - sudo: False - fetch: src=/etc/stunnel/certs/{{ inventory_hostname_short }}-fd.pem - dest=certs/bacula/ - fail_on_missing=yes - flat=yes - tags: - - genkey - -- name: Copy Bacula Dir X.509 certificates - assemble: src=certs/bacula regexp="-dir\.pem$" remote_src=no - dest=/etc/stunnel/certs/bacula-dirs.pem - owner=root group=root - mode=0644 - register: r2 - when: "'bacula-dir' not in group_names" - notify: - - Restart stunnel - -- name: Copy Bacula SD X.509 certificates - copy: src=certs/bacula/{{ hostvars[item].inventory_hostname_short }}-sd.pem - dest=/etc/stunnel/certs/ - owner=root group=root - mode=0644 - register: r3 - with_items: groups['bacula-sd'] | difference([inventory_hostname]) - notify: - - Restart stunnel - -- name: Configure stunnel - template: src=etc/stunnel/bacula-fd.conf.j2 - dest=/etc/stunnel/bacula-fd.conf - owner=root group=root - mode=0644 - register: r4 - when: "'bacula-dir' not in group_names or 'bacula-sd' not in group_names" - notify: - - Restart stunnel - -- name: Start stunnel - service: name=stunnel4 pattern=/usr/bin/stunnel4 state=started - when: not (r1.changed or r2.changed or r3.changed or r4.changed) - -- meta: flush_handlers - - - - name: Install bacula-fd apt: pkg=bacula-fd - name: Create /var/lib/bacula/tmp file: path=/var/lib/bacula/tmp state=directory owner=root group=root mode=0700 - name: Delete /etc/bacula/common_default_passwords file: path=/etc/bacula/common_default_passwords state=absent -# Create with: +# Populate with: # echo $director-dir $(pwgen -sn 64 1) | sudo tee -a /etc/bacula/passwords-fd - name: Ensure /etc/bacula/passwords-fd exists file: path=/etc/bacula/passwords-fd state=file owner=root group=root mode=0600 - name: Configure bacula template: src=etc/bacula/bacula-fd.conf.j2 dest=/etc/bacula/bacula-fd.conf owner=root group=root mode=0644 notify: - Restart bacula-fd - name: Create /etc/bacula/ssl file: path=/etc/bacula/ssl state=directory owner=root group=root mode=0755 @@ -115,36 +37,42 @@ --pubkey=/etc/bacula/ssl/{{ inventory_hostname_short }}.pem --privkey=/etc/bacula/ssl/{{ inventory_hostname_short }}.pem --ou=BaculaFD --cn={{ inventory_hostname }} --dns={{ inventory_hostname }} -t rsa -b 4096 -h sha512 register: r changed_when: r.rc == 0 failed_when: r.rc > 1 notify: - Restart bacula-fd tags: - genkey - name: Copy the master public key for data encryption copy: src=certs/bacula/data-master.pem dest=/etc/bacula/ssl/master.pem owner=root group=root mode=0644 tags: - genkey -- name: Copy bacula-fd.service - copy: src=lib/systemd/system/bacula-fd.service - dest=/lib/systemd/system/bacula-fd.service +- name: Create /etc/systemd/system/bacula-fd.service.d + file: path=/etc/systemd/system/bacula-fd.service.d + state=directory + owner=root group=root + mode=0755 + +- name: Copy bacula-fd.service override + copy: src=etc/systemd/system/bacula-fd.service.d/override.conf + dest=/etc/systemd/system/bacula-fd.service.d/override.conf owner=root group=root mode=0644 notify: - systemctl daemon-reload - Restart bacula-fd - meta: flush_handlers - name: Enable bacula-fd service: name=bacula-fd enabled=yes - name: Start bacula-fd service: name=bacula-fd state=started diff --git a/roles/common/tasks/clamav.yml b/roles/common/tasks/clamav.yml index e1ece0d..437387b 100644 --- a/roles/common/tasks/clamav.yml +++ b/roles/common/tasks/clamav.yml @@ -1,22 +1,32 @@ - name: Install ClamAV - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - clamav - clamav-daemon - clamav-freshclam - name: Configure FreshClam - lineinfile: "dest=/etc/clamav/freshclam.conf - line='DatabaseMirror {{ item }}'" - with_items: - - db.local.clamav.net - - database.clamav.net - - db.other.clamav.net + template: src=etc/clamav/freshclam.conf.j2 + dest=/etc/clamav/freshclam.conf + owner=clamav group=adm + mode=0444 + tags: freshclam notify: - Restart freshclam - name: Start ClamAV service: name={{ item }} state=started with_items: - clamav-daemon - clamav-freshclam + +- name: Add a 'clamav' alias + lineinfile: dest=/etc/aliases create=yes + regexp="^clamav{{':'}} " + line="clamav{{':'}} root" + +- name: Compile the static local Postfix database + postmap: cmd=postalias src=/etc/aliases db=lmdb + owner=root group=root + mode=0644 diff --git a/roles/common/tasks/fail2ban.yml b/roles/common/tasks/fail2ban.yml index be26c79..563075f 100644 --- a/roles/common/tasks/fail2ban.yml +++ b/roles/common/tasks/fail2ban.yml @@ -1,28 +1,63 @@ - name: Install fail2ban apt: pkg=fail2ban -- name: Add addititional filters - copy: src=etc/fail2ban/filter.d/{{ item }} - dest=/etc/fail2ban/filter.d/{{ item }} +- name: Configure fail2ban (fail2ban.local) + copy: src=etc/fail2ban/fail2ban.local + dest=/etc/fail2ban/fail2ban.local owner=root group=root mode=0644 register: r1 - with_items: - - roundcube.conf notify: - Restart fail2ban -- name: Configure fail2ban +- name: Configure fail2ban (jail.local) template: src=etc/fail2ban/jail.local.j2 dest=/etc/fail2ban/jail.local owner=root group=root mode=0644 register: r2 notify: - Restart fail2ban +- name: Configure fail2ban (action.d/nftables-allports.local) + copy: src=etc/fail2ban/action.d/nftables-allports.local + dest=/etc/fail2ban/action.d/nftables-allports.local + owner=root group=root + mode=0644 + register: r3 + notify: + - Restart fail2ban + +- name: Copy filters + copy: src=etc/fail2ban/filter.d/ + dest=/etc/fail2ban/filter.d/ + owner=root group=root + mode=0644 + register: r4 + notify: + - Restart fail2ban + +- name: Create directory /etc/systemd/system/fail2ban.service.d + file: path=/etc/systemd/system/fail2ban.service.d + state=directory + owner=root group=root + mode=0755 + +- name: Harden fail2ban.service + copy: src=etc/systemd/system/fail2ban.service.d/override.conf + dest=/etc/systemd/system/fail2ban.service.d/override.conf + owner=root group=root + mode=0644 + register: r5 + notify: + - systemctl daemon-reload + - Restart fail2ban + - name: Start fail2ban service: name=fail2ban state=started - when: not (r1.changed or r2.changed) + when: not (r1.changed or r2.changed or r3.changed or r4.changed or r5.changed) - meta: flush_handlers + +- name: Delete /var/lib/fail2ban/fail2ban.sqlite3 + file: path=/var/lib/fail2ban/fail2ban.sqlite3 state=absent diff --git a/roles/common/tasks/firewall.yml b/roles/common/tasks/firewall.yml index 29c0e2b..fd1ad92 100644 --- a/roles/common/tasks/firewall.yml +++ b/roles/common/tasks/firewall.yml @@ -1,40 +1,27 @@ -- name: Install some packages required for the firewall - apt: pkg={{ item }} - with_items: - - iptables - - netmask - - bsdutils +- name: Install nftables + apt: pkg=nftables -- name: Create directory /etc/iptables - file: path=/etc/iptables - state=directory - owner=root group=root +- name: Copy /usr/local/sbin/update-firewall + copy: src=usr/local/sbin/update-firewall + dest=/usr/local/sbin/update-firewall + owner=root group=staff mode=0755 -- name: Generate /etc/iptables/services - template: src=etc/iptables/services.j2 - dest=/etc/iptables/services +- name: Copy /etc/nftables.conf + template: src=etc/nftables.conf.j2 + dest=/etc/nftables.conf owner=root group=root - mode=0600 - -- name: Copy /usr/local/sbin/update-firewall.sh - copy: src=usr/local/sbin/update-firewall.sh - dest=/usr/local/sbin/update-firewall.sh - owner=root group=root - mode=0755 - -- name: Make the rulesets persistent - copy: src=etc/network/{{ item }} - dest=/etc/network/{{ item }} - owner=root group=root - mode=0755 - with_items: - - if-pre-up.d/iptables - - if-post-down.d/iptables + mode=0644 - name: Ensure the firewall is up to date - command: /usr/local/sbin/update-firewall.sh -c + command: /usr/local/sbin/update-firewall -c register: rv # A non-zero return value will make ansible stop and show stderr. This # is what we want. changed_when: rv.rc + +- name: Enable nftables.service + service: name=nftables enabled=yes + +- name: Start nftables.service + service: name=nftables state=started diff --git a/roles/common/tasks/haveged.yml b/roles/common/tasks/haveged.yml deleted file mode 100644 index 3f03a28..0000000 --- a/roles/common/tasks/haveged.yml +++ /dev/null @@ -1,5 +0,0 @@ -- name: Install haveged - apt: pkg=haveged - -- name: Start haveged - service: name=haveged state=started diff --git a/roles/common/tasks/ipsec.yml b/roles/common/tasks/ipsec.yml new file mode 100644 index 0000000..917c687 --- /dev/null +++ b/roles/common/tasks/ipsec.yml @@ -0,0 +1,93 @@ +- name: Install strongSwan + apt: pkg={{ packages }} + vars: + packages: + - strongswan-charon + - strongswan-starter + # for the GCM and openssl plugins + - libstrongswan-standard-plugins + notify: + - Update firewall + - Restart IPsec + +- name: Auto-create a dedicated virtual subnet for IPsec + template: src=etc/network/if-up.d/ipsec.j2 + dest=/etc/network/if-up.d/ipsec + owner=root group=root + mode=0755 + +- name: Auto-deactivate the dedicated virtual subnet for IPsec + file: src=../if-up.d/ipsec + dest=/etc/network/if-down.d/ipsec + owner=root group=root state=link force=yes + + +- name: Configure IPsec + template: src=etc/ipsec.conf.j2 + dest=/etc/ipsec.conf + owner=root group=root + mode=0644 + register: r1 + 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: r2 + notify: + - Restart IPsec + +- name: Configure Charon + copy: src=etc/strongswan.d/{{ item }} + dest=/etc/strongswan.d/{{ item }} + owner=root group=root + mode=0644 + with_items: + - charon.conf + - charon/socket-default.conf + register: r3 + notify: + - Restart IPsec + +- name: Generate a key pair for IPsec public key authentication + command: genkeypair.sh keypair + --pubkey=/etc/ipsec.d/certs/{{ inventory_hostname_short }}.pem + --privkey=/etc/ipsec.d/private/{{ inventory_hostname_short }}.key + -t rsa -b 4096 + register: r4 + changed_when: r4.rc == 0 + failed_when: r4.rc > 1 + notify: + - Restart IPsec + tags: + - genkey + +- name: Fetch the public part of IPsec host key + # Ensure we don't fetch private data + become: False + fetch: src=/etc/ipsec.d/certs/{{ inventory_hostname_short }}.pem + dest=certs/ipsec/{{ inventory_hostname_short }}.pem + 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 the public part of IPsec peers' key + copy: src=certs/ipsec/{{ hostvars[item].inventory_hostname_short }}.pem + dest=/etc/ipsec.d/certs/{{ hostvars[item].inventory_hostname_short }}.pem + owner=root group=root + mode=0644 + with_items: "{{ groups.all | difference([inventory_hostname]) }}" + register: r5 + tags: + - genkey + notify: + - Restart IPsec + +- name: Start IPsec + service: name=ipsec state=started + when: not (r1.changed or r2.changed or r3.changed or r4.changed or r5.changed) diff --git a/roles/common/tasks/logging.yml b/roles/common/tasks/logging.yml index 3b86294..699c6e3 100644 --- a/roles/common/tasks/logging.yml +++ b/roles/common/tasks/logging.yml @@ -1,82 +1,83 @@ - name: Install logging server & utilities - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - rsyslog - - syslog-summary - logcheck - logcheck-database - logrotate - name: Configure rsyslog copy: src=etc/rsyslog.conf dest=/etc/rsyslog.conf owner=root group=root mode=0644 register: r1 notify: - Restart rsyslog tags: - syslog - name: Configure postfix's custom rsyslog rules template: src=etc/rsyslog.d/postfix.conf.j2 dest=/etc/rsyslog.d/postfix.conf owner=root group=root mode=0644 register: r2 notify: - Restart rsyslog tags: - syslog - name: Start rsyslog service: name=rsyslog state=started when: not (r1.changed or r2.changed) tags: - syslog - meta: flush_handlers - name: Configure logcheck (1) copy: src=etc/logcheck/{{ item }} dest=/etc/logcheck/{{ item }} - owner=root group=logcheck + owner=root group=root mode=0644 with_items: - logcheck.conf - ignore.d.server/common-local - ignore.d.server/dovecot-local - ignore.d.server/postfix-local + - ignore.d.server/strongswan-local # logcheck-sudo already exists, but changing the filename for our # local modifications would defeat the ruleset - violations.ignore.d/logcheck-sudo tags: - logcheck - name: Configure logcheck (2) lineinfile: dest=/etc/logcheck/logcheck.logfiles line={{ item }} state=present create=yes - owner=root group=logcheck - mode=0640 + owner=root group=root + mode=0644 with_items: - /var/log/syslog - /var/log/auth.log - /var/log/mail.log tags: - logcheck - name: Minimal logging policy (1) lineinfile: dest=/etc/logrotate.d/rsyslog regexp="^/var/log/mail\\.(log|info|sasl)$" state=absent owner=root group=root mode=0644 - name: Minimal logging policy (2) copy: src=etc/logrotate.d/fripost-mail dest=/etc/logrotate.d/fripost-mail owner=root group=root mode=0644 tags: diff --git a/roles/common/tasks/mail.yml b/roles/common/tasks/mail.yml index 1873928..139386f 100644 --- a/roles/common/tasks/mail.yml +++ b/roles/common/tasks/mail.yml @@ -1,114 +1,63 @@ - name: Install Postfix - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: # That one is nicer than GNU mailutils' mailx(1) - - heirloom-mailx + - s-nail - postfix - - postfix-cdb + - postfix-lmdb - name: Create Postfix instances postmulti: instance={{ postfix_instance[item].name }} group={{ postfix_instance[item].group | default('') }} register: r1 - with_items: postfix_instance.keys() | intersect(group_names) | list + with_items: "{{ postfix_instance.keys() | intersect(group_names) | list }}" notify: - Restart Postfix -- name: Link the dynamic maps & master.cf of each children to the master's - # main.cf is specialized to each dedicated role, though - file: src=../postfix/{{ item.1 }} - dest=/etc/postfix-{{ postfix_instance[item.0].name }}/{{ item.1 }} +- name: Link the dynamic maps of each children to the master's + # main.cf and master.cf are specialized to each dedicated role, though + file: src=../postfix/dynamicmaps.cf + dest=/etc/postfix-{{ postfix_instance[item].name }}/dynamicmaps.cf owner=root group=root state=link force=yes register: r2 - with_nested: - - postfix_instance.keys() | intersect(group_names) | list - - [ 'dynamicmaps.cf', 'master.cf' ] - notify: - - Restart Postfix - -- name: Configure Postfix (1) - copy: src=etc/postfix/master.cf - dest=/etc/postfix/master.cf - owner=root group=root - mode=0644 - register: r3 + with_items: "{{ postfix_instance.keys() | intersect(group_names) | list }}" notify: - Restart Postfix -- name: Configure Postfix (2) - template: src=etc/postfix/main.cf.j2 - dest=/etc/postfix/main.cf +- name: Configure Postfix + template: src=etc/postfix/{{ item }}.j2 + dest=/etc/postfix/{{ item }} owner=root group=root mode=0644 + with_items: + - main.cf + - master.cf notify: - Reload Postfix -- 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 - --ou=Postfix --cn={{ ansible_fqdn }} - -t ecdsa -b secp384r1 -h sha512 - register: r4 - changed_when: r4.rc == 0 - failed_when: r4.rc > 1 - notify: - - Restart Postfix - 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: Add a 'root' alias +- name: Add some common aliases lineinfile: dest=/etc/aliases create=yes - regexp="^root:"" " - line="root:"" root@fripost.org" + regexp='^{{ item.src }}{{':'}} ' + line='{{ item.src }}{{':'}} {{ item.dst }}' + with_items: + - { src: mailer-daemon, dst: 'postmaster' } + - { src: postmaster, dst: 'root' } + - { src: nobody, dst: 'root' } + - { src: root, dst: 'root@fripost.org' } - name: Compile the static local Postfix database - postmap: cmd=postalias src=/etc/aliases db=cdb + postmap: cmd=postalias src=/etc/aliases db=lmdb owner=root group=root mode=0644 -# We're using CDB +# We're using LMDB - name: Delete /etc/aliases.db file: path=/etc/aliases.db state=absent -- 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 or 'MX' in group_names" - tags: - - tls_policy - -- 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 or 'MX' in group_names" - tags: - - tls_policy - - name: Start Postfix service: name=postfix state=started - when: not (r1.changed or r2.changed or r3.changed or r4.changed) + when: not (r1.changed or r2.changed) - meta: flush_handlers diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml index 470a6b2..1dc286e 100644 --- a/roles/common/tasks/main.yml +++ b/roles/common/tasks/main.yml @@ -1,70 +1,102 @@ --- -- include: sysctl.yml tags=sysctl -- include: hosts.yml -- include: apt.yml tags=apt +- import_tasks: sysctl.yml + tags: sysctl +- import_tasks: hosts.yml +- import_tasks: apt.yml + tags: apt - name: Install intel-microcode apt: pkg=intel-microcode - when: "ansible_processor[0] | search('^(Genuine)?Intel.*') and not (ansible_virtualization_role == 'guest' and ansible_virtualization_type == 'xen')" + when: "ansible_processor[1] is search('^(Genuine)?Intel.*') and not ansible_virtualization_role == 'guest'" tags: intel -- include: firewall.yml tags=firewall,iptables -- include: samhain.yml tags=samhain -- include: auditd.yml tags=auditd -- include: rkhunter.yml tags=rkhunter -- include: clamav.yml tags=clamav -- include: fail2ban.yml tags=fail2ban -- include: smart.yml tags=smartmontools,smart - when: "not ((ansible_virtualization_role == 'guest' and ansible_virtualization_type == 'xen') or ansible_system_vendor == 'QEMU')" -- include: haveged.yml tags=haveged,entropy +- import_tasks: firewall.yml + tags: + - firewall + - iptables + - nftables + +- import_tasks: stunnel.yml + tags: stunnel + when: "'webmail' in group_names and 'LDAP_provider' not in group_names" +- import_tasks: auditd.yml + tags: auditd +- import_tasks: resolved.yml + tags: + - resolv + - resolved + - dns +- import_tasks: unbound.yml + tags: + - unbound + - dns + when: "ansible_processor[1] is search('^(Genuine)?Intel.*') and not ansible_virtualization_role == 'guest'" +- import_tasks: rkhunter.yml + tags: rkhunter +- import_tasks: clamav.yml + tags: clamav +- import_tasks: fail2ban.yml + tags: fail2ban +- import_tasks: smart.yml + tags: + - smartmontools + - smart + when: "not ansible_virtualization_role == 'guest'" - name: Copy genkeypair.sh and gendhparam.sh copy: src=usr/local/bin/{{ item }} dest=/usr/local/bin/{{ item }} - owner=root group=root + owner=root group=staff mode=0755 tags: genkey with_items: - genkeypair.sh - gendhparam.sh - name: Generate DH parameters - command: gendhparam.sh /etc/ssl/private/dhparams.pem creates=/etc/ssl/private/dhparams.pem + command: gendhparam.sh /etc/ssl/dhparams.pem 2048 + creates=/etc/ssl/dhparams.pem tags: genkey -- include: logging.yml tags=logging -- include: ntp.yml tags=ntp -- include: mail.yml tags=mail,postfix -- include: bacula.yml tags=bacula-fd,bacula -- include: munin-node.yml tags=munin-node,munin +- import_tasks: ipsec.yml + tags: + - strongswan + - ipsec + when: "groups.all | length > 1" +- import_tasks: logging.yml + tags: logging +- import_tasks: ntp.yml + tags: ntp +- import_tasks: mail.yml + tags: + - mail + - postfix +- import_tasks: bacula.yml + tags: + - bacula-fd + - bacula +- import_tasks: munin-node.yml + tags: + - munin-node + - munin - name: Install common packages - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - ca-certificates - etckeeper - ethtool - git - htop - molly-guard - rsync - screen - - telnet-ssl - -# XXX: this is a workaround the CAcert root CAs not being present in -# Jessie. In stretch, we would merely install the 'ca-cacert' package. -- name: Create directory /usr/local/share/ca-certificates/CAcert - file: path=/usr/local/share/ca-certificates/CAcert - state=directory - owner=root group=root - mode=0755 - tags: - - certs + - bind9-dnsutils -- name: Copy CAcert root CAs - copy: src=certs/CAcert/{{ item }} - dest=/usr/local/share/ca-certificates/CAcert/{{ item }} +- name: Disable resume device + # Cf. initramfs-tools(7) and initramfs.conf(5). + copy: src=etc/initramfs-tools/conf.d/resume + dest=/etc/initramfs-tools/conf.d/resume owner=root group=root mode=0644 - with_items: - - root.crt - - class3.crt tags: - - certs + - initramfs + - resume notify: - - Update certificate + - Update initramfs diff --git a/roles/common/tasks/munin-node.yml b/roles/common/tasks/munin-node.yml index 9e5d8f4..2411b59 100644 --- a/roles/common/tasks/munin-node.yml +++ b/roles/common/tasks/munin-node.yml @@ -1,23 +1,24 @@ - name: Install munin-node - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - munin-node - munin-plugins-extra ### - acpi - lm-sensors - ethtool - hdparm - libwww-perl - libxml-simple-perl - logtail - name: Create directory /usr/local/share/munin/plugins file: path=/usr/local/share/munin/plugins state=directory owner=root group=root mode=0755 - name: Copy our own Munin plugins copy: src={{ item }} dest=/usr/local/share/munin/plugins/ @@ -44,68 +45,90 @@ owner=root group=root state=link force=yes register: r2 with_items: - cpu - df - df_inode - diskstats - entropy - fail2ban - forks - fw_conntrack - fw_forwarded_local - fw_packets - hddtemp_smartctl - interrupts - irqstats - load - memory - netstat - - ntp_kernel_err - - ntp_kernel_pll_freq - - ntp_kernel_pll_off - - ntp_offset - open_files - open_inodes - processes - proc_pri - swap - threads - uptime - users - vmstat notify: - Restart munin-node -- name: Delete Munin plugins +- name: Install Munin plugins + file: src=/usr/share/munin/plugins/{{ item }} + dest=/etc/munin/plugins/{{ item }} + owner=root group=root + state=link force=yes + with_items: + - ntp_kernel_err + - ntp_kernel_pll_freq + - ntp_kernel_pll_off + - ntp_offset + when: "'NTP_master' in group_names" + notify: + - Restart munin-node + +- name: Delete unnecessary Munin plugins file: path=/etc/munin/plugins/{{ item }} state=absent register: r3 with_items: - http_loadtime - ip_255.255.255.255 - postfix_mailqueue - postfix_mailvolume notify: - Restart munin-node +- name: Delete unnecessary Munin plugins + file: path=/etc/munin/plugins/{{ item }} + state=absent + with_items: + - ntp_kernel_err + - ntp_kernel_pll_freq + - ntp_kernel_pll_off + - ntp_offset + when: "'NTP_master' not in group_names" + notify: + - Restart munin-node + - name: Install 'if_' Munin wildcard plugin file: src=/usr/share/munin/plugins/{{ item.0 }}_ dest=/etc/munin/plugins/{{ item.0 }}_{{ item.1 }} owner=root group=root state=link force=yes register: r4 with_nested: - [ if, if_err ] - [ lo, "{{ ansible_default_ipv4.interface }}" ] notify: - Restart munin-node - name: Install 'postfix_mailvolume2' Munin plugin file: src=/usr/local/share/munin/plugins/postfix_mailvolume2 dest=/etc/munin/plugins/postfix_mailvolume2 owner=root group=root state=link force=yes register: r5 notify: - Restart munin-node @@ -115,93 +138,49 @@ dest=/etc/munin/plugins/postfix_mailqueue_postfix owner=root group=root state=link force=yes register: r6 notify: - Restart munin-node - name: Install 'postfix_stats_' Munin wildcard plugin file: src=/usr/local/share/munin/plugins/postfix_stats_ dest=/etc/munin/plugins/postfix_stats_{{ item }}_postfix owner=root group=root state=link force=yes register: r7 with_items: - smtpd - qmgr - smtp notify: - Restart munin-node -- name: Start munin-node - service: name=munin-node state=started - when: not (r1.changed or r2.changed or r3.changed or r4.changed or r5.changed or r6.changed or r7.changed) - -- meta: flush_handlers - - - -- name: Install stunnel - apt: pkg=stunnel4 - -- name: Auto-enable stunnel - lineinfile: dest=/etc/default/stunnel4 - regexp='^(\s*#)?\s*ENABLED=' - line='ENABLED=1' - owner=root group=root - mode=0644 - -- name: Create /etc/stunnel/certs - file: path=/etc/stunnel/certs +- name: Create directory /etc/systemd/system/munin-node.service.d + file: path=/etc/systemd/system/munin-node.service.d state=directory owner=root group=root mode=0755 -- name: Generate a private key and a X.509 certificate for munin-node - command: genkeypair.sh x509 - --pubkey=/etc/stunnel/certs/munin-{{ inventory_hostname_short }}.pem - --privkey=/etc/stunnel/certs/munin-{{ inventory_hostname_short }}.key - --ou=Munin --cn={{ inventory_hostname }} --dns={{ inventory_hostname }} - -t rsa -b 4096 -h sha512 - register: r1 - changed_when: r1.rc == 0 - failed_when: r1.rc > 1 - notify: - - Restart stunnel - tags: - - genkey - -- name: Fetch Munin X.509 certificate - # Ensure we don't fetch private data - sudo: False - fetch: src=/etc/stunnel/certs/munin-{{ inventory_hostname_short }}.pem - dest=certs/munin/{{ inventory_hostname }}.pem - fail_on_missing=yes - flat=yes - tags: - - genkey - -- name: Copy munin-master X.509 certificates - assemble: src=certs/munin regexp="{{ groups['munin-master'] | join('|') }}\.pem$" remote_src=no - dest=/etc/stunnel/certs/munin-master.pem - owner=root group=root - mode=0644 - register: r2 - when: "'munin-master' not in group_names" +- name: Copy munin-node.service override + copy: src=etc/systemd/system/munin-node.service.d/override.conf + dest=/etc/systemd/system/munin-node.service.d/override.conf + owner=root group=root + mode=0644 + register: r8 notify: - - Restart stunnel + - systemctl daemon-reload + - Restart munin-node -- name: Configure stunnel - template: src=etc/stunnel/munin-node.conf.j2 - dest=/etc/stunnel/munin-node.conf - owner=root group=root - mode=0644 - register: r3 - when: "'munin-master' not in group_names" - notify: - - Restart stunnel +# We use RuntimeDirectory in our overrride unit to avoid permission +# issues caused by the restrictive Capability Bounding Set +- name: Mask /usr/lib/tmpfiles.d/munin-common.conf + file: src=/dev/null + dest=/etc/tmpfiles.d/munin-common.conf + owner=root group=root + state=link -- name: Start stunnel - service: name=stunnel4 pattern=/usr/bin/stunnel4 state=started - when: not (r1.changed or r2.changed or r3.changed) +- name: Start munin-node + service: name=munin-node state=started + when: not (r1.changed or r2.changed or r3.changed or r4.changed or r5.changed or r6.changed or r7.changed or r8.changed) - meta: flush_handlers diff --git a/roles/common/tasks/ntp.yml b/roles/common/tasks/ntp.yml index f9a01c8..2ff9e49 100644 --- a/roles/common/tasks/ntp.yml +++ b/roles/common/tasks/ntp.yml @@ -1,15 +1,33 @@ -- name: Install ntp - apt: pkg=ntp +- name: Remove ntp package + apt: pkg=ntp state={{ state }} purge=yes + vars: + state: "{{ ('NTP_master' in group_names) | ternary('present', 'absent') }}" + +- name: Install systemd-timesyncd package + apt: pkg=systemd-timesyncd state=present purge=yes + when: "'NTP_master' not in group_names" + +- name: Create /etc/systemd/timesyncd.conf.d + file: path=/etc/systemd/timesyncd.conf.d + state=directory + owner=root group=root + mode=0755 + when: "'NTP_master' not in group_names" - name: Configure ntp - template: src=etc/ntp.conf.j2 - dest=/etc/ntp.conf + template: src=etc/{{ conf }}.j2 + dest=/etc/{{ conf }} owner=root group=root mode=0644 + vars: + conf: "{{ ('NTP_master' in group_names) | ternary('ntp.conf', 'systemd/timesyncd.conf.d/fripost.conf') }}" + service: "{{ ('NTP_master' in group_names) | ternary('ntp', 'systemd-timesyncd') }}" notify: - - Restart ntp + - Restart {{ service }} - meta: flush_handlers -- name: Start ntp - service: name=ntp state=started +- name: Start and enable ntp + service: name={{ service }}.service state=started enabled=true + vars: + service: "{{ ('NTP_master' in group_names) | ternary('ntp', 'systemd-timesyncd') }}" diff --git a/roles/common/tasks/resolved.yml b/roles/common/tasks/resolved.yml new file mode 100644 index 0000000..2834eaa --- /dev/null +++ b/roles/common/tasks/resolved.yml @@ -0,0 +1,36 @@ +- name: Install systemd-resolved + apt: pkg={{ packages }} + vars: + packages: + - systemd-resolved + - libnss-resolve + - libnss-myhostname + +- name: Create directory /etc/systemd/resolved.conf.d + file: path=/etc/systemd/resolved.conf.d + state=directory + owner=root group=root + mode=0755 + +- name: Configure systemd-resolved + template: src=etc/systemd/resolved.conf.d/local.conf.j2 + dest=/etc/systemd/resolved.conf.d/local.conf + owner=root group=root + mode=0644 + notify: + - Restart systemd-resolved + +- name: Start systemd-resolved + service: name=systemd-resolved.service enabled=true state=started + +- meta: flush_handlers + +- name: Remove resolvconf + apt: pkg=resolvconf state=absent purge=yes + +- name: Configure /etc/nsswitch.conf + lineinfile: "dest=/etc/nsswitch.conf create=no + regexp='^(hosts:\\s+).*' + line='\\1resolve [!UNAVAIL=return] files myhostname dns' + backrefs=true" + tags: nsswitch diff --git a/roles/common/tasks/rkhunter.yml b/roles/common/tasks/rkhunter.yml index c9d26fa..64f2aac 100644 --- a/roles/common/tasks/rkhunter.yml +++ b/roles/common/tasks/rkhunter.yml @@ -1,23 +1,24 @@ - name: Install rkhunter - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - rkhunter - curl - iproute2 - lsof - unhide # To test the configuration: # ansible all -m command -a '/usr/bin/rkhunter -c --nomow --rwo' - name: Configure rkhunter copy: src=etc/{{ item }} dest=/etc/{{ item }} owner=root group=root mode=0644 with_items: - rkhunter.conf - default/rkhunter notify: # This might not always be necessary, but it's not like we would # change the config every day... - Update rkhunter's data file diff --git a/roles/common/tasks/samhain.yml b/roles/common/tasks/samhain.yml deleted file mode 100644 index dd5c09b..0000000 --- a/roles/common/tasks/samhain.yml +++ /dev/null @@ -1,26 +0,0 @@ -- name: Install samhain - apt: pkg=samhain - # XXX: Doesn't work out of the box, see #660197. - # Every once in a while, or after a major upgrade, you may want to - # update Samhain's database: - # - # sudo samhain -t update --foreground -l none - # - # To update the database interactively, without sending mails: - # - # sudo samhain -t update --interactive -l none -m none - -- name: Configure samhain - copy: src=etc/samhain/samhainrc - dest=/etc/samhain/samhainrc - owner=root group=root - mode=0644 - notify: - - Reload samhain - -- name: Start samhain - # This task is inconditional because samhain is reloaded not - # restarted. - service: name=samhain state=started - -- meta: flush_handlers diff --git a/roles/common/tasks/smart.yml b/roles/common/tasks/smart.yml index 8d35d9f..68e507f 100644 --- a/roles/common/tasks/smart.yml +++ b/roles/common/tasks/smart.yml @@ -1,12 +1,5 @@ - name: Install smartmontools apt: pkg=smartmontools -- name: Auto-enable smartmontools - lineinfile: dest=/etc/default/smartmontools - regexp='^(\s*#)?\s*start_smartd=' - line='start_smartd=yes' - owner=root group=root - mode=0644 - - name: Start smartd service: name=smartmontools state=started diff --git a/roles/common/tasks/stunnel.yml b/roles/common/tasks/stunnel.yml new file mode 100644 index 0000000..1522f1f --- /dev/null +++ b/roles/common/tasks/stunnel.yml @@ -0,0 +1,16 @@ +- name: Install stunnel4 + apt: pkg=stunnel4 + +- name: Copy stunnel4 service files + copy: src=etc/systemd/system/{{ item }} + dest=/etc/systemd/system/{{ item }} + owner=root group=root + mode=0644 + notify: + - systemctl daemon-reload + with_items: + - stunnel4.service + - stunnel4@.service + +- name: Disable stunnel4 service + service: name=stunnel4.service enabled=false diff --git a/roles/common/tasks/sysctl.yml b/roles/common/tasks/sysctl.yml index 6ac7feb..08a1b13 100644 --- a/roles/common/tasks/sysctl.yml +++ b/roles/common/tasks/sysctl.yml @@ -1,49 +1,48 @@ -- sysctl: name={{ item.name }} "value={{ item.value }}" sysctl_set=yes +- sysctl: name={{ item.name }} value={{ item.value }} sysctl_set=yes with_items: - { name: 'kernel.domainname', value: '{{ ansible_domain }}' } # Networking. See # https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt # Enable Spoof protection (reverse-path filter). Turn on Source # Address Verification in all interfaces to prevent some spoofing # attacks. - { name: 'net.ipv4.conf.default.rp_filter', value: 1 } - { name: 'net.ipv4.conf.all.rp_filter', value: 1 } - # Enable TCP/IP SYN cookies to avoid TCP SYN flood attacks. We - # rate-limit not only the default ICMP types 3, 4, 11 and 12 + # Disable SYN cookies and improve SYN backlog handling, see tcp(7) and + # https://levelup.gitconnected.com/linux-kernel-tuning-for-high-performance-networking-high-volume-incoming-connections-196e863d458a + - { name: 'net.ipv4.tcp_syncookies', value: 0 } + - { name: 'net.ipv4.tcp_synack_retries', value: 1 } + - { name: 'net.ipv4.tcp_max_syn_backlog', value: 32768 } + + # We rate-limit not only the default ICMP types 3, 4, 11 and 12 # (0x1818), but also types 0 and 8. See icmp(7). - - { name: 'net.ipv4.tcp_syncookies', value: 1 } - { name: 'net.ipv4.icmp_ratemask', value: 6425 } - { name: 'net.ipv4.icmp_ratelimit', value: 1000 } - # Disable paquet forwarding between interfaces (we are not a router). + # Disable packet forwarding between interfaces (we are not a router). - { name: 'net.ipv4.ip_forward', value: 0 } - { name: 'net.ipv6.conf.all.forwarding', value: 0 } - # Enable IPv6 Privacy Extensions. - - { name: 'net.ipv6.conf.default.use_tempaddr', value: 2 } - - { name: 'net.ipv6.conf.all.use_tempaddr', value: 2 } - - { name: 'net.ipv6.conf.all.autoconf', value: 0 } - # Do not accept ICMP redirects (prevent MITM attacks). - { name: 'net.ipv4.conf.all.accept_redirects', value: 0 } - { name: 'net.ipv6.conf.all.accept_redirects', value: 0 } # Do not send ICMP redirects (we are not a router). - { name: 'net.ipv4.conf.default.send_redirects', value: 0 } - { name: 'net.ipv4.conf.all.send_redirects', value: 0 } # Do not accept IP source route packets (we are not a router). - { name: 'net.ipv4.conf.all.accept_source_route', value: 0 } - { name: 'net.ipv6.conf.all.accept_source_route', value: 0 } # Log Martian Packets. - { name: 'net.ipv4.conf.all.log_martians', value: 1 } # Ignore ICMP broadcasts. - { name: 'net.ipv4.icmp_echo_ignore_broadcasts', value: 1 } # Ignore bogus ICMP errors. - { name: 'net.ipv4.icmp_ignore_bogus_error_responses', value: 1 } diff --git a/roles/common/tasks/unbound.yml b/roles/common/tasks/unbound.yml new file mode 100644 index 0000000..dda6769 --- /dev/null +++ b/roles/common/tasks/unbound.yml @@ -0,0 +1,21 @@ +- name: Install unbound + apt: pkg={{ packages }} + vars: + packages: + - unbound + - dns-root-data + +- name: Copy unbound configuration + template: src=templates/etc/unbound/unbound.conf.j2 + dest=/etc/unbound/unbound.conf + owner=root group=root + mode=0644 + register: r + notify: + - Restart unbound + +- name: Start unbound + service: name=unbound state=started + when: not r.changed + +#- meta: flush_handlers 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/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" diff --git a/roles/git/files/etc/cgitrc b/roles/git/files/etc/cgitrc index b862dc3..a7e33cd 100644 --- a/roles/git/files/etc/cgitrc +++ b/roles/git/files/etc/cgitrc @@ -1,29 +1,29 @@ # # cgit config # see cgitrc(5) for details # Enable caching of up to 1000 output entries cache-size=1000 # Specify some default clone url prefixes -clone-url=https://$HTTP_HOST/$CGIT_REPO_NAME ssh://$HTTP_HOST/$CGIT_REPO_NAME +clone-url=https://$HTTP_HOST/$CGIT_REPO_NAME ssh://gitolite@$HTTP_HOST/$CGIT_REPO_NAME # Specify the css, logo and favicon urls css=/static/cgit.css logo=/static/cgit.png favicon=/static/favicon.ico # Show owner on index page enable-index-owner=1 # Disallow dumb http transport git clone enable-http-clone=0 # Show extra links for each repository on the index page enable-index-links=1 # Enable ASCII art commit history graph on the log pages enable-commit-graph=1 # Show number of affected files per commit on the log pages enable-log-filecount=1 diff --git a/roles/git/files/etc/nginx/sites-available/git b/roles/git/files/etc/nginx/sites-available/git index 75c1512..9e9d16e 100644 --- a/roles/git/files/etc/nginx/sites-available/git +++ b/roles/git/files/etc/nginx/sites-available/git @@ -1,92 +1,78 @@ server { listen 80; listen [::]:80; server_name git.fripost.org; + include /etc/lacme/nginx.conf; + access_log /var/log/nginx/git.access.log; error_log /var/log/nginx/git.error.log info; - location ^~ /static/ { - alias /usr/share/cgit/; - expires 30d; - } - - # Bypass the CGI to return static files stored on disk. Try first repo with - # a trailing '.git', then without. - location ~* "^/((?U)[^/]+)(?:\.git)?/objects/(?:[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(?:pack|idx))$" { - root /var/lib/gitolite/repositories; - try_files /$1.git/objects/$2 /$1/objects/$2 =404; - expires 30d; - # TODO honor git-daemon-export-ok - } - - # disallow push over HTTP/HTTPS - location ~* "^/[^/]+/git-receive-pack$" { return 403; } - - location ~* "^/[^/]+/(?:HEAD|info/refs|objects/info/[^/]+|git-upload-pack)$" { - gzip off; - include uwsgi_params; - uwsgi_modifier1 9; - uwsgi_param GIT_PROJECT_ROOT /var/lib/gitolite/repositories; - uwsgi_pass unix:/run/uwsgi/app/git-http-backend/socket; - } - - - # send all other URLs to cgit location / { - gzip off; - include uwsgi_params; - uwsgi_modifier1 9; - uwsgi_pass unix:/run/uwsgi/app/cgit/socket; + return 301 https://$host$request_uri; } } server { - listen 443; - listen [::]:443; + listen 443 ssl http2; + listen [::]:443 ssl http2; server_name git.fripost.org; - include ssl/config; - ssl_certificate /etc/nginx/ssl/git.fripost.org.pem; - ssl_certificate_key /etc/nginx/ssl/git.fripost.org.key; - access_log /var/log/nginx/git.access.log; error_log /var/log/nginx/git.error.log info; - location ^~ /static/ { - alias /usr/share/cgit/; - expires 30d; - } + include snippets/headers.conf; + add_header Content-Security-Policy + "default-src 'none'; img-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self'; frame-ancestors 'none'; form-action 'self'"; - # Bypass the CGI to return static files stored on disk. Try first repo with - # a trailing '.git', then without. - location ~* "^/((?U)[^/]+)(?:\.git)?/objects/(?:[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(?:pack|idx))$" { - root /var/lib/gitolite/repositories; - try_files /$1.git/objects/$2 /$1/objects/$2 =404; + include snippets/ssl.conf; + ssl_certificate ssl/git.fripost.org.pem; + ssl_certificate_key ssl/git.fripost.org.key; + include snippets/git.fripost.org.hpkp-hdr; + + gzip on; + gzip_vary on; + gzip_min_length 256; + gzip_types application/javascript application/json application/xml image/svg+xml image/x-icon text/css text/plain; + + location ^~ /static/ { expires 30d; - # TODO honor git-daemon-export-ok + alias /usr/share/cgit/; } # disallow push over HTTP/HTTPS - location ~* "^/[^/]+/git-receive-pack$" { return 403; } + location ~ "^/.+/git-receive-pack$" { return 403; } - location ~* "^/[^/]+/(?:HEAD|info/refs|objects/info/[^/]+|git-upload-pack)$" { + location ~ "^/.+/(?:info/refs|git-upload-pack)$" { + limit_except GET POST { deny all; } + fastcgi_buffering off; gzip off; - include uwsgi_params; - uwsgi_modifier1 9; - uwsgi_param GIT_PROJECT_ROOT /var/lib/gitolite/repositories; - uwsgi_pass unix:/run/uwsgi/app/git-http-backend/socket; + + fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend; + fastcgi_param NO_BUFFERING ""; + + # cf. git-http-backend(1) + fastcgi_param GIT_PROJECT_ROOT /var/lib/gitolite/repositories; + fastcgi_param PATH_INFO $uri; + fastcgi_param CONTENT_TYPE $content_type; + fastcgi_param QUERY_STRING $query_string; + fastcgi_param REQUEST_METHOD $request_method; + fastcgi_pass unix:/run/git-http-backend.socket; } + location = /robots.txt { root /usr/share/cgit; } + location = /favicon.ico { root /usr/share/cgit; } # send all other URLs to cgit location / { - gzip off; - include uwsgi_params; - uwsgi_modifier1 9; - uwsgi_pass unix:/run/uwsgi/app/cgit/socket; + fastcgi_param SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi; + fastcgi_param PATH_INFO $uri; + fastcgi_param CONTENT_TYPE $content_type; + fastcgi_param QUERY_STRING $query_string; + fastcgi_param REQUEST_METHOD $request_method; + fastcgi_pass unix:/run/cgit.socket; } } diff --git a/roles/git/files/etc/systemd/system/cgit.service b/roles/git/files/etc/systemd/system/cgit.service new file mode 100644 index 0000000..08037ac --- /dev/null +++ b/roles/git/files/etc/systemd/system/cgit.service @@ -0,0 +1,23 @@ +[Unit] +Description=hyperfast web frontend for git repositories written in C +Documentation=https://git.zx2c4.com/cgit/ + +[Service] +User=_cgit +Group=nogroup +SupplementaryGroups=gitolite +ExecStart=/usr/sbin/fcgiwrap +SyslogIdentifier=cgit +# +# Hardening +NoNewPrivileges=yes +PrivateDevices=yes +ProtectHome=yes +ProtectSystem=strict +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ReadWriteDirectories=/var/cache/cgit + +[Install] +WantedBy=multi-user.target diff --git a/roles/git/files/etc/systemd/system/cgit.socket b/roles/git/files/etc/systemd/system/cgit.socket new file mode 100644 index 0000000..bba4bef --- /dev/null +++ b/roles/git/files/etc/systemd/system/cgit.socket @@ -0,0 +1,11 @@ +[Unit] +Description=hyperfast web frontend for git repositories written in C +Documentation=https://git.zx2c4.com/cgit/ + +[Socket] +ListenStream=%t/cgit.socket +SocketUser=www-data +SocketMode=0600 + +[Install] +WantedBy=sockets.target diff --git a/roles/git/files/etc/systemd/system/git-http-backend.service b/roles/git/files/etc/systemd/system/git-http-backend.service new file mode 100644 index 0000000..f973370 --- /dev/null +++ b/roles/git/files/etc/systemd/system/git-http-backend.service @@ -0,0 +1,21 @@ +[Unit] +Description=Git HTTP backend +Documentation=man:git-http-backend(1) + +[Service] +DynamicUser=yes +SupplementaryGroups=gitolite +ExecStart=/usr/sbin/fcgiwrap +SyslogIdentifier=git-http-backend +# +# Hardening +NoNewPrivileges=yes +PrivateDevices=yes +ProtectHome=yes +ProtectSystem=strict +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes + +[Install] +WantedBy=multi-user.target diff --git a/roles/git/files/etc/systemd/system/git-http-backend.socket b/roles/git/files/etc/systemd/system/git-http-backend.socket new file mode 100644 index 0000000..c2820d4 --- /dev/null +++ b/roles/git/files/etc/systemd/system/git-http-backend.socket @@ -0,0 +1,11 @@ +[Unit] +Description=Git HTTP backend +Documentation=man:git-http-backend(1) + +[Socket] +ListenStream=%t/git-http-backend.socket +SocketUser=www-data +SocketMode=0600 + +[Install] +WantedBy=sockets.target diff --git a/roles/git/files/etc/uwsgi/apps-available/cgit.ini b/roles/git/files/etc/uwsgi/apps-available/cgit.ini deleted file mode 100644 index 2fb5b25..0000000 --- a/roles/git/files/etc/uwsgi/apps-available/cgit.ini +++ /dev/null @@ -1,6 +0,0 @@ -[uwsgi] -plugins = cgi -procname-master = uwsgi %(deb-confname) -cgi = /usr/lib/cgit/cgit.cgi -uid = cgit -gid = www-data diff --git a/roles/git/files/etc/uwsgi/apps-available/git-http-backend.ini b/roles/git/files/etc/uwsgi/apps-available/git-http-backend.ini deleted file mode 100644 index 6718237..0000000 --- a/roles/git/files/etc/uwsgi/apps-available/git-http-backend.ini +++ /dev/null @@ -1,4 +0,0 @@ -[uwsgi] -plugins = cgi -procname-master = uwsgi %(deb-confname) -cgi = /usr/lib/git-core/git-http-backend diff --git a/roles/git/handlers/main.yml b/roles/git/handlers/main.yml index d52c9cc..6212d91 100644 --- a/roles/git/handlers/main.yml +++ b/roles/git/handlers/main.yml @@ -1,9 +1,18 @@ --- - name: systemctl daemon-reload command: /bin/systemctl daemon-reload -- name: Restart uWSGI - service: name=uwsgi state=restarted +- name: Stop cgit + service: name=cgit.service state=stopped + +- name: Restart cgit + service: name=cgit.socket state=restarted + +- name: Stop git-http-backend + service: name=git-http-backend.service state=stopped + +- name: Restart git-http-backend + service: name=git-http-backend.socket state=restarted - name: Restart Nginx service: name=nginx state=restarted diff --git a/roles/git/tasks/cgit.yml b/roles/git/tasks/cgit.yml index a8be1fc..120f204 100644 --- a/roles/git/tasks/cgit.yml +++ b/roles/git/tasks/cgit.yml @@ -1,111 +1,144 @@ - name: Install cgit - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - cgit - highlight - - uwsgi + - fcgiwrap + +- name: Stop and disable fcgiwrap socket + service: name=fcgiwrap.socket state=stopped enabled=false + +- name: Stop fcgiwrap service + service: name=fcgiwrap.service state=stopped - name: Configure cgit copy: src=etc/cgitrc dest=/etc/cgitrc owner=root group=root mode=0644 - register: r1 notify: - - Restart uWSGI + - Stop cgit - name: Copy /usr/lib/cgit/filters/syntax-highlighting2.sh copy: src=usr/lib/cgit/filters/syntax-highlighting2.sh dest=/usr/lib/cgit/filters/syntax-highlighting2.sh owner=root group=root mode=0755 - register: r2 notify: - - Restart uWSGI + - Stop cgit -- name: Create a user 'cgit' - user: name=cgit system=yes - home=/var/www +- name: Create '_cgit' user + user: name=_cgit system=yes + group=nogroup + home=/nonexistent shell=/usr/sbin/nologin password=! state=present - register: r3 notify: - - Restart uWSGI + - Stop cgit -- name: Create /etc/uwsgi/apps-available/{cgit,git-http-backend}.ini - copy: src=etc/uwsgi/apps-available/{{ item }}.ini - dest=/etc/uwsgi/apps-available/{{ item }}.ini +# Make it sticky: `dpkg-statoverride --add _cgit nogroup 0700 /var/cache/cgit` +- name: Create cache directory /var/cache/cgit + file: path=/var/cache/cgit + state=directory + owner=_cgit group=nogroup + mode=0700 + +- name: Copy cgit service unit + copy: src=etc/systemd/system/cgit.service + dest=/etc/systemd/system/cgit.service owner=root group=root mode=0644 - register: r4 - with_items: - - cgit - - git-http-backend notify: - - Restart uWSGI + - systemctl daemon-reload + - Stop cgit -- name: Create /etc/uwsgi/apps-enabled/{cgit,git-http-backend}.ini - file: src=../apps-available/{{ item }}.ini - dest=/etc/uwsgi/apps-enabled/{{ item }}.ini +- name: Copy cgit socket unit + copy: src=etc/systemd/system/cgit.socket + dest=/etc/systemd/system/cgit.socket owner=root group=root - state=link force=yes - register: r5 - with_items: - - cgit - - git-http-backend + mode=0644 notify: - - Restart uWSGI + - systemctl daemon-reload + - Restart cgit -- name: Start uWSGI - service: name=nginx state=started - when: not (r1.changed or r2.changed or r3.changed or r4.changed or r5.changed) +- name: Disable cgit service + service: name=cgit.service enabled=false + +- name: Start cgit socket + service: name=cgit.socket state=started enabled=true - meta: flush_handlers -- name: Add 'cgit' & 'www-data' to the group 'gitolite' - user: name={{ item }} groups=gitolite append=yes - with_items: - # for the cgit interface - - cgit - # for pulls over HTTP/HTTPS - - www-data +- name: Copy git-http-backend service unit + copy: src=etc/systemd/system/git-http-backend.service + dest=/etc/systemd/system/git-http-backend.service + owner=root group=root + mode=0644 + notify: + - systemctl daemon-reload + - Stop git-http-backend -- name: Generate a private key and a X.509 certificate for Nginx - command: genkeypair.sh x509 - --pubkey=/etc/nginx/ssl/git.fripost.org.pem - --privkey=/etc/nginx/ssl/git.fripost.org.key - --ou=WWW --cn=git.fripost.org --dns=git.fripost.org - -t rsa -b 4096 -h sha512 - register: r1 - changed_when: r1.rc == 0 - failed_when: r1.rc > 1 +- name: Copy git-http-backend socket unit + copy: src=etc/systemd/system/git-http-backend.socket + dest=/etc/systemd/system/git-http-backend.socket + owner=root group=root + mode=0644 notify: - - Restart Nginx - tags: - - genkey + - systemctl daemon-reload + - Restart git-http-backend + +- name: Disable git-http-backend service + service: name=git-http-backend.service enabled=false + +- name: Start git-http-backend socket + service: name=git-http-backend.socket state=started enabled=true + +- meta: flush_handlers + - name: Copy /etc/nginx/sites-available/git copy: src=etc/nginx/sites-available/git dest=/etc/nginx/sites-available/git owner=root group=root mode=0644 - register: r2 + register: r1 notify: - Restart Nginx - name: Create /etc/nginx/sites-enabled/git file: src=../sites-available/git dest=/etc/nginx/sites-enabled/git owner=root group=root state=link force=yes + register: r2 + notify: + - Restart Nginx + +- name: Copy HPKP header snippet + # never modify the pined pubkeys as we don't want to lock out our users + template: src=etc/nginx/snippets/git.fripost.org.hpkp-hdr.j2 + dest=/etc/nginx/snippets/git.fripost.org.hpkp-hdr + validate=/bin/false + owner=root group=root + mode=0644 register: r3 notify: - Restart Nginx - name: Start Nginx service: name=nginx state=started when: not (r1.changed or r2.changed or r3.changed) - meta: flush_handlers + +- name: Fetch Nginx's X.509 certificate + # Ensure we don't fetch private data + become: False + fetch_cmd: cmd="openssl x509 -noout -pubkey" + stdin=/etc/nginx/ssl/git.fripost.org.pem + dest=certs/public/git.fripost.org.pub + tags: + - genkey diff --git a/roles/git/tasks/gitolite.yml b/roles/git/tasks/gitolite.yml index 5cbce23..e7d1fe3 100644 --- a/roles/git/tasks/gitolite.yml +++ b/roles/git/tasks/gitolite.yml @@ -9,28 +9,28 @@ password=* state=present - name: Create directory ~gitolite/.ssh file: path=/var/lib/gitolite/.ssh state=directory owner=gitolite group=gitolite mode=0700 - name: Create /var/lib/gitolite/projects.list file: path=/var/lib/gitolite/projects.list owner=gitolite group=gitolite mode=0644 # See /usr/share/doc/gitolite3/README.txt.gz for gitolite initiation or # migration. # sudo -u gitolite gitolite setup -pk /path/to/id_rsa - name: Configure gitolite lineinfile: dest=/var/lib/gitolite/.gitolite.rc - "regexp=^(\\s*{{ item.var }}\\s*=>\\s*)" - "line= {{ item.var }} => {{ item.value }}," + regexp='^(\\s*{{ item.var }}\\s*=>\\s*)' + line=' {{ item.var }} => {{ item.value }},' owner=root group=root mode=0644 with_items: - # See /usr/share/doc/gitolite3/README.txt.gz - - { var: UMASK, value: "0027" } - - { var: GIT_CONFIG_KEYS, value: "'gitweb\\..* gc\\..*'" } + # See /usr/share/doc/gitolite3/README.markdown.gz + - { var: UMASK, value: "0027" } + - { var: GIT_CONFIG_KEYS, value: "'gitweb\\..* gc\\..* hook\\..*'" } diff --git a/roles/git/tasks/main.yml b/roles/git/tasks/main.yml index da9f876..f65824e 100644 --- a/roles/git/tasks/main.yml +++ b/roles/git/tasks/main.yml @@ -1,2 +1,4 @@ -- include: gitolite.yml tags=gitolite -- include: cgit.yml tags=cgit +- import_tasks: gitolite.yml + tags: gitolite +- import_tasks: cgit.yml + tags: cgit diff --git a/roles/git/templates/etc/nginx/snippets/git.fripost.org.hpkp-hdr.j2 b/roles/git/templates/etc/nginx/snippets/git.fripost.org.hpkp-hdr.j2 new file mode 120000 index 0000000..a8ba598 --- /dev/null +++ b/roles/git/templates/etc/nginx/snippets/git.fripost.org.hpkp-hdr.j2 @@ -0,0 +1 @@ +../../../../../../certs/hpkp-hdr.j2
\ No newline at end of file diff --git a/roles/lacme/files/etc/lacme/lacme.conf b/roles/lacme/files/etc/lacme/lacme.conf new file mode 100644 index 0000000..28633b6 --- /dev/null +++ b/roles/lacme/files/etc/lacme/lacme.conf @@ -0,0 +1,123 @@ +# For certificate issuance (newOrder command), specify a space-separated +# certificate configuration files or directories to use +# +#config-certs = lacme-certs.conf lacme-certs.conf.d/ + + +[client] + +# The value of "socket" specifies the path to the lacme-accountd(1) +# UNIX-domain socket to connect to for signature requests from the ACME +# client. lacme(8) aborts if the socket is readable or writable by +# other users, or if its parent directory is writable by other users. +# This setting is ignored when lacme-accountd(1) is spawned by lacme(8), +# since the two processes communicate through a socket pair. See the +# "accountd" section below for details. +# +#socket = %t/S.lacme + +# username to drop privileges to (setting both effective and real uid). +# Skip privilege drop if the value is empty (not recommended). +# +#user = _lacme-client + +# groupname to drop privileges to (setting both effective and real gid, +# and also setting the list of supplementary gids to that single group). +# Skip privilege drop if the value is empty (not recommended). +# +#group = nogroup + +# ACME client command. +# +#command = /usr/libexec/lacme/client + +# URI of the ACME server's directory. NOTE: Use the staging server +# <https://acme-staging-v02.api.letsencrypt.org/directory> for testing +# as it has relaxed rate-limiting. +# +#server = https://acme-v02.api.letsencrypt.org/directory + +# Timeout in seconds after which the client stops polling the ACME +# server and considers the request failed. +# +#timeout = 30 + +# Whether to verify the server certificate chain. +# +#SSL_verify = yes + +# Specify the version of the SSL protocol used to transmit data. +# +#SSL_version = SSLv23:!TLSv1_1:!TLSv1:!SSLv3:!SSLv2 + +# Specify the cipher list for the connection. +# +#SSL_cipher_list = EECDH+AESGCM:!MEDIUM:!LOW:!EXP:!aNULL:!eNULL + + +[webserver] + +# Comma- or space-separated list of addresses to listen on, for instance +# "0.0.0.0:80 [::]:80". +# +#listen = /run/lacme-www.socket + +# Directory under which an external HTTP daemon is configured to serve +# GET requests for challenge files under "/.well-known/acme-challenge/" +# (for each virtual host requiring authorization) as static files. +# NOTE: the directory must exist and be writable by the lacme client +# user. +# +#challenge-directory = + +# username to drop privileges to (setting both effective and real uid). +# Skip privilege drop if the value is empty (not recommended). +# +#user = _lacme-www + +# groupname to drop privileges to (setting both effective and real gid, +# and also setting the list of supplementary gids to that single group). +# Skip privilege drop if the value is empty (not recommended). +# +#group = nogroup + +# ACME webserver command. +# +#command = /usr/libexec/lacme/webserver + +# Whether to automatically install iptables(8) rules to open the +# ADDRESS[:PORT] specified with listen. Theses rules are automatically +# removed once lacme(8) exits. +# +#iptables = No + + +[accountd] +# lacme-accound(1) section. Comment out this section (including its +# header), or use the --socket= CLI option, to make lacme(8) connect to +# an existing lacme-accountd(1) process via a UNIX-domain socket. + +# username to drop privileges to (setting both effective and real uid). +# Skip privilege drop if the value is empty. +# +#user = + +# groupname to drop privileges to (setting both effective and real gid, +# and also setting the list of supplementary gids to that single group). +# Skip privilege drop if the value is empty. +# +#group = + +# lacme-accountd(1) command. +# +#command = /usr/bin/lacme-accountd + +# Path to the lacme-accountd(1) configuration file. +# +#config = + +# Be quiet. +# +#quiet = Yes + +; vim:ft=dosini diff --git a/roles/lacme/tasks/main.yml b/roles/lacme/tasks/main.yml new file mode 100644 index 0000000..b031b25 --- /dev/null +++ b/roles/lacme/tasks/main.yml @@ -0,0 +1,18 @@ +- name: Install lacme + apt: pkg={{ packages }} install_recommends=no + vars: + packages: + - liblwp-protocol-https-perl + - lacme + +- name: Copy lacme/lacme-certs.conf + copy: src=etc/lacme/lacme.conf + dest=/etc/lacme/lacme.conf + owner=root group=root + mode=0644 + +- name: Copy lacme/lacme-certs.conf + template: src=etc/lacme/lacme-certs.conf.j2 + dest=/etc/lacme/lacme-certs.conf + owner=root group=root + mode=0644 diff --git a/roles/lacme/templates/etc/lacme/lacme-certs.conf.j2 b/roles/lacme/templates/etc/lacme/lacme-certs.conf.j2 new file mode 100644 index 0000000..6694a0c --- /dev/null +++ b/roles/lacme/templates/etc/lacme/lacme-certs.conf.j2 @@ -0,0 +1,72 @@ +hash = sha512 +keyusage = digitalSignature, keyEncipherment + +{% if 'IMAP' in group_names %} +[imap] +certificate-key = /etc/dovecot/ssl/imap.fripost.org.key +certificate-chain = /etc/dovecot/ssl/imap.fripost.org.pem +subject = /O=Fripost/CN=imap.fripost.org +subjectAltName = DNS:imap.fripost.org,DNS:sieve.fripost.org +notify = /bin/systemctl reload dovecot +{% endif %} + +{% if 'MSA' in group_names %} +[smtp] +certificate-key = /etc/postfix-{{ postfix_instance.MSA.name }}/ssl/smtp.fripost.org.key +certificate-chain = /etc/postfix-{{ postfix_instance.MSA.name }}/ssl/smtp.fripost.org.pem +subject = /O=Fripost/CN=smtp.fripost.org +notify = /bin/systemctl reload postfix +{% endif %} + +{% if 'MX' in group_names %} +[mx] +certificate-key = /etc/postfix-{{ postfix_instance.MX.name }}/ssl/mx.fripost.org.key +certificate-chain = /etc/postfix-{{ postfix_instance.MX.name }}/ssl/mx.fripost.org.pem +subject = /O=Fripost/CN=mx{{ mxno }}.fripost.org +notify = /bin/systemctl reload postfix +{% endif %} + +{% if 'lists' in group_names %} +[lists] +certificate-key = /etc/nginx/ssl/lists.fripost.org.key +certificate-chain = /etc/nginx/ssl/lists.fripost.org.pem +subject = /O=Fripost/CN=lists.fripost.org +notify = /bin/systemctl reload nginx +{% endif %} + +{% if 'wiki' in group_names %} +[www] +certificate-key = /etc/nginx/ssl/www.fripost.org.key +certificate-chain = /etc/nginx/ssl/www.fripost.org.pem +subject = /O=Fripost/CN=fripost.org +subjectAltName = DNS:fripost.org,DNS:www.fripost.org,DNS:wiki.fripost.org +notify = /bin/systemctl reload nginx +{% endif %} + +{% if 'webmail' in group_names %} +[webmail] +certificate-key = /etc/nginx/ssl/mail.fripost.org.key +certificate-chain = /etc/nginx/ssl/mail.fripost.org.pem +subject = /O=Fripost/CN=mail.fripost.org +subjectAltName = DNS:mail.fripost.org,DNS:webmail.fripost.org +notify = /bin/systemctl reload nginx +{% endif %} + +{% if 'git' in group_names %} +[git] +certificate-key = /etc/nginx/ssl/git.fripost.org.key +certificate-chain = /etc/nginx/ssl/git.fripost.org.pem +subject = /O=Fripost/CN=git.fripost.org +notify = /bin/systemctl reload nginx +{% endif %} + +{% if 'nextcloud' in group_names %} +[cloud] +certificate-key = /etc/nginx/ssl/cloud.fripost.org.key +certificate-chain = /etc/nginx/ssl/cloud.fripost.org.pem +subject = /O=Fripost/CN=cloud.fripost.org +subjectAltName = DNS:cloud.fripost.org,DNS:www.cloud.fripost.org +notify = /bin/systemctl reload nginx +{% endif %} + +; vim:ft=dosini diff --git a/roles/lists/files/etc/nginx/sites-available/sympa b/roles/lists/files/etc/nginx/sites-available/sympa index fa3cad1..9b6aed2 100644 --- a/roles/lists/files/etc/nginx/sites-available/sympa +++ b/roles/lists/files/etc/nginx/sites-available/sympa @@ -1,65 +1,85 @@ server { listen 80; - listen [::]:80 ipv6only=on; + listen [::]:80; server_name lists.fripost.org; + include /etc/lacme/nginx.conf; + access_log /var/log/nginx/lists.access.log; error_log /var/log/nginx/lists.error.log info; - return 302 https://$host$request_uri; + location / { + return 301 https://$host$request_uri; + } } server { - listen 443; - listen [::]:443 ipv6only=on; + listen 443 ssl http2; + listen [::]:443 ssl http2; server_name lists.fripost.org; access_log /var/log/nginx/lists.access.log; error_log /var/log/nginx/lists.error.log info; - include ssl/config; - ssl_certificate /etc/nginx/ssl/lists.fripost.org.pem; - ssl_certificate_key /etc/nginx/ssl/lists.fripost.org.key; + include snippets/headers.conf; + add_header Content-Security-Policy + "default-src 'none'; connect-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self'; font-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri lists.fripost.org"; + + include snippets/ssl.conf; + ssl_certificate ssl/lists.fripost.org.pem; + ssl_certificate_key ssl/lists.fripost.org.key; + include snippets/lists.fripost.org.hpkp-hdr; + + gzip on; + gzip_vary on; + gzip_min_length 256; + gzip_types application/font-woff application/font-woff2 application/javascript application/json application/xml image/x-icon text/css text/plain; + + location = /robots.txt { + allow all; + log_not_found off; + access_log off; + alias /etc/sympa/robots.txt; + } location = / { return 302 /sympa$args; } - location ^~ /static-sympa/ { - alias /var/lib/sympa/static_content/; - } + location ^~ /static-sympa/ { expires 30d; try_files $uri =404; alias /usr/share/sympa/static_content/; } + location ^~ /css-sympa/ { expires 30d; try_files $uri =404; alias /var/lib/sympa/css/; } + location ^~ /pictures-sympa/ { expires 30d; try_files $uri =404; alias /var/lib/sympa/pictures; } + + location ~* ^/sympa(?:/|$) { + gzip off; # protect against BREACH - location ^~ /sympa { fastcgi_split_path_info ^(/sympa)(.*)$; - include fastcgi/params; - + include snippets/fastcgi.conf; + fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_pass unix:/run/wwsympa.socket; - gzip off; } location ~* ^/([^/]+)/?$ { return 302 /$1/sympa$args; } - location ~* ^/([^/]+)/sympa(/.*)?$ { - set $vhost $1; + location ~* ^/(?<vhost>[^/]+)/sympa(?:/|$) { + gzip off; # protect against BREACH if (!-f /etc/sympa/$vhost/robot.conf) { return 404; } - fastcgi_split_path_info ^(/[^/]+/sympa)(.*)$; - include fastcgi/params; - + fastcgi_split_path_info ^/[^/]+(/sympa)(.*)$; + include snippets/fastcgi.conf; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param SERVER_NAME $vhost; fastcgi_pass unix:/run/wwsympa.socket; - gzip off; - - fastcgi_param SERVER_NAME $vhost; } location / { return 404; } } diff --git a/roles/lists/files/etc/sympa/sympa.conf b/roles/lists/files/etc/sympa/sympa/sympa.conf index 5fe9405..a864a14 100644 --- a/roles/lists/files/etc/sympa/sympa.conf +++ b/roles/lists/files/etc/sympa/sympa/sympa.conf @@ -1,191 +1,191 @@ ###\\\\ Site customization ////### ## Main robot hostname -domain lists.fripost.org +domain lists.fripost.org ## Local part of sympa email address ## Effective address will be [EMAIL]@[HOST] email sympa ## Listmasters email list comma separated ## Sympa will associate listmaster privileges to these email addresses (mail and web interfaces). Some error reports may also be sent to these addresses. listmaster listmaster@fripost.org ## URL of main Web page -#wwsympa_url http://lists.fripost.org/sympa +wwsympa_url https://lists.fripost.org/sympa max_wrong_password 19 ## Directory for storing static contents (CSS, members pictures, documentation) directly delivered by Apache -static_content_path /var/lib/sympa/static_content +static_content_path /usr/share/sympa/static_content ## URL mapped with the static_content_path directory defined above static_content_url /static-sympa -css_url /static-sympa/css +css_url /css-sympa +css_path /var/lib/sympa/css + +pictures_path /var/lib/sympa/pictures +pictures_url /pictures-sympa ## Secret used by Sympa to make MD5 fingerprint in web cookies secure ## Should not be changed ! May invalid all user password -cookie `cat /etc/sympa/cookie` +cookie `/usr/bin/head -n1 /etc/sympa/cookie` ## Who is able to create lists ## This parameter is a scenario, check sympa documentation about scenarios if you want to define one create_list intranet ###\\\\ Directories ////### ## Directory containing mailing lists subdirectories home /var/lib/sympa/list_data ## Directory for configuration files; it also contains scenari/ and templates/ directories etc /etc/sympa ###\\\\ System related ////### ## Syslog facility for sympa ## Do not forget to edit syslog.conf -syslog `cat /etc/sympa/facility` +syslog LOCAL1 ## Log verbosity ## 0: normal, 2,3,4: for debug log_level 0 ## Communication mode with syslogd (unix | inet) log_socket_type unix ## Umask used for file creation by Sympa umask 027 ###\\\\ Sending related ////### ## Path to the MTA (sendmail, postfix, exim or qmail) ## should point to a sendmail-compatible binary (eg: a binary named "sendmail" is distributed with Postfix) sendmail /usr/sbin/sendmail sendmail_aliases none -distribution_mode fork - ## Max. number of Sendmail processes (launched by Sympa) running simultaneously ## Proposed value is quite low, you can rise it up to 100, 200 or even 300 with powerfull systems. maxsmtp 128 log_smtp off ## comma separated list of operations for which blacklist filter is applied ## Setting this parameter to "none" will hide the blacklist feature use_blacklist send,create_list ## Default maximum size (in bytes) for messages (can be re-defined for each list) max_size 5242880 ## Maximum number of recipients per call to Sendmail. The nrcpt_by_domain.conf file allows a different tuning per destination domain. nrcpt 25 ## Max. number of different domains per call to Sendmail avg 10 ## Specify which rfc2369 mailing list headers to add rfc2369_header_fields help,subscribe,unsubscribe,post,owner,archive ## Specify header fields to be removed before message distribution remove_headers X-Sympa-To,X-Family-To,Return-Receipt-To,Precedence,X-Sequence,Disposition-Notification-To ## Reject mail from automates (crontab, etc) sent to a list? reject_mail_from_automates_feature on alias_manager /bin/true ###\\\\ Bulk mailer ////### ## Default priority for a packet to be sent by bulk. sympa_packet_priority 5 ## Minimum number of packets in database before the bulk forks to increase sending rate -## +## bulk_fork_threshold 1 ## Max number of bulks that will run on the same server -## +## bulk_max_count 3 ## The number of seconds a slave bulk will remain running without processing a message before it spontaneously dies. -## +## bulk_lazytime 600 ## The number of seconds a bulk sleeps between starting a new loop if it didn't find a message to send. ## Keep it small if you want your server to be reactive. bulk_sleep 1 ## Number of seconds a master bulk waits between two packets number checks. ## Keep it small if you expect brutal increases in the message sending load. bulk_wait_to_fork 10 ###\\\\ Quotas ////### ###\\\\ Spool related ////### ## Directory containing various specialized spools ## All spool are created at runtime by sympa.pl spool /var/spool/sympa ## Directory for incoming spool queue /var/spool/sympa/msg -queuedistribute /var/spool/sympa/distribute - ## Directory for moderation spool queuemod /var/spool/sympa/moderation ## Directory for digest spool queuedigest /var/spool/sympa/digest ## Directory for authentication spool queueauth /var/spool/sympa/auth ## Directory for outgoing spool queueoutgoing /var/spool/sympa/outgoing ## Directory for subscription spool queuesubscribe /var/spool/sympa/subscribe ## Directory for topic spool queuetopic /var/spool/sympa/topic ## Directory for bounce incoming spool queuebounce /var/spool/sympa/bounce ## Directory for task spool queuetask /var/spool/sympa/task ## Directory for automatic list creation spool queueautomatic /var/spool/sympa/automatic ###\\\\ Internationalization related ////### ## Supported languages ## This is the set of language that will be proposed to your users for the Sympa GUI. Don't select a language if you don't have the proper locale packages installed. -supported_lang sv,en_US +supported_lang sv,en_US,fr ## Default language (one of supported languages) ## This is the default language used by Sympa -lang sv +lang sv ## If set to "on", enables support of legacy character set ## In some language environments, legacy encoding (character set) is preferred for e-mail messages: for example iso-2022-jp in Japanese language. legacy_character_support_feature off ###\\\\ Bounce related ////### verp_rate 100% #tracking_delivery_status_notification on #tracking_message_disposition_notification on ## Welcome message return-path ( unique | owner ) ## If set to unique, new subcriber is removed if welcome message bounce welcome_return_path unique ## Remind message return-path ( unique | owner ) ## If set to unique, subcriber is removed if remind message bounce, use with care remind_return_path owner ## Task name for expiration of old bounces @@ -244,80 +244,154 @@ db_user sympa ## Database private extention to user table ## You need to extend the database format with these fields #db_additional_user_fields age,address ## Number of months that elapse before a log is expired logs_expiration_period 3 ## Default timeout between two scheduled synchronizations of list members with data sources. default_ttl 3600 ## Default timeout between two action-triggered synchronizations of list members with data sources. default_distribution_ttl 300 ## Default timeout while performing a fetch for an include_sql_query sync default_sql_fetch_timeout 300 ###\\\\ Loop prevention ////### ###\\\\ S/MIME configuration ////### -## Path to OpenSSL -## Sympa recognizes S/MIME if OpenSSL is installed -#openssl /usr/bin/ssl - ## Directory containing trusted CA certificates #capath /etc/sympa/ssl.crt ## File containing bundled trusted CA certificates #cafile /usr/local/apache/conf/ssl.crt/ca-bundle.crt -crl_dir /var/lib/sympa/list_data/crl - ## Directory containing user certificates ssl_cert_dir /var/lib/sympa/list_data/X509-user-certs ## Password used to crypt lists private keys #key_passwd your_password ###\\\\ DKIM ////### dkim_feature off ## Insert a DKIM signature to message from the robot, from the list or both dkim_add_signature_to robot,list ## Type of message that is added a DKIM signature before distribution to subscribers. Possible values are "none", "any" or a list of the following keywords: "md5_authenticated_messages", "smime_authenticated_messages", "dkim_authenticated_messages", "editor_validated_messages". dkim_signature_apply_on md5_authenticated_messages,smime_authenticated_messages,dkim_authenticated_messages,editor_validated_messages +## DMARC protection +## https://sympa-community.github.io/manual/customize/dmarc-protection.html +dmarc_protection_mode all + ###\\\\ Antivirus plug-in ////### ## Path to the antivirus scanner engine ## Supported antivirus: McAfee/uvscan, Fsecure/fsav, Sophos, AVP and Trend Micro/VirusWall #antivirus_path /usr/local/uvscan/uvscan ## Antivirus plugin command argument #antivirus_args --secure --summary --dat /usr/local/uvscan ###\\\\ Tag based spam filtering ////### ## If a spam filter (like spamassassin or j-chkmail) add a smtp headers to tag spams, name of this header (example X-Spam-Status) antispam_tag_header_name X-Spam-Status ## Regexp applied on this header to verify message is a spam (example \s*Yes) antispam_tag_header_spam_regexp ^\s*Yes ## Regexp applied on this header to verify message is NOT a spam (example \s*No) antispam_tag_header_ham_regexp ^\s*No ## Messages are supposed to be filtered by an antispam that add one more headers to messages. This parameter is used to select a special scenario in order to decide the message spam status: ham, spam or unsure. This parameter replace antispam_tag_header_name, antispam_tag_header_spam_regexp and antispam_tag_header_ham_regexp. spam_status x-spam-status ###\\\\ Web interface parameters ////### edit_list owner ## URL of a virtual host -#http_host http://fripost.org +#http_host https://fripost.org ## The password validation techniques to be used against user passwords that are added to mailing lists. Options come from Data::Password (http://search.cpan.org/~razinf/Data-Password-1.07/Password.pm#VARIABLES) #password_validation MINLEN=8,GROUPS=3,DICTIONARY=4,DICTIONARIES=/pentest/dictionaries + +## Directory for storing HTML archives +## Better if not in a critical partition +arc_path /var/lib/sympa/wwsarchive + +## Default index organization when entering the web archive: either threaded +## or in chronological order +archive_default_index thrd + +## Directory for storing bounces +## Better if not in a critical partition +bounce_path /var/spool/sympa/wwsbounce + +## HTTP cookies validity domain +cookie_domain lists.fripost.org + +## HTTP cookies lifetime +cookie_expire 0 + +## Average interval to refresh HTTP session ID. +cookie_refresh 60 + +## Activates a custom archiver to use instead of MHonArc. The value of this +## parameter is the absolute path on the file system to the script of the +## custom archiver. +#custom_archiver + +## Type of main Web page ( lists | home ) +default_home home + +## Javascript excerpt that enables and configures the WYSIWYG HTML editor. +#html_editor_init + +#htmlarea_url + +## When using LDAP authentication, if the identifier provided by the user was +## a valid email, if this parameter is set to false, then the provided email +## will be used to authenticate the user. Otherwise, use of the first email +## returned by the LDAP server will be used. +ldap_force_canonical_email 1 + +#log_condition + +## Syslog facility for wwsympa, archived and bounced +## Default is to use previously defined sympa log facility. +log_facility LOCAL1 + +#log_module + +## Path to MHonArc mail2html plugin +## This is required for HTML mail archiving +mhonarc /usr/bin/mhonarc + +## Password case (insensitive | sensitive) +## Should not be changed ! May invalid all user password +password_case insensitive + +## Default number of lines of the array displaying users in the review page +review_page_size 25 + +## Title of main Web page +title Mailing lists service + +## If set to "on", users will be able to post messages in HTML using a +## javascript WYSIWYG editor. +use_html_editor 0 + +## Is fast_cgi module for Apache (or Roxen) installed (0 | 1) +## This module provide much faster web interface +use_fast_cgi 1 + +## Default number of lines of the array displaying the log entries in the logs +## page +viewlogs_page_size 25 + +shared_feature on diff --git a/roles/lists/files/etc/sympa/wwsympa.conf b/roles/lists/files/etc/sympa/wwsympa.conf deleted file mode 100644 index 4d420a3..0000000 --- a/roles/lists/files/etc/sympa/wwsympa.conf +++ /dev/null @@ -1,85 +0,0 @@ -###\\\\ Site customization ////### - -###\\\\ Directories ////### - -###\\\\ System related ////### - -###\\\\ Sending related ////### - -###\\\\ Bulk mailer ////### - -###\\\\ Quotas ////### - -###\\\\ Spool related ////### - -###\\\\ Internationalization related ////### - -###\\\\ Bounce related ////### - -## Directory for storing bounces -## Better if not in a critical partition -bounce_path /var/spool/sympa/wwsbounce - -###\\\\ Tuning ////### - -###\\\\ Database related ////### - -###\\\\ Loop prevention ////### - -###\\\\ S/MIME configuration ////### - -###\\\\ DKIM ////### - -###\\\\ Antivirus plug-in ////### - -###\\\\ Tag based spam filtering ////### - -###\\\\ Web interface parameters ////### - -## Directory for storing HTML archives -## Better if not in a critical partition -arc_path /var/lib/sympa/wwsarchive - -## Default index organization when entering the web archive: either threaded (thrd) or in chronological (mail) order -archive_default_index thrd - -## HTTP cookies lifetime -cookie_expire 0 - -## HTTP cookies validity domain -cookie_domain localhost - -## Average interval to refresh HTTP session ID. -cookie_refresh 60 - -## Type of main Web page ( lists | home ) -default_home home - -## When using LDAP authentication, if the identifier provided by the user was a valid email, if this parameter is set to false, then the provided email will be used to authenticate the user. Otherwise, use of the first email returned by the LDAP server will be used. -ldap_force_canonical_email 1 - -## Syslog facility for wwsympa, archived and bounced -## Default is to use previously defined sympa log facility. -log_facility `cat /etc/sympa/facility` - -## Path to MHonArc mail2html plugin -## This is required for HTML mail archiving -mhonarc /usr/bin/mhonarc - -## Password case (insensitive | sensitive) -## Should not be changed ! May invalid all user password -password_case insensitive - -## Default number of lines of the array displaying users in the review page -review_page_size 25 - -## Title of main Web page -title Mailing lists service - -## Is fast_cgi module for Apache (or Roxen) installed (0 | 1) -## This module provide much faster web interface -use_fast_cgi 1 - -## Default number of lines of the array displaying the log entries in the logs page -viewlogs_page_size 25 - diff --git a/roles/lists/files/etc/systemd/system/wwsympa.service b/roles/lists/files/etc/systemd/system/wwsympa.service new file mode 100644 index 0000000..cff2db7 --- /dev/null +++ b/roles/lists/files/etc/systemd/system/wwsympa.service @@ -0,0 +1,30 @@ +[Unit] +Description=WWSympa Service +After=network.target +PartOf=sympa.service +Requires=wwsympa.socket + +[Service] +StandardInput=socket +User=sympa +Group=sympa +ExecStart=/usr/lib/cgi-bin/sympa/wwsympa.fcgi + +# Hardening +NoNewPrivileges=yes +ReadWriteDirectories=/etc/sympa +ReadWriteDirectories=/var/lib/sympa +ReadWriteDirectories=/var/spool/sympa +ReadWriteDirectories=/run/sympa +PrivateDevices=yes +PrivateNetwork=yes +ProtectHome=yes +ProtectSystem=strict +PrivateTmp=yes +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +RestrictAddressFamilies= + +[Install] +WantedBy=multi-user.target diff --git a/roles/lists/files/lib/systemd/system/wwsympa.socket b/roles/lists/files/etc/systemd/system/wwsympa.socket index 10fe721..10fe721 100644 --- a/roles/lists/files/lib/systemd/system/wwsympa.socket +++ b/roles/lists/files/etc/systemd/system/wwsympa.socket diff --git a/roles/lists/files/lib/systemd/system/wwsympa.service b/roles/lists/files/lib/systemd/system/wwsympa.service deleted file mode 100644 index ea2a78f..0000000 --- a/roles/lists/files/lib/systemd/system/wwsympa.service +++ /dev/null @@ -1,14 +0,0 @@ -[Unit] -Description=WWSympa Service -After=network.target -PartOf=sympa.service -Requires=wwsympa.socket - -[Service] -StandardInput=socket -User=sympa -Group=sympa -ExecStart=/usr/lib/cgi-bin/sympa/wwsympa.fcgi - -[Install] -WantedBy=multi-user.target diff --git a/roles/lists/tasks/mail.yml b/roles/lists/tasks/mail.yml index 85d3103..2821b02 100644 --- a/roles/lists/tasks/mail.yml +++ b/roles/lists/tasks/mail.yml @@ -1,73 +1,62 @@ - name: Install Postfix - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - postfix - - postfix-ldap + - postfix-lmdb - name: Configure Postfix - template: src=etc/postfix/main.cf.j2 - dest=/etc/postfix-{{ postfix_instance[inst].name }}/main.cf + template: src=etc/postfix/{{ item }}.j2 + dest=/etc/postfix-{{ postfix_instance[inst].name }}/{{ item }} owner=root group=root mode=0644 + with_items: + - main.cf + - master.cf notify: - Reload Postfix - name: Copy the transport maps copy: src=etc/postfix/transport dest=/etc/postfix-{{ postfix_instance[inst].name }}/transport owner=root group=root mode=0644 # no need to reload upon change, as cleanup(8) is short-running -- 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 - tags: - - tls_policy - -- 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 - tags: - - tls_policy - - name: Compile the Postfix transport maps # trivial-rewrite(8) is a long-running process, so it's safer to reload - postmap: cmd=postmap src=/etc/postfix-{{ postfix_instance[inst].name }}/transport db=cdb + postmap: cmd=postmap src=/etc/postfix-{{ postfix_instance[inst].name }}/transport db=lmdb owner=root group=root mode=0644 notify: - Reload Postfix - meta: flush_handlers - name: Start Postfix service: name=postfix state=started - name: Copy the 'sympa-queue' wrapper copy: src=usr/local/bin/sympa-queue dest=/usr/local/bin/sympa-queue - owner=root group=root + owner=root group=staff mode=0755 - name: Install 'postfix_mailqueue_' Munin wildcard plugin file: src=/usr/local/share/munin/plugins/postfix_mailqueue_ dest=/etc/munin/plugins/postfix_mailqueue_postfix-{{ postfix_instance[inst].name }} owner=root group=root state=link force=yes tags: - munin - munin-node notify: - Restart munin-node - name: Install 'postfix_stats_' Munin wildcard plugin file: src=/usr/local/share/munin/plugins/postfix_stats_ dest=/etc/munin/plugins/postfix_stats_{{ item }}_postfix-{{ postfix_instance[inst].name }} owner=root group=root state=link force=yes with_items: diff --git a/roles/lists/tasks/main.yml b/roles/lists/tasks/main.yml index f0e8e26..6d53d82 100644 --- a/roles/lists/tasks/main.yml +++ b/roles/lists/tasks/main.yml @@ -1,3 +1,13 @@ -- include: mail.yml tags=postfix,mail -- include: nginx.yml tags=nginx,www,web -- include: sympa.yml tags=sympa,lists +- import_tasks: mail.yml + tags: + - postfix + - mail +- import_tasks: nginx.yml + tags: + - nginx + - www + - web +- import_tasks: sympa.yml + tags: + - sympa + - lists diff --git a/roles/lists/tasks/mlmmj.yml b/roles/lists/tasks/mlmmj.yml index e5e029f..aeaaedc 100644 --- a/roles/lists/tasks/mlmmj.yml +++ b/roles/lists/tasks/mlmmj.yml @@ -1,59 +1,60 @@ - name: Install MLMMJ - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - mlmmj # Weird the debian package doesn't do it by itself... - name: Create a user 'mlmmj' user: name=mlmmj system=yes createhome=no home=/var/spool/mlmmj shell=/usr/sbin/nologin password=! state=present - name: Add 'www-data' to the group 'mlmmj' user: name=www-data groups=mlmmj append=yes - name: Create a home directory for user 'mlmmj' file: path=/var/spool/mlmmj state=directory owner=mlmmj group=mlmmj mode=0700 - name: Create /var/lib/mlmmj file: path=/var/lib/mlmmj state=directory owner=mlmmj group=mlmmj mode=0750 - name: Auto-maintain mlmmj's spool directory copy: src=etc/cron.d/mlmmj dest=/etc/cron.d/mlmmj owner=root group=root mode=0644 - name: Copy mlmmj-newlist.sh and mhonarc-scan.sh copy: src=usr/local/bin/{{ item }} dest=/usr/local/bin/{{ item }} - owner=root group=root + owner=root group=staff mode=0755 with_items: - mlmmj-newlist.sh - mhonarc-scan.sh - name: Copy /etc/mhonarc.rc copy: src=etc/mhonarc.rc dest=/etc/mhonarc.rc owner=root group=root mode=0644 - name: Create /usr/share/mlmmj/static/{css,fonts} file: path=/usr/share/mlmmj/static/{{ item }} state=directory owner=root group=root mode=0755 with_items: - css - fonts diff --git a/roles/lists/tasks/nginx.yml b/roles/lists/tasks/nginx.yml index a0aab68..bbff34a 100644 --- a/roles/lists/tasks/nginx.yml +++ b/roles/lists/tasks/nginx.yml @@ -1,40 +1,46 @@ - name: Install Nginx - apt: pkg=nginx - -- name: Generate a private key and a X.509 certificate for Nginx - command: genkeypair.sh x509 - --pubkey=/etc/nginx/ssl/lists.fripost.org.pem - --privkey=/etc/nginx/ssl/lists.fripost.org.key - --ou=WWW --cn=lists.fripost.org --dns=lists.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 + apt: pkg=nginx-light - name: Copy /etc/nginx/sites-available/sympa copy: src=etc/nginx/sites-available/sympa dest=/etc/nginx/sites-available/sympa owner=root group=root mode=0644 - register: r2 + register: r1 notify: - Restart Nginx - name: Create /etc/nginx/sites-enabled/sympa file: src=../sites-available/sympa dest=/etc/nginx/sites-enabled/sympa owner=root group=root state=link + register: r2 + notify: + - Restart Nginx + +- name: Copy HPKP header snippet + # never modify the pined pubkeys as we don't want to lock out our users + template: src=etc/nginx/snippets/lists.fripost.org.hpkp-hdr.j2 + dest=/etc/nginx/snippets/lists.fripost.org.hpkp-hdr + validate=/bin/false + owner=root group=root + mode=0644 register: r3 notify: - Restart Nginx - name: Start nginx service: name=nginx state=started when: not (r1.changed or r2.changed or r3.changed) - meta: flush_handlers + +- name: Fetch Nginx's X.509 certificate + # Ensure we don't fetch private data + become: False + fetch_cmd: cmd="openssl x509 -noout -pubkey" + stdin=/etc/nginx/ssl/lists.fripost.org.pem + dest=certs/public/lists.fripost.org.pub + tags: + - genkey diff --git a/roles/lists/tasks/sympa.yml b/roles/lists/tasks/sympa.yml index 00e5d9c..27a5823 100644 --- a/roles/lists/tasks/sympa.yml +++ b/roles/lists/tasks/sympa.yml @@ -1,95 +1,88 @@ -- apt: pkg={{ item }} install_recommends=no - with_items: - - mysql-server +- apt: pkg={{ packages }} install_recommends=no + vars: + packages: + - mariadb-server - sympa + - libnet-dns-perl + - libnet-dns-sec-perl + - libmail-dkim-perl + - libcrypt-smime-perl + - libcrypt-openssl-x509-perl -- name: Make the 'sympa' MySQL user use auth_socket - mysql_user2: name=sympa password= auth_plugin=auth_socket - state=present +- name: Make the 'sympa' MySQL user use unix_socket + mysql_user: name=sympa password= plugin=unix_socket + state=present -# XXX We want to change the retun-path for sendpasswd notices from -# 'sympa-request@$robot' to 'noreply@fripost.org'. -# * /usr/lib/cgi-bin/sympa/wwsympa.fcgi -# do_requestpasswd, do_subrequest: add $param->{'return_path'}='noreply@fripost.org'; -# * List::send_global_file -# $data->{'return_path'} //= &Conf::get_robot_conf($robot, 'request'); -# See #787946. - name: Configure Sympa copy: src=etc/sympa/{{ item }} dest=/etc/sympa/{{ item }} owner=root group=sympa mode=0644 with_items: - - sympa.conf - - wwsympa.conf + - sympa/sympa.conf - topics.conf register: r1 notify: - Restart Sympa - name: Create Virtual hosts for Sympa (1) file: path=/etc/sympa/{{ item }} state=directory - owner=root group=root + owner=sympa group=sympa mode=0755 with_items: - lists.fripost.org register: r2 notify: - Restart Sympa - name: Create Virtual hosts for Sympa (2) file: path=/var/lib/sympa/list_data/{{ item }} state=directory owner=sympa group=sympa mode=0770 with_items: - lists.fripost.org register: r3 notify: - Restart Sympa - name: Install robot.conf template: src=etc/sympa/robot.conf.j2 dest=/etc/sympa/{{ item }}/robot.conf - owner=root group=root - mode=0644 + owner=sympa group=sympa + mode=0640 with_items: - lists.fripost.org register: r4 notify: - Restart Sympa -# sympa uses to syslog, there is no need to reload or restart it when -# rotating logs -- name: Don't restart sympa on logrotate - lineinfile: "dest=/etc/logrotate.d/sympa state=absent - regexp='\\bsympa reload\\b'" - tags: - - logrotate +- name: Enable Sympa + service: name=sympa enabled=yes - name: Start Sympa service: name=sympa state=started when: not (r1.changed or r2.changed or r3.changed or r4.changed) - meta: flush_handlers - name: Copy wwsympa.{service,socket} - copy: src=lib/systemd/system/{{ item }} - dest=/lib/systemd/system/{{ item }} + copy: src=etc/systemd/system/{{ item }} + dest=/etc/systemd/system/{{ item }} owner=root group=root mode=0644 notify: - systemctl daemon-reload - Restart wwsympa with_items: - wwsympa.service - wwsympa.socket - meta: flush_handlers - name: Enable WWSympa service: name=wwsympa enabled=yes - name: Start WWSympa service: name=wwsympa state=started diff --git a/roles/lists/templates/etc/nginx/snippets/lists.fripost.org.hpkp-hdr.j2 b/roles/lists/templates/etc/nginx/snippets/lists.fripost.org.hpkp-hdr.j2 new file mode 120000 index 0000000..a8ba598 --- /dev/null +++ b/roles/lists/templates/etc/nginx/snippets/lists.fripost.org.hpkp-hdr.j2 @@ -0,0 +1 @@ +../../../../../../certs/hpkp-hdr.j2
\ No newline at end of file diff --git a/roles/lists/templates/etc/postfix/main.cf.j2 b/roles/lists/templates/etc/postfix/main.cf.j2 index b314d95..2be1b41 100644 --- a/roles/lists/templates/etc/postfix/main.cf.j2 +++ b/roles/lists/templates/etc/postfix/main.cf.j2 @@ -1,97 +1,85 @@ ######################################################################## # Sympa 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 delay_warning_time = 4h maximal_queue_lifetime = 5d myorigin = /etc/mailname myhostname = lists.$mydomain mydomain = fripost.org append_dot_mydomain = no -# Turn off all TCP/IP listener ports except that necessary for Sympa -master_service_disable = !2527.inet inet +mynetworks = 127.0.0.0/8, [::1]/128 +{%- if groups.all | length > 1 -%} +{%- for mx in groups.MX | sort -%} + , {{ ipsec[ hostvars[mx].inventory_hostname_short ] | ansible.utils.ipaddr }} +{%- endfor %} +{% endif %} 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 = all - # No local delivery mydestination = local_transport = error:5.1.1 Mailbox unavailable alias_maps = alias_database = local_recipient_maps = -message_size_limit = 67108864 +message_size_limit = 0 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 relay_domains = sympa.$mydomain -transport_maps = cdb:$config_directory/transport +transport_maps = lmdb:$config_directory/transport sympa_destination_recipient_limit = 1 # Don't rewrite remote headers local_header_rewrite_clients = - -relay_clientcerts = cdb:$config_directory/relay_clientcerts -smtpd_tls_security_level = may -smtpd_tls_exclude_ciphers = EXPORT, LOW, MEDIUM, aNULL, eNULL, DES, RC4, MD5 -smtpd_tls_cert_file = /etc/postfix/ssl/{{ ansible_fqdn }}.pem -smtpd_tls_key_file = /etc/postfix/ssl/{{ ansible_fqdn }}.key -smtpd_tls_dh1024_param_file = /etc/ssl/private/dhparams.pem -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 - +smtp_tls_security_level = none +smtpd_tls_security_level = none 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 smtpd_relay_restrictions = reject_non_fqdn_recipient permit_mynetworks - permit_tls_clientcerts reject smtpd_data_restrictions = reject_unauth_pipelining # vim: set filetype=pfmain : diff --git a/roles/lists/templates/etc/postfix/master.cf.j2 b/roles/lists/templates/etc/postfix/master.cf.j2 new file mode 120000 index 0000000..011f8e0 --- /dev/null +++ b/roles/lists/templates/etc/postfix/master.cf.j2 @@ -0,0 +1 @@ +../../../../common/templates/etc/postfix/master.cf.j2
\ No newline at end of file diff --git a/roles/lists/templates/etc/postfix/relay_clientcerts.j2 b/roles/lists/templates/etc/postfix/relay_clientcerts.j2 deleted file mode 100644 index 42a83b5..0000000 --- a/roles/lists/templates/etc/postfix/relay_clientcerts.j2 +++ /dev/null @@ -1,6 +0,0 @@ -# {{ ansible_managed }} -# /!\ WARNING: smtp_tls_fingerprint_digest MUST be sha256! - -{% for h in groups.MX | difference([inventory_hostname]) | sort %} -{{ lookup('pipe', 'openssl x509 -in certs/postfix/'+h+'.pem -noout -fingerprint -sha256 | cut -d= -f2') }} {{ h }} -{% endfor %} diff --git a/roles/lists/templates/etc/sympa/robot.conf.j2 b/roles/lists/templates/etc/sympa/robot.conf.j2 index 75687d8..28998e3 100644 --- a/roles/lists/templates/etc/sympa/robot.conf.j2 +++ b/roles/lists/templates/etc/sympa/robot.conf.j2 @@ -1,3 +1,2 @@ -http_host {{ item }} +#wwsympa_url_local https://{{ item }}/sympa wwsympa_url https://{{ item }}/sympa -# wwsympa_url https://lists.fripost.org/{{ item }}/sympa diff --git a/roles/munin-master/files/etc/nginx/sites-available/munin b/roles/munin-master/files/etc/nginx/sites-available/munin index ade1888..2f681fb 100644 --- a/roles/munin-master/files/etc/nginx/sites-available/munin +++ b/roles/munin-master/files/etc/nginx/sites-available/munin @@ -1,31 +1,37 @@ server { listen 127.0.0.1:80; listen [::1]:80; server_name munin.fripost.org; + allow 127.0.0.0/8; + allow ::1/128; + deny all; + access_log /var/log/nginx/munin.access.log; error_log /var/log/nginx/munin.error.log info; + include snippets/headers.conf; + location = / { return 302 /munin$args; } location /munin/static/ { alias /etc/munin/static/; } location /munin-cgi/munin-cgi-graph/ { fastcgi_split_path_info ^(/munin-cgi/munin-cgi-graph)(.*); - include fastcgi/params; + fastcgi_param PATH_INFO $fastcgi_path_info; + include snippets/fastcgi.conf; fastcgi_pass unix:/run/munin/cgi-graph.socket; - gzip off; } location /munin/ { fastcgi_split_path_info ^(/munin)(.*); - include fastcgi/params; + fastcgi_param PATH_INFO $fastcgi_path_info; + include snippets/fastcgi.conf; fastcgi_pass unix:/run/munin/cgi-html.socket; - gzip off; } } diff --git a/roles/munin-master/files/etc/systemd/system/munin-cgi-graph.service b/roles/munin-master/files/etc/systemd/system/munin-cgi-graph.service new file mode 100644 index 0000000..b8e6012 --- /dev/null +++ b/roles/munin-master/files/etc/systemd/system/munin-cgi-graph.service @@ -0,0 +1,28 @@ +[Unit] +Description=Munin CGI Graph Service +After=network.target +PartOf=munin.service +Requires=munin-cgi-graph.socket + +[Service] +StandardInput=socket +User=www-data +Group=munin +ExecStart=/usr/lib/munin/cgi/munin-cgi-graph + +# Hardening +NoNewPrivileges=yes +PrivateDevices=yes +ProtectHome=yes +ProtectSystem=strict +ReadWriteDirectories=-/var/log/munin +ReadWriteDirectories=-/var/lib/munin/cgi-tmp/munin-cgi-graph +PrivateDevices=yes +PrivateNetwork=yes +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +RestrictAddressFamilies= + +[Install] +WantedBy=multi-user.target diff --git a/roles/munin-master/files/lib/systemd/system/munin-cgi-graph.socket b/roles/munin-master/files/etc/systemd/system/munin-cgi-graph.socket index d4d2e27..d4d2e27 100644 --- a/roles/munin-master/files/lib/systemd/system/munin-cgi-graph.socket +++ b/roles/munin-master/files/etc/systemd/system/munin-cgi-graph.socket diff --git a/roles/munin-master/files/etc/systemd/system/munin-cgi-html.service b/roles/munin-master/files/etc/systemd/system/munin-cgi-html.service new file mode 100644 index 0000000..0e66b3f --- /dev/null +++ b/roles/munin-master/files/etc/systemd/system/munin-cgi-html.service @@ -0,0 +1,27 @@ +[Unit] +Description=Munin CGI HTML Service +After=network.target +PartOf=munin.service +Requires=munin-cgi-html.socket + +[Service] +StandardInput=socket +User=www-data +Group=munin +ExecStart=/usr/lib/munin/cgi/munin-cgi-html + +# Hardening +NoNewPrivileges=yes +PrivateDevices=yes +ProtectHome=yes +ProtectSystem=strict +ReadWriteDirectories=-/var/log/munin +PrivateDevices=yes +PrivateNetwork=yes +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +RestrictAddressFamilies= + +[Install] +WantedBy=multi-user.target diff --git a/roles/munin-master/files/lib/systemd/system/munin-cgi-html.socket b/roles/munin-master/files/etc/systemd/system/munin-cgi-html.socket index 77be2cf..77be2cf 100644 --- a/roles/munin-master/files/lib/systemd/system/munin-cgi-html.socket +++ b/roles/munin-master/files/etc/systemd/system/munin-cgi-html.socket diff --git a/roles/munin-master/files/lib/systemd/system/munin-cgi-graph.service b/roles/munin-master/files/lib/systemd/system/munin-cgi-graph.service deleted file mode 100644 index 9e4d820..0000000 --- a/roles/munin-master/files/lib/systemd/system/munin-cgi-graph.service +++ /dev/null @@ -1,14 +0,0 @@ -[Unit] -Description=Munin CGI Graph Service -After=network.target -PartOf=munin.service -Requires=munin-cgi-graph.socket - -[Service] -StandardInput=socket -User=www-data -Group=munin -ExecStart=/usr/lib/munin/cgi/munin-cgi-graph - -[Install] -WantedBy=multi-user.target diff --git a/roles/munin-master/files/lib/systemd/system/munin-cgi-html.service b/roles/munin-master/files/lib/systemd/system/munin-cgi-html.service deleted file mode 100644 index 11a7470..0000000 --- a/roles/munin-master/files/lib/systemd/system/munin-cgi-html.service +++ /dev/null @@ -1,14 +0,0 @@ -[Unit] -Description=Munin CGI HTML Service -After=network.target -PartOf=munin.service -Requires=munin-cgi-html.socket - -[Service] -StandardInput=socket -User=www-data -Group=munin -ExecStart=/usr/lib/munin/cgi/munin-cgi-html - -[Install] -WantedBy=multi-user.target diff --git a/roles/munin-master/handlers/main.yml b/roles/munin-master/handlers/main.yml index 4c41033..518a875 100644 --- a/roles/munin-master/handlers/main.yml +++ b/roles/munin-master/handlers/main.yml @@ -2,23 +2,20 @@ - name: systemctl daemon-reload command: /bin/systemctl daemon-reload - name: Restart rrdcached service: name=rrdcached state=restarted - name: Restart munin service: name=munin state=restarted - name: Restart munin-node service: name=munin-node state=restarted - name: Restart munin-cgi-graph service: name=munin-cgi-graph state=restarted - name: Restart munin-cgi-html service: name=munin-cgi-html state=restarted - name: Restart Nginx service: name=nginx state=restarted - -- name: Restart stunnel - service: name=stunnel4 pattern=/usr/bin/stunnel4 state=restarted diff --git a/roles/munin-master/tasks/main.yml b/roles/munin-master/tasks/main.yml index 5dd1151..6dad93b 100644 --- a/roles/munin-master/tasks/main.yml +++ b/roles/munin-master/tasks/main.yml @@ -1,74 +1,87 @@ - name: Install munin - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - munin - rrdcached - libcgi-fast-perl - name: Configure rrdcached lineinfile: "dest=/etc/default/rrdcached - regexp='^#?OPTS=' - line='OPTS=\"-s munin -m 660 -l unix:/var/run/rrdcached.sock -w 1800 -z 1800 -f 3600 -j /var/lib/rrdcached/journal -F -b /var/lib/munin -B\"'" + regexp='^#?{{ item.name }}=' + line='{{ item.name }}=\"{{ item.value }}\"'" + with_items: + - { name: 'BASE_OPTIONS', value: '-B -F' } + - { name: 'BASE_PATH', value: '/var/lib/munin' } + - { name: 'SOCKFILE', value: '/run/rrdcached.sock' } + - { name: 'SOCKGROUP', value: 'munin' } + - { name: 'SOCKMODE', value: '0660' } + - { name: 'WRITE_TIMEOUT', value: '1800' } register: r notify: - Restart rrdcached - name: Start rrdcached service: name=rrdcached state=started when: not r.changed - meta: flush_handlers +- name: Create directory /var/lib/munin/cgi-tmp/munin-cgi-graph + file: path=/var/lib/munin/cgi-tmp/munin-cgi-graph + state=directory + owner=www-data group=www-data + mode=0755 - name: Configure munin template: src=etc/munin/munin.conf.j2 dest=/etc/munin/munin.conf owner=root group=root mode=0644 notify: - Restart munin-cgi-graph - Restart munin-cgi-html - name: chown www-data:adm /var/log/munin/munin-cgi-{graph,html}.log file: path=/var/log/munin/{{ item }} owner=www-data group=adm mode=0640 with_items: - munin-cgi-graph.log - munin-cgi-html.log - name: Copy munin-cgi-graph.{service,socket} - copy: src=lib/systemd/system/{{ item }} - dest=/lib/systemd/system/{{ item }} + copy: src=etc/systemd/system/{{ item }} + dest=/etc/systemd/system/{{ item }} owner=root group=root mode=0644 notify: - systemctl daemon-reload - Restart munin-cgi-graph with_items: - munin-cgi-graph.service - munin-cgi-graph.socket - name: Copy munin-cgi-html.{service,socket} - copy: src=lib/systemd/system/{{ item }} - dest=/lib/systemd/system/{{ item }} + copy: src=etc/systemd/system/{{ item }} + dest=/etc/systemd/system/{{ item }} owner=root group=root mode=0644 notify: - systemctl daemon-reload - Restart munin-cgi-html with_items: - munin-cgi-html.service - munin-cgi-html.socket - meta: flush_handlers - name: Start munin-cgi-{graph,html} service: name={{ item }} state=started enabled=yes with_items: - munin-cgi-graph - munin-cgi-html - name: Copy /etc/nginx/sites-available/munin copy: src=etc/nginx/sites-available/munin @@ -78,59 +91,33 @@ register: r1 notify: - Restart Nginx - name: Create /etc/nginx/sites-enabled/munin file: src=../sites-available/munin dest=/etc/nginx/sites-enabled/munin owner=root group=root state=link force=yes register: r2 notify: - Restart Nginx - name: Start Nginx service: name=nginx state=started when: not (r1.changed or r2.changed) - meta: flush_handlers -- name: Copy munin-node X.509 certificates - copy: src=certs/munin/{{ item }}.pem - dest=/etc/stunnel/certs/munin-{{ hostvars[item].inventory_hostname_short }}.pem - owner=root group=root - mode=0644 - with_items: groups.all | difference([inventory_hostname]) - register: r1 - notify: - - Restart stunnel - -- name: Configure stunnel - template: src=etc/stunnel/munin-master.conf.j2 - dest=/etc/stunnel/munin-master.conf - owner=root group=root - mode=0644 - register: r2 - notify: - - Restart stunnel - -- name: Start stunnel - service: name=stunnel4 pattern=/usr/bin/stunnel4 state=started - when: not (r1.changed or r2.changed) - -- meta: flush_handlers - - - name: Install 'munin_stats' and 'munin_update' plugins file: src=/usr/share/munin/plugins/{{ item }} dest=/etc/munin/plugins/{{ item }} owner=root group=root state=link force=yes with_items: - munin_stats - munin_update tags: - munin-node - munin notify: - Restart munin-node diff --git a/roles/munin-master/templates/etc/munin/munin.conf.j2 b/roles/munin-master/templates/etc/munin/munin.conf.j2 index 8273a83..b53ef0e 100644 --- a/roles/munin-master/templates/etc/munin/munin.conf.j2 +++ b/roles/munin-master/templates/etc/munin/munin.conf.j2 @@ -1,31 +1,31 @@ # Example configuration file for Munin, generated by 'make build' # The next three variables specifies where the location of the RRD # databases, the HTML output, logs and the lock/pid files. They all # must be writable by the user running munin-cron. They are all # defaulted to the values you see here. # #dbdir /var/lib/munin #htmldir /var/cache/munin/www #logdir /var/log/munin -#rundir /var/run/munin +#rundir /run/munin # Where to look for the HTML templates # #tmpldir /etc/munin/templates # Where to look for the static www files # #staticdir /etc/munin/static # temporary cgi files are here. note that it has to be writable by # the cgi user (usually nobody or httpd). # # cgitmpdir /var/lib/munin/cgi-tmp # (Exactly one) directory to include all files from. includedir /etc/munin/munin-conf.d # You can choose the time reference for "DERIVE" like graphs, and show # "per minute", "per hour" values instead of the default "per second" # @@ -66,50 +66,42 @@ graph_strategy cgi # - moving to CGI for HTML means you cannot have graph generated by cron. # - cgi html has some bugs, mostly you still have to launch munin-html by hand # html_strategy cgi # munin-update runs in parallel. # # The default max number of processes is 16, and is probably ok for you. # # If set too high, it might hit some process/ram/filedesc limits. # If set too low, munin-update might take more than 5 min. # # If you want munin-update to not be parallel set it to 0. # #max_processes 16 # RRD updates are per default, performed directly on the rrd files. # To reduce IO and enable the use of the rrdcached, uncomment it and set it to # the location of the socket that rrdcached uses. # -rrdcached_socket /var/run/rrdcached.sock +rrdcached_socket /run/rrdcached.sock # Drop somejuser@fnord.comm and anotheruser@blibb.comm an email everytime # something changes (OK -> WARNING, CRITICAL -> OK, etc) contact.admin.command mail -s "Munin notification" admin@fripost.org # # For those with Nagios, the following might come in handy. In addition, # the services must be defined in the Nagios server as well. #contact.nagios.command /usr/bin/send_nsca nagios.host.comm -c /etc/nsca.conf -local_address 127.0.0.1 - -{% set n = 0 %} {% for node in groups.all | sort %} -{% set n = n + 1 %} [all;{{ hostvars[node].inventory_hostname_short }}] -{% if node == inventory_hostname %} - address 127.0.0.1 -{% else %} - address 127.0.{{ n }}.1 -{% endif %} + address {{ ipsec[ hostvars[node].inventory_hostname_short ] }} port 4994 {% for g in hostvars[node].group_names | sort %} [{{ g }};{{ hostvars[node].inventory_hostname_short }}] update no {% endfor %} {% endfor %} diff --git a/roles/munin-master/templates/etc/stunnel/munin-master.conf.j2 b/roles/munin-master/templates/etc/stunnel/munin-master.conf.j2 deleted file mode 100644 index c025183..0000000 --- a/roles/munin-master/templates/etc/stunnel/munin-master.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/munin-master.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 -client = yes -socket = a:SO_BINDTODEVICE=lo - -; 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) * -; ************************************************************************** - -{% set n = 0 %} -{% for node in groups.all | sort %} -{% set n = n + 1 %} -{% if node != inventory_hostname %} -[{{ hostvars[node].inventory_hostname_short }}] -accept = 127.0.{{ n }}.1:4994 -connect = {{ node }}:4949 -delay = yes -CAfile = /etc/stunnel/certs/munin-{{ hostvars[node].inventory_hostname_short }}.pem -{% endif %} - -{% endfor %} - -; vim:ft=dosini diff --git a/roles/nextcloud/files/etc/cron.d/nextcloud b/roles/nextcloud/files/etc/cron.d/nextcloud new file mode 100644 index 0000000..3c4aac0 --- /dev/null +++ b/roles/nextcloud/files/etc/cron.d/nextcloud @@ -0,0 +1,2 @@ +MAILTO=root +*/5 * * * * _nextcloud php -f /usr/local/share/nextcloud/cron.php diff --git a/roles/nextcloud/files/etc/ldap/ldap.conf b/roles/nextcloud/files/etc/ldap/ldap.conf new file mode 100644 index 0000000..b4ebe34 --- /dev/null +++ b/roles/nextcloud/files/etc/ldap/ldap.conf @@ -0,0 +1,10 @@ +# +# LDAP Defaults +# + +# See ldap.conf(5) for details +# This file should be world readable but not world writable. + +# TLS certificates (needed for GnuTLS) +TLS_CACERT /etc/ldap/ssl/ldap.fripost.org.pem +TLS_REQCERT hard diff --git a/roles/nextcloud/files/etc/logrotate.d/nextcloud b/roles/nextcloud/files/etc/logrotate.d/nextcloud new file mode 100644 index 0000000..b8dd232 --- /dev/null +++ b/roles/nextcloud/files/etc/logrotate.d/nextcloud @@ -0,0 +1,9 @@ +/var/log/nextcloud/*.log { + daily + missingok + rotate 7 + compress + delaycompress + notifempty + create 0640 _nextcloud adm +} diff --git a/roles/nextcloud/files/etc/nginx/sites-available/nextcloud b/roles/nextcloud/files/etc/nginx/sites-available/nextcloud new file mode 100644 index 0000000..f1f4dcc --- /dev/null +++ b/roles/nextcloud/files/etc/nginx/sites-available/nextcloud @@ -0,0 +1,126 @@ +server { + listen 80; + listen [::]:80; + + server_name cloud.fripost.org; + + include /etc/lacme/nginx.conf; + + access_log /var/log/nginx/cloud.access.log; + error_log /var/log/nginx/cloud.error.log info; + + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + + server_name cloud.fripost.org; + + root /usr/local/share/nextcloud; + + include snippets/headers.conf; + add_header X-Robots-Tag "noindex, nofollow"; + add_header X-Download-Options noopen; + add_header X-Permitted-Cross-Domain-Policies none; + + include snippets/ssl.conf; + ssl_certificate ssl/cloud.fripost.org.pem; + ssl_certificate_key ssl/cloud.fripost.org.key; + include snippets/cloud.fripost.org.hpkp-hdr; + + include mime.types; + types { + text/javascript js mjs; + application/wasm wasm; + } + + location = /robots.txt { + allow all; + log_not_found off; + access_log off; + } + + access_log /var/log/nginx/cloud.access.log; + error_log /var/log/nginx/cloud.error.log info; + + index index.php index.html /index.php$request_uri; + + location = /.well-known/carddav { return 301 /remote.php/dav; } + location = /.well-known/caldav { return 301 /remote.php/dav; } + location ^~ /.well-known/ { return 301 /index.php$request_uri; } + + # set max upload size + client_max_body_size 512M; + fastcgi_buffers 64 4K; + fastcgi_buffer_size 32k; + + # Enable gzip but do not remove ETag headers + gzip on; + gzip_vary on; + gzip_comp_level 4; + gzip_min_length 256; + gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; + gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; + + location = / { return 303 /apps/files/; } + + location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/ { internal; } + location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { internal; } + + location ~ \.php(?:$|/) { + # Required for legacy support + rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode(_arm64)?\/proxy) /index.php$request_uri; + + include snippets/fastcgi-php.conf; + fastcgi_param modHeadersAvailable true; + fastcgi_param front_controller_active true; + fastcgi_intercept_errors on; + fastcgi_request_buffering off; + fastcgi_pass unix:/run/php/php8.2-fpm@nextcloud.sock; + } + + location ~ \.(?:css|js|mjs|svg|gif|png|jpg|ico|wasm|tflite|map|ogg|flac)$ { + try_files $uri /index.php$uri$is_args$args; + } + location ~ \.woff2?$ { + try_files $uri /index.php$request_uri; + expires 7d; + } + location ~ \.(?:png|html|ttf|ico|jpg|jpeg|bcmap)$ { + try_files $uri /index.php$uri$is_args$args; + } + + location /remote { + return 301 /remote.php$request_uri; + } + + location / { + try_files $uri $uri/ /index.php$request_uri; + } + + location = /core/img/favicon.ico { + alias /var/www/nextcloud/fripost.ico; + } +} + +server { + listen 80; + listen [::]:80; + listen 443 ssl http2; + listen [::]:443 ssl http2; + + server_name www.cloud.fripost.org; + + include /etc/lacme/nginx.conf; + + access_log /var/log/nginx/cloud.access.log; + error_log /var/log/nginx/cloud.error.log info; + + location / { + return 301 https://cloud.fripost.org$request_uri; + } +} diff --git a/roles/nextcloud/files/etc/php/fpm/pool.d/nextcloud.conf b/roles/nextcloud/files/etc/php/fpm/pool.d/nextcloud.conf new file mode 100644 index 0000000..898ce60 --- /dev/null +++ b/roles/nextcloud/files/etc/php/fpm/pool.d/nextcloud.conf @@ -0,0 +1,24 @@ +[nextcloud] +user = _nextcloud +group = nogroup +listen = /run/php/php8.2-fpm@nextcloud.sock +listen.owner = www-data +listen.group = www-data +listen.mode = 0600 +pm = dynamic +pm.max_children = 5 +pm.start_servers = 2 +pm.min_spare_servers = 1 +pm.max_spare_servers = 3 + +php_value[upload_max_filesize] = 512M +php_value[post_max_size] = 512M +php_value[memory_limit] = 512M + +php_admin_value[open_basedir] = /usr/local/share/nextcloud:/var/www/nextcloud:/mnt/nextcloud-data:/etc/nextcloud:/var/cache/nextcloud:/var/log/nextcloud:/usr/share/php:/tmp:/dev + +env[HOSTNAME] = $HOSTNAME +env[PATH] = /usr/bin:/bin +env[TMP] = /tmp +env[TMPDIR] = /tmp +env[TEMP] = /tmp diff --git a/roles/nextcloud/handlers/main.yml b/roles/nextcloud/handlers/main.yml new file mode 100644 index 0000000..a14d6e1 --- /dev/null +++ b/roles/nextcloud/handlers/main.yml @@ -0,0 +1,9 @@ +--- +- name: Restart php8.2-fpm + service: name=php8.2-fpm state=restarted + +- name: Restart Redis + service: name=redis-server state=restarted + +- name: Restart Nginx + service: name=nginx state=restarted diff --git a/roles/nextcloud/tasks/ldap.yml b/roles/nextcloud/tasks/ldap.yml new file mode 100644 index 0000000..17cd963 --- /dev/null +++ b/roles/nextcloud/tasks/ldap.yml @@ -0,0 +1,17 @@ +- name: Create /etc/ldap/ssl + file: path=/etc/ldap/ssl + state=directory + owner=root group=root + mode=0755 + +- name: Copy the slapd X.509 certificate + copy: src=certs/ldap/ldap.fripost.org.pem + dest=/etc/ldap/ssl/ldap.fripost.org.pem + owner=root group=root + mode=0644 + +- name: Copy ldap.conf(5) + copy: src=etc/ldap/ldap.conf + dest=/etc/ldap/ldap.conf + owner=root group=root + mode=0644 diff --git a/roles/nextcloud/tasks/main.yml b/roles/nextcloud/tasks/main.yml new file mode 100644 index 0000000..14bc02c --- /dev/null +++ b/roles/nextcloud/tasks/main.yml @@ -0,0 +1,200 @@ +- name: Install PHP + apt: pkg={{ packages }} + vars: + packages: + - php-cli + - php-bcmath + - php-fpm + - php-apcu + - php-gd + - php-gmp + - php-imagick + - php-mbstring + - php-xml + - php-curl + - php-intl + - php-ldap + - php-mysql + - php-zip + - php-json + - php-gmp + +- name: Configure PHP 8.2 Zend opcache + lineinfile: dest=/etc/php/8.2/fpm/php.ini + regexp='^;?{{ item.var }}\\s*=' + line="{{ item.var }} = {{ item.value }}" + owner=root group=root + mode=0644 + with_items: + - { var: opcache.memory_consumption, value: 512 } + - { var: opcache.revalidate_freq, value: 180 } + - { var: opcache.interned_strings_buffer, value: 12 } + notify: + - Restart php8.2-fpm + +- name: Configure PHP 8.2 CLI + lineinfile: dest=/etc/php/8.2/cli/php.ini + regexp='^;?{{ item.var }}\\s*=' + line="{{ item.var }} = {{ item.value }}" + owner=root group=root + mode=0644 + with_items: + - { var: apc.enable_cli, value: 1 } + +- name: Create '_nextcloud' user + user: name=_nextcloud system=yes + group=nogroup + createhome=no + home=/nonexistent + shell=/usr/sbin/nologin + password=! + state=present + +- name: Delete PHP 8.2 FPM's www pool + file: path=/etc/php/8.2/fpm/pool.d/www.conf state=absent + notify: + - Restart php8.2-fpm + +- name: Configure PHP 8.2 FPM's nextcloud pool + copy: src=etc/php/fpm/pool.d/nextcloud.conf + dest=/etc/php/8.2/fpm/pool.d/nextcloud.conf + owner=root group=root + mode=0644 + notify: + - Restart php8.2-fpm + +- name: Start php8.2-fpm + service: name=php8.2-fpm state=started + +- name: Copy /etc/cron.d/nextcloud + copy: src=etc/cron.d/nextcloud + dest=/etc/cron.d/nextcloud + owner=root group=root + mode=0644 + +- name: Copy /etc/nginx/sites-available/nextcloud + copy: src=etc/nginx/sites-available/nextcloud + dest=/etc/nginx/sites-available/nextcloud + owner=root group=root + mode=0644 + register: r1 + notify: + - Restart Nginx + +- name: Create /etc/nginx/sites-enabled/nextcloud + file: src=../sites-available/nextcloud + dest=/etc/nginx/sites-enabled/nextcloud + owner=root group=root + state=link force=yes + register: r2 + notify: + - Restart Nginx + +- name: Copy HPKP header snippet + # never modify the pined pubkeys as we don't want to lock out our users + template: src=etc/nginx/snippets/cloud.fripost.org.hpkp-hdr.j2 + dest=/etc/nginx/snippets/cloud.fripost.org.hpkp-hdr + validate=/bin/false + owner=root group=root + mode=0644 + register: r3 + notify: + - Restart Nginx + +- name: Start Nginx + service: name=nginx state=started + when: not (r1.changed or r2.changed or r3.changed) + +- meta: flush_handlers + +- name: Fetch Nginx's X.509 certificate + # Ensure we don't fetch private data + become: False + fetch_cmd: cmd="openssl x509 -noout -pubkey" + stdin=/etc/nginx/ssl/cloud.fripost.org.pem + dest=certs/public/cloud.fripost.org.pub + tags: + - genkey + +- import_tasks: ldap.yml + when: "'LDAP_provider' not in group_names" + tags: + - ldap + +# Note: intentionally don't set an owner/group as we don't want to set +# ownership unless the path is a mountpoint. The service will fail +# unless the data directory is mounted and accessible, and that's what +# we want. +- name: Create directory /mnt/nextcloud-data + file: path=/mnt/nextcloud-data + state=directory + mode=0700 + +- name: Create directory /var/www/nextcloud + file: path=/var/www/nextcloud + state=directory + owner=root group=root + mode=0755 + +# Note: Nextcloud doesn't like symlinked apps +# * https://github.com/nextcloud/server/issues/10437 +# * https://github.com/nextcloud/server/issues/13556 +- name: Create directory /var/www/nextcloud/apps + file: path=/var/www/nextcloud/apps + state=directory + owner=_nextcloud group=nogroup + mode=0755 + +- name: Create directory /var/log/nextcloud + file: path=/var/log/nextcloud + state=directory + owner=_nextcloud group=adm + mode=0750 + +- name: Create directory /var/cache/nextcloud + file: path=/var/cache/nextcloud + state=directory + owner=_nextcloud group=nogroup + mode=0700 + +- name: Copy Nextcloud logrotate snippet + copy: src=etc/logrotate.d/nextcloud + dest=/etc/logrotate.d/nextcloud + owner=root group=root + mode=0644 + tags: + - logrotate + +- name: Install redis-server + apt: pkg={{ packages }} + vars: + packages: + - php-redis + - redis-server + +- name: Configure Redis + lineinfile: dest=/etc/redis/redis.conf + regexp='^#?\\s*{{ item.var }}\\s+' + line="{{ item.var }} {{ item.value }}" + owner=redis group=redis + mode=0640 + with_items: + - { var: port, value: 0 } + - { var: unixsocket, value: /run/redis/redis-server.sock } + - { var: unixsocketperm, value: 660 } + notify: + - Restart Redis + +- name: Start redis-server + service: name=redis-server state=started + +- name: Add '_nextcloud' user to 'redis' group + user: name=_nextcloud groups=redis append=yes + notify: + - Restart php8.2-fpm + +- name: Install other Nextcloud dependencies + apt: pkg={{ packages }} + vars: + packages: + - libmagickcore-6.q16-6-extra diff --git a/roles/nextcloud/templates/etc/nginx/snippets/cloud.fripost.org.hpkp-hdr.j2 b/roles/nextcloud/templates/etc/nginx/snippets/cloud.fripost.org.hpkp-hdr.j2 new file mode 120000 index 0000000..a8ba598 --- /dev/null +++ b/roles/nextcloud/templates/etc/nginx/snippets/cloud.fripost.org.hpkp-hdr.j2 @@ -0,0 +1 @@ +../../../../../../certs/hpkp-hdr.j2
\ No newline at end of file diff --git a/roles/out/tasks/main.yml b/roles/out/tasks/main.yml index 10429b1..7a297f1 100644 --- a/roles/out/tasks/main.yml +++ b/roles/out/tasks/main.yml @@ -1,45 +1,62 @@ - name: Install Postfix - apt: pkg=postfix + apt: pkg={{ packages }} + vars: + packages: + - postfix + - postfix-lmdb - name: Configure Postfix - template: src=etc/postfix/main.cf.j2 - dest=/etc/postfix-{{ postfix_instance[inst].name }}/main.cf + template: src=etc/postfix/{{ item }}.j2 + dest=/etc/postfix-{{ postfix_instance[inst].name }}/{{ item }} owner=root group=root mode=0644 + with_items: + - main.cf + - master.cf notify: - Reload Postfix -- name: Copy the Postfix relay clientcerts map - template: src=etc/postfix/relay_clientcerts.j2 - dest=/etc/postfix-{{ postfix_instance[inst].name }}/relay_clientcerts +- name: Copy the canonical maps + template: src=etc/postfix/canonical.j2 + dest=/etc/postfix-{{ postfix_instance[inst].name }}/canonical owner=root group=root mode=0644 - tags: - - tls_policy -- name: Compile the Postfix relay clientcerts map - postmap: cmd=postmap src=/etc/postfix-{{ postfix_instance[inst].name }}/relay_clientcerts db=cdb +- name: Compile the canonical maps + # no need to reload upon change, as cleanup(8) is short-running + postmap: cmd=postmap src=/etc/postfix-{{ postfix_instance[inst].name }}/canonical db=lmdb owner=root group=root mode=0644 - tags: - - tls_policy + +- name: Copy the SMTP TLS policy maps + template: src=etc/postfix/smtp_tls_policy.j2 + dest=/etc/postfix-{{ postfix_instance[inst].name }}/smtp_tls_policy + owner=root group=root + mode=0644 + +- name: Compile the SMTP TLS policy maps + postmap: cmd=postmap src=/etc/postfix-{{ postfix_instance[inst].name }}/smtp_tls_policy db=lmdb + owner=root group=root + mode=0644 + notify: + - Reload Postfix - meta: flush_handlers - name: Start Postfix service: name=postfix state=started - name: Install 'postfix_mailqueue_' Munin wildcard plugin file: src=/usr/local/share/munin/plugins/postfix_mailqueue_ dest=/etc/munin/plugins/postfix_mailqueue_postfix-{{ postfix_instance[inst].name }} owner=root group=root state=link force=yes tags: - munin - munin-node notify: - Restart munin-node - name: Install 'postfix_stats_' Munin wildcard plugin file: src=/usr/local/share/munin/plugins/postfix_stats_ diff --git a/roles/out/templates/etc/postfix/canonical.j2 b/roles/out/templates/etc/postfix/canonical.j2 new file mode 100644 index 0000000..ed8bb4d --- /dev/null +++ b/roles/out/templates/etc/postfix/canonical.j2 @@ -0,0 +1,10 @@ +# {{ ansible_managed }} +# Do NOT edit this file directly! + +# Addresses under $myhostname are typically not valid as envelope +# recipients (eg, logcheck@, root@, etc.). This breaks the sender +# address verification, so we use the admin team's address in the +# envelope. +{% for host in groups.all | sort %} +@{{ hostvars[host].inventory_hostname }} admin@fripost.org +{% endfor %} diff --git a/roles/out/templates/etc/postfix/main.cf.j2 b/roles/out/templates/etc/postfix/main.cf.j2 index 8766984..f8aa55a 100644 --- a/roles/out/templates/etc/postfix/main.cf.j2 +++ b/roles/out/templates/etc/postfix/main.cf.j2 @@ -1,102 +1,92 @@ ######################################################################## # 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 +smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) +biff = no +readme_directory = no +compatibility_level = 2 +smtputf8_enable = no 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 = !{{ postfix_instance.out.port }}.inet !127.0.0.1:10025.inet inet +mynetworks = 127.0.0.0/8, [::1]/128 +{%- if groups.all | length > 1 -%} + , {{ ipsec_subnet }} +{% endif %} 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 -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 +message_size_limit = 0 recipient_delimiter = + relay_domains = relay_transport = error:5.3.2 Relay Transport unavailable +# Replace internal system addresses under $myhostname with a valid address +canonical_maps = lmdb:$config_directory/canonical +canonical_classes = envelope_sender, envelope_recipient + # All header rewriting happens upstream local_header_rewrite_clients = smtp_tls_security_level = may +smtp_tls_ciphers = medium +smtp_tls_protocols = !SSLv2, !SSLv3 smtp_tls_note_starttls_offer = yes -smtp_tls_session_cache_database = btree:$data_directory/smtp_tls_session_cache +smtp_tls_session_cache_database = lmdb:$data_directory/smtp_tls_session_cache -relay_clientcerts = cdb:$config_directory/relay_clientcerts -smtpd_tls_security_level = may -smtpd_tls_exclude_ciphers = EXPORT, LOW, MEDIUM, aNULL, eNULL, DES, RC4, MD5 -smtpd_tls_cert_file = /etc/postfix/ssl/{{ ansible_fqdn }}.pem -smtpd_tls_key_file = /etc/postfix/ssl/{{ ansible_fqdn }}.key -smtpd_tls_dh1024_param_file = /etc/ssl/private/dhparams.pem -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 +smtp_tls_fingerprint_digest = sha256 +smtp_tls_policy_maps = lmdb:$config_directory/smtp_tls_policy +smtpd_tls_security_level = none strict_rfc821_envelopes = yes smtpd_delay_reject = yes disable_vrfy_command = yes -address_verify_sender = $double_bounce_sender@$mydomain -unverified_recipient_defer_code = 250 -unverified_recipient_reject_code = 550 - 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 smtpd_relay_restrictions = reject_non_fqdn_recipient reject_unknown_recipient_domain - reject_unverified_recipient permit_mynetworks - permit_tls_clientcerts reject smtpd_data_restrictions = reject_unauth_pipelining content_filter = amavisfeed:[127.0.0.1]:10040 # vim: set filetype=pfmain : diff --git a/roles/out/templates/etc/postfix/master.cf.j2 b/roles/out/templates/etc/postfix/master.cf.j2 new file mode 120000 index 0000000..011f8e0 --- /dev/null +++ b/roles/out/templates/etc/postfix/master.cf.j2 @@ -0,0 +1 @@ +../../../../common/templates/etc/postfix/master.cf.j2
\ No newline at end of file diff --git a/roles/out/templates/etc/postfix/relay_clientcerts.j2 b/roles/out/templates/etc/postfix/relay_clientcerts.j2 deleted file mode 100644 index d70432e..0000000 --- a/roles/out/templates/etc/postfix/relay_clientcerts.j2 +++ /dev/null @@ -1,6 +0,0 @@ -# {{ ansible_managed }} -# /!\ WARNING: smtp_tls_fingerprint_digest MUST be sha256! - -{% for h in groups.all | difference([inventory_hostname]) | sort %} -{{ lookup('pipe', 'openssl x509 -in certs/postfix/'+h+'.pem -noout -fingerprint -sha256 | cut -d= -f2') }} {{ h }} -{% endfor %} diff --git a/roles/out/templates/etc/postfix/smtp_tls_policy.j2 b/roles/out/templates/etc/postfix/smtp_tls_policy.j2 new file mode 100644 index 0000000..7722dc8 --- /dev/null +++ b/roles/out/templates/etc/postfix/smtp_tls_policy.j2 @@ -0,0 +1,12 @@ +# Lookup table matching next-hop destinations to TLS security policies; +# this allows pining the key material for chosen recipient domains. +# +# {{ ansible_managed }} +# Do NOT edit this file directly! +{% for nexthop in ['fripost.org','.fripost.org'] %} + +{{ nexthop }} fingerprint ciphers=high protocols=!SSLv2:!SSLv3:!TLSv1:!TLSv1.1 +{% for h in groups.MX | sort %} + match={{ lookup('pipe', 'openssl pkey -pubin -outform DER <"certs/public/mx'+(hostvars[h].mxno | default('') | string)+'.fripost.org.pub" | openssl dgst -sha256 -c | sed "s/[^=]*=\s*//"') }} +{% endfor %} +{% endfor %} diff --git a/roles/webmail/files/etc/cron.d/roundcube-core b/roles/webmail/files/etc/cron.d/roundcube-core new file mode 100644 index 0000000..6d9e7af --- /dev/null +++ b/roles/webmail/files/etc/cron.d/roundcube-core @@ -0,0 +1,7 @@ +# +# Roundcube database cleaning: finally removes all records that are +# marked as deleted. +MAILTO=root + +# m h dom mon dow user command +0 5 * * * _roundcube /usr/share/roundcube/bin/cleandb.sh >/dev/null diff --git a/roles/webmail/files/etc/nginx/sites-available/roundcube b/roles/webmail/files/etc/nginx/sites-available/roundcube index 8251841..602668f 100644 --- a/roles/webmail/files/etc/nginx/sites-available/roundcube +++ b/roles/webmail/files/etc/nginx/sites-available/roundcube @@ -1,73 +1,76 @@ server { listen 80; - listen [::]:80 ipv6only=on; + listen [::]:80; - server_name mail.fripost.org; + server_name mail.fripost.org; + server_name webmail.fripost.org; - access_log /var/log/nginx/roundcube.access.log; - error_log /var/log/nginx/roundcube.error.log info; + include /etc/lacme/nginx.conf; - return 301 https://$host$request_uri; + access_log /var/log/nginx/roundcube.access.log; + error_log /var/log/nginx/roundcube.error.log info; + + location / { + return 301 https://$host$request_uri; + } } server { - listen 443; - listen [::]:443 ipv6only=on; + listen 443 ssl http2; + listen [::]:443 ssl http2; + + server_name mail.fripost.org; + server_name webmail.fripost.org; + + root /var/lib/roundcube/public_html; - server_name mail.fripost.org; - root /var/lib/roundcube; + include snippets/headers.conf; + add_header Content-Security-Policy + "default-src 'none'; frame-src 'self'; connect-src 'self'; object-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; frame-ancestors 'self'; form-action 'self'; base-uri mail.fripost.org webmail.fripost.org"; - include ssl/config; - # include the intermediate certificate, see - # - https://www.ssllabs.com/ssltest/analyze.html?d=mail.fripost.org - # - http://nginx.org/en/docs/http/configuring_https_servers.html - ssl_certificate /etc/nginx/ssl/mail.fripost.org.chained.pem; - ssl_certificate_key /etc/nginx/ssl/mail.fripost.org.key; + include snippets/ssl.conf; + ssl_certificate ssl/mail.fripost.org.pem; + ssl_certificate_key ssl/mail.fripost.org.key; + include snippets/mail.fripost.org.hpkp-hdr; + + gzip on; + gzip_static on; + gzip_vary on; + gzip_min_length 256; + gzip_types application/font-woff application/font-woff2 application/javascript application/json application/xml image/svg+xml image/x-icon text/css text/plain text/vcard; location = /favicon.ico { - root /usr/share/roundcube/skins/default/images; + root /usr/share/roundcube/skins/elastic/images; log_not_found off; access_log off; - expires max; } location = /robots.txt { allow all; log_not_found off; access_log off; } - # Deny all attempts to access hidden files, or files under hidden - # directories. - location ~ /\. { return 404; } - - access_log /var/log/nginx/roundcube.access.log; - error_log /var/log/nginx/roundcube.error.log info; + access_log /var/log/nginx/roundcube.access.log; + error_log /var/log/nginx/roundcube.error.log info; - index index.php; client_max_body_size 64m; + location = / { index index.php; } location = /index.php { - include fastcgi/php; - include fastcgi/php-ssl; - - # From /var/lib/roundcube/.htaccess - fastcgi_param PHP_VALUE "upload_max_filesize=25M - post_max_size=30M - memory_limit=64M - session.gc_maxlifetime=21600 - session.gc_divisor=500 - session.gc_probability=1"; - fastcgi_param PHP_ADMIN_VALUE "open_basedir=$document_root:/usr/share/roundcube:/etc/roundcube:/var/log/roundcube:/usr/share/php:/usr/share/javascript:/usr/share/tinymce:/usr/share/misc/magic - upload_tmp_dir=$document_root/temp"; + # TODO enable gzip for Roundcube >=1.5: it's immune to BREACH attacks once + # $config['session_samesite'] is set to 'Strict', see + # https://github.com/roundcube/roundcubemail/pull/6772 + # https://www.sjoerdlangkemper.nl/2016/11/07/current-state-of-breach-attack/#same-site-cookies + gzip off; + include snippets/fastcgi-php-ssl.conf; + fastcgi_pass unix:/var/run/php/php7.4-fpm@roundcube.sock; } - # Security rules - location ~ ^/(README|INSTALL|LICENSE|CHANGELOG|UPGRADING)$ { - return 404; - } - location ~ ^/(bin|SQL)/ { - return 404; + location ~ "^/(?:plugins|program/js|program/resources|skins)(?:/[[:alnum:]][[:alnum:]\-\._]*)+\.(?:css|eot|gif|html|ico|jpg|js|pdf|png|svg|tiff?|ttf|webp|woff2?)$" { + expires 30d; + try_files $uri =404; } + location / { internal; } } diff --git a/roles/webmail/files/etc/php/fpm/pool.d/roundcube.conf b/roles/webmail/files/etc/php/fpm/pool.d/roundcube.conf new file mode 100644 index 0000000..1a7a1d8 --- /dev/null +++ b/roles/webmail/files/etc/php/fpm/pool.d/roundcube.conf @@ -0,0 +1,22 @@ +[roundcube] +user = _roundcube +group = nogroup +listen = /run/php/php7.4-fpm@roundcube.sock +listen.owner = www-data +listen.group = www-data +listen.mode = 0600 +pm = dynamic +pm.max_children = 5 +pm.start_servers = 2 +pm.min_spare_servers = 1 +pm.max_spare_servers = 3 + +php_value[upload_max_filesize] = 25M +php_value[post_max_size] = 30M +php_value[memory_limit] = 64M +php_value[session.gc_maxlifetime] = 21600 +php_value[session.gc_divisor] = 500 +php_value[session.gc_probability] = 1 + +php_admin_value[upload_tmp_dir] = /var/lib/roundcube/temp +php_admin_value[open_basedir] = /var/lib/roundcube:/usr/share/roundcube:/etc/roundcube:/var/log/roundcube:/usr/share/php:/usr/share/javascript:/usr/lib/nodejs:/usr/share/tinymce:/usr/share/misc/magic:/dev diff --git a/roles/webmail/files/etc/roundcube/plugins/additional_message_headers/config.inc.php b/roles/webmail/files/etc/roundcube/plugins/additional_message_headers/config.inc.php new file mode 100644 index 0000000..6d63284 --- /dev/null +++ b/roles/webmail/files/etc/roundcube/plugins/additional_message_headers/config.inc.php @@ -0,0 +1,14 @@ +<?php + +// $config['additional_message_headers']['X-Remote-Browser'] = $_SERVER['HTTP_USER_AGENT']; +$config['additional_message_headers']['X-Originating-IP'] = null; +// $config['additional_message_headers']['X-RoundCube-Server'] = $_SERVER['SERVER_ADDR']; + +// if( isset( $_SERVER['MACHINE_NAME'] )) { +// $config['additional_message_headers']['X-RoundCube-Server'] .= ' (' . $_SERVER['MACHINE_NAME'] . ')'; +// } + +// To remove (e.g. X-Sender) message header use null value +$config['additional_message_headers']['X-Sender'] = null; + +?> diff --git a/roles/webmail/files/etc/roundcube/plugins/authres_status/config.inc.php b/roles/webmail/files/etc/roundcube/plugins/authres_status/config.inc.php new file mode 100644 index 0000000..6d41d4f --- /dev/null +++ b/roles/webmail/files/etc/roundcube/plugins/authres_status/config.inc.php @@ -0,0 +1,6 @@ +<?php + +$config['use_fallback_verifier'] = false; +$config['trusted_mtas'] = array('mx1.fripost.org', 'mx2.fripost.org'); + +?> diff --git a/roles/webmail/files/etc/roundcube/plugins/html5_notifier/config.inc.php b/roles/webmail/files/etc/roundcube/plugins/html5_notifier/config.inc.php new file mode 100644 index 0000000..1ec7922 --- /dev/null +++ b/roles/webmail/files/etc/roundcube/plugins/html5_notifier/config.inc.php @@ -0,0 +1,6 @@ +<?php + +$config['html5_notifier_duration'] = '3'; +$config['html5_notifier_smbox'] = '1'; + +?> diff --git a/roles/webmail/templates/usr/share/roundcube/plugins/password/config.inc.php.j2 b/roles/webmail/files/etc/roundcube/plugins/password/config.inc.php index f4bdd82..e53b753 100644 --- a/roles/webmail/templates/usr/share/roundcube/plugins/password/config.inc.php.j2 +++ b/roles/webmail/files/etc/roundcube/plugins/password/config.inc.php @@ -1,360 +1,401 @@ <?php // Password Plugin options // ----------------------- // A driver to use for password change. Default: "sql". // See README file for list of supported driver names. -$rcmail_config['password_driver'] = 'ldap_simple'; +$config['password_driver'] = 'ldap_simple'; // Determine whether current password is required to change password. // Default: false. -$rcmail_config['password_confirm_current'] = true; +$config['password_confirm_current'] = true; // Require the new password to be a certain length. // set to blank to allow passwords of any length -$rcmail_config['password_minimum_length'] = 12; +$config['password_minimum_length'] = 12; // Require the new password to contain a letter and punctuation character // Change to false to remove this check. -$rcmail_config['password_require_nonalpha'] = false; +$config['password_require_nonalpha'] = false; // Enables logging of password changes into logs/password -$rcmail_config['password_log'] = false; +$config['password_log'] = false; // Comma-separated list of login exceptions for which password change // will be not available (no Password tab in Settings) -$rcmail_config['password_login_exceptions'] = null; +$config['password_login_exceptions'] = null; // Array of hosts that support password changing. Default is NULL. // Listed hosts will feature a Password option in Settings; others will not. // Example: -//$rcmail_config['password_hosts'] = array('mail.example.com', 'mail2.example.org'); -$rcmail_config['password_hosts'] = null; +//$config['password_hosts'] = array('mail.example.com', 'mail2.example.org'); +$config['password_hosts'] = null; + +// Enables saving the new password even if it matches the old password. Useful +// for upgrading the stored passwords after the encryption scheme has changed. +$config['password_force_save'] = false; + +// Enables forcing new users to change their password at their first login. +$config['password_force_new_user'] = false; // SQL Driver options // ------------------ // PEAR database DSN for performing the query. By default // Roundcube DB settings are used. -$rcmail_config['password_db_dsn'] = ''; +$config['password_db_dsn'] = ''; // The SQL query used to change the password. // The query can contain the following macros that will be expanded as follows: // %p is replaced with the plaintext new password // %c is replaced with the crypt version of the new password, MD5 if available -// otherwise DES. More hash function can be enabled using the password_crypt_hash +// otherwise DES. More hash function can be enabled using the password_crypt_hash // configuration parameter. // %D is replaced with the dovecotpw-crypted version of the new password // %o is replaced with the password before the change // %n is replaced with the hashed version of the new password // %q is replaced with the hashed password before the change // %h is replaced with the imap host (from the session info) // %u is replaced with the username (from the session info) // %l is replaced with the local part of the username // (in case the username is an email address) // %d is replaced with the domain part of the username // (in case the username is an email address) // Escaping of macros is handled by this module. // Default: "SELECT update_passwd(%c, %u)" -$rcmail_config['password_query'] = 'SELECT update_passwd(%c, %u)'; +$config['password_query'] = 'SELECT update_passwd(%c, %u)'; -// By default the crypt() function which is used to create the '%c' -// parameter uses the md5 algorithm. To use different algorithms +// By default the crypt() function which is used to create the '%c' +// parameter uses the md5 algorithm. To use different algorithms // you can choose between: des, md5, blowfish, sha256, sha512. // Before using other hash functions than des or md5 please make sure // your operating system supports the other hash functions. -$rcmail_config['password_crypt_hash'] = 'sha512'; +$config['password_crypt_hash'] = 'sha512'; // By default domains in variables are using unicode. // Enable this option to use punycoded names -$rcmail_config['password_idn_ascii'] = false; +$config['password_idn_ascii'] = false; // Path for dovecotpw (if not in $PATH) -// $rcmail_config['password_dovecotpw'] = '/usr/local/sbin/dovecotpw'; +// $config['password_dovecotpw'] = '/usr/local/sbin/dovecotpw'; // Dovecot method (dovecotpw -s 'method') -$rcmail_config['password_dovecotpw_method'] = 'CRAM-MD5'; +$config['password_dovecotpw_method'] = 'CRAM-MD5'; // Enables use of password with crypt method prefix in %D, e.g. {MD5}$1$LUiMYWqx$fEkg/ggr/L6Mb2X7be4i1/ -$rcmail_config['password_dovecotpw_with_method'] = false; +$config['password_dovecotpw_with_method'] = false; // Using a password hash for %n and %q variables. // Determine which hashing algorithm should be used to generate // the hashed new and current password for using them within the // SQL query. Requires PHP's 'hash' extension. -$rcmail_config['password_hash_algorithm'] = 'sha1'; +$config['password_hash_algorithm'] = 'sha1'; // You can also decide whether the hash should be provided // as hex string or in base64 encoded format. -$rcmail_config['password_hash_base64'] = false; +$config['password_hash_base64'] = false; + +// Iteration count parameter for Blowfish-based hashing algo. +// It must be between 4 and 31. Default: 12. +// Be aware, the higher the value, the longer it takes to generate the password hashes. +$config['password_blowfish_cost'] = 12; // Poppassd Driver options // ----------------------- // The host which changes the password -$rcmail_config['password_pop_host'] = 'localhost'; +$config['password_pop_host'] = 'localhost'; // TCP port used for poppassd connections -$rcmail_config['password_pop_port'] = 106; +$config['password_pop_port'] = 106; // SASL Driver options // ------------------- // Additional arguments for the saslpasswd2 call -$rcmail_config['password_saslpasswd_args'] = ''; +$config['password_saslpasswd_args'] = ''; // LDAP and LDAP_SIMPLE Driver options // ----------------------------------- -// LDAP server name to connect to. +// LDAP server name to connect to. // You can provide one or several hosts in an array in which case the hosts are tried from left to right. // Exemple: array('ldap1.exemple.com', 'ldap2.exemple.com'); // Default: 'localhost' -$rcmail_config['password_ldap_host'] = 'localhost'; +$config['password_ldap_host'] = '127.0.0.1'; // LDAP server port to connect to // Default: '389' -$rcmail_config['password_ldap_port'] = '389'; +$config['password_ldap_port'] = '389'; // TLS is started after connecting // Using TLS for password modification is recommanded. // Default: false -$rcmail_config['password_ldap_starttls'] = false; +$config['password_ldap_starttls'] = false; // LDAP version // Default: '3' -$rcmail_config['password_ldap_version'] = '3'; +$config['password_ldap_version'] = '3'; // LDAP base name (root directory) // Exemple: 'dc=exemple,dc=com' -$rcmail_config['password_ldap_basedn'] = 'ou=virtual,dc=fripost,dc=org'; +$config['password_ldap_basedn'] = 'ou=virtual,dc=fripost,dc=org'; // LDAP connection method // There is two connection method for changing a user's LDAP password. // 'user': use user credential (recommanded, require password_confirm_current=true) // 'admin': use admin credential (this mode require password_ldap_adminDN and password_ldap_adminPW) // Default: 'user' -$rcmail_config['password_ldap_method'] = 'user'; +$config['password_ldap_method'] = 'user'; // LDAP Admin DN // Used only in admin connection mode // Default: null -$rcmail_config['password_ldap_adminDN'] = null; +$config['password_ldap_adminDN'] = null; // LDAP Admin Password // Used only in admin connection mode // Default: null -$rcmail_config['password_ldap_adminPW'] = null; +$config['password_ldap_adminPW'] = null; // LDAP user DN mask // The user's DN is mandatory and as we only have his login, // we need to re-create his DN using a mask // '%login' will be replaced by the current roundcube user's login // '%name' will be replaced by the current roundcube user's name part // '%domain' will be replaced by the current roundcube user's domain part // '%dc' will be replaced by domain name hierarchal string e.g. "dc=test,dc=domain,dc=com" // Exemple: 'uid=%login,ou=people,dc=exemple,dc=com' -$rcmail_config['password_ldap_userDN_mask'] = 'fvl=%name,fvd=%domain,ou=virtual,dc=fripost,dc=org'; +$config['password_ldap_userDN_mask'] = 'fvl=%name,fvd=%domain,ou=virtual,dc=fripost,dc=org'; + // LDAP search DN // The DN roundcube should bind with to find out user's DN // based on his login. Note that you should comment out the default // password_ldap_userDN_mask setting for this to take effect. // Use this if you cannot specify a general template for user DN with // password_ldap_userDN_mask. You need to perform a search based on // users login to find his DN instead. A common reason might be that // your users are placed under different ou's like engineering or // sales which cannot be derived from their login only. -$rcmail_config['password_ldap_searchDN'] = null; +$config['password_ldap_searchDN'] = null; // LDAP search password // If password_ldap_searchDN is set, the password to use for // binding to search for user's DN. Note that you should comment out the default // password_ldap_userDN_mask setting for this to take effect. // Warning: Be sure to set approperiate permissions on this file so this password // is only accesible to roundcube and don't forget to restrict roundcube's access to // your directory as much as possible using ACLs. Should this password be compromised // you want to minimize the damage. -$rcmail_config['password_ldap_searchPW'] = null; +$config['password_ldap_searchPW'] = null; // LDAP search base // If password_ldap_searchDN is set, the base to search in using the filter below. // Note that you should comment out the default password_ldap_userDN_mask setting // for this to take effect. -$rcmail_config['password_ldap_search_base'] = null; +$config['password_ldap_search_base'] = null; // LDAP search filter // If password_ldap_searchDN is set, the filter to use when // searching for user's DN. Note that you should comment out the default // password_ldap_userDN_mask setting for this to take effect. // '%login' will be replaced by the current roundcube user's login // '%name' will be replaced by the current roundcube user's name part // '%domain' will be replaced by the current roundcube user's domain part // '%dc' will be replaced by domain name hierarchal string e.g. "dc=test,dc=domain,dc=com" // Example: '(uid=%login)' // Example: '(&(objectClass=posixAccount)(uid=%login))' -$rcmail_config['password_ldap_search_filter'] = null; +$config['password_ldap_search_filter'] = null; // LDAP password hash type // Standard LDAP encryption type which must be one of: crypt, -// ext_des, md5crypt, blowfish, md5, sha, smd5, ssha, or clear. +// ext_des, md5crypt, blowfish, md5, sha, smd5, ssha, ad, cram-md5 (dovecot style) or clear. // Please note that most encodage types require external libraries // to be included in your PHP installation, see function hashPassword in drivers/ldap.php for more info. -// Default: 'crypt' -$rcmail_config['password_ldap_encodage'] = 'ssha'; +// Multiple password Values can be generated by concatenating encodings with a +. E.g. 'cram-md5+crypt' +// Default: 'crypt'. +$config['password_ldap_encodage'] = 'ssha'; // LDAP password attribute // Name of the ldap's attribute used for storing user password // Default: 'userPassword' -$rcmail_config['password_ldap_pwattr'] = 'userPassword'; +$config['password_ldap_pwattr'] = 'userPassword'; // LDAP password force replace // Force LDAP replace in cases where ACL allows only replace not read // See http://pear.php.net/package/Net_LDAP2/docs/latest/Net_LDAP2/Net_LDAP2_Entry.html#methodreplace // Default: true -$rcmail_config['password_ldap_force_replace'] = true; +$config['password_ldap_force_replace'] = true; // LDAP Password Last Change Date // Some places use an attribute to store the date of the last password change // The date is meassured in "days since epoch" (an integer value) // Whenever the password is changed, the attribute will be updated if set (e.g. shadowLastChange) -$rcmail_config['password_ldap_lchattr'] = ''; +$config['password_ldap_lchattr'] = ''; // LDAP Samba password attribute, e.g. sambaNTPassword // Name of the LDAP's Samba attribute used for storing user password -$rcmail_config['password_ldap_samba_pwattr'] = ''; - +$config['password_ldap_samba_pwattr'] = ''; + // LDAP Samba Password Last Change Date attribute, e.g. sambaPwdLastSet // Some places use an attribute to store the date of the last password change // The date is meassured in "seconds since epoch" (an integer value) // Whenever the password is changed, the attribute will be updated if set -$rcmail_config['password_ldap_samba_lchattr'] = ''; +$config['password_ldap_samba_lchattr'] = ''; // DirectAdmin Driver options // -------------------------- // The host which changes the password // Use 'ssl://host' instead of 'tcp://host' when running DirectAdmin over SSL. // The host can contain the following macros that will be expanded as follows: // %h is replaced with the imap host (from the session info) // %d is replaced with the domain part of the username (if the username is an email) -$rcmail_config['password_directadmin_host'] = 'tcp://localhost'; +$config['password_directadmin_host'] = 'tcp://localhost'; // TCP port used for DirectAdmin connections -$rcmail_config['password_directadmin_port'] = 2222; +$config['password_directadmin_port'] = 2222; // vpopmaild Driver options // ----------------------- // The host which changes the password -$rcmail_config['password_vpopmaild_host'] = 'localhost'; +$config['password_vpopmaild_host'] = 'localhost'; // TCP port used for vpopmaild connections -$rcmail_config['password_vpopmaild_port'] = 89; +$config['password_vpopmaild_port'] = 89; + +// Timout used for the connection to vpopmaild (in seconds) +$config['password_vpopmaild_timeout'] = 10; // cPanel Driver options // -------------------------- // The cPanel Host name -$rcmail_config['password_cpanel_host'] = 'host.domain.com'; +$config['password_cpanel_host'] = 'host.domain.com'; // The cPanel admin username -$rcmail_config['password_cpanel_username'] = 'username'; +$config['password_cpanel_username'] = 'username'; // The cPanel admin password -$rcmail_config['password_cpanel_password'] = 'password'; +$config['password_cpanel_password'] = 'password'; // The cPanel port to use -$rcmail_config['password_cpanel_port'] = 2082; - -// Using ssl for cPanel connections? -$rcmail_config['password_cpanel_ssl'] = true; - -// The cPanel theme in use -$rcmail_config['password_cpanel_theme'] = 'x'; +$config['password_cpanel_port'] = 2087; // XIMSS (Communigate server) Driver options // ----------------------------------------- // Host name of the Communigate server -$rcmail_config['password_ximss_host'] = 'mail.example.com'; +$config['password_ximss_host'] = 'mail.example.com'; // XIMSS port on Communigate server -$rcmail_config['password_ximss_port'] = 11024; +$config['password_ximss_port'] = 11024; // chpasswd Driver options // --------------------- // Command to use -$rcmail_config['password_chpasswd_cmd'] = 'sudo /usr/sbin/chpasswd 2> /dev/null'; +$config['password_chpasswd_cmd'] = 'sudo /usr/sbin/chpasswd 2> /dev/null'; // XMail Driver options // --------------------- -$rcmail_config['xmail_host'] = 'localhost'; -$rcmail_config['xmail_user'] = 'YourXmailControlUser'; -$rcmail_config['xmail_pass'] = 'YourXmailControlPass'; -$rcmail_config['xmail_port'] = 6017; +$config['xmail_host'] = 'localhost'; +$config['xmail_user'] = 'YourXmailControlUser'; +$config['xmail_pass'] = 'YourXmailControlPass'; +$config['xmail_port'] = 6017; // hMail Driver options // ----------------------- // Remote hMailServer configuration // true: HMailserver is on a remote box (php.ini: com.allow_dcom = true) // false: Hmailserver is on same box as PHP -$rcmail_config['hmailserver_remote_dcom'] = false; +$config['hmailserver_remote_dcom'] = false; // Windows credentials -$rcmail_config['hmailserver_server'] = array( +$config['hmailserver_server'] = array( 'Server' => 'localhost', // hostname or ip address 'Username' => 'administrator', // windows username 'Password' => 'password' // windows user password ); // Virtualmin Driver options // ------------------------- // Username format: // 0: username@domain // 1: username%domain // 2: username.domain // 3: domain.username // 4: username-domain // 5: domain-username // 6: username_domain // 7: domain_username $config['password_virtualmin_format'] = 0; // pw_usermod Driver options // -------------------------- // Use comma delimited exlist to disable password change for users // Add the following line to visudo to tighten security: // www ALL=NOPASSWORD: /usr/sbin/pw -$rcmail_config['password_pw_usermod_cmd'] = 'sudo /usr/sbin/pw usermod -h 0 -n'; +$config['password_pw_usermod_cmd'] = 'sudo /usr/sbin/pw usermod -h 0 -n'; // DBMail Driver options // ------------------- // Additional arguments for the dbmail-users call -$rcmail_config['password_dbmail_args'] = '-p sha512'; +$config['password_dbmail_args'] = '-p sha512'; // Expect Driver options // --------------------- // Location of expect binary -$rcmail_config['password_expect_bin'] = '/usr/bin/expect'; +$config['password_expect_bin'] = '/usr/bin/expect'; // Location of expect script (see helpers/passwd-expect) -$rcmail_config['password_expect_script'] = ''; +$config['password_expect_script'] = ''; // Arguments for the expect script. See the helpers/passwd-expect file for details. // This is probably a good starting default: // -telent -host localhost -output /tmp/passwd.log -log /tmp/passwd.log -$rcmail_config['password_expect_params'] = ''; +$config['password_expect_params'] = ''; // smb Driver options // --------------------- // Samba host (default: localhost) -$rcmail_config['password_smb_host'] = 'localhost'; +// Supported replacement variables: +// %n - hostname ($_SERVER['SERVER_NAME']) +// %t - hostname without the first part +// %d - domain (http hostname $_SERVER['HTTP_HOST'] without the first part) +$config['password_smb_host'] = 'localhost'; // Location of smbpasswd binary -$rcmail_config['password_smb_cmd'] = '/usr/bin/smbpasswd'; +$config['password_smb_cmd'] = '/usr/bin/smbpasswd'; + +// gearman driver options +// --------------------- +// Gearman host (default: localhost) +$config['password_gearman_host'] = 'localhost'; + + + +// Plesk/PPA Driver options +// -------------------- +// You need to allow RCP for IP of roundcube-server in Plesk/PPA Panel + +// Plesk RCP Host +$config['password_plesk_host'] = '10.0.0.5'; + +// Plesk RPC Username +$config['password_plesk_user'] = 'admin'; + +// Plesk RPC Password +$config['password_plesk_pass'] = 'password'; + +// Plesk RPC Port +$config['password_plesk_rpc_port'] = '8443'; + +// Plesk RPC Path +$config['password_plesk_rpc_path'] = 'enterprise/control/agent.php'; diff --git a/roles/webmail/files/etc/roundcube/plugins/thunderbird_labels/config.inc.php b/roles/webmail/files/etc/roundcube/plugins/thunderbird_labels/config.inc.php new file mode 100644 index 0000000..2abb423 --- /dev/null +++ b/roles/webmail/files/etc/roundcube/plugins/thunderbird_labels/config.inc.php @@ -0,0 +1,5 @@ +<?php + +$rcmail_config['tb_label_enable'] = true; + +?> diff --git a/roles/webmail/files/etc/systemd/system/stunnel4@ldap.socket b/roles/webmail/files/etc/systemd/system/stunnel4@ldap.socket new file mode 100644 index 0000000..72aa82c --- /dev/null +++ b/roles/webmail/files/etc/systemd/system/stunnel4@ldap.socket @@ -0,0 +1,11 @@ +[Unit] +Description=SSL tunnel for network daemons (instance %i) +Documentation=man:stunnel4(8) + +[Socket] +BindToDevice=lo +ListenStream=127.0.0.1:389 +NoDelay=yes + +[Install] +WantedBy=sockets.target diff --git a/roles/webmail/files/var/lib/roundcube/skins/logo_webmail.png b/roles/webmail/files/usr/share/roundcube/program/resources/fripost_logo_black.png Binary files differindex 7af586a..7af586a 100644 --- a/roles/webmail/files/var/lib/roundcube/skins/logo_webmail.png +++ b/roles/webmail/files/usr/share/roundcube/program/resources/fripost_logo_black.png diff --git a/roles/webmail/files/usr/share/roundcube/program/resources/fripost_logo_white.png b/roles/webmail/files/usr/share/roundcube/program/resources/fripost_logo_white.png Binary files differnew file mode 100644 index 0000000..c581a30 --- /dev/null +++ b/roles/webmail/files/usr/share/roundcube/program/resources/fripost_logo_white.png diff --git a/roles/webmail/handlers/main.yml b/roles/webmail/handlers/main.yml index f7e403e..8c70168 100644 --- a/roles/webmail/handlers/main.yml +++ b/roles/webmail/handlers/main.yml @@ -1,6 +1,15 @@ --- -- name: Restart stunnel - service: name=stunnel4 pattern=/usr/bin/stunnel4 state=restarted +- name: Restart stunnel@ldap + service: name=stunnel4@ldap state=restarted + +- name: Restart php7.4-fpm + service: name=php7.4-fpm state=restarted - name: Restart Nginx service: name=nginx state=restarted + +- name: Stop stunnel4@ldap.service + service: name=stunnel4@ldap.service state=stopped + +- name: Restart stunnel4@ldap.socket + service: name=stunnel4@ldap.socket state=restarted diff --git a/roles/webmail/tasks/ldap.yml b/roles/webmail/tasks/ldap.yml new file mode 100644 index 0000000..f0b461c --- /dev/null +++ b/roles/webmail/tasks/ldap.yml @@ -0,0 +1,36 @@ +- name: Copy stunnel4@ldap.socket + copy: src=etc/systemd/system/stunnel4@ldap.socket + dest=/etc/systemd/system/stunnel4@ldap.socket + owner=root group=root + mode=0644 + notify: + - systemctl daemon-reload + - Restart stunnel4@ldap.socket + +- name: Create /etc/stunnel/certs + file: path=/etc/stunnel/certs + state=directory + owner=root group=root + mode=0755 + +- name: Copy the slapd X.509 certificate + copy: src=certs/ldap/ldap.fripost.org.pem + dest=/etc/stunnel/certs/ldap.pem + owner=root group=root + mode=0644 + notify: + - Stop stunnel4@ldap.service + +- name: Configure stunnel + template: src=etc/stunnel/ldap.conf.j2 + dest=/etc/stunnel/ldap.conf + owner=root group=root + mode=0644 + notify: + - Stop stunnel4@ldap.service + +- name: Disable stunnel4@ldap.service + service: name=stunnel4@ldap.service enabled=false + +- name: Start stunnel4@ldap.socket socket + service: name=stunnel4@ldap.socket state=started enabled=true diff --git a/roles/webmail/tasks/mail.yml b/roles/webmail/tasks/mail.yml deleted file mode 100644 index 7603a56..0000000 --- a/roles/webmail/tasks/mail.yml +++ /dev/null @@ -1,39 +0,0 @@ -- name: Install stunnel - apt: pkg=stunnel4 - -- name: Auto-enable stunnel - lineinfile: dest=/etc/default/stunnel4 - regexp='^(\s*#)?\s*ENABLED=' - line='ENABLED=1' - owner=root group=root - mode=0644 - -- name: Create /etc/stunnel/certs - file: path=/etc/stunnel/certs - state=directory - owner=root group=root - mode=0755 - -- name: Copy the SMTP outgoing proxy's X.509 certificate - assemble: src=certs/postfix regexp="{{ groups.out | difference([inventory_hostname]) | join('|') }}\.pem$" remote_src=no - dest=/etc/stunnel/certs/postfix.pem - owner=root group=root - mode=0644 - register: r1 - notify: - - Restart stunnel - -- name: Configure stunnel - template: src=etc/stunnel/postfix.conf.j2 - dest=/etc/stunnel/postfix.conf - owner=root group=root - mode=0644 - register: r2 - notify: - - Restart stunnel - -- name: Start stunnel - service: name=stunnel4 pattern=/usr/bin/stunnel4 state=started - when: not (r1.changed or r2.changed) - -- meta: flush_handlers diff --git a/roles/webmail/tasks/main.yml b/roles/webmail/tasks/main.yml index 030a547..146c36f 100644 --- a/roles/webmail/tasks/main.yml +++ b/roles/webmail/tasks/main.yml @@ -1,3 +1,9 @@ -- include: mail.yml tags=postfix,mail - when: "'out' not in group_names" -- include: roundcube.yml tags=roundcube,webmail +- import_tasks: ldap.yml + when: "'LDAP_provider' not in group_names" + tags: + - ldap + - stunnel +- import_tasks: roundcube.yml + tags: + - roundcube + - webmail diff --git a/roles/webmail/tasks/roundcube.yml b/roles/webmail/tasks/roundcube.yml index 5392242..bd174bc 100644 --- a/roles/webmail/tasks/roundcube.yml +++ b/roles/webmail/tasks/roundcube.yml @@ -1,117 +1,246 @@ - name: Install PHP - apt: pkg={{ item }} + apt: pkg={{ packages }} + vars: + packages: + - php-fpm + - php-ldap + - php-gd + - php + # spell-checking + - php-enchant + +## TODO: run php as a dedicated system user +- name: Configure PHP 7.4 Zend opcache + lineinfile: dest=/etc/php/7.4/fpm/php.ini + regexp='^;?{{ item.var }}\\s*=' + line="{{ item.var }} = {{ item.value }}" + owner=root group=root + mode=0644 with_items: - - php5-fpm - - php5-ldap - - php5-gd - - php5-pspell + - { var: opcache.memory_consumption, value: 128 } + - { var: opcache.revalidate_freq, value: 60 } + notify: + - Restart php7.4-fpm + +- name: Create '_roundcube' user + user: name=_roundcube system=yes + group=nogroup + createhome=no + home=/nonexistent + shell=/usr/sbin/nologin + password=! + state=present + +- name: Delete PHP 7.4 FPM's www pool + file: path=/etc/php/7.4/fpm/pool.d/www.conf state=absent + notify: + - Restart php7.4-fpm + +- name: Configure PHP 7.4 FPM's roundcube pool + copy: src=etc/php/fpm/pool.d/roundcube.conf + dest=/etc/php/7.4/fpm/pool.d/roundcube.conf + owner=root group=root + mode=0644 + notify: + - Restart php7.4-fpm + +- name: Start php7.4-fpm + service: name=php7.4-fpm state=started + +# Make it sticky: `dpkg-statoverride --add _roundcube nogroup 0700 /var/lib/roundcube/temp` +- name: Create cache directory /var/lib/roundcube/temp + file: path=/var/lib/roundcube/temp + state=directory + owner=_roundcube group=nogroup + mode=0700 + +# Make it sticky: `dpkg-statoverride --add _roundcube adm 0750 /var/log/roundcube` +- name: Create cache directory /var/log/roundcube + file: path=/var/log/roundcube + state=directory + owner=_roundcube group=adm + mode=0750 + +- name: Install GNU Aspell and some dictionaries + apt: pkg={{ packages }} + vars: + packages: + - aspell + - aspell-da + - aspell-de + - aspell-en + - aspell-es + - aspell-fr + - aspell-no + - aspell-sv - name: Install Roundcube - apt: pkg={{ item }} default_release={{ ansible_lsb.codename }}-backports - with_items: + apt: pkg={{ packages }} + vars: + packages: - roundcube-core - roundcube-mysql - roundcube-plugins + - roundcube-plugins-extra + +- name: Install plugin dependencies + apt: pkg={{ packages }} + vars: + packages: + - php-net-sieve - name: Copy fripost's logo - copy: src=var/lib/roundcube/skins/logo_webmail.png - dest=/var/lib/roundcube/skins/logo_webmail.png + copy: src=usr/share/roundcube/program/resources/{{ item }} + dest=/usr/share/roundcube/program/resources/{{ item }} owner=root group=root mode=0644 + with_items: + - fripost_logo_black.png + - fripost_logo_white.png - name: Configure Roundcube - lineinfile: dest=/etc/roundcube/main.inc.php - "regexp=^\\s*\\$rcmail_config\\['{{ item.var }}'\\]\\s*=" - "line=$rcmail_config['{{ item.var }}'] = {{ item.value }};" - owner=root group=www-data - mode=0640 + lineinfile: dest=/etc/roundcube/config.inc.php + regexp='^\\s*\\$config\\[\'{{ item.var }}\'\\]\\s*=' + line='$config[\'{{ item.var }}\'] = {{ item.value }};' + owner=_roundcube group=nogroup + mode=0600 with_items: # Logging/Debugging - - { var: smtp_log, value: "FALSE" } + - { var: smtp_log, value: "false" } # IMAP - - { var: default_host, value: "'localhost'" } - - { var: default_port, value: "143" } - - { var: imap_auth_type, value: "'plain'" } - - { var: imap_cache, value: "null" } - - { var: messages_cache, value: "null" } + # WARNING: After hostname change update of mail_host column in users + # table is required to match old user data records with the new host. + - { var: default_host, value: "'{{ imapsvr_addr | ansible.utils.ipaddr }}'" } + - { var: default_port, value: "143" } + - { var: imap_auth_type, value: "'PLAIN'" } + - { var: imap_cache, value: "null" } + - { var: imap_timeout, value: "180" } + - { var: imap_force_ns, value: "true" } + - { var: messages_cache, value: "false" } # SMTP - - { var: smtp_server, value: "'localhost'" } - - { var: smtp_port, value: "2525" } + - { var: smtp_server, value: "'{{ postfix_instance.MSA.addr | ansible.utils.ipaddr }}'" } + - { var: smtp_port, value: "{{ postfix_instance.MSA.port }}" } + - { var: smtp_auth_type, value: "'PLAIN'" } + - { var: smtp_user, value: "'%u'" } + - { var: smtp_pass, value: "'%p'" } + # avoid timeout + - { var: max_recipients, value: "15" } # System - - { var: force_https, value: "TRUE" } - - { var: login_autocomplete, value: "2" } - - { var: skin_logo, value: "'skins/logo_webmail.png'" } - - { var: username_domain, value: "'fripost.org'" } - - { var: product_name, value: "'Fripost'" } + - { var: force_https, value: "true" } + - { var: login_autocomplete, value: "2" } + - { var: username_domain, value: "'fripost.org'" } + - { var: product_name, value: "'Fripost Webmail'" } + - { var: password_charset, value: "'UTF-8'" } + - { var: skin_logo, value: 'array("classic:*" => "program/resources/fripost_logo_black.png", "larry:*" => "program/resources/fripost_logo_white.png", "elastic:login[favicon]" => "", "elastic:login" => "program/resources/fripost_logo_black.png")' } # Plugins - - { var: plugins, value: "array('additional_message_headers','managesieve','password')" } + - { var: plugins, value: "array('archive','additional_message_headers','attachment_reminder','authres_status','emoticons','hide_blockquote','html5_notifier','managesieve','password','thunderbird_labels','vcard_attachments')" } + # Spell Checking + - { var: enable_spellcheck, value: "'true'" } + - { var: spellcheck_engine, value: "'enchant'" } + - { var: spellcheck_languages, value: "array('da','de','en','es','fr','no','sv')" } # User Interface - - { var: skin, value: "'classic'" } - - { var: language, value: "'sv_SE'" } - - { var: create_default_folders, value: "TRUE" } + - { var: skin, value: "'elastic'" } + - { var: language, value: "'sv_SE'" } + - { var: create_default_folders, value: "true" } + - { var: support_url, value: "'https://fripost.org/kontakt/'" } # User Preferences - - { var: htmleditor, value: "TRUE" } - - { var: skip_deleted, value: "TRUE" } - - { var: check_all_folders, value: "FALSE" } + - { var: htmleditor, value: "3" } + - { var: skip_deleted, value: "true" } + - { var: check_all_folders, value: "false" } + - { var: hide_blockquote_limit, value: "8" } + - { var: attachment_reminder, value: "true" } + # Don't allow overriding these settings + - { var: dont_override, value: "array('use_fallback_verifier', 'trusted_mtas')" } - name: Make the logo a hyperlink to the website lineinfile: dest=/usr/share/roundcube/skins/{{ item }}/templates/login.html - regexp='^(<roundcube:object name="logo" src="/images/roundcube_logo.png"[^>]* />)$' - line='<a href="https://fripost.org">\1</a>' + regexp='^(\s*)(<roundcube:object name="logo" src="[^"]*"[^>]* />)' + line='\1<a href="https://fripost.org">\2</a>' backrefs=yes owner=root group=root mode=0644 with_items: - classic - larry + - elastic - name: Configure Roundcube plugins - template: src=usr/share/roundcube/plugins/{{ item }}/config.inc.php.j2 - dest=/usr/share/roundcube/plugins/{{ item }}/config.inc.php + copy: src=etc/roundcube/plugins/{{ item }}/config.inc.php + dest=/etc/roundcube/plugins/{{ item }}/config.inc.php + owner=root group=root + mode=0644 + with_items: + - additional_message_headers + - authres_status + - password + - html5_notifier + - thunderbird_labels + +- name: Configure Roundcube plugins (2) + template: src=etc/roundcube/plugins/{{ item }}/config.inc.php.j2 + dest=/etc/roundcube/plugins/{{ item }}/config.inc.php owner=root group=root mode=0644 with_items: - - additional_message_headers - managesieve - - password -- name: Start php5-fpm - service: name=php5-fpm state=started +- name: Start php7.4-fpm + service: name=php7.4-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 - --ou=WWW --cn=mail.fripost.org --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/cron.d/roundcube-core + copy: src=etc/cron.d/roundcube-core + dest=/etc/cron.d/roundcube-core + owner=root group=root + mode=0644 + +- name: Tweak /etc/logrotate.d/roundcube-core + lineinfile: dest=/etc/logrotate.d/roundcube-core + regexp='^(\s*)create\s+[0-9]+\s+\S+\s+adm$' + backrefs=yes + line='\1create 0640 _roundcube adm' + owner=root group=root + mode=0644 - 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 + register: r1 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: r2 + notify: + - Restart Nginx + +- name: Copy HPKP header snippet + # never modify the pined pubkeys as we don't want to lock out our users + template: src=etc/nginx/snippets/mail.fripost.org.hpkp-hdr.j2 + dest=/etc/nginx/snippets/mail.fripost.org.hpkp-hdr + validate=/bin/false + owner=root group=root + mode=0644 register: r3 notify: - Restart Nginx - name: Start Nginx service: name=nginx state=started when: not (r1.changed or r2.changed or r3.changed) - meta: flush_handlers + +- name: Fetch Nginx's X.509 certificate + # Ensure we don't fetch private data + become: False + fetch_cmd: cmd="openssl x509 -noout -pubkey" + stdin=/etc/nginx/ssl/mail.fripost.org.pem + dest=certs/public/mail.fripost.org.pub + tags: + - genkey diff --git a/roles/webmail/templates/etc/nginx/snippets/mail.fripost.org.hpkp-hdr.j2 b/roles/webmail/templates/etc/nginx/snippets/mail.fripost.org.hpkp-hdr.j2 new file mode 120000 index 0000000..a8ba598 --- /dev/null +++ b/roles/webmail/templates/etc/nginx/snippets/mail.fripost.org.hpkp-hdr.j2 @@ -0,0 +1 @@ +../../../../../../certs/hpkp-hdr.j2
\ No newline at end of file diff --git a/roles/webmail/templates/etc/roundcube/plugins/managesieve/config.inc.php.j2 b/roles/webmail/templates/etc/roundcube/plugins/managesieve/config.inc.php.j2 new file mode 100644 index 0000000..7b424e4 --- /dev/null +++ b/roles/webmail/templates/etc/roundcube/plugins/managesieve/config.inc.php.j2 @@ -0,0 +1,100 @@ +<?php + +// managesieve server port. When empty the port will be determined automatically +// using getservbyname() function, with 4190 as a fallback. +$config['managesieve_port'] = 4190; + +// managesieve server address, default is localhost. +// Replacement variables supported in host name: +// %h - user's IMAP hostname +// %n - http hostname ($_SERVER['SERVER_NAME']) +// %d - domain (http hostname without the first part) +// For example %n = mail.domain.tld, %d = domain.tld +$config['managesieve_host'] = '{{ imapsvr_addr | ansible.utils.ipaddr }}'; + +// authentication method. Can be CRAM-MD5, DIGEST-MD5, PLAIN, LOGIN, EXTERNAL +// or none. Optional, defaults to best method supported by server. +$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. +$config['managesieve_auth_cid'] = null; + +// Optional managesieve authentication password to be used for imap_auth_cid +$config['managesieve_auth_pw'] = null; + +// use or not TLS for managesieve server connection +// Note: tls:// prefix in managesieve_host is also supported +$config['managesieve_usetls'] = false; + +// Connection scket context options +// See http://php.net/manual/en/context.ssl.php +// The example below enables server certificate validation +//$config['managesieve_conn_options'] = array( +// 'ssl' => array( +// 'verify_peer' => true, +// 'verify_depth' => 3, +// 'cafile' => '/etc/openssl/certs/ca.crt', +// ), +// ); +$config['managesieve_conn_options'] = null; + +// default contents of filters script (eg. default spam filter) +$config['managesieve_default'] = '/etc/dovecot/sieve/global'; + +// The name of the script which will be used when there's no user script +$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 +$config['managesieve_mbox_encoding'] = 'UTF-8'; + +// I need this because my dovecot (with listescape plugin) uses +// ':' delimiter, but creates folders with dot delimiter +$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. +// Note: not all extensions are implemented +$config['managesieve_disabled_extensions'] = array('reject','ereject'); + +// Enables debugging of conversation with sieve server. Logs it into <log_dir>/sieve +$config['managesieve_debug'] = false; + +// Enables features described in http://wiki.kolab.org/KEP:14 +$config['managesieve_kolab_master'] = false; + +// Script name extension used for scripts including. Dovecot uses '.sieve', +// Cyrus uses '.siv'. Doesn't matter if you have managesieve_kolab_master disabled. +$config['managesieve_filename_extension'] = '.sieve'; + +// List of reserved script names (without extension). +// Scripts listed here will be not presented to the user. +$config['managesieve_filename_exceptions'] = array(); + +// List of domains limiting destination emails in redirect action +// If not empty, user will need to select domain from a list +$config['managesieve_domains'] = array(); + +// Enables separate management interface for vacation responses (out-of-office) +// 0 - no separate section (default), +// 1 - add Vacation section, +// 2 - add Vacation section, but hide Filters section +$config['managesieve_vacation'] = 0; + +// Default vacation interval (in days). +// Note: If server supports vacation-seconds extension it is possible +// to define interval in seconds here (as a string), e.g. "3600s". +$config['managesieve_vacation_interval'] = 0; + +// Some servers require vacation :addresses to be filled with all +// user addresses (aliases). This option enables automatic filling +// of these on initial vacation form creation. +$config['managesieve_vacation_addresses_init'] = false; + +// Supported methods of notify extension. Default: 'mailto' +$config['managesieve_notify_methods'] = array('mailto'); diff --git a/roles/common/templates/etc/stunnel/munin-node.conf.j2 b/roles/webmail/templates/etc/stunnel/ldap.conf.j2 index de6c156..6fce2bc 100644 --- a/roles/common/templates/etc/stunnel/munin-node.conf.j2 +++ b/roles/webmail/templates/etc/stunnel/ldap.conf.j2 @@ -1,53 +1,41 @@ ; ************************************************************************** ; * 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 +pid = +foreground = yes ; 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 +client = yes ; Some performance tunings -socket = l:TCP_NODELAY=1 socket = r:TCP_NODELAY=1 ; Prevent MITM attacks -verify = 4 +verifyPeer = yes ; 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 +sslVersionMin = TLSv1.2 +options = NO_COMPRESSION ; Select permitted SSL ciphers -ciphers = EECDH+AES:EDH+AES:!MEDIUM:!LOW:!EXP:!aNULL:!eNULL:!SSLv2:!SSLv3:!TLSv1:!TLSv1.1 +ciphers = EECDH+AESGCM:EECDH+CHACHA20!MEDIUM!LOW!EXP!aNULL!eNULL ; ************************************************************************** ; * 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 +[ldaps] +; dummy address (socket-activated) +accept = 127.0.0.1:0 +connect = {{ ipsec[ hostvars[groups.LDAP_provider[0]].inventory_hostname_short ] }}:636 +checkHost = ldap.fripost.org +CAfile = /etc/stunnel/certs/ldap.pem ; vim:ft=dosini diff --git a/roles/webmail/templates/etc/stunnel/postfix.conf.j2 b/roles/webmail/templates/etc/stunnel/postfix.conf.j2 deleted file mode 100644 index 78922c8..0000000 --- a/roles/webmail/templates/etc/stunnel/postfix.conf.j2 +++ /dev/null @@ -1,55 +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/postfix.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/postfix/ssl/{{ ansible_fqdn }}.pem -key = /etc/postfix/ssl/{{ ansible_fqdn }}.key -client = yes -socket = a:SO_BINDTODEVICE=lo - -; 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) * -; ************************************************************************** - -[smtp] -accept = localhost:2525 -connect = outgoing.fripost.org:{{ postfix_instance.out.port }} -CAfile = /etc/stunnel/certs/postfix.pem -protocol = smtp - -; vim:ft=dosini diff --git a/roles/webmail/templates/usr/share/roundcube/plugins/additional_message_headers/config.inc.php.j2 b/roles/webmail/templates/usr/share/roundcube/plugins/additional_message_headers/config.inc.php.j2 deleted file mode 100644 index e9d0d3d..0000000 --- a/roles/webmail/templates/usr/share/roundcube/plugins/additional_message_headers/config.inc.php.j2 +++ /dev/null @@ -1,14 +0,0 @@ -<?php - -// $rcmail_config['additional_message_headers']['X-Remote-Browser'] = $_SERVER['HTTP_USER_AGENT']; -$rcmail_config['additional_message_headers']['X-Originating-IP'] = null; -// $rcmail_config['additional_message_headers']['X-RoundCube-Server'] = $_SERVER['SERVER_ADDR']; - -// if( isset( $_SERVER['MACHINE_NAME'] )) { -// $rcmail_config['additional_message_headers']['X-RoundCube-Server'] .= ' (' . $_SERVER['MACHINE_NAME'] . ')'; -// } - -// To remove (e.g. X-Sender) message header use null value -$rcmail_config['additional_message_headers']['X-Sender'] = null; - -?> 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 deleted file mode 100644 index d88a09a..0000000 --- a/roles/webmail/templates/usr/share/roundcube/plugins/managesieve/config.inc.php.j2 +++ /dev/null @@ -1,67 +0,0 @@ -<?php - -// managesieve server port -$rcmail_config['managesieve_port'] = 4190; - -// managesieve server address, default is localhost. -// Replacement variables supported in host name: -// %h - user's IMAP hostname -// %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'] = 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. -// Note: not all extensions are implemented -$rcmail_config['managesieve_disabled_extensions'] = array(); - -// Enables debugging of conversation with sieve server. Logs it into <log_dir>/sieve -$rcmail_config['managesieve_debug'] = false; - -// Enables features described in http://wiki.kolab.org/KEP:14 -$rcmail_config['managesieve_kolab_master'] = false; - -// Script name extension used for scripts including. Dovecot uses '.sieve', -// Cyrus uses '.siv'. Doesn't matter if you have managesieve_kolab_master disabled. -$rcmail_config['managesieve_filename_extension'] = '.sieve'; - -// List of reserved script names (without extension). -// Scripts listed here will be not presented to the user. -$rcmail_config['managesieve_filename_exceptions'] = array(); - -?> diff --git a/roles/wiki/files/etc/nginx/sites-available/website b/roles/wiki/files/etc/nginx/sites-available/website index 3513510..4aeb3db 100644 --- a/roles/wiki/files/etc/nginx/sites-available/website +++ b/roles/wiki/files/etc/nginx/sites-available/website @@ -1,52 +1,63 @@ server { listen 80; listen [::]:80; server_name fripost.org; server_name www.fripost.org; - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log info; + include /etc/lacme/nginx.conf; - return 301 https://fripost.org$request_uri; + access_log /var/log/nginx/www.access.log; + error_log /var/log/nginx/www.error.log info; + + location / { + return 301 https://$host$request_uri; + } } server { - listen 443; - listen [::]:443; + listen 443 ssl http2; + listen [::]:443 ssl http2; - server_name fripost.org; + server_name fripost.org; + server_name www.fripost.org; - include ssl/config; - # include the intermediate certificate, see - # - https://www.ssllabs.com/ssltest/analyze.html?d=fripost.org - # - http://nginx.org/en/docs/http/configuring_https_servers.html - ssl_certificate /etc/nginx/ssl/fripost.org.chained.pem; - ssl_certificate_key /etc/nginx/ssl/fripost.org.key; + access_log /var/log/nginx/www.access.log; + error_log /var/log/nginx/www.error.log info; - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log info; + include snippets/headers.conf; + add_header Content-Security-Policy + "default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; frame-ancestors 'none'; form-action https://www.paypal.com/; base-uri fripost.org www.fripost.org"; + + include snippets/ssl.conf; + ssl_certificate ssl/www.fripost.org.pem; + ssl_certificate_key ssl/www.fripost.org.key; + include snippets/fripost.org.hpkp-hdr; + + gzip on; + gzip_vary on; + gzip_min_length 256; + gzip_types application/font-woff application/font-woff2 application/javascript application/json application/xml image/svg+xml image/x-icon text/css text/plain; location / { try_files $uri $uri/ =404; index index.html; root /var/lib/ikiwiki/public_html/fripost-wiki/website; } + location = /ikiwiki.cgi { internal; } location /static/ { + expires 30d; + try_files $uri =404; alias /var/lib/ikiwiki/public_html/fripost-wiki/static/; } location /material/ { alias /var/www/fripost.org/material/; } location /minutes/ { alias /var/www/fripost.org/minutes/; } location /.well-known/autoconfig/ { alias /var/www/fripost.org/autoconfig/; } - - location = /ikiwiki.cgi { - return 403; - } } diff --git a/roles/wiki/files/etc/nginx/sites-available/wiki b/roles/wiki/files/etc/nginx/sites-available/wiki index 304ea1a..b201ef5 100644 --- a/roles/wiki/files/etc/nginx/sites-available/wiki +++ b/roles/wiki/files/etc/nginx/sites-available/wiki @@ -1,54 +1,59 @@ server { listen 80; listen [::]:80; server_name wiki.fripost.org; + include /etc/lacme/nginx.conf; + access_log /var/log/nginx/wiki.access.log; error_log /var/log/nginx/wiki.error.log info; location / { location ~ ^/website(/.*)?$ { return 302 $scheme://fripost.org$1; } - try_files $uri $uri/ =404; - index index.html; - root /var/lib/ikiwiki/public_html/fripost-wiki; - } - - location = /ikiwiki.cgi { - return 302 https://$host$request_uri; + return 301 https://$host$request_uri; } } server { - listen 443; - listen [::]:443; + listen 443 ssl http2; + listen [::]:443 ssl http2; server_name wiki.fripost.org; - include ssl/config; - # include the intermediate certificate, see - # - https://www.ssllabs.com/ssltest/analyze.html?d=wiki.fripost.org - # - http://nginx.org/en/docs/http/configuring_https_servers.html - ssl_certificate /etc/nginx/ssl/fripost.org.chained.pem; - ssl_certificate_key /etc/nginx/ssl/fripost.org.key; - access_log /var/log/nginx/wiki.access.log; error_log /var/log/nginx/wiki.error.log info; + include snippets/headers.conf; + add_header Content-Security-Policy + "default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri wiki.fripost.org"; + + include snippets/ssl.conf; + ssl_certificate ssl/www.fripost.org.pem; + ssl_certificate_key ssl/www.fripost.org.key; + include snippets/fripost.org.hpkp-hdr; + + gzip on; + gzip_vary on; + gzip_min_length 256; + gzip_types application/font-woff application/font-woff2 application/javascript application/json application/xml image/svg+xml image/x-icon text/css text/plain; + + root /var/lib/ikiwiki/public_html/fripost-wiki; + + location /static/ { expires 30d; try_files $uri =404; } location / { location ~ ^/website(/.*)?$ { return 302 $scheme://fripost.org$1; } - try_files $uri $uri/ =404; index index.html; - root /var/lib/ikiwiki/public_html/fripost-wiki; + try_files $uri $uri/ =404; } location = /ikiwiki.cgi { - fastcgi_param DOCUMENT_ROOT /var/lib/ikiwiki/public_html/fripost-wiki; + fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SCRIPT_FILENAME /var/lib/ikiwiki/public_html/ikiwiki.cgi; fastcgi_index ikiwiki.cgi; - include fastcgi/params; - fastcgi_pass unix:/var/run/fcgiwrap.socket; - gzip off; + include snippets/fastcgi.conf; + fastcgi_pass unix:/run/ikiwiki.socket; + gzip off; # protect against BREACH } } diff --git a/roles/wiki/files/etc/systemd/system/ikiwiki.service b/roles/wiki/files/etc/systemd/system/ikiwiki.service new file mode 100644 index 0000000..3ee7d66 --- /dev/null +++ b/roles/wiki/files/etc/systemd/system/ikiwiki.service @@ -0,0 +1,23 @@ +[Unit] +Description=wiki compiler (CGI script) +Documentation=https://ikiwiki.info/ + +[Service] +User=ikiwiki +Group=ikiwiki +ExecStart=/usr/sbin/fcgiwrap +SyslogIdentifier=ikiwiki +# +# Hardening +NoNewPrivileges=yes +ReadWriteDirectories=/var/lib/ikiwiki/fripost-wiki +ReadWriteDirectories=/var/lib/ikiwiki/public_html/fripost-wiki +PrivateDevices=yes +ProtectHome=yes +ProtectSystem=strict +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes + +[Install] +WantedBy=multi-user.target diff --git a/roles/wiki/files/etc/systemd/system/ikiwiki.socket b/roles/wiki/files/etc/systemd/system/ikiwiki.socket new file mode 100644 index 0000000..8dc1a0e --- /dev/null +++ b/roles/wiki/files/etc/systemd/system/ikiwiki.socket @@ -0,0 +1,11 @@ +[Unit] +Description=wiki compiler (CGI script) +Documentation=https://ikiwiki.info/ + +[Socket] +ListenStream=%t/ikiwiki.socket +SocketUser=www-data +SocketMode=0600 + +[Install] +WantedBy=sockets.target diff --git a/roles/wiki/files/var/lib/ikiwiki/IkiWiki/Plugin/pandoc.pm b/roles/wiki/files/var/lib/ikiwiki/IkiWiki/Plugin/pandoc.pm new file mode 100644 index 0000000..34bdd89 --- /dev/null +++ b/roles/wiki/files/var/lib/ikiwiki/IkiWiki/Plugin/pandoc.pm @@ -0,0 +1,818 @@ +#!/usr/bin/env perl + +package IkiWiki::Plugin::pandoc; + +use warnings; +use strict; +use IkiWiki; +use FileHandle; +use IPC::Open2; +use File::Path qw/make_path/; +use JSON; + +# activate with 'generate_$format' in meta; turn on all with 'generate_all_formats'. +my %extra_formats = ( + pdf => { ext=>'pdf', label=>'PDF', format=>'latex', extra=>[], order=>1 }, + docx => { ext=>'docx', label=>'DOCX', format=>'docx', extra=>[], order=>2 }, + odt => { ext=>'odt', label=>'ODT', format=>'odt', extra=>[], order=>3 }, + beamer => { ext=>'beamer.pdf', label=>'Beamer', format=>'beamer', extra=>[], order=>4 }, + revealjs => { ext=>'revealjs.html', label=>'RevealJS', format=>'revealjs', extra=>['--self-contained'], order=>5 }, + epub => { ext=>'epub', label=>'EPUB', format=>'epub3', extra=>[], order=>6 }, + latex => { ext=>'tex', label=>'LaTeX', format=>'latex', extra=>['--standalone'], order=>7 }, +); + +my @scalar_meta_keys = qw/ + title date bibliography csl subtitle abstract summary description + version lang locale titlesort tag fripost_debug_inner + /; + +my @list_meta_keys = qw/ + author + /; + +my @hash_meta_keys = qw/ + experiment + /; + +my @list_hash_meta_keys = qw/ + references + /; + +sub import { + my $markdown_ext = $config{pandoc_markdown_ext} || "mdwn"; + + # May be both a string with a single value, a string containing commas or an arrayref + if ($markdown_ext =~ /,/) { + $markdown_ext = [split /\s*,\s*/, $markdown_ext]; + } + + hook(type => "getsetup", id => "pandoc", call => \&getsetup); + hook(type => "pagetemplate", id => "pandoc", call => \&pagetemplate); + hook(type => "pageactions", id => "pandoc", call => \&pageactions); + + if (ref $markdown_ext eq 'ARRAY') { + foreach my $mde (@$markdown_ext) { + hook(type => 'htmlize', id => $mde, + call => sub{ htmlize("markdown", @_) }); + } + } else { + hook(type => "htmlize", id => $markdown_ext, + call => sub { htmlize("markdown", @_) }); + } + if ($config{pandoc_latex}) { + hook(type => "htmlize", id => "tex", + call => sub { htmlize("latex", @_) }); + } + if ($config{pandoc_rst}) { + hook(type => "htmlize", id => "rst", + call => sub { htmlize("rst", @_) }); + } + if ($config{pandoc_textile}) { + hook(type => "htmlize", id => "textile", + call => sub { htmlize("textile", @_) }); + } + if ($config{pandoc_mediawiki}) { + hook(type => "htmlize", id => "mediawiki", + call => sub { htmlize("mediawiki", @_) }); + } + if ($config{pandoc_opml}) { + hook(type => "htmlize", id => "opml", + call => sub { htmlize("opml", @_) }); + } + if ($config{pandoc_org}) { + hook(type => "htmlize", id => "org", + call => sub { htmlize("org", @_) }); + } +} + + +sub getsetup () { + return + plugin => { + safe => 1, + rebuild => 1, + }, + pandoc_command => { + type => "string", + example => "/usr/local/bin/pandoc", + description => "Path to pandoc executable", + safe => 1, + rebuild => 0, + }, + pandoc_citeproc => { + type => "string", + example => "/usr/local/bin/pandoc-citeproc", + description => "Path to pandoc-citeproc executable", + safe => 1, + rebuild => 0, + }, + pandoc_markdown_ext => { + type => "string", + example => "mdwn,md,markdown", + description => "File extension(s) for Markdown files handled by Pandoc", + safe => 1, + rebuild => 1, + }, + pandoc_markdown_fmt => { + type => "string", + example => "markdown", + description => "Format string to use when processing files handled by Pandoc.", + safe => 1, + rebuild => 1, + }, + pandoc_latex => { + type => "boolean", + example => 0, + description => "Enable Pandoc processing of LaTeX documents (extension=tex)", + safe => 1, + rebuild => 1, + }, + pandoc_rst => { + type => "boolean", + example => 0, + description => "Enable Pandoc processing of reStructuredText documents (extension=rst)", + safe => 1, + rebuild => 1, + }, + pandoc_textile => { + type => "boolean", + example => 0, + description => "Enable Pandoc processing of Textile documents (extension=textile)", + safe => 1, + rebuild => 1, + }, + pandoc_mediawiki => { + type => "boolean", + example => 0, + description => "Enable Pandoc processing of MediaWiki documents (extension=mediawiki)", + safe => 1, + rebuild => 1, + }, + pandoc_org => { + type => "boolean", + example => 0, + description => "Enable Pandoc processing of Emacs org-mode documents (extension=org)", + safe => 1, + rebuild => 1, + }, + pandoc_opml => { + type => "boolean", + example => 0, + description => "Enable Pandoc processing of OPML documents (extension=opml)", + safe => 1, + rebuild => 1, + }, + pandoc_smart => { + type => "boolean", + example => 1, + description => "Use smart quotes, dashes, and ellipses", + safe => 1, + rebuild => 1, + }, + pandoc_obfuscate => { + type => "boolean", + example => 1, + description => "Obfuscate emails", + safe => 1, + rebuild => 1, + }, + pandoc_html5 => { + type => "boolean", + example => 0, + description => "Generate HTML5", + safe => 1, + rebuild => 1, + }, + pandoc_ascii => { + type => "boolean", + example => 0, + description => "Generate ASCII instead of UTF8", + safe => 1, + rebuild => 1, + }, + pandoc_html_extra_options => { + type => "internal", + default => [], + description => "List of extra pandoc options for html", + safe => 0, + rebuild => 0, + }, + pandoc_numsect => { + type => "boolean", + example => 0, + description => "Number sections", + safe => 1, + rebuild => 1, + }, + pandoc_sectdiv => { + type => "boolean", + example => 0, + description => "Attach IDs to section DIVs instead of Headers", + safe => 1, + rebuild => 1, + }, + pandoc_codeclasses => { + type => "string", + example => "", + description => "Classes to use for indented code blocks", + safe => 1, + rebuild => 1, + }, + pandoc_math => { + type => "string", + example => "mathjax", + description => "How to process TeX math (mathjax, katex, mathml, mathjs, latexmathml, asciimathml, mimetex, webtex)", + safe => 1, + rebuild => 1, + }, + pandoc_math_custom_js => { + type => "string", + example => "", + description => "Link to local/custom javascript for math (or to server-side script for mimetex and webtex)", + safe => 1, + rebuild => 1, + }, + pandoc_math_custom_css => { + type => "string", + example => "", + description => "Link to local/custom CSS for math (requires appropriate pandoc_math setting)", + safe => 1, + rebuild => 1, + }, + pandoc_bibliography => { + type => "string", + example => "", + description => "Path to default bibliography file", + safe => 1, + rebuild => 1, + }, + pandoc_csl => { + type => "string", + example => "", + description => "Path to CSL file (for references and bibliography)", + safe => 1, + rebuild => 1, + }, + pandoc_csl_default_lang => { + type => "string", + example => "", + description => "Default language code (RFC 1766) for citations processing", + safe => 1, + rebuild => 1, + }, + pandoc_filters => { + type => "string", + example => "", + description => "A comma-separated list of custom pandoc filters", + safe => 1, + rebuild => 1, + }, + pandoc_latex_template => { + type => "string", + example => "", + description => "Path to pandoc template for LaTeX and normal PDF output", + safe => 1, + rebuild => 0, + }, + pandoc_latex_extra_options => { + type => "internal", + default => [], + description => "List of extra pandoc options for LaTeX (and PDF) generation", + safe => 0, + rebuild => 0, + }, + pandoc_beamer_template => { + type => "string", + example => "", + description => "Path to pandoc template for Beamer PDF output", + safe => 1, + rebuild => 0, + }, + pandoc_beamer_extra_options => { + type => "internal", + default => [], + description => "List of extra pandoc options for Beamer PDF generation", + safe => 0, + rebuild => 0, + }, + pandoc_pdf_export_cleanup => { + type => "boolean", + example => "0", + description => "Whether to clean up LaTeX auxiliary files after PDF generation", + safe => 0, + rebuild => 0, + }, + pandoc_revealjs_template => { + type => "string", + example => "", + description => "Path to pandoc template for Reveal.js slides output", + safe => 1, + rebuild => 0, + }, + pandoc_revealjs_extra_options => { + type => "internal", + default => [], + description => "List of extra pandoc options for Reveal.js slides generation", + safe => 0, + rebuild => 0, + }, + pandoc_docx_template => { + type => "string", + example => "", + description => "Path to pandoc template for MS Word (docx) output", + safe => 1, + rebuild => 0, + }, + pandoc_docx_extra_options => { + type => "internal", + default => [], + description => "List of extra pandoc options for DOCX generation", + safe => 0, + rebuild => 0, + }, + pandoc_odt_template => { + type => "string", + example => "", + description => "Path to pandoc template for OpenDocument (odt) output", + safe => 1, + rebuild => 0, + }, + pandoc_odt_extra_options => { + type => "internal", + default => [], + description => "List of extra pandoc options for ODT generation", + safe => 0, + rebuild => 0, + }, + pandoc_epub_template => { + type => "string", + example => "", + description => "Path to pandoc template for EPUB3 output", + safe => 1, + rebuild => 0, + }, + pandoc_epub_extra_options => { + type => "internal", + default => [], + description => "List of extra pandoc options for EPUB3 generation", + safe => 0, + rebuild => 0, + }; +} + + +sub htmlize ($@) { + my $format = shift; + my %params = @_; + my $page = $params{page}; + my $htmlformat = 'html'; + + local(*PANDOC_IN, *JSON_IN, *JSON_OUT, *PANDOC_OUT); + my @args = (); + + # The default assumes pandoc is in PATH + my $command = $config{pandoc_command} || "pandoc"; + + if ($config{pandoc_smart}) { + push @args, '--smart'; + } + + if ($config{pandoc_obfuscate}) { + push @args, '--email-obfuscation=references'; + } else { + push @args, '--email-obfuscation=none'; + } + + if ($config{pandoc_html5}) { + $htmlformat = 'html5'; + } + + if ($config{pandoc_ascii}) { + push @args, '--ascii'; + } + + if ($config{pandoc_numsect}) { + push @args, '--number-sections'; + } + + if ($config{pandoc_sectdiv}) { + push @args, '--section-divs'; + } + + if ($config{pandoc_codeclasses} && ($config{pandoc_codeclasses} ne "")) { + push @args, '--indented-code-classes=' . $config{pandoc_codeclasses}; + } + + # How to process math. Normally either mathjax or katex. + my %mathconf = map {($_=>"--$_")} qw( + jsmath mathjax latexmathml asciimathml mathml katex mimetex webtex + ); + my %with_urls = qw/mimetex 1 webtex 1/; + my $mathopt = $1 if $config{pandoc_math} && $config{pandoc_math} =~ /(\w+)/; + my $custom_js = $config{pandoc_math_custom_js} || ''; + # cleanup pandoc-prefixed keys from persistent meta + if (ref $pagestate{$page}{meta} eq 'HASH') { + my @delkeys = (); + foreach my $k (%{ $pagestate{$page}{meta} }) { + push @delkeys, $k if $k =~ /^pandoc_/; + } + delete $pagestate{$page}{meta}{$_} for @delkeys; + } + if ($mathopt && $mathconf{$mathopt}) { + if ($with_urls{$mathopt} && $custom_js) { + # In these cases, the 'custom js' is a misnomer: actually a server-side script + push @args, $mathconf{$mathopt} ."=". $custom_js; + } else { + push @args, $mathconf{$mathopt}; + } + $pagestate{$page}{meta}{"pandoc_math"} = $mathopt; + $pagestate{$page}{meta}{"pandoc_math_$mathopt"} = 1; + $pagestate{$page}{meta}{"pandoc_math_custom_js"} = $custom_js if $custom_js; + } + # Convert to intermediate JSON format so that the title block + # can be parsed out + # We must omit the 'bibliography' parameter here, otherwise the list of + # references will be doubled. + my $markdown_fmt = $config{pandoc_markdown_fmt} || 'markdown'; + my $to_json_pid = open2(*JSON_OUT, *PANDOC_OUT, $command, + '-f', $markdown_fmt, + '-t', 'json', + @args); + error("Unable to open $command") unless $to_json_pid; + + # Workaround for perl bug (#376329) + require Encode; + my $content = Encode::encode_utf8($params{content}); + + # Protect inline plugin placeholders from being mangled by pandoc: + $content =~ s{<div class="inline" id="(\d+)"></div>} + {::INLINE::PLACEHOLDER::$1::}g; + + print PANDOC_OUT $content; + close PANDOC_OUT; + + my $json_content = <JSON_OUT>; + close JSON_OUT; + + waitpid $to_json_pid, 0; + + # Parse the title block out of the JSON and set the meta values + my $meta = undef; + my $decoded_json = decode_json($json_content); + # The representation of the meta block changed in pandoc version 1.18 + if (ref $decoded_json eq 'HASH' && $decoded_json->{'meta'}) { + $meta = $decoded_json->{'meta'} || {}; # post-1.18 version + } elsif (ref $decoded_json eq 'ARRAY') { + $meta = $decoded_json->[0]->{'unMeta'} || {}; # pre-1.18 version + } + unless ($meta) { + warn "WARNING: Unexpected format for meta block. Incompatible version of Pandoc?\n"; + } + + # Get some selected meta attributes, more specifically: + # (title date bibliography csl subtitle abstract summary description + # version lang locale references author [+ num_authors primary_author]), + # as well as some configuration options (generate_*, *_extra_options, *_template). + + my @format_keys = grep { $_ ne 'pdf' } keys %extra_formats; + my %scalar_meta = map { ($_=>undef) } @scalar_meta_keys; + $scalar_meta{$_.'_template'} = undef for @format_keys; + my %bool_meta = map { ("generate_$_"=>0) } keys %extra_formats; + my %list_meta = map { ($_=>[]) } ( + @list_meta_keys, @list_hash_meta_keys, @hash_meta_keys); + $list_meta{$_.'_extra_options'} = [] for @format_keys; + my $have_bibl = 0; + foreach my $k (keys %scalar_meta) { + next unless $meta->{$k}; + $scalar_meta{$k} = compile_string($meta->{$k}->{c}); + # NB! Note that this is potentially risky, since pagestate is sticky, and + # we only cleanup the pandoc_* values in {meta}. + $pagestate{$page}{meta}{$k} = $scalar_meta{$k}; + $pagestate{$page}{meta}{"pandoc_$k"} = $pagestate{$page}{meta}{$k}; + } + foreach my $k (keys %bool_meta) { + my $gen_all = $meta->{generate_all_formats} || {}; + next unless $meta->{$k} || $gen_all->{c}; + my $val = $meta->{$k} ? $meta->{$k}->{c} : $gen_all->{c}; + # simplifies matters with JSON::(PP::)Boolean objects + $val = 1 if $val == 1 || $val eq 'true'; + if (ref $val || $val =~ /^\s*(?:off|no|false|0)\s*$/i) { + $bool_meta{$k} = 0; + } else { + $bool_meta{$k} = 1; + $pagestate{$page}{meta}{"pandoc_$k"} = 1; + } + } + foreach my $k (keys %list_meta) { + next unless $meta->{$k}; + $list_meta{$k} = unwrap_c($meta->{$k}); + $list_meta{$k} = [$list_meta{$k}] unless ref $list_meta{$k} eq 'ARRAY'; + $have_bibl = 1 if $k eq 'references'; + $pagestate{$page}{meta}{$k} = $list_meta{$k}; + $pagestate{$page}{meta}{"pandoc_$k"} = $list_meta{$k}; + } + # Try to add other keys as scalars, with pandoc_ prefix only. + foreach my $k (keys %$meta) { + next if exists $scalar_meta{$k} || exists $list_meta{$k}; + eval { + $pagestate{$page}{meta}{"pandoc_$k"} = compile_string($meta->{$k}->{c}); + }; + } + my $num_authors = scalar @{ $list_meta{author} }; + $scalar_meta{num_authors} = $num_authors; + $pagestate{$page}{meta}{num_authors} = $num_authors; + if ($num_authors) { + $scalar_meta{primary_author} = $list_meta{author}->[0]; + $pagestate{$page}{meta}{author} = join(', ', @{$list_meta{author}}); + $pagestate{$page}{meta}{pandoc_primary_author} = $scalar_meta{primary_author} + } + + # The bibliography may be set in a Meta block in the page or in the .setup file. + # If both are present, the Meta block has precedence. + for my $bibl ($scalar_meta{bibliography}, $config{pandoc_bibliography}) { + if ($bibl) { + $have_bibl = 1; + $pagestate{$page}{meta}{pandoc_bibliography} = $bibl; + push @args, '--bibliography='.$bibl; + last; + } + } + # Similarly for the CSL file... + for my $cslfile ($scalar_meta{csl}, $config{pandoc_csl}) { + if ($cslfile) { + $pagestate{$page}{meta}{pandoc_csl} = $cslfile; + push @args, '--csl='.$cslfile; + last; + } + } + # If a default CSL language is specified, add that to args, + # (unless it is overridden by meta) + unless ($scalar_meta{lang} || $scalar_meta{locale}) { + if ($config{pandoc_csl_default_lang}) { + push @args, "--metadata=lang:".$config{pandoc_csl_default_lang}; + } + } + # Turn on the pandoc-citeproc filter if either global bibliography, + # local bibliography or a 'references' key in Meta is present. + if ($have_bibl) { + my $citeproc = $config{pandoc_citeproc} || 'pandoc-citeproc'; + push @args, "--filter=$citeproc"; + } + + # Other pandoc filters. Note that currently there is no way to + # configure a filter to run before pandoc-citeproc has done its work. + if ($config{pandoc_filters}) { + my @filters = split /\s*,\s*/, $config{pandoc_filters}; + s/^["']//g for @filters; # get rid of enclosing quotes + foreach my $filter (@filters) { + push @args, "--filter=$filter"; + } + } + + # html_extra_options my be set in Meta block in the page or in the .setup + # file. If both are present, the Meta block has precedence, even if it is + # an empty list + my @html_args = @args; + if (ref $meta->{html_extra_options}{c} eq 'ARRAY') { + if (ref unwrap_c($meta->{html_extra_options}{c}) eq 'ARRAY') { + push @html_args, @{unwrap_c($meta->{html_extra_options}{c})}; + } else { + push @html_args, unwrap_c($meta->{html_extra_options}{c}); + } + } elsif (ref $config{'pandoc_html_extra_options'} eq 'ARRAY') { + push @html_args, @{$config{'pandoc_html_extra_options'}}; + } + + my $to_html_pid = open2(*PANDOC_IN, *JSON_IN, $command, + '-f', 'json', + '-t', $htmlformat, + @html_args); + error("Unable to open $command") unless $to_html_pid; + + $pagestate{$page}{pandoc_extra_formats} = {}; + foreach my $ext (keys %extra_formats) { + if ($bool_meta{"generate_$ext"}) { + export_file($page, $ext, $json_content, $command, @args); + } else { + remove_exported_file($page, $ext); + } + } + + print JSON_IN $json_content; + close JSON_IN; + + my @html = <PANDOC_IN>; + close PANDOC_IN; + + waitpid $to_html_pid, 0; + + $content = Encode::decode_utf8(join('', @html)); + + # Reinstate placeholders for inline plugin: + $content =~ s{::INLINE::PLACEHOLDER::(\d+)::} + {<div class="inline" id="$1"></div>}g; + + return $content; +} + + +sub pagetemplate (@) { + my %params = @_; + my $page = $params{page}; + my $template = $params{template}; + foreach my $k (keys %{$pagestate{$page}{meta}}) { + next unless + (grep {/^$k$/} ( + @scalar_meta_keys, @list_meta_keys, + @hash_meta_keys, @list_hash_meta_keys)) || + ($k =~ /^(pandoc_)/); + $template->param($k => $pagestate{$page}{meta}{$k}); + } + return $template; +} + +sub pageactions { + my %args = @_; + my $page = $args{page}; + my @links = (); + return unless $pagestate{$page}{pandoc_extra_formats}; + my @exts = sort { + $extra_formats{$a}->{order} <=> $extra_formats{$b}->{order} + } keys %{ $pagestate{$page}{pandoc_extra_formats} }; + foreach my $ext (@exts) { + my $url = $pagestate{$page}{pandoc_extra_formats}{$ext}; + next unless $url; + my $label = $extra_formats{$ext}->{label} || $ext; + push @links, qq[ + <a href="$url" + class="extra-format-link" + title="Download $label version of this page" + target="_blank">$label</a> + ]; + } + return @links; +} + +sub export_file { + my ($page, $ext, $json_content, $command, @args) = @_; + my ($export_path, $export_url) = _export_file_path_and_url($page, $ext); + my $subdir = $1 if $export_path =~ /(.*)\//; + my @extra_args = @{ $extra_formats{$ext}->{extra} }; + my $eopt = $ext eq 'pdf' ? 'latex' : $ext; + # Note that template in meta OVERRIDES template in config, + # while extra_options in meta are ADDED to extra_options in config. + my $template = $pagestate{$page}{meta}{"pandoc_".$eopt."_template"} + || $config{"pandoc_".$eopt."_template"} || ''; + if ($template) { + push @extra_args, ($ext =~ /^(docx|odt)$/ + ? "--reference-$ext=$template" + : "--template=$template"); + } + my $conf_extra = $config{"pandoc_".$eopt."_extra_options"}; + my $conf_extra_custom = $pagestate{$page}{meta}{"pandoc_".$eopt."_extra_options"}; + foreach my $cnf ($conf_extra, $conf_extra_custom) { + if (ref $cnf eq 'ARRAY' && @$cnf) { + push @extra_args, @$cnf; + } + } + my $pdf_cleanup = 0; + if (defined $pagestate{$page}{meta}{"pandoc_pdf_export_cleanup"}) { + $pdf_cleanup = $pagestate{$page}{meta}{"pandoc_pdf_export_cleanup"}; + } elsif ($config{"pandoc_pdf_export_cleanup"}) { + $pdf_cleanup = 1; + } + # If the user has asked for native LaTeX bibliography handling in the + # extra_args for this export format (using --biblatex or --natbib), + # some extra care is needed. Among other things, we need an external + # tool for PDF generation. In this case, $indirect_pdf will be true. + my %maybe_non_citeproc = qw/latex 1 pdf 1 beamer 1/; + my $indirect_pdf = 0; + if ($maybe_non_citeproc{$ext} && grep { /^(?:--biblatex|--natbib)$/ } @extra_args) { + $indirect_pdf = 1 unless $ext eq 'latex'; # both for pdf and beamer + @args = grep { ! /--filter=.*pandoc-citeproc/ } @args; + } + eval { + if ($subdir && !-d $subdir) { + make_path($subdir) or die "Could not make_path $subdir: $!"; + } + my $to_format = $extra_formats{$ext}->{format} || $ext; + my $tmp_export_path = $export_path; + $tmp_export_path =~ s/\.pdf$/.tex/ if $indirect_pdf; + open(EXPORT, "|-", + $command, + '-f' => 'json', + '-t' => $to_format, + '-o' => $tmp_export_path, + @args, @extra_args) or die "Could not open pipe for $ext: $!"; + print EXPORT $json_content; + close EXPORT or die "Could not close pipe for $ext: $!"; + if ($indirect_pdf && $tmp_export_path ne $export_path) { + my @latexmk_args = qw(-quiet -silent); + if (grep { /xelatex/ } @extra_args) { + push @latexmk_args, '-xelatex'; + } elsif (grep { /lualatex/ } @extra_args) { + push @latexmk_args, '-lualatex'; + } else { + push @latexmk_args, '-pdf'; + } + chdir $subdir or die "Could not chdir to $subdir: $!"; + my $plain_fn = $1 if $tmp_export_path =~ /([^\/]+)$/; + $plain_fn =~ s/\.tex//; + system('latexmk', @latexmk_args, $plain_fn) == 0 + or die "Could not run latexmk for pdf generation ($export_path): $!"; + if ($pdf_cleanup) { + system('latexmk', '-c', '-quiet', '-silent', $plain_fn) == 0 + or die "Could not run latexmk for cleanup ($export_path): $!"; + # These files are apparently not cleaned up by latexmk -c. + foreach ('run.xml', 'bbl') { + my $fn = "$subdir/$plain_fn.$_"; + unlink($fn) if -f $fn; + } + } + } + $pagestate{$page}{pandoc_extra_formats}{$ext} = $export_url; + }; + if ($@) { + warn "EXPORT ERROR FOR $page (format: $ext): $@\n"; + } +} + +sub remove_exported_file { + my ($page, $ext) = @_; + my ($export_path, $export_url) = _export_file_path_and_url($page, $ext); + if (-f $export_path) { + eval { unlink $export_path or die "Could not unlink $export_path: $!" }; + if ($@) { + warn "WARNING: remove_exported_file; page=$page, ext=$ext: $@\n"; + } + } +} + +sub _export_file_path_and_url { + my ($page, $ext) = @_; + # the html file will end up in "$destdir/$page/index.html", + # while e.g. a pdf will be in "$destdir/$page/$page_minus_dirs.pdf". + my $extension = $extra_formats{$ext}->{ext} || $ext; + my $destdir = $config{destdir} || '.'; + my $page_minus_dirs = $1 if $page =~ /([^\/]*)$/; + $page_minus_dirs ||= 'index'; + my $export_path = "$destdir/$page/$page_minus_dirs.$extension"; + my $export_url = $config{url}; + $export_url .= "/" unless $export_url =~ /\/$/; + $export_url .= "$page/$page_minus_dirs.$extension"; + return ($export_path, $export_url); +} + + +## compile_string and unwrap_c are used to make the meta data structures +## easier to work with for perl. + +sub compile_string { + # Partially represents an item from the data structure in meta as a string. + my @uncompiled = @_; + return $uncompiled[0] if @uncompiled==1 && !ref($uncompiled[0]); + @uncompiled = @{$uncompiled[0]} if @uncompiled==1 && ref $uncompiled[0] eq 'ARRAY'; + my $compiled_string = ''; + foreach my $word_or_space (@uncompiled) { + next unless ref $word_or_space eq 'HASH'; + my $type = $word_or_space->{'t'} || ''; + $compiled_string .= compile_string(@{ $word_or_space->{c} }) if $type eq 'MetaInlines'; + next unless $type eq 'Str' || $type eq 'Space' || $type eq 'MetaString'; + $compiled_string .= $type eq 'Space' ? ' ' : $word_or_space->{c}; + } + return $compiled_string; +} +sub unwrap_c { + # Unwrap pandoc's MetaLists, MetaInlines, etc. + # Finds the deepest-level scalar value for 'c' in the data structure. + # Lists with one element are replaced with the scalar, lists with more + # than one element are returned as an arrayref containing scalars. + # + # Elements containing hash as keys are unwrapped. That is to + # support *MetaList* containing *MetaMap* with keys pointing to + # *MetaInlines*. Reference are examples of that structure. (hash unwrap) + # + my $container = shift; + if (ref $container eq 'ARRAY' && @$container > 1) { + if (ref $container->[0] eq 'HASH' && $container->[0]->{t} =~ /^(?:Str|Space)$/) { + # handles scalar author fields + return join('', map { compile_string($_) } @$container); + } else { + return [map {unwrap_c($_)} @$container]; + } + } elsif (ref $container eq 'ARRAY' && @$container) { + return unwrap_c($container->[0]); + } elsif (ref $container eq 'ARRAY') { + return; + } elsif (ref $container eq 'HASH' && $container->{c}) { + return unwrap_c($container->{c}); + } elsif (ref $container eq 'HASH' && keys $container->%*) { # (hash unwrap) + return {map { $_ => unwrap_c($container->{$_}) } keys $container->%*}; + } elsif (ref $container) { + return; + } else { + return $container; + } +} + +1; diff --git a/roles/wiki/files/var/lib/ikiwiki/fripost-wiki.setup b/roles/wiki/files/var/lib/ikiwiki/fripost-wiki.setup index 6768629..4af3d59 100644 --- a/roles/wiki/files/var/lib/ikiwiki/fripost-wiki.setup +++ b/roles/wiki/files/var/lib/ikiwiki/fripost-wiki.setup @@ -6,70 +6,73 @@ # wrappers and build the wiki. # # Remember to re-run ikiwiki --setup any time you edit this file. # # name of the wiki wikiname: Fripost wiki # contact email for wiki adminemail: admin@fripost.org # users who are wiki admins adminuser: - gustaveek - Grégoire - moza # users who are banned from the wiki banned_users: [] # where the source of the wiki is located srcdir: /var/lib/ikiwiki/fripost-wiki # where to build the wiki destdir: /var/lib/ikiwiki/public_html/fripost-wiki # base url to the wiki -url: http://wiki.fripost.org +url: https://wiki.fripost.org # url to the ikiwiki.cgi -cgiurl: http://wiki.fripost.org/ikiwiki.cgi +cgiurl: https://wiki.fripost.org/ikiwiki.cgi # do not adjust cgiurl if CGI is accessed via different URL reverse_proxy: 0 # filename of cgi wrapper to generate cgi_wrapper: /var/lib/ikiwiki/public_html/ikiwiki.cgi # mode for cgi_wrapper (can safely be made suid) -cgi_wrappermode: 06755 +cgi_wrappermode: 0755 # number of seconds to delay CGI requests when overloaded cgi_overload_delay: '' # message to display when overloaded (may contain html) cgi_overload_message: '' # enable optimization of only refreshing committed changes? only_committed_changes: 0 # rcs backend to use rcs: git # plugins to add to the default configuration add_plugins: - goodstuff - websetup - 404 - remove - attachment - highlight - toc - htmlbalance - comments + - notifyemail + - getsource ### - isWebsite + - pandoc # plugins to disable disable_plugins: - smiley # additional directory to search for template files templatedir: /usr/share/ikiwiki/templates # base wiki source location underlaydir: /usr/share/ikiwiki/basewiki # display verbose messages? #verbose: 1 # log to syslog? syslog: 1 # create output files named page/index.html? usedirs: 1 # use '!'-prefixed preprocessor directives? prefix_directives: 1 # use page/index.mdwn source files indexpages: 0 # enable Discussion pages? discussion: 1 # name of Discussion pages @@ -118,43 +121,43 @@ cookiejar: useragent: ikiwiki/3.20141016.2 ###################################################################### # core plugins # (editpage, git, htmlscrubber, inline, link, meta, parentlinks, # templatebody) ###################################################################### # git plugin # git hook to generate git_wrapper: /var/lib/ikiwiki/wiki.fripost.org # shell command for git_wrapper to run, in the background #git_wrapper_background_command: git push github # mode for git_wrapper (can safely be made suid) #git_wrappermode: 06755 # git pre-receive hook to generate #git_test_receive_wrapper: /git/wiki.git/hooks/pre-receive # unix users whose commits should be checked by the pre-receive hook #untrusted_committers: [] # gitweb url to show file history ([[file]] substituted) -historyurl: http://git.fripost.org/fripost-wiki/tree/[[file]] +historyurl: https://git.fripost.org/fripost-wiki/tree/[[file]] # gitweb url to show a diff ([[file]], [[sha1_to]], [[sha1_from]], [[sha1_commit]], and [[sha1_parent]] substituted) -diffurl: http://git.fripost.org/fripost-wiki/diff/[[file]]/?id=[[sha1_commit]] +diffurl: https://git.fripost.org/fripost-wiki/diff/[[file]]/?id=[[sha1_commit]] # where to pull and push changes (set to empty string to disable) gitorigin_branch: origin # branch that the wiki is stored in gitmaster_branch: master # htmlscrubber plugin # PageSpec specifying pages not to scrub #htmlscrubber_skip: '!*/Discussion' # inline plugin # enable rss feeds by default? rss: 1 # enable atom feeds by default? atom: 1 # allow rss feeds to be used? #allowrss: 0 # allow atom feeds to be used? #allowatom: 0 # urls to ping (using XML-RPC) on feed update #pingurl: http://rpc.technorati.com/rpc/ping @@ -266,41 +269,41 @@ allowed_attachments: virusfree() and (mimetype(application/mbox) or mimetype(tex virus_checker: clamdscan - # comments plugin # PageSpec of pages where comments are allowed comments_pagespec: tracker/* # PageSpec of pages where posting new comments is not allowed #comments_closed_pagespec: blog/controversial or blog/flamewar # Base name for comments, e.g. "comment_" for pages like "sandbox/comment_12" #comments_pagename: '' # Interpret directives in comments? #comments_allowdirectives: 0 # Allow anonymous commenters to set an author name? #comments_allowauthor: 0 # commit comments to the VCS #comments_commit: 1 # Restrict formats for comments to (no restriction if empty) #comments_allowformats: mdwn txt # getsource plugin # Mime type for returned source. -#getsource_mimetype: text/plain; charset=utf-8 +getsource_mimetype: text/plain; charset=utf-8 # mirrorlist plugin # list of mirrors #mirrorlist: {} # generate links that point to the mirrors' ikiwiki CGI #mirrorlist_use_cgi: 1 # repolist plugin # URIs of repositories containing the wiki's source #repositories: #- svn://svn.example.org/wiki/trunk # search plugin # path to the omega cgi program #omega_cgi: /usr/lib/cgi-bin/omega/omega # use google site search rather than internal xapian index? #google_search: 1 # theme plugin # name of theme to enable @@ -394,20 +397,23 @@ comments_pagespec: tracker/* # name of the recentchanges page #recentchangespage: recentchanges # number of changes to track #recentchangesnum: 100 # rsync plugin # command to run to sync updated pages #rsync_command: rsync -qa --delete . user@host:/path/to/docroot/ # sidebar plugin # show sidebar page on all pages? #global_sidebars: 1 # tag plugin # parent page tags are located under #tagbase: tag # autocreate new tag pages? #tag_autocreate: 1 # commit autocreated tag pages #tag_autocreate_commit: 1 + +# pandoc plugin +pandoc_html5: 1 diff --git a/roles/wiki/files/var/www/fripost.org/autoconfig/mail/config-v1.1.xml b/roles/wiki/files/var/www/fripost.org/autoconfig/mail/config-v1.1.xml index e70b0be..00c2d0e 100644 --- a/roles/wiki/files/var/www/fripost.org/autoconfig/mail/config-v1.1.xml +++ b/roles/wiki/files/var/www/fripost.org/autoconfig/mail/config-v1.1.xml @@ -1,39 +1,39 @@ <?xml version="1.0" encoding="UTF-8"?> <clientConfig version="1.1"> <emailProvider id="fripost.org"> <domain>fripost.org</domain> - <displayName>Fripost &endash; demokratisk e-post</displayName> + <displayName>Fripost — demokratisk e-post</displayName> <displayShortName>Fripost</displayShortName> <incomingServer type="imap"> <hostname>imap.fripost.org</hostname> <port>993</port> <socketType>SSL</socketType> <username>%EMAILADDRESS%</username> <authentication>password-cleartext</authentication> </incomingServer> <outgoingServer type="smtp"> <hostname>smtp.fripost.org</hostname> - <port>587</port> - <socketType>STARTTLS</socketType> + <port>465</port> + <socketType>SSL</socketType> <username>%EMAILADDRESS%</username> <authentication>password-cleartext</authentication> </outgoingServer> - <documentation url="http://wiki.fripost.org/konfigurera/"> + <documentation url="https://wiki.fripost.org/konfigurera/"> <descr lang="en">Configure your email client for Fripost</descr> <descr lang="sv">Konfigurerar din e-postklient för Fripost</descr> </documentation> </emailProvider> <webMail> <loginPage url="https://mail.fripost.org/" /> <loginPageInfo url="https://mail.fripost.org/"> <username>%EMAILADDRESS%</username> <usernameField id="rcmloginuser" name="_user" /> <passwordField id="rcmloginpwd" name="_pass" /> </loginPageInfo> </webMail> </clientConfig> diff --git a/roles/wiki/handlers/main.yml b/roles/wiki/handlers/main.yml index 42ae6ef..ac9ad2b 100644 --- a/roles/wiki/handlers/main.yml +++ b/roles/wiki/handlers/main.yml @@ -1,7 +1,16 @@ --- - name: Restart Nginx service: name=nginx state=restarted - name: Refresh ikiwiki - sudo_user: ikiwiki + become_user: ikiwiki command: ikiwiki --setup /var/lib/ikiwiki/fripost-wiki.setup --refresh --wrappers + +- name: systemctl daemon-reload + command: /bin/systemctl daemon-reload + +- name: Stop ikiwiki + service: name=ikiwiki.service state=stopped + +- name: Restart ikiwiki + service: name=ikiwiki.socket state=restarted diff --git a/roles/wiki/tasks/main.yml b/roles/wiki/tasks/main.yml index 7aa4d1d..74c11f8 100644 --- a/roles/wiki/tasks/main.yml +++ b/roles/wiki/tasks/main.yml @@ -1,113 +1,166 @@ - name: Install ikiwiki - apt: pkg={{ item }} - with_items: + apt: pkg={{ packages }} + vars: + packages: - ikiwiki + - libauthen-passphrase-perl - highlight-common - libhighlight-perl - libimage-magick-perl + - libmail-sendmail-perl + - libnet-dns-sec-perl - fcgiwrap + - pandoc + ### + - fonts-font-awesome + - libjs-bootstrap4 + - libjs-jquery + +- name: Stop and disable fcgiwrap socket + service: name=fcgiwrap.socket state=stopped enabled=false + +- name: Stop fcgiwrap service + service: name=fcgiwrap.service state=stopped - name: Create a user 'ikiwiki' user: name=ikiwiki system=yes home=/var/lib/ikiwiki shell=/usr/sbin/nologin password=! state=present generate_ssh_key=yes ssh_key_comment=ikiwiki@{{ ansible_fqdn }} -- name: Add 'www-data' to the group 'ikiwiki' - user: name=www-data groups=ikiwiki append=yes - - name: Create directory ~ikiwiki/IkiWiki/Plugin file: path=/var/lib/ikiwiki/IkiWiki/Plugin state=directory owner=ikiwiki group=ikiwiki mode=0755 -- name: Copy isWebsite plugin - copy: src=var/lib/ikiwiki/IkiWiki/Plugin/isWebsite.pm - dest=/var/lib/ikiwiki/IkiWiki/Plugin/isWebsite.pm +- name: Copy ikiwiki plugins + copy: src=var/lib/ikiwiki/IkiWiki/Plugin/{{ item }}.pm + dest=/var/lib/ikiwiki/IkiWiki/Plugin/{{ item }}.pm owner=root group=root mode=0644 + with_items: + - isWebsite + - pandoc notify: - Refresh ikiwiki # Add the ikiwiki git wrapper as a post-update hook in the git repos in # gitolite: "config hook.ikiwiki-wrapper = /var/lib/ikiwiki/wiki.fripost.org" # where the 'git_wrapper' can be found in # /var/lib/ikiwiki/fripost-wiki.setup # To create a new wiki: # $ /usr/bin/sudo -u ikiwiki git config --global user.name "Fripost Admins" # $ /usr/bin/sudo -u ikiwiki git config --global user.email "admin@fripost.org" # $ /usr/bin/sudo -u ikiwiki ikiwiki --setup /etc/ikiwiki/auto.setup # ## Add ikiwiki's key to gitolite -# sudo ln -s /var/lib/ikiwiki/wiki.fripost.org /var/lib/gitolite/repositories/fripost-wiki.git/hooks/post-update +# ## Create post-update hook, cf. http://rtime.felk.cvut.cz/~sojka/blog/using-ikiwiki-with-gitolite/ # $ /usr/bin/sudo -u ikiwiki git clone ssh://gitolite@localhost/fripost-wiki.git - name: Configure ikiwiki copy: src=var/lib/ikiwiki/fripost-wiki.setup dest=/var/lib/ikiwiki/fripost-wiki.setup owner=root group=root mode=0644 notify: - Refresh ikiwiki - name: Add fripost-wiki to /etc/ikiwiki/wikilist lineinfile: dest=/etc/ikiwiki/wikilist - "line=ikiwiki /var/lib/ikiwiki/fripost-wiki.setup" + line='ikiwiki /var/lib/ikiwiki/fripost-wiki.setup' owner=root group=root mode=0644 - meta: flush_handlers -- name: Generate a private key and a X.509 certificate for Nginx - command: genkeypair.sh x509 - --pubkey=/etc/nginx/ssl/fripost.org.pem - --privkey=/etc/nginx/ssl/fripost.org.key - --ou=WWW --cn=fripost.org --dns=fripost.org --dns=wiki.fripost.org - -t rsa -b 4096 -h sha512 - register: r1 - changed_when: r1.rc == 0 - failed_when: r1.rc > 1 +- name: Copy ikiwiki service unit + copy: src=etc/systemd/system/ikiwiki.service + dest=/etc/systemd/system/ikiwiki.service + owner=root group=root + mode=0644 notify: - - Restart Nginx - tags: - - genkey + - systemctl daemon-reload + - Stop ikiwiki + +- name: Copy ikiwiki socket unit + copy: src=etc/systemd/system/ikiwiki.socket + dest=/etc/systemd/system/ikiwiki.socket + owner=root group=root + mode=0644 + notify: + - systemctl daemon-reload + - Restart ikiwiki + +- name: Disable ikiwiki service + service: name=ikiwiki.service enabled=false + +- name: Start ikiwiki socket + service: name=ikiwiki.socket state=started enabled=true + +- meta: flush_handlers - name: Copy /etc/nginx/sites-available/{wiki,website} copy: src=etc/nginx/sites-available/{{ item }} dest=/etc/nginx/sites-available/{{ item }} owner=root group=root mode=0644 - register: r2 + register: r1 with_items: - website - wiki notify: - Restart Nginx - name: Create /etc/nginx/sites-enabled/{wiki,website} file: src=../sites-available/{{ item }} dest=/etc/nginx/sites-enabled/{{ item }} owner=root group=root state=link force=yes - register: r3 + register: r2 with_items: - website - wiki notify: - Restart Nginx +- name: Copy HPKP header snippet + # never modify the pined pubkeys as we don't want to lock out our users + template: src=etc/nginx/snippets/fripost.org.hpkp-hdr.j2 + dest=/etc/nginx/snippets/fripost.org.hpkp-hdr + validate=/bin/false + owner=root group=root + mode=0644 + register: r3 + notify: + - Restart Nginx + +- name: Start Nginx + service: name=nginx state=started + when: not (r1.changed or r2.changed or r3.changed) + +- meta: flush_handlers + +- name: Fetch Nginx's X.509 certificate + # Ensure we don't fetch private data + become: False + fetch_cmd: cmd="openssl x509 -noout -pubkey" + stdin=/etc/nginx/ssl/www.fripost.org.pem + dest=certs/public/fripost.org.pub + tags: + - genkey + - name: Create directory /var/www/fripost.org/autoconfig/mail file: path=/var/www/fripost.org/autoconfig/mail state=directory owner=root group=root mode=0755 - name: Copy /var/www/fripost.org/autoconfig/mail/config-v1.1.xml copy: src=var/www/fripost.org/autoconfig/mail/config-v1.1.xml dest=/var/www/fripost.org/autoconfig/mail/config-v1.1.xml owner=root group=root mode=0644 diff --git a/roles/wiki/templates/etc/nginx/snippets/fripost.org.hpkp-hdr.j2 b/roles/wiki/templates/etc/nginx/snippets/fripost.org.hpkp-hdr.j2 new file mode 120000 index 0000000..a8ba598 --- /dev/null +++ b/roles/wiki/templates/etc/nginx/snippets/fripost.org.hpkp-hdr.j2 @@ -0,0 +1 @@ +../../../../../../certs/hpkp-hdr.j2
\ No newline at end of file |