Ein Server im produktiven Umfeld soll möglichst lange unbeaufsichtigt laufen können - und das Ganze auch noch störungsfrei!
Das klingt im ersten Moment nach dem Motto "never change a running system", "dont't touch" oder so...
Aber wie ist es um die Sicherheit des Servers bestellt, wenn in den betriebenen Anwendungen Sicherheitslücken auftreten?
Mittels dem APT-Tool unattended-upgrades
können sicherheitsrelevante und stabile Aktualisierungen automatisert eingespielt werden. Im Anschluss kann der Server entweder sofort oder zu einem bestimmten Zeitpunkt neu starten. Magisch, nicht?
Die Installation erfolgt durch das gleichnamige Paket unattended-upgrades.
$ sudo apt-get install unattended-upgrades
Über dpkg-reconfigure
lässt sich das Paket "konfigurieren". Bei mir ließ sich das Tool lediglich de-/aktivieren, aber das ist besser als nichts.
$ sudo dpkg-reconfigure unattended-upgrades
Anschließend sollte zumindest die Datei /etc/apt/apt.conf.d/20auto-upgrades
angelegt worden sein.
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
Um nun detaillierter die Intervalle angeben zu können, müssen wir selber eine neue Datei /etc/apt/apt.conf.d/10periodic
anlegen:
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "3";
APT::Periodic::Unattended-Upgrade "1";
Jedes Mal, wenn unattended-upgrades
laufen, werden neue Pakete geladen und jedes dritte Mal werden alte Pakete entfernt.
Da die Uhrzeiten für den Download sehr variabel eingestellt sind, konkretisieren wir den Zeitplan nun.
Uhrzeit | Varianz | Dauer (ca.) | Aktion |
---|---|---|---|
06:00 | keine | 5min | Neustart des Systems |
06:15 | 5min | 1min | Herunterladen neuer Pakete |
06:30 | 5min | 2-5min | Aktualisieren der Pakete |
Die Konfiguration für den Neustart folgt weiter unten.
Die Konfiguration für den Download läuft über systemctl edit apt-daily.timer
:
[Timer]
OnCalendar=
OnCalendar=*-*-* 06:15
RandomizedDelaySec=5m
Die Konfiguration für die Aktualisierung läuft über systemctl edit apt-daily-upgrade.timer
:
[Timer]
OnCalendar=
OnCalendar=*-*-* 06:30
RandomizedDelaySec=5m
Anschließend widmen wir uns noch der Datei /etc/apt/apt.conf.d/50unattended-upgrades
und betreiben ein bisschen Finetuning, um das System auch automatisch neu starten zu lassen (zu einem Zeitpunkt, der uns passt).
Relativ weit unten in der Datei sind ein paar Zeilen zu finden, die einkommentiert (Kommentar-Zeichen am Anfang der Zeile entfernen) und dann die Werte angepasst werden sollten.
Wer sich traut, kann mit dieser Zeile alte Kernel-Versionen entfernen lassen. Dies hat zur Folge, dass unter blöden Zufällen der Server nicht neu hochfährt, jedoch kein alter (funktionierender) Kernel mehr verfügbar ist.
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Auch wenn alte Kernels verfügbar bleiben, so können doch alte Abhängigkeiten, die nicht weiter benötigt werden, deinstalliert werden, um Speicherplatz freizugeben und das System von Altlasten zu befreien (apt-get autoremove
)
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Aber auf jeden Fall möchten wir dafür sorgen, dass der Server nach den Aktualisierungen neu startet, um die Patches anzuwenden.
Unattended-Upgrade::Automatic-Reboot "true";
Auch wenn es eher unwahrscheinlich ist, so möchten wir verhindern, dass der Neustart durch einen angemeldeten Benutzer unterbrochen wird, dies kann mit dieser Zeile erreicht werden:
Unattended-Upgrade::Automatic-Reboot-WithUsers "true";
... und, wie am Anfang des Abschnitts bereits erwähnt, definieren wir, wann der Server neu startet (hier: am folgenden Morgen um 06:00 Uhr, nachdem zwischen 06:15 und 06:45 Uhr die Updates laufen).
Unattended-Upgrade::Automatic-Reboot-Time "06:00";
Erweiterung für den Raspberry Pi (unter Raspbian):
Auf dem Raspberry Pi müssen wir im Bereich der Quellen (Unattended-Upgrade::Origins-Pattern
) die Raspbian-spezifischen Pakete noch "freigeben", indem wir folgende Zeilen in diesem Bereich hinzufügen:
"origin=Raspbian,codename=${distro_codename},label=Raspbian";
"origin=Raspberry Pi Foundation,codename=${distro_codename},label=Raspberry Pi Foundation";
Im Anschluss kann der Prozess geprüft werden, indem es einen Trokenlauf gibt.
$ sudo unattended-upgrades --dry-run
Es sollten sowohl in der Ausgabe, als auch im Log (/var/log/unattended-upgrades/unattended-upgrades.log
) keine Fehler auftauchen.
Wenn man bei Aktualisierungen per Mail informiert werden möchte, so sollte noch Software installiert werden, die den Aufruf von mailx
unterstützt. Dies ist so in der Dokumentation vermerkt. Ich habe mich für bsd-mailx entschieden, sowie msmtp als MTA (Mail Transfer Agent), um den Versandt über eine gültige E-Mail zu ermöglichen.
$ sudo apt-get install msmtp msmtp-mta bsd-mailx ca-certificates
Nach der Installation sollte der Absender (Adresse, Server, ...) noch eingestellt werden.
Hier einmal exemplarisch für WEB.DE.
/etc/msmtprc
:
# common settings
defaults
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile ~/.msmtp.log
# transport encryption
auth on
tls on
tls_starttls on
tls_certcheck off
# account settings
account webde
host smtp.web.de
port 587
from me@web.de
user me@web.de
password web-de-password
# define default account (needed for unattended-upgrades)
account default : webde
Wichtig: Die Zugriffsrechte korrekt setzen: chmod 0600 /etc/msmtprc
Anschließend kann die korrekte Einrichtung geprüft werden:
$ sudo mailx -s "Test-Mail subject" you@example.org <<<"Test-Body of your mail"
Sofern hier eine gütlige E-Mail ankommt und keine Fehlermeldungen auftauchen, so hat alles geklappt und den automatischen Updates mit E-Mail Information steht nichts mehr im Wege. Dies kann hier aktiviert werden.
/etc/apt/apt.conf.d/50unattended-upgrades
:
Unattended-Upgrade::Mail "your@address.com";
Unattended-Upgrade::Sender "me@web.de";
Unattended-Upgrade::MailReport "only-on-error";
Der Hostname, der im Betreff der E-Mail steht, lässt sich über einen Eintrag in der Datei /etc/hosts
steuern.
# resolving localhost
127.0.0.1 localhost
# resolving the FQDN for Unattended Upgrades, etc.
# the first entry is used (uu.domain.tld)
127.0.1.1 uu.domain.tld abc.provider.com
# other entries
[...]
Falls man noch einmal auf Nummer sicher gehen möchte, kann mit dem folgenden Skript der Versand getestet werden. Dieses Python-Skript verwendet die gleichen Mechanismen, wie sie bei den unattended-upgrades
eingesetzt werden:
#!/usr/bin/python3
import os
import subprocess
import email.charset
import locale
import socket
from subprocess import (Popen,PIPE)
from email.message import Message
### EDIT THIS PART ####
from_email = "me@web.de"
to_email = "your@address.com"
###################################
# variables for testing
MAIL_BINARY = "/usr/bin/mail"
SENDMAIL_BINARY = "/usr/sbin/sendmail"
subject = "Test mail for unattended-upgrades on " + socket.getfqdn()
body = "When you read this mail, the mail delivery should also work for unattended upgrades! :)"
# function to send via /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
# function to send via /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
# sending mail via '/usr/sbin/sendmail' or '/usr/bin/mail'
if os.path.exists(SENDMAIL_BINARY):
print("Sending e-mail using 'sendmail'")
ret = _send_mail_using_sendmail(from_email, to_email, subject, body)
elif os.path.exists(MAIL_BINARY):
print("Sending e-mail using 'mail'")
ret = _send_mail_using_mailx(from_email, to_email, subject, body)
# output
if ret == 0:
print("E-mail successfully sent.")
else:
print("E-mail could not be delivered.")