--- 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 = ``` 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 ``` /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.*\[\]$ ^.*:.*\[\]: SASL LOGIN authentication failed:.*$ ^.*: warning: hostname .* does not resovlve to address $ ^.*: warning: non-SMTP command from .*\[\].*$ ``` 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=,.*$ ``` 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: ,.*$ ``` 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