Im der DSGVO steht drin, daß man seinen Datenschutzbeauftragten bei der Aufsichtsbehörde zu melden hat. Der zuständige Landesdatenschutzbeauftragte hier in Thüringen hat dafür ein sehr interessantes Formular erstellt.

Das Formular

Zum einen ist das Formular keine online-Einreichung, die dem TLfDI die Arbeit erleichtern würde. Stattdessen ist handelt es sich um ein Microsoft Word-Dokument, das man ausfüllen und ausdrucken soll. Anschließend ist das Dokument auf dem Postweg zu versenden, da in der Einreichungsaufforderung keine eMail-Adresse angeben wird.

Der Inhalt des Formulars ist ebenfalls überraschend:

2018-05-24-161013_626x678_scrot

Da stellt sich die Frage, ob denn die Angaben überhaupt alle benötigt werden. Wozu braucht die Aufsichtsbehörde beispielsweise die Webseite des bestellten Datenschutzbeauftragten?

Und dann stellt sich die Frage, was man da eigentlich melden muß.

  • Die verantwortliche Stelle ist ja noch klar.
  • Kommt bei Datenschutzbeauftragter aber nun der Datenschutzbeauftragte hin oder nur dann, wenn es ein interner ist?
  • Oder wird ein interner DSB als externer gemeldet, bei dem das Auswahlfeld externer auf Nein gestellt wurde?

Für die Kirchgemeinde kann ich das gut ausfüllen. Zuerst die Gemeinde, dann der örtlich zuständige DSB der Landeskirche, und dann der extern bestellte DSB des Verbands der evangelischen Kirchen in Berlin. Auf Beschluß der Landessynode wurde also der (externe) DSB an Stelle des (örtlichen) DSB für zuständig erklärt. Aber sonst?

Das Leak

Öffnet man aber die Eigenschaften des Dokumentes (was ich getan habe, weil das Dokument sich heute am Tag verändert zu haben schien), dann staunt man:

2018-05-24-154240_520x675_scrot

Autsch.

Wo meldet man diesen Datenschutzverstoß eigentlich?

Update

Seit heute früh (25.5.2018) gibt es ein neues Formular. Der Link zum alten Formular wird redirected.

Diesmal ist es besser ...

2018-05-25-131455_282x221_scrot

Danke.

Da nun keine Notwendigkeit für die Speicherung dieser personenbezogenen Daten besteht, habe ich auch die Klarnamen und E-Mail-Adressen unkenntlich gemacht.

Ich hatte kürzlich geschrieben, wie man die DSGVO als Blogger umsetzen kann. Der Text ist ziemlich abstrakt geworden, weil ich einen generellen Leitfaden beibehalten wollte. Nun also mal konkret für meine eigene Webseite.

Analyse

Ich schrieb: Man nehme sich seine Webseite her, und schau im Entwickler-Modus des Browsers erst einmal nach, welche Datenquellen die Webseite so alles einbindet.

Bei Firefox drücke ich F12 für den Entwickermodus und wähle den Reiter Netzwerk aus. Dann lade ich die Seite erneut mit F5.

2018-05-22-102920_715x381_scrot

Es fällt auf, das bei allen Zugriffen das Schlosssymbol aktiv ist. Das bedeutet die Zugriffe erfolgen über HTTPS. Wenn möglich sollte man HTTPS benutzen, besonders aber, wenn man Formulare benutzt. Den Punkt habe ich also schon mal erfüllt, es extra aufzuschreiben ist aber nicht nötig.

Als nächstes sticht ein Zugriff auf einen externen Server ins Auge: Ein Stylesheet wird fonts.googleapis.com nachgeladen. Das werde ich dokumentieren müssen.

Weiter mit der Faktensammlung: Wie schaut's mit Cookies aus? Unter Einstellungen - Privacy findet sich der Punkt Cookies.

2018-05-22-110102_973x407_scrot

Ohja. Das ist eine ganze Menge. Allerdings bin ich aktuell auch als Bearbeiter im Backend angemeldet und das ist keine öffentliche Funktion. Löscht man alle diese Cookies und versucht es nochmal (z.B: von einem anderen Rechner aus), so bleibt die Liste leer. Wie schön.

Die grundlegende Analyse ist damit schon abgeschlossen. Bleiben nun die gewollten Eigenschaften der Webseite, also die Dinge, die ich bewußt entwickelt, eingeschaltet und aktiviert habe. Die findet man natürlich nicht automatisch, sondern die weiß man als Autor. Im Zweifel hilft ein Blick über die verschiedenen Bereiche der eigenen Webseite, um das Gedächtnis aufzufrischen.

Was ist denn nun besonders an der Seite? Achja, Kommentare. Und da kann man sich tatsächlich einen Cookie setzen lassen, der die Angaben des Formulars automatisch vorab ausfüllt. Den wird man erwähnen müssen. Dabei fiel mir auf, daß das Formular den Cookiebutton automatisch aktiviert hat. Das habe ich gleich mal umgestellt.

Des weiteren habe ich interaktive Inhalte auf der Seite. Auf die will ich nicht verzichten, also muß ich sie beschreiben.

Und damit ist die Analyse schon beendet.

Eigner Server

Jetzt geht's ans Eingemachte: Was tut eigentlich mein Webserver so?

Ich weiß, daß er Logfiles führt. Die lese ich zwar nur bei Bedarf (und den hatte ich schon) und mache keine Statistik damit.

Und dann gibt's noch Logfiles für verschiedene Fehlerzustände. Die sind verdammt umfangreich, denn ich will wissen, was genau den Fehler ausgelöst hat.

Bleibt noch die Frage, wie lange die Logfiles liegen bleiben: Aha, eine Woche. Das scheint mir angemessen, denn ich brauche diese Zeit vor allem nach einem langen Wochenende.

Schreiben wir's auf:

Beim Zugriff auf diese Webseiten übermittelt der Browser des Besuchers
automatisch Daten über die gewünschte Ressouce, die IP-Adresse, an die
die Daten übermittelt werden sollen, gegebenenfalls auch spezifische Daten
aus vorherigen Anfragen (Cookies, Referer, Formulare, etc.).

Ein Teil dieser Daten wird automatisch geloggt, um den technischen Betrieb
sicherstellen zu können. Diese Daten werden grundsätzlich nur zur manuellen
Post-Mortem-Analyse herangezogen anderenfalls automatisch nach sieben
Tagen gelöscht.

Fertig.

Cookies

Meist ein heißer Punkt. Aber auch hier habe ich Glück, denn normalerweise setze ich keine Cookies, da ich auf Anmeldungen verzichte.

Nicht-angemeldete Nutzer erhalten grundsätzlich keine Cookies seitens des Webservers.

Und der Cookie bei Formularen?

Auf explizite Aufforderung des Nutzers werden die Angaben für Stammdaten
des Kommentarfeldes per Cookie im Browser gespeichert. Dieses dient dazu,
bei späterer Nutzung des Kommentarfelds diese Daten schon initial einzutragen.
Dieser Cookie wird vom Browser spätestens nach einem Jahr gelöscht,
eine manuelle Löschung ist jederzeit vom Nutzer selbst in seinem Browser möglich.
Eine Speicherung des Cookies auf Serverseite erfolgt nicht.

Der Teil mit der Anmeldung zwecks Bearbeitung betrifft nur mich allein. Aber der Vollständigkeit halber schreibe ich es auf. Es schadet ja nicht.

Mit dem Beginn einer Anmeldung oder Registrierung wird ein Session-Cookie
gesetzt, der dazu dient, den Nutzer bis zur Abmeldung wieder zu erkennen.
Dieser Cookie wird automatisch gelöscht, wenn der Browser geschlossen wird.
Eine Speicherung des Cookies auf Serverseite erfolgt nicht.

Für angemeldete Nutzer können weitere Cookies gesetzt werden,
um verschiedene Workflows technisch umzusetzen. Eine Speicherung dieser
Cookies auf Serverseite erfolgt nicht.

Nur für administrative Aufgaben ist eine Anmeldung notwendig,
die normale Nutzung erfolgt generell ohne Anmeldung. 

Und schon wieder fertig.

Externe Server

Warum mache ich das eigentlich? Weil ich es allein kann. Ich bin schlicht nicht in der Lage, abertausende von Besonderheiten von Browsern in verschiedenen Konfigurationen korrekt mit einem gewünschten Style zu beliefern. Ich kann das auch nicht mal eben kopieren, weil die Auslieferung ja browserabhängig ist. Und selbst wenn ich kopieren könnte, darf ich das überhaupt? Ganz zu schweigen davon, daß ich es nicht aktuell halten könnte. Deswegen binde ich diese Inhalte ja extern ein, von jemanden der es kann.

Grundsätzlich werden alle Inhalte direkt vom Server ausgeliefert.

Eine Einbettung externer Inhalte erfolgt automatisch dann, wenn die Inhalte
technisch nicht lokal bereit gestellt werden können. Dies betrifft generell die
browserabhängige Bereitstellung von Javascript/CSS/Font-Daten für eine
einheitliche Darstellung der Webinhalte. Typischerweise werden diese Daten
vom Browser gecached und nur einmal direkt geholt. Eine Korrelation zwischen
den einzelnen Webseiten-Besuchen und dem Nachladen dieser Ressourcen
ist also nicht zwingend gegeben.

Soweit so klar. Was ist mit Videos? Ich nehme gern mal ein Video auf und lasse es von YouTube streamen. Warum? Weil ich es lokal nicht kann. Der Aufwand wäre unverhältnismäßig.

Fallweise können auch Videos einbettet sein, um auf die nur extern
vorhandenen Streamingressoucen zugreifen zu können. 

Warum ist das alles überhaupt erwähnenswert? Weil es den Nutzer dazu bringt, evtl. Daten an Dritte weiter zu reichen.

