Info-Mails für Unattended-Upgrades

Im Mai hatte ich zuletzt zum Thema automatische Updates für Linux Server geschrieben und einen zweiten Teil versprochen. Dieser kommt mit diesem Beitrag, wenn auch etwas verspätet. Wie sich Updates ohne manuelles zutun installieren lassen, wurde ja bereits geklärt. Doch was passiert, wenn ein solches Update schief geht? Natürlich lässt sich das nie vermeiden und im schlimmsten Fall, müsste der betroffene Server aus einem Backup wiederhergestellt werden.

Das ist aber kein Problem, denn wir haben ja alle geprüfte Backups. Richtig?

Dennoch möchte man als Administrator natürlich benachrichtigt werden, bevor die ersten Kunden, Partner oder Mitarbeiter sich mit dem entsprechenden Server verbinden wollen und feststellen, dass nichts funktioniert wie es eigentlich sollte.

Das Einrichten der E-Mail-Funktion ist eigentlich nicht recht aufwändig, allerdings gibt es viele Anleitungen die nicht den gewünschten Erfolg bringen. Diese ermöglichen zwar das Versenden von E-Mails über die Kommandozeile, ob das Programm unattended-upgrades dies dann aber auch kann, ist eine andere Frage.

Die einfachste und aus meiner Erfahrung bisher am stabilsten laufende Lösung ist über den Linux Mail-Client msmtp. Dieser lässt sich bequem über sudo apt install msmtp installieren und benötigt keine weiteren Pakete wie sendmail, heirloom-mailx oder s-nail, um zu funktionieren. Eine simple Konfigurationsdatei, ein bestehender E-Mail-Account und ein paar Symlinks reichen aus, um die ersten E-Mails zu versenden.

Um Probleme zu vermeiden, sollte sichergestellt werden, dass die Datei /etc/ssl/certs/ca-certificates.crt existiert. Falls nicht, muss das Programm ca-certificates über apt installiert werden.

Installation von msmtp

$ sudo apt install msmtp ca-certificates

$ sudo vi /etc/msmtprc
# Allgemeine Optionen
defaults
auth           on
tls            on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile        ~/.msmtp.log

# Konfiguration für benutzer@anbieter.com
account        unattendedupgrades
host           mail.anbieter.com
port           587
from           "Absender Name <benutzer@anbieter.com>"
user           benutzer@anbieter.com
password       SuperGeheimesStarkesPasswort

# Standard Account für E-Mails
account default : unattendedupgrades

Da die Datei ein Passwort im Klartext enthält, sind die folgenden Sicherheitsvorkehrungen empfehlenswert.

  1. Es sollte ein eigener E-Mail-Account für diese Funktion verwendet werden.
  2. Die Datei sollte dem Benutzer und der Gruppe root zugeordnet sein.
  3. Nur der Besitzer (root) sollte die Datei lesen und darin schreiben dürfen
$ sudo chown root:root /etc/msmtprc
$ sudo chmod 600
$ ls -lah /etc/msmtprc
-rw------- 1 root root 436 Aug 31 19:25 /etc/msmtprc

Über die Option accounts lassen sich auch weitere Accounts hinzufügen, die sich über msmtp -a accountname auf der Kommandozeile angeben lassen. Das Programm unattended-upgrades verwendet jedoch immer den Standard-Account, da es in dessen Konfigurationsdatei keine Optionen für msmtp gibt.

Ob die E-Mails versendet werden können, lässt sich mit dem folgenden Befehl prüfen.

$ echo -e "Subject: Betreff kommt hier hin \n\n Nachricht kommt hier hin \n\n" |sudo mail empfaenger@mailserver.com

Dabei ist wichtig, dass E-Mails in diesem Setup nur mit sudo oder als root-Benutzer gesendet werden können, da die Konfigurationsdatei für msmtp nur mit root-Rechten lesbar ist. Persönliche Mail-Accounts können in eine zusätzlichen Konfigurationsdatei in ~/.msmtprc abgelegt werden. Diese wird dann aber nicht von unattende-upgrades verwendet.

Damit die E-Mails auch über sendmail versendet werden können, welches von unattended-upgrades bevorzugt verwendet wird, muss ein Symlink erstellt werden.

$ sudo ln -s /usr/bin/msmtp /usr/sbin/sendmail

E-Mail Tests

