1665 lines
44 KiB
Markdown
1665 lines
44 KiB
Markdown
---
|
|
title: 14 Mail
|
|
description:
|
|
published: true
|
|
date: 2023-07-04T15:38:32.450Z
|
|
tags:
|
|
editor: markdown
|
|
dateCreated: 2023-05-03T02:09:12.610Z
|
|
---
|
|
|
|
# Email
|
|
Most people would recommend against hosting your own mailserver, But if you have a stable connection I don't see the issue. Yes you need to secure it, But imagine how much it would do for decentralisation if everyone hosted their own mailserver. That is why I present this easy guide to a full fledged mail server :)
|
|
|
|
# Dockerfile
|
|
There is no official docker container for postfix or dovecot, so we will write our own, first we need to create a directory
|
|
|
|
mkdir -p ~/docker/mailserver
|
|
|
|
Now create a Dockerfile
|
|
|
|
vim ~/docker/mailserver/Dockerfile
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
FROM alpine
|
|
RUN apk update && apk add mysql postfix postfix-mysql dovecot dovecot-lmtpd dovecot-mysql dovecot-pigeonhole-plugin rspamd-client
|
|
RUN mkdir -p /var/spool/postfix/etc
|
|
RUN echo "nameserver 9.9.9.9" > /var/spool/postfix/etc/resolv.conf
|
|
ENTRYPOINT apk update && apk upgrade && cp /usr/bin/rspamc /usr/lib/dovecot/sieve/rspamc && dovecot && postfix start-fg
|
|
```
|
|
|
|
This simply installs postfix, dovecot and the extras we need. It also includes the rspamd client to control the spamserver we will set up later.
|
|
|
|
Now we need to create a docker-compose file
|
|
|
|
vim ~/docker/mailserver/docker-compose.yml
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
version: '3'
|
|
|
|
networks:
|
|
mail:
|
|
external: true
|
|
name: mail
|
|
ipam:
|
|
config:
|
|
- subnet: 172.20.11.0/24
|
|
|
|
services:
|
|
postfix:
|
|
build: .
|
|
container_name: postfix
|
|
restart: always
|
|
ports:
|
|
- 25:25
|
|
- 465:465
|
|
- 587:587
|
|
- 993:993
|
|
- 995:995
|
|
volumes:
|
|
- /data/mailserver/log:/var/log
|
|
- /data/mailserver/mail:/var/mail
|
|
#- /data/mailserver/postfix:/etc/postfix
|
|
- /data/mailserver/dovecot:/etc/dovecot
|
|
#- /data/mailserver/sieve:/usr/lib/dovecot/sieve
|
|
- /etc/letsencrypt/:/etc/letsencrypt/
|
|
- /etc/localtime:/etc/localtime:ro
|
|
- /etc/timezone:/etc/timezone:ro
|
|
networks:
|
|
mail:
|
|
ipv4_address: 172.20.11.10
|
|
```
|
|
|
|
We also need a network so create it with the following command
|
|
|
|
sudo docker network create --subnet=172.20.11.0/24 mail
|
|
|
|
We left 2 directories commented out in the compose file, this is because we want to copy the default configuration, so lets start the container
|
|
|
|
sudo docker-compose -f ~/docker/mailserver/docker-compose.yml up -d
|
|
|
|
The container should be running, so now we are going to copy over the config directories.
|
|
|
|
sudo docker cp postfix:/etc/postfix /data/mailserver
|
|
sudo docker cp postfix:/usr/lib/dovecot/sieve /data/mailserver
|
|
|
|
Now you can remove the # before the 3 volume lines in the docker compose file
|
|
|
|
vim ~/docker/mailserver/docker-compose.yml
|
|
|
|
So it looks like this
|
|
|
|
```
|
|
services:
|
|
postfix:
|
|
volumes:
|
|
- /data/mailserver/postfix:/etc/postfix
|
|
- /data/mailserver/sieve:/usr/lib/dovecot/sieve
|
|
```
|
|
|
|
And restart the container
|
|
|
|
sudo docker-compose -f ~/docker/nginx/docker-compose.yml down && sudo docker-compose -f ~/docker/nginx/docker-compose.yml up -d
|
|
|
|
# Configuring Postfix
|
|
We need to configure postfix to send and receive emails, first we are going to start with our main config file
|
|
|
|
sudo vim /data/mailserver/postfix/main.cf
|
|
|
|
Replace the entire content with the following
|
|
|
|
```
|
|
## Acceptation
|
|
mydestination = localhost
|
|
mynetworks = localhost 172.20.0.0/16
|
|
|
|
## Network
|
|
mydomain = example.com
|
|
myhostname = example.com
|
|
myorigin = example.com
|
|
inet_protocols = ipv4
|
|
smtpd_banner = PTR_RECORD ESMTP $mail_name
|
|
|
|
## Recipient Restrictions
|
|
smtpd_recipient_restrictions =
|
|
permit_mynetworks
|
|
permit_sasl_authenticated
|
|
reject_unauth_destination
|
|
#reject_non_fqdn_sender
|
|
#reject_non_fqdn_recipient
|
|
#reject_invalid_hostname
|
|
#reject_unknown_client_hostname
|
|
#reject_unknown_sender_domain
|
|
reject_unknown_recipient_domain
|
|
reject_unlisted_recipient
|
|
#reject_unknown_reverse_client_hostname
|
|
check_recipient_access texthash:/etc/postfix/blocked_recipients
|
|
check_sender_access texthash:/etc/postfix/blocked_senders
|
|
|
|
## Relay Restrictions
|
|
smtpd_relay_restrictions = $smtpd_recipient_restrictions
|
|
|
|
## HELO/EHLO Restrictions
|
|
smtpd_helo_restrictions =
|
|
permit_mynetworks
|
|
permit_sasl_authenticated
|
|
#reject_invalid_helo_hostname
|
|
#reject_non_fqdn_helo_hostname
|
|
#reject_unknown_helo_hostname
|
|
|
|
## Sender Restrictions
|
|
smtpd_sender_restrictions =
|
|
permit_mynetworks
|
|
permit_sasl_authenticated
|
|
#reject_unknown_reverse_client_hostname
|
|
#reject_unknown_client_hostname
|
|
|
|
## Milter Settings
|
|
milter_protocol = 6
|
|
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
|
|
milter_default_action = accept
|
|
smtpd_milters = inet:172.20.11.40:11332
|
|
non_smtpd_milters = inet:172.20.11.40:11332
|
|
|
|
## Config
|
|
compatibility_level=3.6
|
|
|
|
## Logging
|
|
smtpd_tls_loglevel = 2
|
|
smtp_tls_loglevel = 2
|
|
smtpd_delay_reject = yes
|
|
maillog_file = /var/log/postfix.log
|
|
|
|
## Mapping to database
|
|
virtual_alias_maps = proxy:mysql:/etc/postfix/virtual_alias_maps.cf,proxy:mysql:/etc/postfix/virtual_alias_domains_maps.cf
|
|
virtual_alias_domains = proxy:mysql:/etc/postfix/virtual_alias_domains.cf
|
|
virtual_mailbox_maps = proxy:mysql:/etc/postfix/virtual_mailbox_maps.cf
|
|
virtual_mailbox_domains = proxy:mysql:/etc/postfix/virtual_mailbox_domains.cf
|
|
|
|
## Mailbox Settings
|
|
virtual_mailbox_base = /var/mail
|
|
virtual_mailbox_limit = 512000000
|
|
virtual_minimum_uid = 5000
|
|
virtual_transport = lmtp:unix:private/dovecot-lmtp
|
|
virtual_uid_maps = static:5000
|
|
virtual_gid_maps = static:5000
|
|
local_transport = virtual
|
|
virtual_uid_maps = static:5000
|
|
local_recipient_maps = $virtual_mailbox_maps
|
|
|
|
## HELO/EHLO Settings
|
|
smtpd_helo_required = yes
|
|
|
|
## SASL Settings
|
|
smtpd_sasl_auth_enable = yes
|
|
smtpd_sasl_type = dovecot
|
|
smtpd_sasl_path = private/auth
|
|
smtpd_sasl_local_domain = $mydomain
|
|
smtpd_sasl_authenticated_header = yes
|
|
broken_sasl_auth_clients = no
|
|
#smtpd_sasl_security_options = noanonymous
|
|
#smtpd_sasl_tls_security_options = noanonymous
|
|
|
|
## TSL Settings
|
|
tls_preempt_cipherlist = yes
|
|
tls_ssl_options = NO_COMPRESSION
|
|
tls_random_source = dev:/dev/urandom
|
|
#tls_high_cipherlist = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:+CAMELLIA256:+AES256:+CAMELLIA128:+AES128:+SSLv3:AES256-SHA:CAMELLIA128-SHA:AES128-SHA
|
|
smtpd_use_tls = yes
|
|
smtpd_tls_auth_only = yes
|
|
smtpd_tls_protocols = !SSLv2, !SSLv3, TLSv1, TLSv1.1, TLSv1.2, TLSv1.3
|
|
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3
|
|
smtpd_tls_received_header = yes
|
|
smtpd_tls_session_cache_timeout = 3600s
|
|
smtpd_tls_security_level = encrypt
|
|
smtpd_tls_cert_file = /etc/letsencrypt/live/example.com/fullchain.pem
|
|
smtpd_tls_key_file = /etc/letsencrypt/live/example.com/privkey.pem
|
|
#smtpd_tls_eecdh_grade = strong
|
|
#smtpd_tls_mandatory_ciphers = high
|
|
#smtpd_tls_exclude_ciphers = aNULL:eNULL:LOW:3DES:MD5:MEDIUM:EXP:PSK:DSS:RC4:SEED:ECDSA:CAMELLIA256-SHA
|
|
smtp_use_tls = yes
|
|
smtp_tls_protocols = !SSLv2, !SSLv3, TLSv1, TLSv1.1, TLSv1.2, TLSv1.3
|
|
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3
|
|
smtp_tls_session_cache_timeout = 3600s
|
|
smtp_tls_security_level = encrypt
|
|
smtp_tls_cert_file = /etc/letsencrypt/live/example.com/fullchain.pem
|
|
smtp_tls_key_file = /etc/letsencrypt/live/example.com/privkey.pem
|
|
#smtp_tls_mandatory_ciphers = high
|
|
#smtp_tls_exclude_ciphers = aNULL:eNULL:LOW:3DES:MD5:MEDIUM:EXP:PSK:DSS:RC4:SEED:ECDSA:CAMELLIA256-SHA
|
|
|
|
## Limit Settings
|
|
smtpd_client_connection_rate_limit = 100
|
|
smtpd_client_message_rate_limit = 10000
|
|
anvil_rate_time_unit = 60
|
|
message_size_limit = 51200000
|
|
header_size_limit = 102400
|
|
default_process_limit = 1000
|
|
queue_minfree = 100000000
|
|
smtpd_error_sleep_time = 1s
|
|
smtpd_soft_error_limit = 10
|
|
smtpd_hard_error_limit = 20
|
|
|
|
## Privacy
|
|
disable_vrfy_command = yes
|
|
header_checks = regexp:/etc/postfix/header_checks
|
|
```
|
|
|
|
The configuration is much to big to cover in this guide, But I recommend you look up each and every line to figure out what it does exactly.
|
|
|
|
Just replace example.com with your own domain and put in the PTR record, You can also comment out the smtp banner line.
|
|
|
|
Now we are going to create the master.cf file
|
|
|
|
sudo vim /data/mailserver/postfix/master.cf
|
|
|
|
Replace the entire content with the following
|
|
|
|
```
|
|
# ==========================================================================
|
|
# service type private unpriv chroot wakeup maxproc command + args
|
|
# (yes) (yes) (no) (never) (100)
|
|
# ==========================================================================
|
|
|
|
## Incoming
|
|
smtp inet n - y - - smtpd
|
|
|
|
submission inet n - y - - smtpd
|
|
-o syslog_name=postfix/submission
|
|
-o smtpd_reject_unlisted_recipient=no
|
|
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
|
|
-o milter_macro_daemon_name=ORIGINATING
|
|
# -o smtpd_etrn_restrictions=reject
|
|
# -o smtpd_helo_restrictions=permit_mynetworks,permit
|
|
# -o smtpd_sender_restrictions=$mua_sender_restrictions
|
|
# -o milter_macro_daemon_name=ORIGINATING
|
|
|
|
smtps inet n - y - - smtpd
|
|
-o syslog_name=postfix/smtps
|
|
-o smtpd_tls_wrappermode=yes
|
|
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
|
|
-o milter_macro_daemon_name=ORIGINATING
|
|
# -o smtpd_reject_unlisted_recipient=no
|
|
# -o smtpd_helo_restrictions=$mua_helo_restrictions
|
|
# -o smtpd_sender_restrictions=$mua_sender_restrictions
|
|
# -o milter_macro_daemon_name=ORIGINATING
|
|
|
|
pickup unix n - y 60 1 pickup
|
|
|
|
## Processing
|
|
cleanup unix n - y - 0 cleanup
|
|
qmgr unix n - n 300 1 qmgr
|
|
#qmgr unix n - n 300 1 oqmgr
|
|
rewrite unix - - y - - trivial-rewrite
|
|
|
|
## Outbound
|
|
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
|
|
smtp unix - - y - - smtp
|
|
relay unix - - y - - smtp
|
|
|
|
## Helper
|
|
bounce unix - - y - 0 bounce
|
|
defer unix - - y - 0 bounce
|
|
trace unix - - y - 0 bounce
|
|
|
|
## Logging
|
|
postlog unix-dgram n - n - 1 postlogd
|
|
anvil unix - - y - 1 anvil
|
|
|
|
## Cache
|
|
scache unix - - y - 1 scache
|
|
tlsmgr unix - - y 1000? 1 tlsmgr
|
|
flush unix n - y 1000? 0 flush
|
|
|
|
## Verification
|
|
verify unix - - y - 1 verify
|
|
|
|
## Proxy
|
|
#tlsproxy unix - - y - 0 tlsproxy
|
|
proxymap unix - - n - - proxymap
|
|
proxywrite unix - - n - 1 proxymap
|
|
-o syslog_name=postfix/$service_name
|
|
# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
|
|
|
|
## mailq
|
|
showq unix n - y - - showq
|
|
|
|
## External Delivery Methods
|
|
maildrop unix - n n - - pipe
|
|
flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
|
|
uucp unix - n n - - pipe
|
|
flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
|
|
ifmail unix - n n - - pipe
|
|
flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
|
|
bsmtp unix - n n - - pipe
|
|
flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
|
|
scalemail-backend unix - n n - 2 pipe
|
|
flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
|
|
mailman unix - n n - - pipe
|
|
flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
|
|
${nexthop} ${user}
|
|
vacation unix - n n - - pipe
|
|
flags=Rq user=vacation argv=/var/spool/vacation/vacation.pl -f ${sender} -- ${recipient}
|
|
#dovecot unix - n n - - pipe
|
|
# flags=DRhu user=vmail:vmail argv=/usr/libexec/dovecot/deliver -f ${sender} -d ${recipient}
|
|
```
|
|
|
|
We need to create a simple file that will strip private information from the header like the client IP and username.
|
|
|
|
sudo vim /data/mailserver/postfix/header_checks
|
|
|
|
paste in the following content
|
|
|
|
```
|
|
/^Received:.*\(Authenticated sender:/ IGNORE
|
|
```
|
|
|
|
next up is a simple file that lists blocked recipients, very handy if you use a catchall and one of your emails has been compromised
|
|
|
|
sudo vim /data/mailserver/postfix/blocked_recipients
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
compromised@example.tld 550 Email address has been compromised and is no longer in use.
|
|
spam@example.tld 550 You can keep your spam.
|
|
retired@example.tld 550 The email address you are trying to reach has been retired.
|
|
```
|
|
|
|
The ones here are just examples, but you can add your own in the same format
|
|
|
|
Now we are going to create the same file, just for blocked senders
|
|
|
|
sudo vim /data/mailserver/postfix/blocked_senders
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
spamsender@example.tld 550 You can keep your spam.
|
|
spamsender@example2.tld 550 Server not accepting email from this sender.
|
|
```
|
|
|
|
Again you can add what you want, very handy for repeated offenders who don't offer an unsubscribe link.
|
|
|
|
In the main.cf configuration file we call for 5 files that do not exist yet, these files contain credentials and instructions to look into a database, we are going to create all 5 files, paste in the content and adjust the credentials so that they match your configuration
|
|
|
|
Create the file
|
|
|
|
sudo vim /data/mailserver/postfix/virtual_alias_domains.cf
|
|
|
|
paste in the following content and adjust the credentials
|
|
|
|
```
|
|
user = postfix
|
|
password = POSTFIXDATABASEUSERPASSWORD
|
|
hosts = 172.20.11.30
|
|
dbname = postfix
|
|
query = SELECT alias_domain FROM alias_domain WHERE alias_domain='%s' AND active = '1'
|
|
```
|
|
|
|
Create the file
|
|
|
|
sudo vim /data/mailserver/postfix/virtual_alias_domains_maps.cf
|
|
|
|
paste in the following content and adjust the credentials
|
|
|
|
```
|
|
user = postfix
|
|
password = POSTFIXDATABASEUSERPASSWORD
|
|
hosts = 172.20.11.30
|
|
dbname = postfix
|
|
query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('%u', '@', alias_domain.target_domain) AND alias.active = '1' AND alias_domain.active='1'
|
|
```
|
|
|
|
Create the file
|
|
|
|
sudo vim /data/mailserver/postfix/virtual_alias_maps.cf
|
|
|
|
paste in the following content and adjust the credentials
|
|
|
|
```
|
|
user = postfix
|
|
password = POSTFIXDATABASEUSERPASSWORD
|
|
hosts = 172.20.11.30
|
|
dbname = postfix
|
|
table = alias
|
|
select_field = goto
|
|
where_field = address
|
|
```
|
|
|
|
Create the file
|
|
|
|
sudo vim /data/mailserver/postfix/virtual_mailbox_domains.cf
|
|
|
|
paste in the following content and adjust the credentials
|
|
|
|
```
|
|
user = postfix
|
|
password = POSTFIXDATABASEUSERPASSWORD
|
|
hosts = 172.20.11.30
|
|
dbname = postfix
|
|
table = domain
|
|
select_field = domain
|
|
where_field = domain
|
|
```
|
|
|
|
Create the file
|
|
|
|
sudo vim /data/mailserver/postfix/virtual_mailbox_maps.cf
|
|
|
|
paste in the following content and adjust the credentials
|
|
|
|
```
|
|
user = postfix
|
|
password = POSTFIXDATABASEUSERPASSWORD
|
|
hosts = 172.20.11.30
|
|
dbname = postfix
|
|
table = mailbox
|
|
select_field = maildir
|
|
where_field = username
|
|
```
|
|
|
|
Postfix configuration should be done now
|
|
|
|
# Configuring Dovecot
|
|
Dovecot will be our imap server, so that we can use a client to actually read our email
|
|
|
|
Lets start with the main configuration file
|
|
|
|
sudo vim /data/mailserver/dovecot/dovecot.conf
|
|
|
|
Paste in the following bytes
|
|
|
|
```
|
|
# Logging
|
|
log_path = /var/log/dovecot.log
|
|
mail_debug = yes
|
|
|
|
# Protocols
|
|
protocols = imap pop3 lmtp sieve
|
|
auth_mechanisms = login plain
|
|
#disable_plaintext_auth = yes
|
|
|
|
# Security
|
|
ssl = required
|
|
ssl_min_protocol = TLSv1.2
|
|
ssl_prefer_server_ciphers = yes
|
|
ssl_cert = </etc/letsencrypt/live/example.com/fullchain.pem
|
|
ssl_key = </etc/letsencrypt/live/example.com/privkey.pem
|
|
ssl_dh = </etc/dovecot/dh.pem
|
|
ssl_options = no_compression no_ticket
|
|
ssl_cipher_list = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
|
|
|
|
# Storage
|
|
mail_home = /var/mail/%d/%n
|
|
mail_location = maildir:~
|
|
|
|
# Inbox Config
|
|
namespace inbox {
|
|
inbox = yes
|
|
separator = /
|
|
|
|
mailbox "Drafts" {
|
|
auto = subscribe
|
|
special_use = \Drafts
|
|
}
|
|
mailbox "Sent" {
|
|
auto = subscribe
|
|
special_use = \Sent
|
|
}
|
|
mailbox "Trash" {
|
|
auto = subscribe
|
|
special_use = \Trash
|
|
}
|
|
mailbox "Spam" {
|
|
auto = subscribe
|
|
special_use = \Junk
|
|
}
|
|
mailbox "Archive" {
|
|
auto = subscribe
|
|
special_use = \Archive
|
|
}
|
|
}
|
|
|
|
passdb {
|
|
driver = sql
|
|
args = /etc/dovecot/dovecot-sql.conf
|
|
}
|
|
|
|
userdb {
|
|
driver = sql
|
|
args = /etc/dovecot/dovecot-sql.conf
|
|
}
|
|
|
|
service auth {
|
|
unix_listener /var/spool/postfix/private/auth {
|
|
group = postfix
|
|
mode = 0660
|
|
user = postfix
|
|
}
|
|
unix_listener /var/spool/postfix/private/dovecot-auth {
|
|
group = postfix
|
|
mode = 0660
|
|
user = postfix
|
|
}
|
|
unix_listener auth-userdb {
|
|
mode = 0600
|
|
user = vmail
|
|
}
|
|
user = dovecot
|
|
}
|
|
|
|
service lmtp {
|
|
unix_listener /var/spool/postfix/private/dovecot-lmtp {
|
|
group = postfix
|
|
mode = 0600
|
|
user = postfix
|
|
}
|
|
}
|
|
|
|
protocol imap {
|
|
mail_plugins = $mail_plugins imap_sieve
|
|
postmaster_address = postmaster@example.com
|
|
}
|
|
|
|
protocol pop3 {
|
|
mail_plugins = $mail_plugins
|
|
}
|
|
|
|
protocol lmtp {
|
|
mail_plugins = $mail_plugins sieve
|
|
}
|
|
|
|
!include conf.d/*.conf
|
|
```
|
|
|
|
now the mysql configuration
|
|
|
|
sudo vim /data/mailserver/dovecot/dovecot-sql.conf
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
driver = mysql
|
|
connect = host=172.20.11.30 dbname=postfix user=postfix password=POSTFIXDATABASEUSERPASSWORD
|
|
default_pass_scheme = SHA512-CRYPT
|
|
user_query = SELECT '/var/mail/%d/%n' as home, 'maildir:/var/mail/%d/%n' as mail, 5000 AS uid, 5000 AS gid, concat('dirsize:storage=', quota) AS quota FROM mailbox WHERE username = '%u' AND active = '1'
|
|
password_query = SELECT username as user, password, '/var/mail/%d/%n' as userdb_home, 'maildir:/var/mail/%d/%n' as userdb_mail, 5000 as userdb_uid, 5000 as userdb_gid FROM mailbox WHERE username = '%u' AND active = '1'
|
|
```
|
|
|
|
Now create the Sieve config file
|
|
|
|
sudo vim /data/mailserver/dovecot/conf.d/90-sieve.conf
|
|
|
|
Replace the entire content with the following
|
|
|
|
```
|
|
plugin {
|
|
sieve = file:/usr/lib/dovecot/sieve/%d/%n/scripts;active=/usr/lib/dovecot/sieve/%d/%n/active-script.sieve
|
|
sieve_before = /usr/lib/dovecot/sieve/spam-global.sieve
|
|
imapsieve_mailbox1_name = Spam
|
|
imapsieve_mailbox1_causes = COPY
|
|
imapsieve_mailbox1_before = file:/usr/lib/dovecot/sieve/report-spam.sieve
|
|
imapsieve_mailbox2_name = *
|
|
imapsieve_mailbox2_from = Spam
|
|
imapsieve_mailbox2_causes = COPY
|
|
imapsieve_mailbox2_before = file:/usr/lib/dovecot/sieve/report-ham.sieve
|
|
sieve_pipe_bin_dir = /usr/lib/dovecot/sieve
|
|
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
|
|
sieve_extensions = +fileinto +mailbox
|
|
sieve_plugins = sieve_imapsieve sieve_extprograms
|
|
}
|
|
```
|
|
|
|
and finally the managesieve config file
|
|
|
|
sudo vim /data/mailserver/dovecot/conf.d/20-managesieve.conf
|
|
|
|
Replace the entire content with the following
|
|
|
|
```
|
|
service managesieve-login {
|
|
inet_listener sieve {
|
|
port = 4190
|
|
}
|
|
}
|
|
service managesieve {
|
|
process_limit = 1024
|
|
}
|
|
protocol sieve {
|
|
}
|
|
```
|
|
|
|
make sure the vmail user owns the mail directory
|
|
|
|
sudo chown -R 5000:root /data/mailserver/mail
|
|
|
|
Finally we are going to generate the dh file
|
|
|
|
sudo openssl dhparam -out /data/mailserver/dovecot/dh.pem 4096
|
|
|
|
For now it is done, we will continue with Sieve later
|
|
|
|
# Create Database and user
|
|
We need to create a MariaDB database for postfix, so lets do that
|
|
|
|
sudo docker exec -it maridb mysql -p
|
|
|
|
Enter the Mysql root password you provided during the creation of the mariadb container and you should be in.
|
|
|
|
Now create a databases with the following command
|
|
|
|
create database postfix;
|
|
|
|
Create the user with
|
|
|
|
create user postfix@'172.20.11.10' identified by 'POSTFIXDATABASEPASSWORD';
|
|
|
|
Give privileges to the user on the database with
|
|
|
|
grant all privileges on postfix.* to postfix@'172.20.11.10';
|
|
|
|
And Flush the privileges with
|
|
|
|
flush privileges;
|
|
|
|
Exit mysql with
|
|
|
|
quit;
|
|
|
|
Now we have a user, but the database is still not in the mail network so we need to add the mariadb container to the mail network hy editing the mariadb compose file
|
|
|
|
vim ~/docker/mariadb/docker-compose.yml
|
|
|
|
Here add the following in the right place
|
|
|
|
```
|
|
services:
|
|
mariadb:
|
|
networks:
|
|
mail:
|
|
ipv4_address: 172.20.11.30
|
|
|
|
networks:
|
|
mail:
|
|
external: true
|
|
name: mail
|
|
```
|
|
|
|
You probably already have the network keys, so in that case only copy the mail: keys with their properties and put them under the other networks in your compose file.
|
|
|
|
Finally we restart the mariadb container so it will be in the mailserver network
|
|
|
|
sudo docker-compose -f ~/docker/mariadb/docker-compose.yml down && sudo docker-compose -f ~/docker/mariadb/docker-compose.yml up -d
|
|
|
|
# Configuring Sieve
|
|
Now we need to configure sieve so that messages put into spam are forwarded to the spamserver to train it. The starting of the container should already have created the folder, so we dive right into the configuration files
|
|
|
|
First we create the report ham file, when files are moved out of spam.
|
|
|
|
sudo vim /data/mailserver/sieve/report-ham.sieve
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
require ["vnd.dovecot.pipe", "copy", "imapsieve"];
|
|
pipe :copy "rspamc" ["-h", "172.20.11.40:11334", "learn_ham"];
|
|
```
|
|
|
|
Next we create the report spam file, for when files are moved into spam
|
|
|
|
sudo vim /data/mailserver/sieve/report-spam.sieve
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
require ["vnd.dovecot.pipe", "copy", "imapsieve"];
|
|
pipe :copy "rspamc" ["-h", "172.20.11.40:11334", "learn_spam"];
|
|
```
|
|
|
|
Finally we create the global spam file which will move incoming mail into spam
|
|
|
|
sudo vim /data/mailserver/sieve/spam-global.sieve
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
require ["fileinto","mailbox"];
|
|
|
|
if anyof(
|
|
header :contains ["X-Spam-Flag"] "YES",
|
|
header :contains ["X-Spam"] "Yes",
|
|
header :contains ["Subject"] "*** SPAM ***"
|
|
)
|
|
{
|
|
fileinto :create "Spam";
|
|
stop;
|
|
}
|
|
```
|
|
|
|
Now we only need to compile the 3 files so that sieve can use them
|
|
|
|
first we need to get into the mailserver
|
|
|
|
sudo docker exec -it postfix sh
|
|
|
|
Now we simply execute the commands 1 by 1
|
|
|
|
For report ham
|
|
|
|
sievec /usr/lib/dovecot/sieve/report-ham.sieve
|
|
|
|
For report spam
|
|
|
|
sievec /usr/lib/dovecot/sieve/report-spam.sieve
|
|
|
|
for global spam
|
|
|
|
sievec /usr/lib/dovecot/sieve/spam-global.sieve
|
|
|
|
finally exit the docker container
|
|
|
|
exit
|
|
|
|
Sievec should be ready to go.
|
|
|
|
# Postfixadmin
|
|
We use Postfixadmin for creating and managing mailboxes and aliases We let it run in its own container with its own network so create a new directory
|
|
|
|
mkdir -p ~/docker/postfixadmin
|
|
|
|
Now create the docker-compose file
|
|
|
|
vim ~/docker/postfixadmin/docker-compose.yml
|
|
|
|
Add in the following lines
|
|
|
|
```
|
|
version: '3'
|
|
|
|
services:
|
|
postfixadmin:
|
|
image: postfixadmin
|
|
container_name: postfixadmin
|
|
restart: always
|
|
volumes:
|
|
- /data/postfixadmin/config.local.php:/var/www/html/config.local.php:ro
|
|
- /etc/localtime:/etc/localtime:ro
|
|
networks:
|
|
postfixadmin:
|
|
ipv4_address: 172.20.71.10
|
|
environment:
|
|
POSTFIXADMIN_DB_TYPE: mysqli
|
|
POSTFIXADMIN_DB_HOST: 172.20.71.30
|
|
POSTFIXADMIN_DB_USER: postfix
|
|
POSTFIXADMIN_DB_NAME: postfix
|
|
POSTFIXADMIN_DB_PASSWORD: POSTFIXDATABASEUSERPASSWORD
|
|
|
|
networks:
|
|
postfixadmin:
|
|
external: true
|
|
name: postfixadmin
|
|
ipam:
|
|
config:
|
|
- subnet: 172.20.71.0/24
|
|
```
|
|
|
|
Create the config directory
|
|
|
|
sudo mkdir /data/postfixadmin
|
|
|
|
Lets take a look at the configuration file
|
|
|
|
sudo vim /data/postfixadmin/config.local.php
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
<?php
|
|
$CONF['configured'] = true;
|
|
$CONF['database_type'] = 'mysqli';
|
|
$CONF['database_host'] = '172.20.71.30';
|
|
$CONF['database_user'] = 'postfix';
|
|
$CONF['database_password'] = 'POSTFIXDATABASEUSERPASSWORD';
|
|
$CONF['database_name'] = 'postfix';
|
|
$CONF['setup_password'] = 'POSTFIXADMINSETUPPASSWORD';
|
|
#$CONF['encrypt'] = 'SHA512-CRYPT';
|
|
?>
|
|
```
|
|
|
|
The Setup Password is for setting up the admin account but first we need to create a network
|
|
|
|
sudo docker network create --subnet=172.20.71.0/24 postfixadmin
|
|
|
|
Now we need to add nginx to the network so open the compose file of nginx
|
|
|
|
vim ~/docker/nginx/docker-compose.yml
|
|
|
|
Here add the following in the right place
|
|
|
|
```
|
|
services:
|
|
nginx:
|
|
networks:
|
|
postfixadmin:
|
|
ipv4_address: 172.20.71.20
|
|
|
|
networks:
|
|
postfixadmin:
|
|
external: true
|
|
name: postfixadmin
|
|
```
|
|
|
|
You probably already have the network keys, so in that case only copy the postfixadmin: keys with their properties and put them under the other networks in your compose file.
|
|
|
|
Now we need to do the same for mariadb
|
|
|
|
vim ~/docker/mariadb/docker-compose.yml
|
|
|
|
Here add the following in the right place
|
|
|
|
```
|
|
services:
|
|
mariadb:
|
|
networks:
|
|
postfixadmin:
|
|
ipv4_address: 172.20.71.30
|
|
|
|
networks:
|
|
postfixadmin:
|
|
external: true
|
|
name: postfixadmin
|
|
```
|
|
|
|
Now we are going to create a second postfix user that can access from the postfixadmin container so get access to the mariaDB prompt
|
|
|
|
sudo docker exec -it maridb mysql -p
|
|
|
|
Enter the Mysql root password you provided during the creation of the mariadb container and you should be in.
|
|
|
|
Create the user with
|
|
|
|
create user postfix@'172.20.71.10' identified by 'POSTFIXDATABASEPASSWORD';
|
|
|
|
Give privileges to the user on the database with
|
|
|
|
grant all privileges on postfix.* to postfix@'172.20.71.10';
|
|
|
|
And Flush the privileges with
|
|
|
|
flush privileges;
|
|
|
|
Exit mysql with
|
|
|
|
quit;
|
|
|
|
Now we need to add the server block to the nginx config so create a new file
|
|
|
|
sudo vim /data/nginx/config/services/postfixadmin.active
|
|
|
|
Paste in the following and be sure to replace the domain name 6 times
|
|
|
|
```
|
|
server {
|
|
server_name admin.example.com;
|
|
listen 443 ssl;
|
|
|
|
# Settings
|
|
autoindex off;
|
|
|
|
# Locations
|
|
location / {
|
|
auth_basic "Restricted Content";
|
|
auth_basic_user_file /etc/nginx/auth/.postfixadmin;
|
|
proxy_pass http://postfixadmin:80;
|
|
proxy_http_version 1.1;
|
|
proxy_cache_bypass $http_upgrade;
|
|
proxy_ssl_server_name on;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_set_header X-Forwarded-Host $host;
|
|
proxy_set_header X-Forwarded-Port $server_port;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host $host;
|
|
proxy_connect_timeout 60s;
|
|
proxy_send_timeout 60s;
|
|
proxy_read_timeout 60s;
|
|
}
|
|
|
|
location ~ /\.(?!well-known) {
|
|
deny all;
|
|
}
|
|
|
|
location = /favicon.ico {
|
|
log_not_found off;
|
|
}
|
|
|
|
location = /robots.txt {
|
|
log_not_found off;
|
|
}
|
|
|
|
# GZip
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_proxied any;
|
|
gzip_comp_level 6;
|
|
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
|
|
|
|
# Headers
|
|
add_header X-XSS-Protection "1; mode=block" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header X-Frame-Options "SAMEORIGIN";
|
|
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
|
add_header Content-Security-Policy "default-src 'self' http: https: ws: wss: data: blob: 'unsafe-inline'; frame-ancestors 'self';" always;
|
|
add_header Permissions-Policy "interest-cohort=()" always;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
|
|
|
# SSL
|
|
ssl_certificate /etc/letsencrypt/live/admin.example.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/admin.example.com/privkey.pem;
|
|
ssl_trusted_certificate /etc/letsencrypt/live/admin.example.com/chain.pem;
|
|
}
|
|
|
|
# Redirect
|
|
server {
|
|
listen 80;
|
|
server_name admin.example.com;
|
|
return 301 https://admin.example.com$request_uri;
|
|
}
|
|
```
|
|
|
|
Make sure you have a valid certificate for the domain
|
|
|
|
One last thing we need to do is create a authpasswd file so the interface is behind a extra layer of security
|
|
|
|
to do this we need some apache tools so install them
|
|
|
|
sudo pacman -S apache
|
|
|
|
now execute the following command be sure to replace USERNAME with a username
|
|
|
|
sudo htpasswd -c /data/nginx/config/auth/.postfixadmin USERNAME
|
|
|
|
now it will ask for a password, give it one and store it well.
|
|
|
|
Finally we can restart the relevant containers
|
|
|
|
first the database so that postfixadmin can access it
|
|
|
|
sudo docker-compose -f ~/docker/mariadb/docker-compose.yml down && sudo docker-compose -f ~/docker/mariadb/docker-compose.yml up -d
|
|
|
|
Now we start the postfixadmin container so nginx can find it
|
|
|
|
sudo docker-compose -f ~/docker/postfixadmin/docker-compose.yml up -d
|
|
|
|
Finally restart the nginx container
|
|
|
|
sudo docker-compose -f ~/docker/nginx/docker-compose.yml down && sudo docker-compose -f ~/docker/nginx/docker-compose.yml up -d
|
|
|
|
You should be able to go example.com/setup.php, first login with the http auth user and password you given with the htpasswd command then scroll down to generate setup password
|
|
|
|
Copy the setup password hash into your configuration file
|
|
|
|
sudo vim /data/postfixadmin/config.local.php
|
|
|
|
Now you should be able to create an admin user with the password you have given it You might have to restart the container for the setup password to work
|
|
|
|
# Rspamd
|
|
Rspamd will be our spam solution, it will scan incoming and outgoing emails, it requires quite a lot of configuration files, but it simply runs circles around spamassassin.
|
|
|
|
Like always we start with a project directory
|
|
|
|
mkdir -p ~/docker/rspamd
|
|
|
|
for Rspamd there is again no official dockerfile, so we will create our own minimal image
|
|
|
|
vim ~/docker/rspamd/Dockerfile
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
FROM alpine
|
|
RUN mkdir /run/rspamd/ && touch /run/rspamd/rspamd.sock && chmod 600 /run/rspamd/rspamd.sock
|
|
RUN apk add --no-cache rspamd rspamd-client rspamd-controller rspamd-utils redis
|
|
ENTRYPOINT apk update && apk upgrade && redis-server /etc/redis.conf --daemonize yes && rspamd -i && while true; do sleep 10000; done
|
|
```
|
|
|
|
Now we create a docker compose file
|
|
|
|
vim ~/docker/rspamd/docker-compose.yml
|
|
|
|
Now paste in the following content
|
|
|
|
```
|
|
version: '3'
|
|
|
|
services:
|
|
rspamd:
|
|
build: .
|
|
container_name: rspamd
|
|
restart: always
|
|
volumes:
|
|
- /data/rspamd/log:/var/log/rspamd
|
|
#- /data/rspamd/config:/etc/rspamd
|
|
#- /data/rspamd/database:/var/lib/redis
|
|
#- /data/rspamd/stats:/var/lib/rspamd
|
|
#- /data/rspamd/www:/usr/share/rspamd/www
|
|
- /etc/letsencrypt/:/etc/letsencrypt/
|
|
- /etc/localtime:/etc/localtime:ro
|
|
- /etc/timezone:/etc/timezone:ro
|
|
networks:
|
|
mail:
|
|
ipv4_address: 172.20.11.40
|
|
|
|
networks:
|
|
mail:
|
|
external: true
|
|
name: mail
|
|
ipam:
|
|
config:
|
|
- subnet: 172.20.11.0/24
|
|
```
|
|
|
|
You might have noticed the # characters again, and the same strategy applies
|
|
|
|
sudo docker-compose -f ~/docker/rspamd/docker-compose.yml up -d
|
|
|
|
Now we need to copy the directories to the right place
|
|
|
|
sudo docker cp rspamd:/etc/rspamd /data/rspamd/config
|
|
sudo docker cp rspamd:/var/lib/redis /data/rspamd/database
|
|
sudo docker cp rspamd:/var/lib/rspamd /data/rspamd/stats
|
|
sudo docker cp rspamd:/usr/share/rspamd/www /data/rspamd/www
|
|
|
|
Now remove the # from the docker-compose file
|
|
|
|
vim ~/docker/rspamd/docker-compose.yml
|
|
|
|
So it looks like this
|
|
|
|
```
|
|
services:
|
|
rspamd:
|
|
volumes:
|
|
- /data/rspamd/config:/etc/rspamd
|
|
- /data/rspamd/database:/var/lib/redis
|
|
- /data/rspamd/stats:/var/lib/rspamd
|
|
- /data/rspamd/www:/usr/share/rspamd/www
|
|
```
|
|
|
|
Now simply restart the container
|
|
|
|
sudo docker-compose -f ~/docker/rspamd/docker-compose.yml down && sudo docker-compose -f ~/docker/rspamd/docker-compose.yml up -d
|
|
|
|
We need to adjust the redis config
|
|
|
|
sudo vim /data/rspamd/config/local.d/redis.conf
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
servers = "127.0.0.1";
|
|
```
|
|
|
|
This will add spamscore details to the headers.
|
|
|
|
sudo vim /data/rspamd/config/override.d/milter_headers.conf
|
|
|
|
add in the following content
|
|
|
|
```
|
|
extended_spam_headers = true;
|
|
```
|
|
|
|
Now we need to adjust some small files for the listeners
|
|
|
|
first the worker-proxy
|
|
|
|
sudo vim /data/rspamd/config/local.d/worker-proxy.inc
|
|
|
|
Paste in the following lines
|
|
|
|
```
|
|
bind_socket = "0.0.0.0:11332";
|
|
milter = yes;
|
|
timeout = 120s;
|
|
upstream "local" {
|
|
default = yes;
|
|
self_scan = yes;
|
|
hosts = "rspamd:11332";
|
|
}
|
|
count = 4;
|
|
max_retries = 5;
|
|
discard_on_reject = false;
|
|
quarantine_on_reject = false;
|
|
spam_header = "X-Spam";
|
|
reject_message = "Spam message rejected";
|
|
```
|
|
|
|
Now the worker-normal
|
|
|
|
sudo vim /data/rspamd/config/local.d/worker-normal.inc
|
|
|
|
paste in the following content
|
|
|
|
```
|
|
bind_socket = "0.0.0.0:11333";
|
|
```
|
|
|
|
And finally the worker-controller
|
|
|
|
We first need a hashed password to put in the file so go into the container
|
|
|
|
sudo docker exec -it rspamd sh
|
|
|
|
Now generate a hashed password
|
|
|
|
rspamadm pw
|
|
|
|
Give it your desired password and copy the result into the following file
|
|
|
|
sudo vim /data/rspamd/config/local.d/worker-controller.inc
|
|
|
|
Paste in the following content replacing the password obviously
|
|
|
|
```
|
|
password = "A Very Strong Password";
|
|
bind_socket = "0.0.0.0:11334";
|
|
mode=0622
|
|
secure_ip = "172.20.11.10";
|
|
```
|
|
|
|
Finally we create a file that will prevent outgoing mail being marked as spam
|
|
|
|
sudo vim /data/rspamd/config/local.d/settings.conf
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
authenticated {
|
|
priority = high;
|
|
authenticated = yes;
|
|
apply {
|
|
actions {
|
|
"rewrite subject" = 100.0;
|
|
"add header" = 100.0;
|
|
"reject" = 100.0;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Now we can restart rspamd to make the changes take effect
|
|
|
|
sudo docker-compose -f ~/docker/rspamd/docker-compose.yml down && sudo docker-compose -f ~/docker/rspamd/docker-compose.yml up -d
|
|
|
|
Rspamd configuration is complete, now we just need to configure the reverse proxy so we can access it.
|
|
|
|
We simply create a file in the services directory of nginx
|
|
|
|
sudo vim /data/nginx/config/services/rspamd.active
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
server {
|
|
server_name spam.example.com;
|
|
listen 443 ssl;
|
|
|
|
# Settings
|
|
client_max_body_size 100M;
|
|
autoindex off;
|
|
root /data/rspamd/www;
|
|
|
|
# Locations
|
|
location / {
|
|
auth_basic "Restricted Content";
|
|
auth_basic_user_file /etc/nginx/auth/.rspamdhtpasswd;
|
|
proxy_pass http://172.20.11.40:11334;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header Host $http_host;
|
|
}
|
|
|
|
# Headers
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
|
|
add_header X-Content-Type-Options nosniff;
|
|
add_header X-Frame-Options SAMEORIGIN;
|
|
add_header X-XSS-Protection "1; mode=block";
|
|
|
|
# SSL
|
|
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;
|
|
ssl_prefer_server_ciphers on;
|
|
ssl_session_cache shared:TLS:10m;
|
|
ssl_session_timeout 1d;
|
|
ssl_stapling on;
|
|
ssl_stapling_verify on;
|
|
server_tokens off;
|
|
ssl_certificate /etc/letsencrypt/live/spam.example.com/fullchain.pem;
|
|
ssl_trusted_certificate /etc/letsencrypt/live/spam.example.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/spam.example.com/privkey.pem;
|
|
}
|
|
|
|
# Redirect
|
|
server {
|
|
listen 80;
|
|
server_name spam.example.com;
|
|
return 301 https://spam.example.com$request_uri;
|
|
}
|
|
```
|
|
|
|
And we add Nginx to the mail network
|
|
|
|
vim ~/docker/nginx/docker-compose.yml
|
|
|
|
Here add the following in the right place
|
|
|
|
```
|
|
services:
|
|
nginx:
|
|
networks:
|
|
mail:
|
|
ipv4_address: 172.20.11.20
|
|
|
|
networks:
|
|
mail:
|
|
external: true
|
|
name: mail
|
|
```
|
|
|
|
now execute the following command be sure to replace USERNAME with a username
|
|
|
|
sudo htpasswd -c /data/nginx/config/auth/.rspamdpasswd USERNAME
|
|
|
|
now it will ask for a password, give it one and store it well.
|
|
|
|
Now we simply restart nginx
|
|
|
|
sudo docker-compose -f ~/docker/nginx/docker-compose.yml down && sudo docker-compose -f ~/docker/nginx/docker-compose.yml up -d
|
|
|
|
RspamD should be up and running you can access it by going to spam.example.com first entering the auth user/pass and then the rspamd controller password you set.
|
|
|
|
# Roundcube
|
|
Roundcube is a nice web email client, it is optional for this mailstack, but it can be very handy.
|
|
|
|
We start with a project directory
|
|
|
|
mkdir -p ~/docker/roundcube
|
|
|
|
Now create the docker-compose file
|
|
|
|
vim ~/docker/roundcube/docker-compose.yml
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
version: '3'
|
|
|
|
networks:
|
|
roundcube:
|
|
external: true
|
|
name: roundcube
|
|
|
|
services:
|
|
roundcube:
|
|
image: roundcube/roundcubemail
|
|
container_name: roundcube
|
|
restart: always
|
|
volumes:
|
|
- /data/roundcube/:/var/www/html/
|
|
networks:
|
|
roundcube:
|
|
ipv4_address: 172.20.32.10
|
|
```
|
|
|
|
Roundcube will run in its own network so lets create that too
|
|
|
|
sudo docker network create --subnet=172.20.32.0/24 roundcube
|
|
|
|
Now we will add Nginx to the roundcube network
|
|
|
|
vim ~/docker/nginx/docker-compose.yml
|
|
|
|
Here add the following in the right place
|
|
|
|
```
|
|
services:
|
|
nginx:
|
|
networks:
|
|
roundcube:
|
|
ipv4_address: 172.20.32.20
|
|
|
|
networks:
|
|
roundcube:
|
|
external: true
|
|
name: roundcube
|
|
```
|
|
|
|
And create the config file for Nginx
|
|
|
|
sudo vim /data/nginx/config/services/roundcube.active
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
server {
|
|
server_name example.com;
|
|
listen 443 ssl;
|
|
|
|
# Settings
|
|
autoindex off;
|
|
client_max_body_size 5000M;
|
|
|
|
# Locations
|
|
location / {
|
|
proxy_pass http://roundcube:80;
|
|
proxy_http_version 1.1;
|
|
proxy_cache_bypass $http_upgrade;
|
|
proxy_ssl_server_name on;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_set_header X-Forwarded-Host $host;
|
|
proxy_set_header X-Forwarded-Port $server_port;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host $host;
|
|
proxy_connect_timeout 6000s;
|
|
proxy_send_timeout 6000s;
|
|
proxy_read_timeout 6000s;
|
|
}
|
|
|
|
location ~ /\.(?!well-known) {
|
|
deny all;
|
|
}
|
|
|
|
location = /favicon.ico {
|
|
log_not_found off;
|
|
}
|
|
|
|
location = /robots.txt {
|
|
log_not_found off;
|
|
}
|
|
|
|
# GZip
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_proxied any;
|
|
gzip_comp_level 6;
|
|
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
|
|
|
|
# Headers
|
|
add_header X-XSS-Protection "1; mode=block" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header X-Frame-Options "SAMEORIGIN";
|
|
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
|
#add_header Content-Security-Policy "default-src 'self' http: https: ws: wss: data: blob: 'unsafe-inline'; frame-ancestors 'self';" always;
|
|
add_header Permissions-Policy "interest-cohort=()" always;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
|
|
|
# SSL
|
|
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
|
|
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
|
|
}
|
|
|
|
# Redirect
|
|
server {
|
|
listen 80;
|
|
server_name example.com;
|
|
return 301 https://example.com$request_uri;
|
|
}
|
|
```
|
|
|
|
Now we will add MariaDB to the Roundcube network
|
|
|
|
vim ~/docker/mariadb/docker-compose.yml
|
|
|
|
Here add the following in the right place
|
|
|
|
```
|
|
services:
|
|
mariadb:
|
|
networks:
|
|
roundcube:
|
|
ipv4_address: 172.20.32.30
|
|
|
|
networks:
|
|
roundcube:
|
|
external: true
|
|
name: roundcube
|
|
```
|
|
|
|
Now we are going to restart the mariadb container so it is actually part of the network
|
|
|
|
sudo docker-compose -f ~/docker/mariadb/docker-compose.yml down && sudo docker-compose -f ~/docker/mariadb/docker-compose.yml up -d
|
|
|
|
Now we will create a database and user
|
|
|
|
sudo docker exec -it mariadb mysql -p
|
|
|
|
Enter the Mysql root password you provided during the creation of the mariadb container and you should be in.
|
|
|
|
Create a database with
|
|
|
|
create database roundcube;
|
|
|
|
Create the user with
|
|
|
|
create user roundcube@'172.20.32.10' identified by 'ROUNDCUBEDATABASEPASSWORD';
|
|
|
|
Give privileges to the user on the database with
|
|
|
|
grant all privileges on roundcube.* to roundcube@'172.20.32.10';
|
|
|
|
And Flush the privileges with
|
|
|
|
flush privileges;
|
|
|
|
Exit mysql with
|
|
|
|
quit;
|
|
|
|
Create the config directory
|
|
|
|
sudo mkdir -p /data/roundcube/config
|
|
|
|
Finally we create the configuration file
|
|
|
|
sudo vim /data/roundcube/config/config.inc.php
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
<?php
|
|
$config['db_dsnw'] = 'mysql://roundcube:ROUNDCUBEDATABASEPASSWORD@172.20.32.30/roundcube';
|
|
$config['db_dsnr'] = '';
|
|
$config['smtp_host'] = 'ssl://mail.example.com';
|
|
$config['imap_host'] = 'ssl://mail.example.com';
|
|
$config['skin'] = 'elastic';
|
|
$config['request_path'] = '/';
|
|
$config['log_driver'] = 'file';
|
|
$config['log_logins'] = true;
|
|
$config['support_url'] = '';
|
|
$config['temp_dir'] = '/tmp/roundcube-temp';
|
|
$config['enable_spellcheck'] = true;
|
|
$config['spellcheck_engine'] = 'pspell';
|
|
$config['zipdownload_selection'] = true;
|
|
#include(__DIR__ . '/config.docker.inc.php');
|
|
```
|
|
|
|
Now we start the container
|
|
|
|
sudo docker-compose -f ~/docker/roundcube/docker-compose.yml up -d
|
|
|
|
# SPF
|
|
SPF is very simple, you just need to add the following line as a TXT record to your sending domain DNS records
|
|
|
|
v=spf1 mx -all
|
|
|
|
# DKIM
|
|
DKIM is a little more complicated, but everything should be fine if you follow the guide
|
|
|
|
First we need to create a directory for the keys
|
|
|
|
sudo mkdir -p /data/rspamd/stats/dkim
|
|
|
|
Now we go into the rspamd container
|
|
|
|
sudo docker exec -it rspamd sh
|
|
|
|
Now we generate the key
|
|
|
|
rspamadm dkim_keygen -b 2048 -s 1 -k /var/lib/rspamd/dkim/1.key > /var/lib/rspamd/dkim/1.txt
|
|
|
|
Now cat the key
|
|
|
|
cat /var/lib/rspamd/dkim/1.txt
|
|
|
|
Now put the v the k and the p in your DNS records like so
|
|
|
|
1._domainkey TXT v=DKIM1; k=rsa; p=MIIBI.....IDAQAB
|
|
|
|
Be sure to put in the whole p= value it should start with MIIBI and and with IDAQAB with no spaces or " in between
|
|
|
|
You can exit the container
|
|
|
|
exit
|
|
|
|
Now we need to adjust the dkim configuration file for Rspamd
|
|
|
|
sudo vim /data/rspamd/config/local.d/dkim_signing.conf
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
path = "/var/lib/rspamd/dkim/$selector.key";
|
|
selector = "1";
|
|
allow_username_mismatch = true;
|
|
```
|
|
|
|
Restart RspamD to make it take effect
|
|
|
|
sudo docker-compose -f ~/docker/rspamd/docker-compose.yml down && sudo docker-compose -f ~/docker/rspamd/docker-compose.yml up -d
|
|
|
|
# DMARC
|
|
DMARC is again very simple, It just tells the receiving server what to do if SPF or DKIM fail
|
|
|
|
Add the following TXT record to your DNS record
|
|
|
|
_dmarc TXT v=DMARC1; p=reject;
|
|
|
|
# ARC
|
|
We can simply use the DKIM keys, so no need to generate anything, we just need to add some configuration
|
|
|
|
sudo vim /data/rspamd/config/local.d/arc.conf
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
path = "/var/lib/rspamd/dkim/$selector.key";
|
|
selector = "1";
|
|
allow_username_mismatch = true;
|
|
sign_authenticated = true;
|
|
sign_incoming = true;
|
|
use_domain = "header";
|
|
use_domain_sign_inbound = "recipient";
|
|
symbol_signed = "ARC_SIGNED";
|
|
sign_local = true;
|
|
auth_only = true;
|
|
```
|
|
|
|
Restart RspamD to make it take effect
|
|
|
|
sudo docker-compose -f ~/docker/rspamd/docker-compose.yml down && sudo docker-compose -f ~/docker/rspamd/docker-compose.yml up -d
|
|
|
|
# Fail2ban
|
|
|
|
Rspamd and Postfixadmin are protected by nginx auth, We just need to protect Postfix, Dovecot and Roundcube so lets do them one by one
|
|
|
|
create the postfix file
|
|
|
|
sudo vim /etc/fail2ban/filter.d/postfixx.local
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
[INCLUDES]
|
|
before = common.conf
|
|
|
|
[Definition]
|
|
failregex = ^.*: lost connection after.*\[<HOST>\]$
|
|
^.*:.*\[<HOST>\]: SASL LOGIN authentication failed:.*$
|
|
^.*: warning: hostname .* does not resovlve to address <HOST>$
|
|
^.*: warning: non-SMTP command from .*\[<HOST>\].*$
|
|
```
|
|
|
|
create the dovecot file
|
|
|
|
sudo vim /etc/fail2ban/filter.d/dovecott.local
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
[INCLUDES]
|
|
before = common.conf
|
|
|
|
[Definition]
|
|
failregex = ^.*auth failed.*rip=<HOST>,.*$
|
|
```
|
|
|
|
create the roundcube file
|
|
|
|
sudo vim /etc/fail2ban/filter.d/roundcube.local
|
|
|
|
Paste in the following content
|
|
|
|
```
|
|
[INCLUDES]
|
|
before = common.conf
|
|
|
|
[Definition]
|
|
failregex = ^.*Login failed for .*Real-IP: <HOST>,.*$
|
|
```
|
|
|
|
Now we need to add the filters to our jails file
|
|
|
|
sudo vim /etc/fail2ban/jail.local
|
|
|
|
Add in the following lines
|
|
|
|
```
|
|
## Postfix
|
|
[postfixx]
|
|
enabled = true
|
|
logpath = /data/mailserver/log/postfix.log
|
|
|
|
## Dovecot
|
|
[dovecott]
|
|
enabled = true
|
|
logpath = /data/mailserver/log/dovecot.log
|
|
|
|
## Roundcube
|
|
[roundcube]
|
|
enabled = true
|
|
logpath = /data/roundcube/logs/errors.log
|
|
```
|
|
|
|
Now simply restart the service to make the changes take effect
|
|
|
|
sudo systemctl restart fail2ban
|
|
|
|
Be absolutely sure that it is running
|
|
|
|
sudo systemctl status fail2ban
|