In all diesen Fällen sendet der Browser des Besuchers selbst
entsprechende Daten an den jeweiligen externen Server.

Fertig.

Interaktive Inhalte

Jetzt ist meine Spielwiese dran. All die schönen Tools.

Es wäre sinnfrei, jedes Tool im Detail zu erklären. Dazu kommt, daß die meisten Tools komplett im Browser des Nutzers ablaufen. Schließlich will ich die Last ja nicht auf meinem Server haben.

Auf der Webseite können interaktive Inhalte angeboten werden.
Diese werden – soweit technisch möglich – als Javascript im Browser des Nutzers ausgeführt.

Unglücklicherweise kann Javascript nicht beliebige Dinge im Browser machen und braucht externe Informationen, die es nur vom ausliefernden Server bekommen kann (Same-Origin-Policy). Diese Zugriffe sind zu dokumentieren. Sie unterscheiden sich aber nicht wesentlich von normalen Zugriffen.

Werden bestimmte Inhalte dynamisch auf der Serverseite ermittelt,
so wird grundsätzlich kein weiteres Log über diese Aktionen geführt.
Beim Auftreten von Verarbeitungsfehlern können zur Fehlersuche
beliebig detaillierte Logs geführt werden. Diese werden automatisch
nach sieben Tagen gelöscht.

Und dann habe ich noch Scantools, die selbst aktiv nach außen greifen.

Einzelne aktive Inhalts können auch selbst Zugriffe auf andere
externe Ressourcen durchführen, z.B. einen Sicherheitsscan über
vom Nutzer definierte Netzbereiche.

Fertig.

Kommentare

Was gibt es persönlicheres an Daten als nutzergenerierte Inhalte? Die will ich haben, denn die machen die Würze eines Blogs aus.

Es ist grundsätzlich möglich, bereit gestellte Inhalte zu kommentieren.
Diese Kommentare sind öffentlich einsehbar und werden
von Suchmaschinen indiziert.

Wenn man einen Kommentar schreibt, will man diskutieren und agiert als handelnde Person. Welche Persönlichkeit man darstellen will, ist dabei egal. Darüber hinaus habe ich aber auch mit Mißbrauch und Rückfragen zu tun. Da möchte ich neben dem öffentlichen Kommentar auch direkt kommunizieren können. Und anhand der IP, der einzigen Information die man nicht beliebig wählen kann, möchte ich auf schweren Mißbrauch reagieren können.

Bei der Erstellung eines Kommentars wird der Nutzer gebeten,
seinen Namen und seine E-Mail-Adresse anzugeben.
Die Angabe eines Weblinks ist optional.

Der angegebene Name und die optionale URL werden zusammen
mit dem Kommentar angezeigt. Dies dient der Verbesserung
der Kommunikation, da dies die Zuordnung der Beiträge in einer
Diskussion ermöglicht. Der Name und die URL werden nicht validiert.

Die E-Mail-Adresse wird nicht angezeigt. Sie dient ausschließlich
der Kontaktaufnahme mit dem Kommentator zu administrativen
Zwecken. Sie wird bei der Eingabe nicht validiert.

Die IP Adresse des Kommentators wird automatisch erfaßt und wie
die anderen Angaben zusammen mit dem Kommentar gespeichert.
Dies dient dazu, einen Kommentar zweifelsfrei einer Quelle zuzuordnen.
Diese IP wird herausgegeben, wenn Strafverfolgungsbehörden anfragen.
Darüber hinaus wird die IP herangezogen, um automatisch
Kommentarspam zu blockieren.

Da hier nun erstmals und im engeren Sinne personenbezogene Daten verarbeitet werden, sollte man auch über Korrektur- und Löschmöglichkeiten informieren.

Es besteht kein Anspruch auf Veröffentlichung von Kommentaren.
Kommentare werden administrativ gelöscht, wenn sie störend oder
themenfremd sind. Eine manuelle Löschung von Kommentaren ist möglich,
wenn die Löschanfrage mit der passenden E-Mail-Adresse abgesendet wurde
und die Löschung nicht einen thematischen Zusammenhang zerstört.

Klingt schräg? Mag sein, aber ich lasse mir nicht vorschreiben, was ich bei mir zu veröffentlichen habe. Des weiteren habe ich auch einen dicken Hals bei Personen, die Geschichtsklitterung betreiben wollen. Da müssen dann schon ernsthaft triftige Gründe her, um die Abwägung zugunsten einer Änderung oder Löschung ausschlagen zu lassen.

Beschwerde

Da man sich schlecht bei mir selbst über mich beschweren kann, stellt sich die Frage, wer mir denn auf die Finger schauen darf.

Für einen eigenen Datenschutzbeauftragten bin ich … Hallo? Vergeßt es.

Trotzdem muß ich herausfinden, wer für mich zuständig ist. Denn das kann man im Fall einer Beschwerde kaum dem Nutzer überlassen. Er wird sonst von Pontius zu Pilatus geschickt und kommt nie an der richtigen Stelle an.

In meinem Fall ist das einfach: Der Landesdatenschutzbeauftragte.

Bei Beschwerden wenden Sie sich bitte an den Thüringer Landesbeauftragten
für den Datenschutz und die Informationsfreiheit (TLfDI).

Den werde ich dann noch bei meiner zuständigen Aufsichtsbehörde (TLfDI) formgerecht melden.

Noch Fragen?

Das deutsche Datenschutzrecht gilt ja schon lange, zumindest ich habe mich daran gewöhnt. Mit der europäischen Vereinheitlichung zur DSGVO ist vor allem in den letzten Monaten viel Panik entstanden. Wir geht man praktisch als Blogger damit um?

Was soll das?

Die DSGVO ist kein Teufelszeug, sondern folgt einigen klaren Prinzipen:

  • Personenbezogene Daten sollen möglichst gar nicht erhoben werden. Wo nichts anfällt, kann auch nichts missbraucht werden.
  • Wenn man schon personenbezogene Daten verarbeitet, dann sollte der Betroffene wissen, welche Daten wozu erhoben werden und was damit bis zur Löschung passiert. Dadurch kann er informiert entscheiden, ob er einen Dienst in Anspruch nimmt oder nicht.
  • Ist diese Verarbeitung von Daten nicht zwingend für das eigentliche Angebot notwendig, muss diese Verarbeitung optional gestaltet werden.
  • Wenn man schon anderen die Verarbeitung von persönlichen Daten (implizit oder explizit) gestattet, dann muss man nachträglich über seine Daten bestimmen können. Das kann der Wunsch nach Änderung, Löschung oder Übergabe (Export) sein. Ob jedem Wunsch auch immer entsprochen werden muss, ist aber nicht zwingend.
  • Wenn man sich ungerecht behandelt fühlt, muss man wissen, an wen man sich wenden kann.

Im Endeffekt handelt es sich also um ein Verbraucherschutzrecht. Dies impliziert eine gesetzliche Trennung zwischen Anbieter und Konsument. Im Internet ist diese Trennung aber nicht ganz so einfach.

Dreh- und Angelpunkt der gesamten DSGVO für den Anbieter ist die Dokumentationspflicht. Er muss erklären, warum er welche Daten erhebt.

Dabei hat er abzuwägen zwischen den Interessen des Konsumenten, seinen eigenen Interessen und evtl. den Interessen Dritter. Kann er erklären, warum diese Datenerhebung notwendig ist, benötigt er für diese Datenerhebung keine Einwilligung.

Eine Einwilligung ist nur dann notwendig, wenn die auch verweigert werden kann. Ist also die Diensterbringung in vergleichbarer Weise auch möglich, ohne diese Daten zu haben, dann liegt eine einwilligungspflichtige Datenerhebung vor. Natürlich muss diese Einwilligung dokumentiert werden und ist damit selbst dokumentationspflichtig.

Wie der Konsument den Wunsch nach Änderung und Löschung artikulieren kann, muss man dokumentieren. Es ist ebenfalls zu dokumentieren, unter welchen Bedingungen der Wunsch ausgeführt bzw. verweigert werden kann. Insbesondere bei komplexeren Systemen ist es nachträglich oft gar nicht möglich einen Teil zu entfernen. Eine Diskussion zwischen verschiedenen Nutzern kann ihren Sinn verlieren, wenn ein Teilnehmer nachträglich seine Beträge löschen oder sinnentstellend ändern lassen will. In diesem Fall überwiegt das Interesse an der Konsistenz über dem Interesse des Einzelnen. Das Recht auf Vergessen hat also Schranken in der archivarischen Dokumentation, auch wenn der Zugriff erschwert werden kann (Sperre in der Suche).

Für Widersprüche benötigt der Konsument einen Ansprechpartner, i.d.R. den zuständigen Datenschutzbeauftragten. Logischerweise darf dieser Ansprechpartner nicht mit der Person identisch sein, über die Beschwerde geführt werden soll. Es ist also grundsätzlich unzulässig gleichzeitig Verantwortlicher und Datenschutzbeauftragter des gleichen Angebots zu sein. Ein Datenschutzbeauftragter ist für kleine Angebote unnötig, in dem Fall übernehmen übergeordneten Stellen, also die Landes- oder Bundesdatenschutzbeauftragten.

Und wie mache ich das?

Man nehme sich seine Webseite her, und schau im Entwickler-Modus des Browsers erst einmal nach, welche Datenquellen die Webseite so alles einbindet. Dabei findet sich typischerweise:

  • Ressourcen (Javascript, CSS, Bilder, Fonts, ...) des eigenen Webservers
  • Cookies
  • Ressourcen (Javascript, CSS, Bilder, Fonts, ...) fremder Webseiten
  • Aktive Einbindungen anderer Dienste, wie Zählpixel, Webanalyse, Soziale Netze (Like-Count), (Facebook/Disqus)-Kommentare, Videos, etc.
  • interne Kontaktformulare
  • interne Kommentare, Bewertungen, Querverlinkungen für Artikel

