

Cloud Engineer, aus Ostermundigen
Datenmaskierung von AWS Lambda Funktions-Logs
Wo liegt das Problem?
Aktuelle Ansätze:
CloudWatch Logs Native Datenmaskierung
AWS CloudWatch Logs ermöglicht die Maskierung von Daten durch die Verwendung von Datenerkennungen und Datenschutzrichtlinien. Die Datenerkennungen sind musterabgleichende oder maschinell lernende Modelle, die sensible Daten erkennen. Datenschutzrichtlinien sind JSON-Dokumente, die Prozesse mit dem Umgang von identifizierten sensiblen Daten definieren. Der Prozess kann auf «Auditieren» oder «De-Identifizierung» der Daten eingestellt werden. In diesem Fall können nur Personen die Daten einsehen, die Rechte zur Durchführung der log:Unmask-Aktion haben.
Es ist zu beachten, dass die angepassten Richtlinien nur neue Daten maskieren, welche nach CloudWatch Logs geschrieben werden. Jemand mit Zugang zu den Logs wäre immer noch in der Lage, die vor der Aktivierung der Maskierung geschriebenen sensiblen Daten zu lesen.
Obwohl dieser Ansatz den Zugang zu sensiblen Daten für unbefugte Personen verhindert, hilft er nicht bei der Einhaltung des Rechts auf Löschung der Daten.
AWS Lambda Powertools Datenmaskierung
AWS Lambda Powertools ist ein Entwickler-Toolkit zur Implementierung von Serverless-Best-Practices und zur Erhöhung der Entwicklungsgeschwindigkeit, welches ursprünglich für Python entwickelt wurde, aber nun auch für Java, Typescript und .NET verfügbar ist. Bislang bietet aber nur die Python-Version eine Funktionalität für die Datenmaskierung.
Es werden zwei Ansätze vorgeschlagen. Ein Ansatz, der einen KMS-Schlüssel zur Ver-/Entschlüsselung der sensiblen Informationen im Protokoll verwendet. Ein zweiter Ansatz, bei dem die sensiblen Informationen vor dem Schreiben der Protokolle gelöscht werden. Um den ersten Ansatz umzusetzen und gleichzeitig Vorschriften wie das Recht auf Löschung der Daten einzuhalten, müsste man einen Verschlüsselungsschlüssel pro Kunde erstellen und eine Möglichkeit finden, die Informationen jedes Kunden mit seinem eigenen Schlüssel zu verschlüsseln. Sollte der Kunde von seinem Recht auf Löschung seiner Daten Gebrauch machen, könnte der Verschlüsselungsschlüssel einfach gelöscht werden, so wären seine Daten für immer unlesbar.
Obwohl diese Ansätze beide Probleme lösen können, muss man dafür genau wissen, was verschlüsselt/gelöscht werden muss. Um zum Beispiel die Telefonnummer in einer Kundenliste zu löschen, müsste man wie folgt vorgehen:
data_masker.erase(data, fields=["customers[*].phone_number"]
Was aber, wenn man sich zu Beginn eines Projekts unsicher über die Datenstruktur und deren Inhalt ist? Was ist, wenn sich das Datenschema ändert? Was ist, wenn man ein Feld in einer verschachtelten JSON-Struktur vergessen hat?
Alle PII standardmässig löschen
Braucht man sensible Informationen wie PII in Anwendungsprotokollen?
Wahrscheinlich nicht.
In diesem Fall scheint die Datenlöschung mit den AWS Lambda Powertools der einfachste Ansatz zu sein. Aber auch hier gilt: Es funktioniert, solange die Datenstruktur bekannt ist und sich diese nicht ändert. Wie kann ich als Sicherheits-/Compliance-Beauftragter sicherstellen, dass die Entwickler nicht vergessen sensible Daten zu löschen?
Ich wollte den Ansatz der AWS Lambda Powertools verbessern, um sensible Informationen zu löschen, wo auch immer sie sich in den Logs befinden...
Auf der Grundlage des AWS Lambda Powertools- Datenmaskierungsdienstprogramms habe ich Folgendes entwickelt.
1 | Erstellen einer Funktion zum Löschen sensibler Daten
import json from warnings import catch_warnings from functools import wraps, partial from decimal import Decimal from typing import Any from aws_lambda_powertools import Logger from aws_lambda_powertools.utilities.data_masking import DataMasking from aws_lambda_powertools.utilities.data_masking.provider import BaseProvider def is_valid_json_string(json_string: str) -> bool: if isinstance(json_string, str): try: result = json.loads(json_string) return isinstance(result, dict) except json.JSONDecodeError: return False def log_masking_decorator(masked_fields: list[str]): def decorator(func): @wraps(func) def wrapper(self, msg, *args, **kwargs): if is_valid_json_string(msg) or isinstance(msg, dict): with catch_warnings(action="ignore"): msg = self.data_masker.erase(msg, fields=masked_fields) return func(self, msg, *args, **kwargs) return wrapper return decorator
Erläuterungen zum Code:
- Die Funktion data_masker.erase() funktioniert nur bei Verzeichnissen und Strings, die ein JSON-Objekt enthalten. Wir müssen also den Datentyp überprüfen, bevor wir die Daten löschen.
- Der AWS Lambda Powertools Datenmaskierer gibt eine Warnung aus, wenn er angewiesen wird, ein nicht vorhandenes Feld zu maskieren. Wenn man bei dem Ansatz eine Liste von Feldern definieren möchte, die überall maskiert werden sollen, führt dies zu vielen Warnungen in CloudWatch- Logs. Daher unterdrücke ich diese Warnungen, bevor ich die erase() Methode aufrufe.
2 | Die Funktion auf alle Logging Methoden anwenden
def decorate_log_methods(decorator): def decorate(cls): for attr in dir(cls): if callable(getattr(cls, attr)) and attr in [ "info", "error", "warning", "exception", "debug", "critical", ]: setattr(cls, attr, decorator(getattr(cls, attr))) return cls return decorate
3 | Erstellen einer benutzerdefinierten Logger-Klasse
def decimal_serializer(obj: Any) -> Any: if isinstance(obj, Decimal): obj = str(obj) return obj @decorate_log_methods( log_masking_decorator( masked_fields=[ "$.[*].phoneNumber", "$..[*].phoneNumber", "$.[*].name", "$..[*].name", ] ) ) class CustomLogger(Logger): def __init__(self): super().__init__() self.datamasking_provider = BaseProvider( json_serializer=partial(json.dumps, default=decimal_serializer), json_deserializer=json.loads, ) self.data_masker = DataMasking( provider=self.datamasking_provider, raise_on_missing_field=False )
Erläuterungen zum Code:
- Ich verwende hier einen benutzerdefinierten JSON-Serializer, um Python Decimal-Werte in Strings zu konvertieren, um Fehler zu verhindern.
4 | Verwendung
from log_helpers import CustomLogger logger = CustomLogger() @logger.inject_lambda_context(log_event=True) def lambda_handler(event: dict, context: LambdaContext): response = boto3_client.whatever_service_api() logger.info(response)
Erläuterungen zum Code:
- Der Dekorator inject_lambda_context ruft die Funktion logger.info() auf. Da der Logger unser benutzerdefinierter Logger ist, werden alle PII, die in unserem Dekorator der Klasse CustomLogger aufgeführt sind, aus den Lambda-Eventlogs gelöscht.
Damit wird das Ziel erreicht, die Löschung aller definierten PII zu erzwingen, ohne dass der Entwickler jedes zu löschendes Feld bei jeder Logging-Aktion speziell auflisten muss.
Der vollständige Code des benutzerdefinierten Loggers ist hier verfügbar. Das Repository enthält eine vollständige Demo, die zeigt, wie ein AWS API Gateway gesichert werden kann.
Würde ich das in der Produktion verwenden?
Nein.
Das Umwandeln der gesamten JSON-Struktur jedes Logs wird unnötigerweise die Latenz der Antwort der Lambda-Funktion erhöhen. Wie aus der Dokumentation der AWS Lambda Power Tools hervorgeht, sollte das Logging von Ereignissen nur in Nicht-Produktiven Umgebungen durchgeführt werden. Ausserdem sollte man die Daten kennen, die von der Lambda-Funktion verarbeitet werden und daher spezifisch die sensiblen Daten löschen, bei denen dies nötig ist.
Ich halte es dennoch für einen interessanten Ansatz, der in einigen Fällen nützlich sein könnte. Testumgebungen sollten keine Produktionsdaten haben, aber wir haben alle schon solche Fälle erlebt...
Es war dennoch eine interessante Übung.