IP-Adressen in Webserverlogs datenschutzgemäß anonymisieren

Konfiguration Symbol eines Kalenders 2018-02-04 Symbol eines Stiftes 2019-01-28 06:27 Symbol eines Auges 12

IP-Adressen sind persönliche Daten - doch werden diese häufig nicht korrekt gespeichert. Dieses Problem kann schnell mit einem Skript behoben werden.

Dieser Abschnitt über die gesetzliche Lage ist nur einen Überblick und möglicherweise ungenau.

IP-Adressen sind persönliche Daten - als solche sind diese zu schützen und nur bei Notwendigkeit und besonderem Interesse zu erheben. Um über längere Zeit z.B. für statistische Auswertungen doch Logdateien anzulegen zu dürfen, sollten bzw. müssen daher IP-Adressen anonymisert (maskiert) oder ganz entfernt werden.

Unter der Maskierung von IP-Adressen wird das Entfernen eindeutig zuordnebarer Bestandteile verstanden. Bei IPv4-Adressen sind dies meist die letzten beiden Oktetts (79.133.35.12079.133.x.x). Bei IPv6-Adresse sollten stattdessen mindestens die letzten 64 bis 80 Bits entfernt werden.

Status quo

Die meisten Webserver legen Logs im (NCSA) Common log Format an:

{remote} - {user} [{when}] "{method} {uri} {proto}" {status} {size}
  • remote: IP-Adresse bzw. Hostname des Clients, z.B. 79.133.35.120
  • user: Nutzername, wenn HTTP-Authentifizierung verwendet wird, sonst "-" (ohne Anführungszeichen)
  • when: Zeitstempel
  • method: HTTP-Methode, z.B. GET
  • uri: Angefragte URI, z.B. /
  • proto: Protokoll der Anfrage, z.B. HTTP/1.1
  • status: HTTP-Statuscode der Antwort
  • size: Antwortlänge

Einige Webserver ergänzen:

"{Referer}" "{User-Agent}

So ergibt sich dann Beispielhaft:

79.133.35.120 - - [03/Feb/2018:17:10:35 +0100] "GET / HTTP/1.1" 200 1024 "-" "Mozilla/5.0"

Quelle: http.log - Caddy User Guide

Viele Webserver bieten die Option an, dieses Logformat zu ändern - viele Anwendung zur Auswertung der Logs benötigen allerdings dieses Format. Meist können IP-Adressen zwar vollständig (und damit auch datenschutzkonform) enfernt werden - häufig jedoch nicht einfach "nur" anonymisiert.

Lösung

Anstelle, dass der Webserver seine Logs in eine "echte" Datei schreibt, werden diese in eine benannten Pipe geleitet und von einem zusätzlichem Skript "nachbearbeitet". Die originalen Logdaten werden so nicht gespeichert.

Folgendes Skript liest Daten von /var/log/access.fifo und schreibt diese bereinigt in /var/log/access.log:

import os
import ipaddress

from daemonize import Daemonize

UNKNOWN_IP = "0.0.0.0"
MASK_IPV4 = int(os.environ.get("MASK_IPV4", 16))
MASK_IPV6 = int(os.environ.get("MASK_IPV6", 48))


def mask(line):
    ip, rest = line.split(maxsplit=1)
    if "[" in ip: ip = ip.replace("[", "")
    if "]" in ip: ip = ip.replace("]", "")

    try:
        parsed_ip = ipaddress.ip_address(ip)
    except ValueError:
        fixed_ip = UNKNOWN_IP
    else:
        if isinstance(parsed_ip, ipaddress.IPv4Address):
            network_width = MASK_IPV4
        else:
            network_width = MASK_IPV6
        fixed_ip = str(ipaddress.ip_network(
            '{}/{}'.format(ip, network_width), False)[0])

    return ' '.join((fixed_ip, rest))


def main():
    with open("/var/log/access.fifo") as fifo:
        with open("/var/log/access.log") as logfile:
            while True:
                line = fifo.readline()
                logfile.write(mask(line))            


if __name__ == "__main__":
    daemon = Daemonize(app="ipclear", pid="/var/run/ipclear.pid", action=main,
                       foreground="-f" in sys.argv)
    daemon.start()

ipclear.py; Die verlinkte Version steht unter 3-Klausel-BSD-Lizenz und ist gegebenenfalls neuer.

Das Skript hat folgende Abhängigkeit:
  • daemonize (pip: daemonize; apt: python3-daemonize; pkg: py36-daemonize-2.4.7; ports: devel/daemonize)

Angelegt werden kann die Named Pipe mit dem Befehl:

mkfifo /var/log/access.fifo

Da eine benannte Pipe (fast) wie eine normale Datei behandelt wird, sollten die Berechtigungen korrekt gesetzt werden:

chown www:www /var/log/access.fifo
chmod 700 /var/log/access.fifo

Resultat

Aus dem obrigen Beispiel

79.133.35.120 - - [03/Feb/2018:17:10:35 +0100] "GET / HTTP/1.1" 200 1024 "-" "Mozilla/5.0"

wird durch das Skript

79.133.0.0 - - [03/Feb/2018:17:10:35 +0100] "GET / HTTP/1.1" 200 1024 "-" "Mozilla/5.0"

Über den Autor

Simon Biewald

Der Autor des Blog programmiert bereits seit jungen Jahren in Python und beschäftigt sich nun mit den Themen der IT-Sicherheit und studiert deshalb nun "IT-Sicherheit und Mobile System" an der Hochschule Stralsund.