Für jeden dieser Punkte muss man nun prüfen:

  • Wird der Zugriff in dieser Form gebraucht?
  • Wenn ja, dann dokumentiere ich die Zugriffsart und begründe deren Zweck. Damit ist der DSGVO hinsichtlich Abwägung und Dokumentation Genüge getan und die Datenerhebung ist ohne Einwilligung zulässig.
  • Wenn nein, dann kann ich den Zugriff entweder entfernen oder ich verlange eine Einwilligung vom Webseitenbesucher.
  • Wird die Einwilligung erteilt, dann kann ich diesen Zugriff benutzen.
  • Wird die Einwilligung nicht erteilt (oder später zurück gezogen), dann muss ich dafür sorgen dass diese Funktion auch nicht aktiviert ist.

Mir ist wichtig an dieser Stelle fest zu halten, dass die DSGVO nicht pauschal irgendetwas verbietet, sondern im Gegenteil all das erlaubt, was begründbar ist. Die DSGVO führt also dazu, dass der Webseiten-Betreiber sich über die Implikationen seines Angebot für den Konsumenten Gedanken macht!

Bei all diesen Dokumentationen sind die drei Punkte aufzuschreiben:

  • Um welche Daten handelt es sich? Wie und wo entstehen sie?
  • Warum werden diese Daten erfasst? Was ist Sinn der Aktion?
  • Wie lange werden die Daten benötigt? Wann werden sie (automatisch) gelöscht? Wie kann man sie nachträglich ändern?

Hält man sich an diese Grundsätze hat man schon fast alles richtig gemacht.

Bei Übergabe von Daten an Dritte oder die Verarbeitung von Daten durch Dritte (Webhoster, Forumsanbieter, Webanalyst, ...) ist es empfehlenswert, eine vertragliche Fixierung mittels einer Auftragsdatenverarbeitung vorzunehmen. Das wird nicht immer individuell möglich sein, jedoch gestattet die DSGVO (im Gegensatz zur bisherigen deutschen Recht) auch den Vertragsvorschlag des Anbieters zu akzeptieren. Nachfragen lohnt sich!

Wem das zu theoretisch war, der kann sich das auch ganz konkret anschauen.

Aber die Juristen!

Richtig, das ist ihr Job. Das Feld des Datenschutzes ist aktuell stark im Umbruch. Juristen neigen leider dazu, keine klare Aussage zu formulieren, stattdessen zeigen sie gern Schwachstellen in der bisherigen Lagebeurteilung auf. Das Ergebnis ist unseriöse Panikmache und massive Verunsicherung.

Dazu trägt bei, dass die zuständigen Landes- und Bundesdatenschutzbeauftragten selbst keine Erfahrungen mit dem neuen Recht haben, geschweige denn den konkreten Fall eines Webauftritts beurteilen können. Deswegen geben auch sie nur allgemeine, meist scharf formulierte Aussagen von sich, wie die folgende: Personenbezogene Daten sind gar nicht erfassen, wenn doch umgehend zu löschen, also spätestens nach 10 Jahren, wenn das Finanzamt nicht mehr auf der Dokumentation besteht.

Kurz und gut: Wenn man einen Rechtsanwalt beauftragt, dann haftet der mit seiner Versicherung für eine Fehlberatung. Sein Interesse besteht also in der Vermeidung eines Versicherungsfalls, nicht in der praktischen Umsetzung seiner Empfehlungen vor Ort. Hat man dagegen den Rechtsanwalt nicht einmal selbst beauftragt, dann …

Natürlich soll man die verschiedenen Aussagen der Juristen nicht komplett verwerfen, jedoch schadet es nicht, sie mit einer gehörigen Portion Skepsis zu betrachten. Ob die betreffenden Vorschläge überhaupt auf den eigenen Sachverhalt anwendbar sind, ist meist gar nicht so klar.

Fazit

Ich bin durch mein eigenes Blog, meine eigenen Projekte auf diesem Webserver durch gegangen und habe meine eigene Datenschutzformulierung geschrieben. Dabei sind mir einige Einsprengsel sozialer Netze aufgefallen, die ich bisher nur ausgeblendet, aber nie richtig abgeschaltet hatte. Jetzt sind sie komplett weg.

Dann habe ich die restlichen Dinge angeschaut und beschlossen, dass ich die weiter haben will. Kommentare unter Artikeln sind mir wichtig, also erkläre ich wie sie funktionieren und was ich als erhaltenswert ansehe. Eine Änderung zur vorherigen Lage stellte das nicht da, ich hab's halt nur mal aufgeschrieben.

Bei Cookies bin ich in der glücklichen Lage, auf sie weitestgehend verzichten zu können. Es gibt aber z.T. lange Cookies, die dokumentiert werden mussten. Dabei habe ich gleich noch den Default-Wert zum Setzen des Cookies ausgeschaltet. Man muss ihn aktiv auswählen. Auch keine schlechte Idee.

Richtig problematisch schienen mir anfangs die Einbindungen externer Inhalte wie Youtube-Videos und CSS/Fonts. Also habe ich versucht, diese Dinge auf meinem Server allein zu erledigen und bin krachend gescheitert. Da mich der Teil technisch weit überfordert, habe ich einen validen Grund die externen Inhalte einzubinden, wie sie halt gebraucht werden. Die DSGVO hat da entsprechende Abwägungsklauseln. Dokumentiert und fertig.

Was man aber nie machen sollte ist: Einfach alles hinschmeißen. Das Internet ist groß und interessant geworden, weil wir nie aufgegeben haben. Lassen wir uns nicht von einer unbegründeten Angst ins Bockshorn jagen.

Beim Durchsehen der Logfiles stieß ich darauf, dass wiki.mozilla.org nicht erreichbar ist. Grund dafür ist ein externer Dienstleister und ein Fehler, über den sich die Experten uneinig sind. Natürlich hängt es mit DNSSEC zusammen.

$ host wiki.mozilla.org 100.100.100.100
Host wiki.mozilla.org not found: 2(SERVFAIL)

$ host wiki.mozilla.org 10.10.10.10
Host wiki.mozilla.org not found: 2(SERVFAIL)

$ host wiki.mozilla.org 8.8.8.8
wiki.mozilla.org is an alias for www.wiki.prod.core.us-west-2.appsvcs-generic.nubis.allizom.org.
www.wiki.prod.core.us-west-2.appsvcs-generic.nubis.allizom.org is an alias for wiki-prod-1394614349.us-west-2.elb.amazonaws.com.
wiki-prod-1394614349.us-west-2.elb.amazonaws.com has address 52.89.171.193
wiki-prod-1394614349.us-west-2.elb.amazonaws.com has address 34.213.203.225
wiki-prod-1394614349.us-west-2.elb.amazonaws.com has address 54.68.243.126

$ host wiki.mozilla.org 1.1.1.1
wiki.mozilla.org is an alias for www.wiki.prod.core.us-west-2.appsvcs-generic.nubis.allizom.org.
www.wiki.prod.core.us-west-2.appsvcs-generic.nubis.allizom.org is an alias for wiki-prod-1394614349.us-west-2.elb.amazonaws.com.
wiki-prod-1394614349.us-west-2.elb.amazonaws.com has address 34.213.203.225
wiki-prod-1394614349.us-west-2.elb.amazonaws.com has address 52.89.171.193
wiki-prod-1394614349.us-west-2.elb.amazonaws.com has address 54.68.243.126

$ host wiki.mozilla.org 9.9.9.9
Host wiki.mozilla.org not found: 2(SERVFAIL) 

Das ist ein Chaos, oder etwa nicht?

Was sagt denn DNSViz?

wiki.mozilla.org-2018-04-20-18_49_55-UTC

Die Erklärungen zu den Fehlerdreiecken umfassen:

  • Die Liste der Nameserver für allizom.org unterscheidet sich bei der Mutterzone "org" von der realen Zone. Dieser Glue-Mismatch ist aber nicht tragisch.
  • Bestimmte Antworten für SOA etc. werden bei der unsignierten Zone nicht beantwortet. Das wird als kritisch bewertet, ist es aber in der Regel nicht, da diese Anfragen für die Namensauflösung nicht notwendig sind.

Woran scheitert also ein Teil der Nameserver?

unbound info: validation failure <wiki.mozilla.org. A IN>:
 no NSEC3 closest encloser from 184.26.160.65 for DS nubis.allizom.org.
 while building chain of trust 

Was ist denn nubis?

wiki.mozilla.org.  CNAME  www.wiki.prod.core.us-west-2.appsvcs-generic.nubis.allizom.org.
;; Received 107 bytes from 2600:1401:2::f0#53(ns1-240.akam.net) in 16 ms 

Ahja, eine Zwischenzone auf dem Weg zum eigentlichen Ziel.

www.wiki.prod.core.us-west-2.appsvcs-generic.nubis.allizom.org.  CNAME  wiki-prod-1394614349.us-west-2.elb.amazonaws.com.
;; Received 275 bytes from 2600:9000:5302:cf00::1#53(ns-719.awsdns-25.net) in 17 ms 

Der entscheidende Teil ist, dass allizom.org signiert ist.

allizom.org.  SOA infoblox1.private.mdc2.mozilla.com. sysadmins.mozilla.org. 2018032753 180 180 1209600 3600
allizom.org.  RRSIG   SOA 7 2 3600 20180423125954 20180420115954 42215 allizom.org. E...
;; Received 563 bytes from 23.211.133.65#53(use4.akam.net) in 22 ms 

Allerdings scheint die Implementierung der beteiligten Nameserver (oder gar der Quelle, also der Infoblox) defekt zu sein, denn die notwendigen Nichtexistenzbeweise über die Abwesenheit einer sicheren Delegierung (also eine unsichere Delegierung) fehlen.

Genau genommen handelt es sich um ein empty non-terminal, also einen DNS-Namen, den der keine eigenen Datensätze enthält. Stattdessen existiert er ausschließlich virtuell. Denn die reale Delegierung lautet:

