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?

The German data protection law has been effective for a long time, at least I got used to it. With the European standardization to the GDPR a lot of panic has arisen especially in the last months. How should a blogger deal with it practically?

Why?

The GDRP is not a devil's stuff, it follows some clear principles:

  • Personal data should not be collected in the first hand. Where nothing was collected, nothing can be abused.
  • If you are already processing personal data, then the user should know what data is collected for which purpose and what happens until deletion of it. This guide him to use this service or not.
  • If this processing of data is not strictly necessary for the actual offer, the processing step must be optional.
  • If you (as a user) allow others to process your own personal data (implicitly or explicitly), then you have to be able to decide differently (and any time later) how to deal with our data. This may be the desire for change, deletion or transfer (export) of your data. Whether every wish must be met or not, is not that clear beforehand.
  • If you feel misguided, you need to know where to complain about this data processing.

In a nutshell the GDRP is a consumer protection law. This implies a separation of provider and consumer. On the internet, this separation is not so easy.

Central point of the entire GDRP is the documentation requirement. The provider has to explain why he collects which data.

He has to balance between the interests of the consumer, his own interests, and possibly the interests of third parties. If he can explain, why this data collection is necessary, he does not need to request consent for this data collection.

Consent is only necessary, if it can be denied. So if the service is possible in a comparable way, without having this data, then there is a consent acquisition is compulsory. Of course, this consent must be documented and is itself required to be documented.

How the consumer can articulate the desire for change and deletion must be documented. It must also be documented under which conditions the request can be executed or denied. Especially with more complex systems, it is often not possible to remove arbitary parts afterwards. A discussion between different users may lose their meaning, if a participant later wants to delete his comments or change their meaning. In this case, the interest in consistency outweighs the interest of the individual. The right to forget therefore has limitations in the archival requirements, even if the access can be made more difficult (block the search).

The consumer needs a contact person, usually the responsible data protection officer, in order to place his objections. Logically, this contact person must not be identical with the person about whom the complaint should be made. It is therefore basically inadmissible to have the same person responsible for data protection and data protection at the same time. A data protection officer is unnecessary for smaller ones, in this case higher-level bodies, i.e. the state or federal data protection officer take over.

How do I do that?

Take your website, open it in the developer mode of your browser first. This shows, which data sources the website integrates. Typically:

  • resources (Javascript, CSS, images, fonts, ...) of your own web server
  • cookies
  • resources (Javascript, CSS, images, fonts, ...) of foreign websites
  • active integration of other services, such as counting pixels, web analytics, social networks (like-count), (Facebook / Disqus) comments, videos, etc.
  • internal contact forms
  • internal comments, reviews, cross-links for articles

For each of these points you have to check:

  • Is access required in this form?
  • If so, then document the type of access and justify its purpose. Thus, the GDRP is fulfilled in terms of balance and documentation, and the data collection is permitted without consent.
  • If not, then either remove this access or ask for permission from the website visitor.
  • If the consent is given, then you can use this access.
  • If the consent is not granted (or withdrawn later), then you have to that this feature is not activated.

It is important to note at this point, that the GDRP does not prohibit anything, but - on the contrary - allows everything that is justifiable. The GDRP thus stresses that the website operator is concerned about the implications of its offer for the consumer!

In all these documentations, the three points need to be written down:

  •  What data is this? How and where do they arise?
  •  Why is this data collected? What is the purpose of the action?
  •  How long is the data needed? When will it be deleted (automatically)? How can you change it later?

If you stick to these principles, you have already done almost everything right.

When transferring data to third parties or the processing of data by third parties (web host, forum provider, web analyst, ...), it is advisable to make a contractual fix by means of data processing. This will not always be possible individually, but the GDRP (in contrast to the previous German law) allows you to accept the contract proposal of your provider. It is worth asking!

Sounds theoretical? Look at a real example (in German).

But the lawyers!

Right, that's her job. The field of data protection is currently undergoing major changes. Unfortunately, lawyers tend not to formulate a clear statement, instead they like to point out weak points in the current situation. The results are dubious scare tactics and massive uncertainty.

Contributing to this is that the responsible state and federal data protection officers themselves have no experience with the new law, let alone assess the specific case of your website. Therefore, they give only general, usually sharply worded statements, such as the following: Personal data must not recorded, if so deleted immediately, stored not longer than 10 years, when the tax office no longer insists on the documentation.

In short: If you hire a lawyer, then he is liable with his insurance for a wrong advice. His interest is thus in the avoidance of the insurance case, not in the practical implementation of his recommendations to your concrete problem. On the other hand, if you have not even commissioned the lawyer, then …

Of course you should not completely reject the various statements of different lawyers, but it does not hurt to consider them with a good deal of scepticism. Whether the relevant proposals are applicable to your own circumstances is usually not so clear.

 Conclusion