Mit dem Script uu-mailtest.py (siehe Code am Ende des Artikels) kann im Anschluss der Versand überprüft werden. Das Skript simuliert den Mail-Versand von unattended-upgrades.

$ sudo ./uu-mailtest.py
Sende E-Mail mit 'sendmail' Programm
E-Mail erfolgreich versendet.

Theoretisch könnte man den gleichen Symlink auch für das Program mail setzen, welches von unattended-upgrades alternativ verwendet wird. Dabei stößt man jedoch auf das Problem, dass hierfür ein Schalter verwendet wird, der von msmtp nicht unterstützt wird. Das lässt sich ebenfalls durch das Skript prüfen.

$ sudo ln -s /usr/bin/msmtp /usr/sbin/mail
$ sudo ./uu-mailtest.py
Sende E-Mail mit 'mail' Programm
/usr/bin/mail: invalid option -- 'r'
E-Mail konnte nicht versendet werden.

Da sendmail die erste Wahl ist, sollte mail jedoch keine Probleme machen, selbst wenn es installiert ist.

E-Mails nur bei Fehlern

Wenn sichergestellt wurde, dass unattended-upgrades auf allen Server Mails korrekt versendet, kann die Konfiguration angepasst werden, damit zukünftig Benachrichtigungen nur noch bei Fehlern zugestellt werden. Das vermindert die Anzahl an unnötigen Nachrichten im Posteingang und schont den Admin am Morgen.

$ sudo vi /etc/apt/apt.conf.d/50unattended-upgrades
...
// Legt fest, ob E-Mails bei jedem Update oder nur bei Fehlern verschickt werden sollen
Unattended-Upgrade::MailOnlyOnError "true";

Skript "uu-mailtest.py"

Im Skript müssen nur die beiden Variablen from_email und to_email entsprechend angepasst werden. Anschließend muss das Skript noch mit dem Befehl sudo chmod +x uu-mailtest.py ausführbar gemacht werden.

#!/usr/bin/python3

import os
import subprocess
import email.charset
import locale
from subprocess import (Popen,PIPE)
from email.message import Message

### DIESEN TEIL BITTE ANPASSEN ####
from_email = "absender@email.com"
to_email = "empfaenger@email.com"
###################################

# Variablen für Tests
MAIL_BINARY = "/usr/bin/mail"
SENDMAIL_BINARY = "/usr/sbin/sendmail"
subject = "Unattended-Upgrades Test Mail"
body = "Wenn diese E-Mail ankommt, funktioniert die E-Mail-Funktion auch für Unattended-Upgrades! :)"

# Funktion für das Versenden über /usr/bin/mail
def _send_mail_using_mailx(from_address, to_address, subject, body):
    encoded_body = body.encode(locale.getpreferredencoding(False), errors="replace")
    mail = subprocess.Popen(
        [MAIL_BINARY, "-r", from_address, "-s", subject, to_address],
        stdin=subprocess.PIPE, universal_newlines=False)
    mail.stdin.write(encoded_body)
    mail.stdin.close()
    ret = mail.wait()
    return ret

# Funktion für das Versenden über /usr/sbin/sendmail
def _send_mail_using_sendmail(from_address, to_address, subject, body):
    msg = Message()
    msg['Subject'] = subject
    msg['From'] = from_address
    msg['To'] = to_address
    msg['Auto-Submitted'] = "auto-generated"
    charset = email.charset.Charset("utf-8")
    charset.body_encoding = email.charset.QP  # type: ignore
    msg.set_payload(body, charset)
    sendmail = subprocess.Popen(
        [SENDMAIL_BINARY, "-oi", "-t"],
        stdin=subprocess.PIPE, universal_newlines=True)
    sendmail.stdin.write(msg.as_string())
    sendmail.stdin.close()
    ret = sendmail.wait()
    return ret

# Versendet die Test E-Mail über '/usr/sbin/sendmail' oder '/usr/bin/mail'
if os.path.exists(SENDMAIL_BINARY):
    print("Sende E-Mail mit 'sendmail' Programm")
    ret = _send_mail_using_sendmail(from_email, to_email, subject, body)
elif os.path.exists(MAIL_BINARY):
    print("Sende E-Mail mit 'mail' Programm")
    ret = _send_mail_using_mailx(from_email, to_email, subject, body)

# Ausgabe je nach Ergebnis
if ret == 0:
    print("E-Mail erfolgreich versand.")
else:
    print("E-Mail konnte nicht versendet werden.")