appsvcs-generic.nubis.allizom.org. NS   ns-795.awsdns-35.net.
appsvcs-generic.nubis.allizom.org. NS   ns-1743.awsdns-25.co.uk.
appsvcs-generic.nubis.allizom.org. NS   ns-426.awsdns-53.com.
appsvcs-generic.nubis.allizom.org. NS   ns-1109.awsdns-10.org.
;; Received 481 bytes from 2600:1401:2::f0#53(ns1-240.akam.net) in 16 ms

Der Name "nubis.allizom.org" entsteht ausschließlich durch die Verwendung von Punkten im längeren Namen.

DNSSEC ist das aber völlig egal: Der Name ist ein gültiger Name im DNS, also muss darüber eine Aussage her. Und wenn es ein Nichtexistenzbeweis ist. Deswegen verlangt DNSSEC, dass auch für derartige virtuellen "empty non-terminals" entsprechende DNSSEC Records erzeugt werden.

Und genau die fehlen hier.

nubis.allizom.org-2018-04-20-19_30_18-UTC-1

Damit kann ein validierender Resolver nicht beweisen, dass die unsichere Delegation der Zone "appsvcs-generic.nubis.allizom.org" korrekt ist. Es könnte ja ein Man in the Middle versuchen, die mögliche Existenz einer sicheren Delegierung für "nubis.allizom.org" auszufiltern!

An dieser Stelle unterscheiden sich die Implementierungen der DNS-Resolver.

  • Es gibt welche, die jedes noch so kleine Detail prüfen. Wie unbound, den ich für die Server 10.10.10.10 und 100.100.100.100 benutze. Oder knot, der bei 1.1.1.1 läuft.
  • Und es gibt welche, die nur dem eigentlichen Pfad folgen und sich für die Infrastruktur auf andere interne Mechanismen verlassen. Wie Google und Cloudflare.

Interessant ist, dass bei Cloudflare und Google eigene DNS-Implementierungen laufen, die nicht auf die klassischen Opensource-Produkte zurück greifen. Auch Infoblox fährt sein eigenen Süppchen. Und Route66 von Amazon ist auch eine Eigenentwicklung.

Ich bin sehr an einigen Details interessiert, warum die Eigenentwicklungen genau versagen: Die einen beim Erzeugen oder Verteilen, die anderen beim Validieren.

Merke, Mozilla: Größe ist nicht alles.

Während der ICANN Diskussionen@AtLarge kam die Frage auf, wie man als einfacher Nutzer testen kann, ob man von dem DNSSEC Key Rollover der Root Zone betroffen ist. Das ist erstaunlich schwierig oder vollkommen trivial. Je nachdem was man will.

Das Rollover Problem

DNSSEC funktioniert im Grundsatz so, dass die Elternzone einen Verweis auf den aktiven Schlüssel der Kindzone setzt. Auf diese Weise kann man in der Kindzone der Schlüssel leicht gewechselt werden, indem man parallel den Verweis in der Elternzone aktualisiert.

Für die Root-Zone ist es nicht so einfach, weil es schlicht keine Elterzone für die Root gibt. Das Schlüsselmaterial der Root kommt stattdessen aus den (Festplatten-)Speichern der Resolver-Server. Aber wie kommt es da rein?

Zum einen kann man das Schlüsselmaterial manuell eintragen. Das hat man anfangs auch gemacht. Die Daten fanden sich in Zeitungen, auf Webseiten etc. pp. Inzwischen wird das Schlüsselmaterial von den Software- / Betriebssystemherstellern vorinstalliert mit ausgeliefert. Eine noch andere Möglichkeit besteht darin, während der Erstinstallation den aktuellen Schlüssel ungeprüft abzurufen und zu speichern.

Das Problem besteht nun darin, den Schlüssel zu wechseln. Die Abermillionen von Installationen müssen also neue Schlüsseldaten bekommen. Normalerweise eine unmögliche Aufgabe. Seit einigen Jahren gibt es aber ein automatisiertes Verfahren: RFC5011. Im Kern besagt der RFC, der Resolver soll alle Schlüssel, die er mit Hilfe der bekannten Schlüssel validieren kann, abzuspeichern hat. Damit kennt und vertraut er den neuen Schlüsseln, sobald er sie sieht.

Ob das in der Praxis funktioniert, wurde nicht wirklich getestet. Dieser Root KSK Rollover ist der erste Versuch, das zu tun. Und natürlich muss man damit rechnen, dass es schief geht. Es kann aus ganz unterschiedlichen Gründen schief gehen, z.B. könnte der Festplattenbereich durch den Resolver nicht beschreibbar sein.

Wenn es schief geht, kann der Resolver keinerlei DNS Anfragen mehr beantworten. Gar keine. Und nun stelle man sich vor, dieser betroffene Resolver steht bei einem großen Internetprovider: Es gibt einen Blackout für alle Kunden dieses Providers.

Konsequenzen

Wie viele solche Resolver es gibt, ist nicht bekannt. ICANN hat also beschlossen, den für letzten Oktober geplanten Rollover auszusetzen.

Neuere Methoden, ob ein Resolver den neuen Schlüssel schon gelernt hat, wurde in den letzten Monaten entwickelt und ausgerollt. Allerdings ist die Verbreitung dieser neuen Methoden auf die gut gepflegten Systeme beschränkt, die sowieso kein Problem mit dem Rollover haben werden. Eine Aufklärung des problematischen Dunkelfelds ist nicht zu erwarten.

Nun steht die Frage im Raum, ob und wann ein neuer Anlauf genommen wird. Neue Daten sind jedenfalls nicht zu erwarten.

Andererseits besteht das Problem, dass immer mehr Geräte auf den Markt kommen, die gar nicht in der Lage sind einen Root Schlüssel zu wechseln. Hier stehen die vielen unsupporteten IoT-Geräte im Vordergrund. Je länger man also mit dem Rollover wartet, desto mehr Anbieter können einen Rollover ignorieren. Nur ein regelmäßiger Wechsel zwingt die Industrie und die Admins zu eine korrekten Vorgehensweise.

Ängste

Das Thema ist emotionsgeladen:

  • Will ich riskieren, dass mein Internet ausfällt?
  • Wer wird mich (als Admin) verantwortlich machen, wenn es zu Ausfällen kommt?
  • Wer wird mich (als ICANN) verantwortlich machen, wenn es zu Ausfällen kommt?
  • Wie viele Leute werden einfach DNSSEC ausschalten, anstatt das Problem richtig zu beheben?

Dazu muss vorrangig die Frage beantwortet werden, ob man überhaupt betroffen ist.

Dazu gibt es eigentlich nur zwei Fälle:

  • Mein ISP (Resolver) kann DNSSEC und validiert. Dann kann es passieren, dass der KSK Rollover schief geht.
  • Mein ISP (Resolver) validiert DNSSEC nicht. Dann betrifft der Rollover mich (und ihn) gar nicht.

Um diese beiden Fälle einfach zu überprüfen, habe ich auf Anregung von Alan Greenberg eine Webseite erstellt, die den aktuellen Stand beim Nutzer ermittelt und den betreffenden Fall anzeigt.

Wie man den Stand von DNSSEC ermittelt

Natürlich kann man nicht aktiv DNS im Browser sprechen, um diese Fälle zu unterscheiden. Eine aktive Programmierung jeder Art fällt also schon mal flach.

Was man aber machen kann, ist Teile der Webseite so abzulegen, dass ein DNSSEC validierender Resolver diese nicht abrufen kann. Ich habe mich dazu entschieden eine Webseite mit unterschiedlichem CSS zu machen.

<link rel="stylesheet" href="dnssec.css">
<link rel="stylesheet" href="http://css.fail.donnerhacke.de/dnssec-fail.css">

Der zweite Teil des CSS überschreibt die vorherige Einstellung:

$ cat dnssec.css
.failed { display: none; }
.dnssec { display: block; }
$ cat dnssec-fail.css
.failed { display: block; }
.dnssec { display: none; }

Er kann aber nur abgerufen werden, wenn der Resolver keine DNSSEC Validerung macht.

Um das hin zu bekommen, muss absichtlich ein stabiler Fehler im DNSSEC-Setup erzeugt werden. Der Fehler muss so stabil sein, dass er den normalen Betrieb und automatische Korrekturmaßnahmen überlebt. Ich habe mich zu einer fehlerhaften Delegation entschieden:

$ORIGIN donnerhacke.de.
fail            NS      avalon.iks-jena.de.
                NS      broceliande.iks-jena.de.
                DS      12345 8 2 1234...

Die delegierte Zone ist dann nicht signiert, obwohl die Elternzone behauptet, sie müsse es sein.

Das schaut dann so aus:

fail.donnerhacke.de

Ein validierender Resolver kann das nicht auflösen und antwortet:

;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 10476
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; QUESTION SECTION:
;css.fail.donnerhacke.de. IN AAAA

;; Query time: 49 msec
;; SERVER: 100.100.100.100#53

Ein nicht validierender Resolver kommt dagegen zu folgendem Ergebnis:

donnerhacke.de.         NS      avalon.iks-jena.de.
donnerhacke.de.         NS      broceliande.iks-jena.de.
;; Received 185 bytes from 2001:678:2::53#53(a.nic.de) in 25 ms

css.fail.donnerhacke.de. CNAME  pro.donnerhacke.de.
pro.donnerhacke.de.     AAAA    2001:4bd8:1:1:209:6bff:fe49:79ea
donnerhacke.de.         NS      broceliande.iks-jena.de.
donnerhacke.de.         NS      avalon.iks-jena.de.
;; Received 219 bytes from 2001:4bd8:52:1:20a:e4ff:fe80:bec8#53(broceliande.iks-jena.de) in 1 ms