I went through my own blog, my own projects on this web server and wrote my own privacy policy. In this process, I noticed a few blips of social networks that I had only hidden, but never turned off properly. Now they are completely gone.

Then I looked at the rest and decided that I wanted to have them on. Comments on articles are important to me, so I explain how they work and what I consider to be worth preserving. There was no change to the previous situation, I just wrote it down.

With cookies, I am in the fortunate position of being able to do not use them as far as possible. But there are some long-lasting cookies that need to be documented. At the same time I turned off the default value for setting this type of cookie. Now you have to actively select it. Not a bad idea either.

The integration of external content such as Youtube videos and CSS / fonts seemed to me to be really problematic at first. So I tried to do these things on my server alone and failed miserably. Since this part does outreach my technical experience, I have a valid reason to integrate the external content, as they are needed. The GDRP has as appropriate clauses for this case. Documented and finished.

What you should never do is: Just throw it all down. The internet has become big and interesting, because we never gave up. Let us not chase down our own work for an unfounded fear.

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.

During ICANN discussions@AtLarge the question arose, how a ordinary user can verify, if he will run into problems during the DNSSEC Root Zone Key Rollover. Determining this is surprisingly difficult or extremely easy.

The Rollover Problem

DNSSEC basically works by pointing to the active key in the child zone from the parent zone. Carefully updating this Delegate Signer in the parent zone, it's trivial to roll a key in the child zone.

But for the Root it's hard, because there is not parent zone for the Root itself. The key material for the Root is stored and managed on the hard disk of each of the DNS resolvers out there. But where does it come from originally?

The most basic method is a manual import. In the early days, it was the most prominent solution. The key material was distributed in newspapers, web pages and so on. Nowadays the keys are preinstalled by the software and operating system vendors. An other possibility is to gather the keys from the (unvalidated) DNS during the installation and store them locally.

Now the problem is to change those keys. Millions of servers need to get learn the new keys. Usually that would be an impossible task. Since several years the trust anchor keys can be updates automatically: RFC5011. The approach is to trust all new keys, the resolver can validate using the existing ones. This ways the resolver can learn, store, and trust new keys.

But does it work in reality? Nobody did a comprehensive test so far. The Root Zone KSK Rollover will be this test. And of course, the test might fail. The reasons for failure are numerous: I.e. configuration can be write protected at the local hard disk.

If something goes wrong, the resolver will not answer to any question anymore. Consider for a moment, that this resolver is the central one of a large ISP with lot's of broadband customers: There will be a total blackout.

Consequences

How many resolvers out there are a problem? Nobody knows. Therefore ICANN decided to postphone the rollover planned for last October.

New methods to determine if a resolver already learned the new key, were developed during the last few month. Unfortunately it's not very widespread. Only well maintained systems are likely to deploy the new requests, but those systems are not the problem anyway. So even this approach will not bring any more light into the shadows.

Now we have to answer the question, if and when a rollover should take place. New data to reason about are not to expect.

On the other hand there is a problem with new devices. More and more small, networked devices are pushed into the market. Most IoT-vendors do not care about updates or support. But the probability raises, that even those devices will contain validiating resolvers, which are unable to update their configuration. Delaying the rollover will allow such vendors to ignore the problem. Only chaninging the Root Zone KSK periodically will force the industry and operators to behave correctly.

Fears

It's an emotional topic:

  • Am I on risk to lose my Internet?
  • Who will sue me (as an operator) for outages?
  • Who will sue me (as ICANN) for outages?
  • How many people will simply disable DNSSEC, instead of fixing the setup?

So the main interest is a personal one: Am I on risk?

Luckily there are only two real possibilities:

  • My ISP (resolver) is validating DNSSEC. It might be possible, that this thing will fail.
  • My ISP (resolver) is ignoring DNSSEC. Then any change will not touch it or me.

In order to determine those basic cases, Alan Greenberg recommended to set up a simple web page, showing the local state at the users system.

How to determine the local state of DNSSEC?

Of course the browser is unable to natively speak arbitrary DNS at the web page in order to distinguish various cases. Any active programming is not an option,

It is possible to split the page into different sections and load them. A special section is not resolvable, if the DNS resolver is validating. I chose CSS for differentiation,

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

The second CSS is overwriting the former one:

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

This second style sheet should not be loadable if the resolver is validating.

So I have to introduce a stable error in my DNSSEC signed zones. The error have to be designed as stable, so that the automatic repair scripts are unable to fix it (and should not complain about it). I chose an erroneous delegation:

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

The child zone is not signed, despite the manually crafted DS record in the parent zone claims otherwise.

In reality this results in the following picture:

fail.donnerhacke.de

Any validating resolver can't prove this and responds:

;; ->>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

A non-validating resolver will come to a different conclusion:

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

So the rendering of the web page depends on the ability of the resolver to validate DNSSEC.

And now go and test your 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 ...