Virtualisierter Server - Mail-Relay für die VMS mit Postfix und Sendgrid
In diesem Beitrag beschreibe ich, wie ich für alle VMs zentral einen Mailserver eingerichtet habe, der zum Versand der E-Mails nach außen zuständig ist. So muss ich die Verwendung eines Smarthost nur einmal konfigurieren. Alle anderen VMs dürfen keine Mails raus schicken - nur über den Mailserver.
Bisherige Beiträge in der Reihe:
- SSL überall mit Let’s Encrypt, verteilt durch Puppet
- Puppet Server aufsetzen
- pfsense-Firewall zur Einteilung des Netzwerks mit ipv4 und ipv6
- IPv6-Vorüberlegungen
- Hardware-Setup und Proxmox
Voraussetzungen
- Ein Account bei einem Smarthost, also bei einem Mailserver, der SMTP im Internet macht und dort auf keiner Blacklist steht. Ich nehme dafür Sendgrid, habe aber keine umfangreichen Recherchen unternommen, da den besten Anbieter zu finden. Bei Sendgrid kann man bis zu 100 Mails am Tag im kostenfreien Account versenden. Nachdem ich nicht vor habe, meine privaten Mails über meinen Server laufen zu lassen sondern nur Benachrichtigungsmails von internen Diensten herumschicken möchte, reicht das locker.
- Eine Domain. Es geht sicherlich irgendwie auch ohne aber wenn man eine hat und dort Dinge wie SPF selbst konfigurieren kann, ist alles viel einfacher. Notfalls holt man sich bei Freenom eine mit seltsamem Suffix und macht DNS über CloudFlare.
- Funktionierendes Puppet-Setup (siehe Link oben), im Idealfall außerdem bereits funktionierende SSL-Zertifikate von Let’s Encrypt
Beschreibung des Setups
Wie oben kurz abgerissen, plane ich, in der Firewall alle Kommunikation
mit den üblichen Ports 25
,
465
und 587
zu sperren und
nur der einen VM, die als ausgehender Mailserver dienen soll,auf diesen
Ports Traffic zu erlauben und dann auch nur zum Smarthost. Diese
Sicherheitsmaßnahmen dienen nicht so sehr dem Schutz vor Spammern in
meinem (vollkommen virtuellen) eigenen Netzwerk sondern vor
unkontrolliertem Mailfluß nach Außen durch unzureichend konfigurierte
Dienste.
Die verschiedenen VMs authentifizieren nicht mit Benutzernamen und Passwörtern mit dem zentralen Mailserver sondern mit SSL-Clientzertifikaten, weil sich das schöner und sinnvoller automatisieren lässt. Grusliger Trick an der Stelle: Ich verwende dazu einfach die von Puppet. Prinzipielles Problem an so einer Sache ist immer, die privaten Schlüssel generieren zu lassen, den CSR (Zertifikatsanfrage) zu prüfen und im Erfolgsfall zu signieren und wieder zurück an den Client zu übertragen. Genau auf diese Art und Weise funktioniert auch die Autentifizierung bei Puppet. Der MTA auf den VMs wird sowieso als root starten, also kann er auch gleich die SSL-Zertifikate und -Schlüssel lesen.
Als Software verwende ich Postfix, weil ich damit die meiste Erfahrung habe und es ein ganz gutes Modul dazu in der Puppet Forge gibt.
Der zentrale Server authentifiziert gegenüber Sendgrid mit
Benutzername und Passwort, alles über TLS. Mails, die auf irgend einer
VM irgendwann mal an root
gingen, landen in meinem
normalen Mail-Postfach.
Aufsetzen des Mailservers
Für den Mailserver setze ich einen neuen Ubuntu 16.04-Container auf
und lasse mein normales Puppet-Setup drüber laufen. Ich weise dabei
auch das Profil ssl
zu (siehe Anleitung ganz
oben), damit der Server ein gültiges Zertifikat für StartTLS aufweisen
kann. Das ist nicht notwendig, vor allem nicht, weil bei mir alles
virtuell läuft, aber gute Praxis und wer weiß, welche VM mal irgendwohin
umzieht. Als Hostname wähle ich einfach mal
mailout.domain.tld
.
Der ganze Rest funktioniert jetzt über Puppet. Wie genau Ihr Euren Puppet-Code auf den Puppet Server bringt, ist wohl bei allen unterschiedlich. Ich verwende r10k und eine Strkturierung in Roles und Profiles. Andere nehmen nur git. Nachdem es zahlreiche Methoden gibt, beschreibe ich hier das Setup direkt auf dem Server, Ihr müsst für Euer Setup übersetzen.
Wir brauchen zwei Module aus der Forge, also führen wir auf dem Puppet Server aus:
sudo puppet module install camptocamp-augeas
sudo puppet module install camptocamp-postfix
Wir legen ein neues Profil an:
sudo mkdir -p /etc/puppetlabs/code/modules/profiles/{manifests,data}
In die dazugehörge hiera.yaml
des Moduls nur kurz:
# Datei /etc/puppetlabs/code/modules/profiles/hiera.yaml
---
version: 5
defaults:
datadir: data
data_hash: yaml-data
hierarchy:
- name: Common
path: 'common.yaml'
Nun schreiben wir das Profil mailrelay in Puppet-Code:
# Datei /etc/puppetlabs/puppet/code/modules/profiles/manifests/mailrelay.pp
class profiles::mailrelay {
$relayhost = lookup('profiles::mailrelay::relayhost')
$relayport = lookup('profiles::mailrelay::relayport', String, 'first', '587')
$relay_user = lookup('profiles::mailrelay::relay_user')
$relay_password = lookup('profiles::mailrelay::relay_password')
$root_mail_recipient = lookup('profiles::mailrelay::root')
class { 'postfix':
smtp_listen => 'all',
master_submission => 'submission inet n - - - - smtpd',
relayhost => "[${relayhost}]:${relayport}",
myorigin => $facts['domain'],
root_mail_recipient => $root_mail_recipient,
}
postfix::config {
'mydomain' : value => $facts['domain'];
'smtp_sasl_auth_enable' : value => 'yes';
'smtp_sasl_security_options' : value => 'noanonymous';
'smtp_sasl_password_maps' : value => 'hash:/etc/postfix/sasl_password';
'smtp_tls_security_level' : value => 'encrypt';
'smtpd_tls_security_level' : value => 'encrypt';
'smtpd_tls_cert_file' : value => "/etc/letsencrypt/certs/${facts['fqdn']}_fullchain.pem";
'smtpd_tls_key_file' : value => "/etc/letsencrypt/private/${facts['fqdn']}.key";
'smtpd_client_restrictions' : value => 'permit_tls_all_clientcerts,reject';
'smtpd_relay_restrictions' : value => '$smtpd_client_restrictions';
'smtpd_tls_CAfile' : value => '/etc/puppetlabs/puppet/ssl/certs/ca.pem';
'smtpd_tls_ask_ccert' : value => 'yes';
'smtpd_tls_req_ccert' : value => 'yes';
'smtpd_enforce_tls' : value => 'yes';
}
postfix::hash { '/etc/postfix/sasl_password':
ensure => present,
content => "${relayhost} ${relay_user}:${relay_password}",
}
}
Der Reihe nach erklärt:
- Die Konfiguration wird geladen. Ist kein Port für den Smarthost
vorgegeben, wird
587
(Submission) verwendet. - Die Klasse
postfix
wird instanziert und grob vorkonfiguriert:- auf allen Netzwerkinterfaces wird gelauscht.
- der Port
587
(Submission) wird geöffnet. - der
relayhost
wird ohne Nachfrage nach MX-Records (deswegen die eckigen Klammern) konfiguriert. - als Absender-Domain wird die lokale Domain, also im Fall des
Beispiels hier
domain.tld
verwendet. Das ergibt sich aus dem Hostname. - Mails an
root
werden umgeleitet.
- Postfix wird fein konfiguriert, hauptsächlich was TLS und Auth
angeht:
- nochmal Domain sicherstellen.
- Ausgehende Mails zum Smarthost:
- Mit Benutzername und Kennwort authentifizieren.
smtp_sasl_security_options
wäre eigentlichnoplaintext noanonymous
, Sendgrid möchte aber Authentifizierung in Plain Text, dafür über TLS. Das ist in Ordnung, muss man aber in Postfix erst konfigurieren.- Die Datei mit Benutzername und Passwort für den Smarthost wird eingebunden.
- TLS-Verschlüsselung aktivieren.
- Eingehende Mails von VMs:
- TLS-Verschlüsselung aktivieren.
- Das Zertifikat von Let’s Encrypt wird für StartTLS verwendet.
- Allen Zertifikaten, die von der als nächstes spezifizierten CA signiert sind, wird vertraut und der VM, die sich damit ausweist, wird das Versenden von E-mails erlaubt.
- Als CA wird die von Puppet angegeben.
- Die Nachfrage und Notwendigkeit von SSL Client-Zertifikaten wird aktiviert
- Nichts geht ohne TLS.
- Benutzername und Passwort für den Smarthost werden nach
/etc/postfix/sasl_password
geschrieben und für Postfix nutzbar gemacht.
Daran muss auch nichts angepasst werden; das Manifest ist allgemeingültig. Der zur VM des Mailservers gehörende FQDN, die Domain, die Zugangsdaten etc werden alle von woanders geholt.
Als Standardkonfiguration muss noch etwas hinterlegt werden:
# Datei /etc/puppetlabs/code/modules/profiles/data/common.yaml
# [...]
profiles::mailrealy::relayhost: smtp.sendgrid.net
profiles::mailrelay::relay_user: apikey
profiles::mailrelay::relay_password: SELBSTWASAUSDENKEN
profiles::mailrelay::root_mail_recipient: [email protected]
Jetzt muss das Profil noch der VM zugewiesen werden. In meinem Beispiel also:
# Datei /etc/puppetlabs/code/environments/production/data/nodes/mailout.domain.tld.yaml
classes:
- profiles::ssl
- profiles::mailrelay
Als letztes noch die eigentliche Konfiguration der Zugangsdaten:
# Datei /etc/puppetlabs/code/environments/production/data/email.yaml
profiles::mailrelay::relayhost: "smtp.sendgrid.net"
profiles::mailrelay::relay_user: "apikey"
profiles::mailrelay::relay_password: "DASISTNICHTMEINAPIKEY"
profiles::mailrelay::root: "[email protected]"
Sinnvoll ist es hier, das Passwort verschlüsselt abzulegen.
Alles Abspeichern und Puppet laufen lassen!
Nach erfolgreichem Lauf von Puppet sollte das Versenden von E-Mails auf der VM des Mailservers bereits gehen:
mail [email protected] -s testmail
Hi, hier eine Testmail!
Viele Gruesse,
--
ich
.
Um es explizit darzulegen: Die Mail endet mit einem Punkt in einer
eigenen Zeile und dann einer Leerzeile (also zweimal Enter drücken!).
So beendet sich mail
wieder.
VMs konfigurieren
Nun muss noch ein Profil angelegt werden, mit dessen Hilfe VMs die Mails an den zentralen Mailserver übertragen bekommen. Dazu verwenden sie ebenfalls Postfix, in einem etwas kleineren Setup:
# Datei /etc/puppetlabs/code/modules/profiles/mail.pp
# send mail out via mail relay
# authentication is done via puppet's ssl certificates
class profiles::mail {
$internal_relayhost = lookup('profiles::mail::internal_relayhost')
class {'postfix':
satellite => true,
relayhost => $internal_relayhost,
}
postfix::config {
'mydomain': value => $facts['domain'];
'smtp_tls_security_level': value => 'encrypt';
'smtp_tls_cert_file': value => "/etc/puppetlabs/puppet/ssl/certs/${facts['fqdn']}.pem";
'smtp_tls_key_file': value => "/etc/puppetlabs/puppet/ssl/private_keys/${facts['fqdn']}.pem";
'smtp_enforce_tls': value => 'yes';
}
}
Der Reihe nach:
- Einzige Konfigurationseinstellung ist die Adresse des internen Relays, also des zentralen Mailservers.
- Die üblichen Feineinstellungen:
- TLS verwenden.
- SSL-Zertifikate anhand des FQDN ermitteln und nutzen
Standardeinstellungen hinterlegen:
# Datei /etc/puppetlabs/code/modules/profiles/common.yaml
# [...]
profiles::mail::internal_relayhost: invalid.example.com
Richtige Konfiguration gleich hinterher schieben:
# Datei /etc/puppetlabs/code/environments/production/data/email.yaml
profiles::mail::internal_relayhost: mailout.domain.tld
Alle VMs, die in der Lage sein sollen, über den zentralen Mailserver Mails zu versenden, brauchen jetzt das zusätzliche Profil. Möchte man beispielsweise Mails vom Puppet Server bekommen, geht das so:
# Datei /etc/puppetlabs/code/environments/production/data/nodes/puppet.domain.tld.yaml
classes:
# [...]
- profiles::mail
Alles Abspeichern und Puppet laufen lassen
Absicherung
Die Mailserver auf den VMs sind so konfiguriert, dass sie lediglich
Mails an den zentralen Mailserver weiterleiten und nicht auf
Netzwerkschnittstellen außer localhost
lauschen.
Der zentrale Mailserver nimmt nur Anfragen über SMTP an, die sich mit
einem gültigen und vom Puppet Server signierten Zertifikat ausweisen
können, also längst viel gravierendere Hürden im Sicherheitskonzept
genommen haben.
Die Mails werden ausschließlich über TLS an Sendgrid übertragen.
Das ist insofern schon mal recht sicher, es ist aber ausdrücklich nur eine Basiskonfiguration, die keinerlei Schutz vor etwa ausgenutzten Sicherheitslücken in Webanwendungen auf VMs bietet, die zum Spamversand missbraucht werden. Solche Maßnahmen sind je nach Anwendung natürlich selbst zu treffen!