Die abgerufene Webseite wird also, in Abhängigkeit von den DNSSEC-Fähigkeiten des Resolvers, unterschiedlich aussehen.

Und nun gehe hin und teste Deinen Resolver: *click*

Die ASAs von Cisco können ja Portchannels, d.h. mehrere Leitungen zu Einer bündeln. Außerdem können Sie Failover, ein Gerät kann für ein anderes einspringen. Beides zusammen ist allerdings trickreich, da es im ungünstigen Fall zu Verbindungsabbrüchen führt.

Aufbau

Zwei ASAs im Active/Standby-Failover sind an getrennte Switche angeschlossen. Zur Erhöhung der Bandbreite wird ein Interface als LACP-Bündel zum übergeben. Ist die primäre ASA aktiv, funktioniert es prächtig.

asa# sh port-channel summary 
Group  Port-channel  Protocol  Span-cluster  Ports
------+-------------+---------+------------+------------------------------------
1      Po1(U)            LACP          No     Gi0/0(P)   Gi0/1(P)   
asa# failover exec mate show port-channel summary
Group  Port-channel  Protocol  Span-cluster  Ports
------+-------------+---------+------------+------------------------------------
1      Po1(U)            LACP          No     Gi0/0(P)   Gi0/1(P)   

Die Interfaces haben folgende Eigenschaften:

asa# sh interface | in Interface|MAC
Interface GigabitEthernet0/0 "", is up, line protocol is up
        MAC address 00.01.01, MTU 1500
Interface GigabitEthernet0/1 "", is up, line protocol is up
        MAC address 00.01.02, MTU 1500
Interface Port-channel1 "outside", is up, line protocol is up
        MAC address 00.01.01, MTU 1500

und

asa# fail exec mate sh interface | in Interface|MAC
Interface GigabitEthernet0/0 "", is up, line protocol is up
        MAC address 00.02.01, MTU 1500
Interface GigabitEthernet0/1 "", is up, line protocol is up
        MAC address 00.02.02, MTU 1500
Interface Port-channel1 "outside", is up, line protocol is up
        MAC address 00.02.01, MTU 1500

Wie man sieht übernimmt der Port-Channel die MAC Adresse des ersten Interfaces. Soweit so dokumentiert.

asa-portchannel-active

Wie man sieht, tauchen die beiden MAC Adressen der Interfaces am jeweiligen Switch auf. Die ASA antwortet im ARP mit der MAC Adresse des Port-Channels und alles ist gut.

Failover

Bei Umbauten kam es vor, dass die ASAs umschalteten. Das ist beabsichtigt und stellt kein Problem dar.

Die aktive ASA ist einfach das andere Gerät, der Status incl. der IPs und MAC Adressen wechselt zwischen den beiden ASAs und es funktioniert.

asa-portchannel-failover

Man sieht sehr schön, wie die Haupt-MAC des Portchannel wechselt und nun zur aktiven ASA zeigt.

Ein Blick in die ASA selbst bestätigt den Wechsel:

asa# sh interface | in Interface|MAC
Interface GigabitEthernet0/0 "", is up, line protocol is up
        MAC address 00.02.01, MTU 1500
Interface GigabitEthernet0/1 "", is up, line protocol is up
        MAC address 00.02.02, MTU 1500
Interface Port-channel1 "outside", is up, line protocol is up
        MAC address 00.01.01, MTU 1500

und

asa# fail exec mate sh interface | in Interface|MAC
Interface GigabitEthernet0/0 "", is up, line protocol is up
        MAC address 00.02.01, MTU 1500
Interface GigabitEthernet0/1 "", is up, line protocol is up
        MAC address 00.02.02, MTU 1500
Interface Port-channel1 "outside", is up, line protocol is up
        MAC address 00.01.01, MTU 1500

Achtung! Da die Funktion der ASAs gewechselt hat ist jetzt die aktive ASA auf der Hardware des zweiten Geräts. Deswegen haben sich die Hardware-MAC der Interfaces nicht verschoben, wohl aber die Funktion (mate ist jetzt das, was vorher aktiv war).

Probleme

Denkt man genauer über das nach, was da zu sehen ist, bleiben Fragen:

  • Warum wechseln die MAC Adressen der realen Interfaces nicht? Die MACs aller anderen Interfaces wechseln nämlich.
  • Warum erscheinen die MAC Adressen der Interfaces beim Switch? Eigentlich sollte doch nur die MAC des Port-Channels auftauchen.

Tatsächlich gibt es zeitweise Ausfälle, bei denen keine Außenkommunikation mehr möglich ist. Und da sieht es real so aus:

asa-portchannel-failover-fail

Die MAC Adresse des aktiven Port-Channels zeigt wieder auf die alte ASA, die aktuell passiv ist. Und die verwirft natürlich alle Pakete.

Wenn kurze Zeit später die aktive ASA ein Paket aussendet, lernen die Switche wieder um und es funktioniert wieder alles. Bis zum nächsten Vorfall.

Lösung

Ganz offensichtlich sendet eine ASA neben dem Port-Channel auch Daten über die Interfaces selbst aus. Und dabei benutzt sie die nicht wechselnden MAC-Adressen der Interfaces!

Man kann darüber diskutieren, ob es sich um einen Bug handelt. Man kann auch anfangen zu untersuchen, welche Art von Paketen die ASA auf den individuellen Ports eines LACP-Bundles aussendet. Das ist alles spannend, aber nicht zielführend.

Man muss die MAC des Port-Channels von der MAC der Interfaces trennen, und das geht!

asa(conf)# int Po1
asa(config-if)# mac-address 00.01.00 standby 00.02.00

Und das schaut dann so aus:

asa-portchannel-failover-ok1

Cool nicht?!

Die neue MAC taucht separat von den Interface-MACs auf und damit sollte ein Umschalten auch klappen:

asa-portchannel-failover-ok2

Tatsächlich springt nur die virtuelle, manuell gesetzte MAC des Port-Channels um.

Und die Probleme sind weg.

Seit einiger Zeit wird eine Webseite mit offenbar unliebsamen Inhalt, die wir hosten, angegriffen. Nach einem lockeren Anfang kam die inzwischen obligatorische Traffic-Bombe. Und die lies sich erstaunlich leicht entschärfen.

Entwicklung eines DDoS

Dieser Distributed Denial of Service ist eine Protestaktion. Das Ziel ist bekannt, die mögliche Ursache auch, der Initiator des Angriffs allerdings nicht. Also müssen wir mit den Folgen leben.

Zuerst fing es mit einem dauerhaften Abruf der Webseite (F5-Bombe) einiger weniger Quellen an. Da die betroffene Seite statisch ist, war die Last des Webservers zwar hoch, aber nicht bedrohlich.

Als nächstes schwenkte der Angriff auf HTTPS um, was den Server wegen der TLS-Bearbeitung stärker belastete.

Kurz darauf kamen die Anfragen aus aller Welt. Offenbar hatte der Angreifer ein Botnet von übernommen PCs und Webservern zugeschaltet. Nun war es nervig.

Als nächstes kam es zu einer verbesserten Form von Syn-Floods: Der TLS Verbindungsaufbau wurde komplett durchgeführt, aber keine Webseite abgefragt. Das führte zu einem ersten Ausfall der Verfügbarkeit, da nun die Maximalanzahl der Webserver-Prozesse/Threads in der Warteschleife fest saßen.

Die Gegenmaßname war eine Limitierung von TCP-Syns pro Host und Zeiteinheit mit dem recent-Modul von iptables. Zusätzlich wurden die entsprechenden Hosts, die zu viele 408-Einträge im Webserverlog hinterließen, per Script und iptables geblockt (und bei Inaktivität automatisch freigegeben). Selbstverständlich gibt auch der Abruf der angegriffenen Webseite selbst schon Strafpunkte.

Jeden Werktag gegen 16 Uhr kommt eine Verfügbarkeitsabfrage über einen der vielen Ist-Mein-Server-Erreichbar-Dienste. Kurz danach beginnt eine neue Variante des Angriffs. Meist sucht sich der Angreifer eine andere Webseite auf diesem Shared-Hosting-Webserver aus und bearbeitet diese.

Dabei kann der Angreifer auch mal eine Seite erwischen, die PHP und datenbanklastig ist. Das stört sehr, wird aber von dem inzwischen installierten Block-Skript kurzfristig eingefangen.

Und dann knallte es rein: Unmengen von UDP/GRE und anderen verbindungslosen Protokollen schlugen auf die IP ein. Es wurde ernst.

Umgang mit der Traffic-Welle

Glücklicherweise hält die Traffic-Welle immer nur kurze Zeit an. Es genügt aber, im Netz der Uplink-Carrieren Störungen zu verursachen.

Mit dem Einsatz von Paketfiltern (für z.B. UDP) gegen die betroffene IP möglichst weit außen, konnten die Auswirkungen gedämpft werden. Die kurzen Störungen zu Beginn der täglichen Welle blieben aber im Netz der Uplinks.

Zur leichteren Separierung des Traffics bekam die Webseite eine neue IP. Aber welche?

Der Massentraffic kommt i.d.R. von IoT-Geräten: Schlecht gewarteten und vor allem schlecht entwickelten Kleinstgeräten mit Internet-Anschluss. Könnte das ein Ansatzpunkt der Verteidigung sein?

Die meisten IP-Stacks sind inzwischen (1993) classless, d.h. sie müssen zu jeder IP die Netzmaske kennen, damit sie die Pakete routen können. Früher war das anders: Da ergab sich aus der IP die Netzmaske von allein.

Darüber hinaus werden beim Anschluss von IP-Netzen an einer Broadcast-Netztopologie (z.B. Ethernet) bestimmte IP-Adressen reserviert: Die letzte IP im Netz (Hostteil besteht nur aus 1-Bits) ist die Broadcast-Adresse.

Des weiteren gilt die erste Adresse im Netz (Hostteil  besteht nur ais 0-Bits) als Netzadresse. Diese soll nicht verwendet werden, weil einige IP-Stacks die Adressen intern invertiert ablegten und somit Netz- und Broadcast-Adressen nicht auseinander halten konnten. Um mit diesen Hosts weiter arbeiten zu können, wird bis heute generell nicht an die Netzadresse gesendet.

Es war also nahe liegend, als neue IP Adresse die a.b.c.0 aus einem (ehemaligen) Class-C Netz zu verwenden.

Gesagt, getan. Die Traffic-Spitzen sind weg.

Fazit

Es ist gelungen, Angriffe durch kaputte System an der Quelle zu unterbinden. Eben weil die Systeme auch noch anders kaputt sind.

Inzwischen hat sich der TCP-Angriff auch auf IPv6 ausgebreitet, alles vorher war immer nur IPv4.

Warten wir's ab.

Hinweis für Manager: Es nützt nichts zu fragen, ob der Angriff vorbei ist. Die Frage ist sinnlos.

Die Commerzbank verlangt für beleghafte Überweisungen Geld. Das kann ich akzeptieren. Ich habe schon jahrelang am Automaten die Aufträge selbst erfasst. Onlinenbanking hat jahrelang nicht für mich funktoiniert. Jetzt tut's. Und jetzt soll das auch noch kostenpflichtig werden.

Rückblick

Mit der Dresdner Bank war ich als Kunde immer zufrieden, nicht nur, weil ich meine feste Ansprechpartnerin dort kannte.

Kurz vor der Jahrtausendwende kam Online-Banking (in Form von HBCI) auf und ich wollte das auch mal ausprobieren. Also hab ich mich an die Bank gewendet und einen Smartcard-Leser mit seriellem Anschluss per Post bekommen. Als ich den zwei Monate nicht einmal benutzt hatte, rief jemand schüchtern aus dem Rechenzentrum in Frankfurt an: Herr Donnerhacke, ich kenn' Sie ja aus dem Usenet. Sie haben unser Gerät bekommen und nie benutzt. Haben Sie Sicherheitsbedenken oder gar etwas schlimmes gefunden?

Das hatte ich natürlich nicht. Mein Problem war, dass das Teil an einer NeXTstation nicht lief. Aber da konnte er mir nicht helfen. So blieb ich bei beleghafter Kontoführung und dann die Benutzung der Automaten. Da hin zu gehen ist nicht aufwendig, wenn es in der Nähe der Arbeitsstelle ist.

Später ging die Bank in der Commerzbank auf und ich bin mit gegangen. Man ist ja bequem. Einen feste Ansprechpartner brauchte ich nicht mehr, freute mich aber die paar Mal, die ich dort war, immer an die gleiche und kompetente Person zu gelangen.

Irgendwann mochte ich den doch etwas weiteren Fußweg nicht mehr und beantragte wieder mal Online-Banking. Es stellte sich heraus, dass bis auf einen extra PIN/TAN-Brief nichts weiter geschah, als dass man die gleiche Oberfläche wie am Automaten nun auch zu Hause hatte. Intern ist beides Online-Banking nur halt von verschiedenen Zonen aus. Das ist clever und gestattete mir, Online-Banking von außerhalb Deutschlands zu untersagen.

Etwas später wurde TAN durch iTAN und mTAN abgelöst. ChipTAN, wie es die Sparkasse anbietet, war leider nicht im Angebot. Noch etwas später kam PhotoTAN.

Diskriminierung

Und heute lese ich in der Bank-Mitteilung:

Die Commerzbank empfiehlt ihren Kunden im Onlinebanking die Nutzung der photoTAN.
Sie beruht auf modernsten Sicherheitsstandards, ist kostenlos und es 
gilt die Commerzbank Sicherheitsgarantie. 
Mit der photoTAN ist Banking mit dem Smartphone ohne Zusatzgeräte möglich.
Die mobileTAN als bisherige Alternative zur TAN-Generierung kann von Kunden
weiterhin genutzt werden, ist künftig jedoch nicht mehr kostenlos. 
Wechseln Sie jetzt und testen Sie unsere PhotoTAN-App auf www.commerzbank.de

Ab 1.05.2018 werden wir den Versand einer (angeforderten, tatsächlich genutzten)
mobileTAN per SMS mit 0,09 EUR bepreisen.
Sollten Sie mit dieser Änderung nicht einverstanden sein, sprechen Sie uns gern
bis zum 30.04.2018 an. Bis zu diesem Termin haben Sie die Möglichkeit zu widersprechen,
oder das Konto fristlos und kostenfrei zu kündigen. 
Widersprechen oder kündigen Sie nicht, gilt Ihre Zustimmung zu der Vertragsänderung
als erteilt. Das geänderte Preis- und Leistungsverzeichnis kann in unseren 
Geschäftsräumen eingesehen werden und wird auf Wunsch ausgehändigt oder zugesandt.

Hallo? Ich muss ein Smartphone haben und eine Software von Euch installieren, damit ich Euch Kosten spare?

Wenigstens erkennt Ihr, dass Eure Anwendung nicht auf allen Smartphones laufen wird. Ihr schreibt selbst:

Wenn Sie kein Mobiltelefon besitzen, auf dem Sie die Commerzbank photoTAN-App
installieren können, haben Sie die Möglichkeit, bei Anmeldung zur photoTAN
ein preisgünstiges Lesegerät zu erwerben.

Bevor ich jetzt schaue, ob es für mein Handy überhaupt Eure Software gibt (Nein, das ist kein NeXTphone), lese ich nochmal nach, dass ihr bei mTAN die gleichen Sicherheit garantiert wie bei PhotoTAN.

Das ist deswegen witzig, weil ihr bei PhotoTAN schreibt, dass Eure Software das Hauptsicherheitsrisiko implementiert hat:

Auf Ihrem Smartphone können Sie Überweisungen noch schneller und einfacher durchführen
als am Computer. Nutzen Sie dafür die kostenlose Banking-App der Commerzbank.
In Verbindung mit der photoTAN-App entfällt das Scannen der Grafik. Durch eine
ausgeklügelte Technik sind die Apps direkt miteinander gekoppelt.

Selbst Wikipedia weiß:

Werden allerdings beim mobilen photoTAN-Banking sowohl die Banking- wie auch die photoTAN-Funktion via App auf einem einzigen Smartphone zusammengeführt, sodass die photoTAN-App und die Banking-App direkt miteinander kommunizieren, lassen sich die Überweisungen wieder manipulieren. Solche Apps werden zum Beispiel von der Deutschen Bank, der Commerzbank und der Norisbank bereitgestellt.

Und selbstverständlich gibt es die App nur für Android und iOS. Nicht für Windows Phone, Symbian, etc.

So und nun müssen wir reden. Zumindest müsst Ihr auf meinen Widerspruch, der bei Euch eingehen wird, reagieren. Sonst bleibt alles beim Alten. Klar?

Haskell ist derartig faul, dass die gängigen Methoden zur Abschätzung der Programmlaufzeit scheitern. Nach der bahnbrechenden Arbeit von Okasaki bestand aber Hoffnung, dieses Thema in den Griff zu bekommen. Einige der Annahmen dieser Arbeit sind nicht anwendbar, wenn Haskell selbst zum Aufräumen von Zwischenergebnissen zu faul ist. Um selbst einige Ideen auszuprobieren, brauche ich reale Messwerte. Und die will ich jetzt erzeugen.

Profiling

Der klassische Weg mit Performance Problemen umzugehen, ist Profiling. Als Beispiel muss wieder die Fibonacci-Folge herhalten. Und das besonders ineffizient:

fib :: Int -> Int
fib 0 = 1
fib 1 = 1
fib n = a + b
 where
  a = fib (n-1)
  b = fib (n-2)

Ich habe die Zwischen-Terme a und b extra benannt, damit sie im Profiling dann auch auftauchen.

Ich möchte die Zeit messen, die Haskell zur Abarbeitung braucht, dazu lasse ich mir die verstrichene CPU Zeit vor und nach der Abarbeitung geben. Um das Programm mit verschiedenen Werten zu testen, lese ich das Argument der Funktion von der Kommando-Zeile ein.

import System.Environment
import System.CPUTime

main = do
  [arg] <- getArgs
  limit <- readIO arg
  print limit
  start <- getCPUTime
  let result = fib limit
  finish <- getCPUTime
  print (finish - start) -- picoseconds

Nun noch kompilieren und probieren:

$ ghc -O3 -prof -fprof-auto -rtsopts fib.hs
[1 of 1] Compiling Main             ( fib.hs, fib.o )
Linking fib ...
$ fib 20
20
14000000
$ fib 200
200
14000000

Huch? Warum sollte das Programm die gleiche Zeit brauchen, wenn es doch so unterschiedliche Berechnungen zu vollführen hat? Die angegebene Zeit ist in Picosekunden, also stehen da real 14µs. Etwas wenig.

Schauen wir mal in das Profiling:

$ fib 200 +RTS -p
200
5000000
$ cat fib.prof
                                                  individual      inherited
COST CENTRE MODULE             no.     entries  %time %alloc   %time %alloc

MAIN        MAIN                54          0    0.0    1.1     0.0  100.0
 CAF        Main               107          0    0.0    0.1     0.0    2.2
  main      Main               108          1    0.0    0.0     0.0    0.0
 CAF        GHC.IO.Encoding     95          0    0.0    5.5     0.0    5.5
 CAF        GHC.IO.Handle.FD    92          0    0.0   56.5     0.0   56.5
 CAF        GHC.IO.Handle.Text  91          0    0.0    0.1     0.0    0.1
 CAF        Text.Read.Lex       88          0    0.0    1.1     0.0    1.1
 CAF        GHC.IO.Encoding.Ic  83          0    0.0    0.4     0.0    0.4
 CAF        GHC.Conc.Signal     73          0    0.0    1.0     0.0    1.0
 CAF        GHC.Read            68          0    0.0    1.5     0.0    1.5
 main       Main               109          0    0.0   30.5     0.0   30.5

Man sieht sehr schön. dass die übergebene Zahl vom Lexer bearbeitet, Daten ausgegeben wurden. Was aber komplett fehlt, ist jedoch die Abarbeitung der fib-Funktion?

Haskell ist schlicht zu faul, die Berechnung überhaupt auszuführen, wenn sowieso niemand das Ergebnis braucht! Um dem abzuhelfen, muss man das Ergebnis also auch noch ausgeben.

main = do
  [arg] <- getArgs
  limit <- readIO arg
  print limit
  start <- getCPUTime
  let result = fib limit
  finish <- getCPUTime
  print (finish - start)
  print result

Einfach am Ende noch das Ergebnis ausgeben. Sollte tun, oder?

$ fib 35
35
9000000
[ ... Pause ...]
14930352

Das Ergebnis erscheint mit deutlicher Verzögerung nach der Ausgabe. Insofern sind  die angegebenen 9µs definitiv unpassend. Was ist passiert?

Haskell war zu faul, das Ergebnis sofort zu berechnen. Erst als es durch die Ausgabe gezwungen wurde, hat es die Berechnung nachgeholt. Da war die Zeitmessung allerdings schon lange vorbei.

Die Verwendung des Ergebnisses muss also vor dem Ende der Zeitmessung erfolgen!

main = do
  [arg] <- getArgs
  limit <- readIO arg
  print limit
  start <- getCPUTime
  let result = fib limit
  print result
  finish <- getCPUTime
  print (finish - start)

Und das gibt:

$ fib 35 +RTS -p
35
14930352
1354134000000

Jetzt zeigt die Zeitmessung auch passende 1,3 Sekunden. Und was sagt das  Profilinging?

                                                  individual      inherited
COST CENTRE   MODULE                   entries  %time %alloc   %time %alloc

MAIN          MAIN                          0    0.0    1.1   100.0  100.0
 CAF          Main                          0    0.0    0.0     0.0    2.1
  main        Main                          1    0.0    2.0     0.0    2.0
 CAF          GHC.IO.Encoding               0    0.0    5.4     0.0    5.4
 CAF          GHC.IO.Handle.FD              0    0.0   54.6     0.0   54.6
 CAF          GHC.IO.Handle.Text            0    0.0    0.1     0.0    0.1
 CAF          Text.Read.Lex                 0    0.0    1.1     0.0    1.1
 CAF          GHC.IO.Encoding.Iconv         0    0.1    0.4     0.1    0.4
 CAF          GHC.Conc.Signal               0    0.0    1.0     0.0    1.0
 CAF          GHC.Read                      0    0.0    1.5     0.0    1.5
 main         Main                          0    0.0   32.8    99.9   32.8
  main.result Main                          1    0.0    0.0    99.9    0.0
   fib        Main                   29860703   28.3    0.0    99.9    0.0
    fib.a     Main                   14930351   40.8    0.0    40.8    0.0
    fib.b     Main                   14930351   30.8    0.0    30.8    0.0

Die eine Zahl Differenz zwischen a und b macht allein 10% der benötigten Rechenleistung aus. Das ist beachtlich.

Zeitverhalten

Zurück zum urspünglichen Problem: Wie schnell sind Algorithmen in Haskell? Die Frage bezieht sich normalerweise auf die Entwicklung des Zeitverhaltens in Abhängigkeit von der zu verarbeitenden Datenmenge.

Grundsätzlich könnte man das bisherige Programm für jeden Zahlenwert einzeln aufrufen und die Zeiten aufschreiben. Aber das ist aufwändig und kann von Haskell selbst gemacht werden.

Zunächst einmal ist zuverlässig sicher zu stellen, dass der Versuch der Berechnung nicht über alle Zeiten hinauswächst. Die Berechnung muss notfalls abgebrochen werden. Das löst die Funktion System.Timeout.timeout: Sie lässt die Berechnung in parallelen Thread laufen und bricht den notfalls ab. Das ganze Signalisierungsverhalten ist zwar prinzipell einfach, aber die Bibliotheksfunktion löst auch die kleinen Dreckeffekte, über die man selbst noch nicht gestolpert ist.

Anderseits ist der bisherige Rückgabewerte von CPUTime schwer verständlich. Besser wäre ein klarer Wert ins Sekunden. Deswegen wechsle ich hier auf Data.Time.getCurrentTime.

Der dritte Punkt, der zu beachten ist, behandelt die vollständige Evaluierung der Eingabedaten vor der Ausführung der zu testenden Funktion, sowie das Erzwingen der notwendigen Berechnung. Dazu verlange ich zwei Hilfsfunktionen: pre dient dazu, aus dem numerischen Wert der Datenmenge auch brauchbare Ausgangsdaten zu erzeugen, z.B. eine Baumstruktur, über die der Algorithmus dann laufen soll. post dagegen extrahiert aus dem Ergebnis des Algorithmus eine Zahl. Diese Extraktion muss nicht zwingend alle Teile des Ergebnisses auswerten, es genügt z.B. die Länge eines Rückgabestrings zu ermitteln, wenn es sich um einen Algorithmus zur Stringverarbeitung handelt, schließlich sind die konkreten Inhalte des Strings dabei nicht relevant.

Die Eingabedaten werden komplett angezeigt, um alle Details zu evaluieren. Zum Erzwingen der Auswertung an der richtigen Stelle werden die Zahlenwerte auf positive Wertebereiche per Control.Monad.guard geprüft.

import Data.Time
import Control.Monad

profile :: (Show a) => (a -> b) -> (Int -> a) -> (b -> Int) -> Int
        -> IO (Maybe NominalDiffTime)
profile test pre post n = do
  let input = pre n
  guard $ 0 <= length (show input) -- fully evaluate before measuring
  timeout 5000000 (do
      start <- getCurrentTime
      let output = test input
      guard $ 0 <= post output -- evaluate the relevant part
      finish <- getCurrentTime
      return $ finish `diffUTCTime` start
    )

Nochmal in Worten:

  • Die Funktion pre macht aus einem Zahlenwert die Datenstruktur a, auf der der Algorithmus arbeiten soll.
  • Die Funktoin test führt den Algorithmus aus, in dem sie als Argument eine Datenstruktur a übergeben bekommt und eine Datenstruktur b zurück liefert.
  • Die Funktion post presst aus der Ergebnisstruktur b einen positiven Zahlenwert heraus, wobei sie die Ausführung des Algorithmus erzwingt.
  • Klappt alles, erhalten wir die Zeit, die die Funktionen test und post benötigt haben.
  • Die Berechnung ist mit einem Timeout von 5 Sekunden umschlossen: Endet die Berechnung nicht rechtzeitig gibt es Nothing.

Mit einer solche Mess-Funktion kann man nun eine Mess-Reihe automatisch erzeugen:

mkProfile :: (Show a) => (a -> b) -> (Int -> a) -> (b -> Int) -> IO ()
mkProfile test pre post = worker (profile test pre post) testpoints
 where
  testpoints = [ 1 .. 9 ]
            ++ [ x*10^c | c <- [0 .. ], x <- [10 .. 99] ]
  worker f (x:xs) = do
    r <- f x
    case r of
      (Just y) -> do putStrLn . shows x . showChar ',' . init $ show y
                     when (y < 5) $ worker f xs
      _        -> putStrLn "# Timed out"

Für eine Reihe von Testpunkten, die immer größere Abstände annehmen, wird die Laufzeit ermittelt. Da die Darstellung der Laufzeit ein finales "s" für Sekunden am Ende hat, wird es per init abgeschnitten. Der Rest der Ausgabe ist ein CSV.

Die Messung wird fortgeführt, wenn überhaupt ein Ergebnis ermittelt werden konnte (kein Timeout) und die Laufzeit die 5 Sekunden nicht überschreitet. Die Timeout-Funktion garantiert nur, dass der Abbruch nicht vor 5 Sekunden stattfindet. Länger kann es durchaus dauern, da die typische Haskell-Runtime kooperatives Multitasking betreibt.

Und was sagt das zu unserer Fibonacci-Folge?

main = mkProfile fib id id

Die Hauptfunktion ist denkbar einfach: Erstelle ein Profil der fib-Funktion, wobei die Eingabemenge aus der Zahl besteht, die übergeben wird. Das Ergebnis ist ebenfalls eine Zahl, die so übernommen wird wie sie ist.

image005

Das ist klar expotentielles Wachstum.

Damit steht nun ein Handwerkszeug zur Verfügung, um andere - interessantere Algorithmen - zu untersuchen.

Nachbetrachtung

Natürlich würde man die Fibonacci-Folge in Haskell niemals so schreiben, sondern eher so:

fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
fib1 x = fibs !! x

main = mkProfile fib1 id abs

Die Fibonacci-Folge besteht aus den Werten 1, 1 und dann der Summe der Fibonacci-Werte beginnend am Anfang und um ein Element versetzt. Die Liste ist unendlich lang, aber dank des faulen Haskells besteht da kein Problem: Denn nur der x-te Wert wird dann heraus genommen.

Da wir mit Int rechnen, gibt es Überläufe während der Rechnung. Anstatt auf den korrekten Typ Integer auszuweichen, nehme ich hier einfach den Absolutwert als Ergebnis. Es geht ja nicht um die Berechnung der Werte an sich, sondern um die Art der Ermittlung (also den Algorithmus).

Und das ergibt dann?

image007

Das ist spannend! Mit einigen Ausreißern geht die Berechnung fast linear durch und zwar in die zig Millionen, statt bis 42.

Beobachtet man allerdings den RAM-Verbrauch während der Abarbeitung, so steigt dieser bis zu 4 GB an (mehr gebe ich dem Prozess hier nicht). Dann bricht die Abarbeitung ab. Die Stellen, bei denen die Laufzeit so stark ansteigt, entspricht den Stellen, wo das System massiv nach RAM verlangt.

Was ist passiert?

Die Definition von fibs ist global, d.h. der Wert steht immer zur Verfügung. Anders gesagt, verwirft Haskell die ermittelten Werte des Feldes nie wieder. Deswegen steigt der RAM-Verbrauch immer weiter an. Am Ende sind über 40 Mio. Werte berechnet worden. Incl. Overhead für die verkettete Liste sind das also ungefähr 80 Byte pro Eintrag.

Andererseits führt das zu den niedrigen Laufzeiten, da ja alle Werte schon berechnet wurden.

Alle Werte? Nein, die Schrittweite steigt ja immer um eine Größenordung. Damit müssen auch immer mehr Zwischenwerte berechnet werden, um auf das nächste Ergebnis zu kommen. Dies erklärt die sprunghaft steigende Abarbeitungszeit, die mit der größeren Schrittweite korreliert.

Was wäre nun, wenn man korrekterweise nicht mit den übrig gebliebenen Berechnungen des vorherigen Versuchen zu betrügen versucht?

fib2 x = fib' !! x
  where fib' = 1 : 1 : zipWith (+) fib' (tail fib')

Hier werden die Zwischenergebnisse in einer lokalen Variablen gehalten, die außerhalb des Aufrufs keine Bedeutung hat. Es besteht sogar die Hoffnung, dass die nicht benötigten Zwischenergebnisse direkt verworfen werden, und so der RAM-Bedarf nicht exorbitant steigt.

image007

Zwei Dinge sind auffällig:

  • Der RAM-Bedarf steigt wieder so an, das Programm hält aber länger durch, wenn die Grenze erreicht ist.
  • Das Laufzeitverhalten ist ähnlich, erreicht aber deutlich höhere Werte.

Was ist passiert?

Haskell erkennt durchaus, dass die Definition von fib' unabhängig vom Aufrufparameter ist und benutzt diese Tatsache zu dem Memorization-Trick, der bei fibs noch explizit aufgeschrieben wurde. Das führt zu dem ähnlichen Verhalten bis zu 40 Mio. Danach muss (und kann) Haskell die Zwischenergebnisse immer wieder verwerfen, um weiter arbeiten zu können.

Kann man Haskell den Algorithmus expliziter vermitteln? Schließlich ist es ja wenig elegant, eine Liste von uninteressanten Werten jedesmal von Anfang an zu durchlaufen (was schonmal minimal lineare Laufzeit generiert)

fib3 x = head $ drop x fib'
 where fib' = 1 : 1 : zipWith (+) fib' (tail fib') 

Der einzige Unterschied ist hier, dass statt dem (!!) Operator, der eine Liste bis zum x-ten Element durchläuft, der Anfang der Liste weggeworfen wird, um dann das erste Element zu nehmen.

image007

Das gleiche Bild, allerdings sind die Laufzeiten kürzer und die Abarbeitung erfolgt glatter. Es ist nicht so erratisch. Das exorbitante RAM-Bedarf ist allerdings noh da.

Also muss auf die Liste als solche verzichtet werden.

fib4 x = fib' x (1,1)
 where
  fib' 0 (a,_) = a
  fib' n (a,b) = fib' (n-1) (b,a+b) 

Dieses Programm nimmt den Kern der zipWith (+) Funktion explizit her und zählt parallel rückwärts. Es sammelt die Werte gar nicht erst auf, sondern verwirft sie sofort.

image007

Der RAM-Bedarf ist diesmal konstant. Das ist allerdings erwartungskonform.

Eine echte Überraschung ist allerdings die Laufzeit dieses Algorithmus:

  • Die Laufzeit ist praktisch linear mit 2 Sekunden je 10 Mio.
  • Allerdings sind die anderen Algorithmen durch die Bank deutlich schneller als die direkte Berechnung der Werte.
Laufzeit im Vergleich
Mio fib1 fib2 fib3 fib4
10 53ms 53ms 52ms 1750ms
20 192ms 189ms 189ms 3714ms
30 349ms 264ms 238ms  
40 321ms 361ms 298ms  
50   839ms 339ms  
60   366ms 365ms  

Fazit

Die von Haskell angewendeten Methoden der Memorization gestatten es, perfomante Algorithmen zu schreiben, die im Gegensatz zu den klassischen Algorithmen geschickter betrügen. Man muss bei Performance Messungen also höllisch aufpassen.

Wir haben eine Reihe von ASAs im Einsatz, die auf Kundenwunsch gepflegt werden. Wie immer betreffen Änderungswünsche nur die Einrichtung von Freischaltungen, aber nie das Wegräumen von Altlasten. Für diesen Fall gibt es zwei Tools, die ich vorstellen möchte.

Information des Kunden

Zunächst ist es wichtig, den Kunden regelmäßig über die aktuelle Konfiguration zu informieren. Trivialerweise würde man ihm die Konfiguration zugänglich machen.

Aber das ist eine schlechte Idee, da die ASAs einiges an Konfiguration enthalten, die man nicht ständig durch die Gegend reichen möchte. Es gibt auch genug Fälle, wo eine ASA mehrere Kunden bedient (z.B. mehrere Hosting-VLANs). Dann darf natürlich keine Information über die Konfiguration für andere Kunden leaken.

Es ist also notwendig, sich auf die wesentlichen und interessanten Teile zu beschränken. In diesem Fall interessiert den Kunden, welche ACLs und welche NAT Regeln für ihn aktiv sind. Was den Kunden interessiert, hängt von den Interfaces ab, die ihm zugeordnet sind.

Das Script mkacl-from-asaconfig.pl liest auf Standardeingabe eine komplette abgespeicherte ASA-Konfig ein, erwartet als Argument die Liste der interessanten Interfaces (nameif) und gibt aus:

  • die den Interfaces zugeordneten access-groupKommandos
  • die den access-group zugeordneten access-list Einträge
  • die den Interfaces zugeordneten nat Kommandos
  • die dabei referenzierten object-group Definitionen
  • die dabei referenzierten object Definitionen

Ganz praktisch sieht das dann so aus:

$ zcat /archiv/cf.asa.20180111.gz | mkacl-from-asaconfig.pl iks-lab
: Written by enable_15 at 11:13:55.766 CET Thu Jan 11 2018
!
!!!!!!
! active ACLs at interfaces
!!!!!
access-group to_lab out interface iks-lab
!!!!!
! ACLs
!!!!!
access-list to_lab extended permit ip object-group iks any 
access-list to_lab extended permit tcp any object obj-lab1 object-group http_s 
access-list to_lab extended permit tcp any object obj-lab2 object-group http_s 
!
!!!!!!
! extra NATs
!!!!!
!!!!!
! Object groups
!!!!!
object-group network iks
 network-object object o4-iks
 network-object object o6-iks
!
object-group service http_s tcp
 port-object eq www
 port-object eq https
!
!!!!!
! Objects
!!!!!
object network obj-lab1
 host 192.168.91.10
 nat (iks-lab,outside) static o4-lab1-public
!
object network obj-lab2
 host 192.168.91.11
 nat (iks-lab,outside) static o4-lab2-public
!
...
!
!!!!!
! Generated at Thu, 11 Jan 2018 13:35:03 +0100

Diese Auszüge können dem Kunden auf definiertem Wege (z.B. Kundenportal) zur Verfügung gestellt werden.

Es gestattet dem Kunden, sich selbst die aktuelle Konfiguration anzusehen und entlastet die Hotline bei uns.

Inkonsistenzen

Ein ganz anderer Teil des Problem sind Inkonsistenzen und Altlasten. Die Definition eines Objektes, das nicht verwendet wird, ist eine Inkonsistenz. Die Verwendung einer IP Adresse, die nicht mehr in Benutzung ist, ist eine Altlast. Beides soll gefunden werden.

Wie eine Konfiguration auszusehen hat, ist selbstverständlich Ansichtssache. Bei uns hat es sich als günstig herausgestellt, sämtliche IP Adressen in Objekten und Objektgruppen zu sammeln, damit diese schon allein anhand ihrer Benennung oder description auch noch Jahre später zugeordnet werden können. Nur mit klarer Zuordnung ist ein späterer Rückbau überhaupt möglich.

Das Script clean-asa-config.pl liest von der Standardeingabe eine komplette Konfigurationsdatei und listet auf, was unstimmig erscheint:

  • Nicht mehr benutzte access-list, object, object-group. name, tunnel-group, class-map, transform-set, crypto map, ...
  • Elemente, die verwendet werden, aber nicht definiert wurden, werden aufgelistet.
  • Es wird das Anlegen von object oder name Definitionen empfohlen, wenn IPs direkt in der Konfiguration stehen.
  • Ist bei Objekten ein DNS-Name zur IP hinterlegt, so wird geprüft, ob der Name noch zu dieser IP auflöst. Es ist nicht empfehlenswert, direkt mit fqdn statt host zu arbeiten, wenn man feststellen will, ob die intendierte Eintragung noch korrekt ist. fqdn sollte man nur bei dynamischen IPs benutzen.
  • Bei name Definitionen wird geprüft, oben IP und Name noch per DNS zusammen passen.
  • Da name Definitionen agnostisch gegenüber der Protokollfamilie sind, wird für IPv4 ein trailing dot am Namen empfohlen, bei IPv6 nicht.
  • Ausgelaufene Zertifikate führen zur Empfehlung, den trust-point zu löschen.
  • Ebenso soll man unvollständige crypto map Einträge löschen.

Die Ausgabe enthält:

  • die Empfehlung incl, Begründung
  • die direkt ausführbaren Kommandos, um die Empfehlung umzusetzen, ohne den Betrieb zu gefährden
  • alle anderen Verwendungen des Namens oder der IP in der Konfiguration

Wenn Euch etwas auffällt, was man noch testen sollte oder könnte ...