Image

Portrait Mathieu Lienart
von Matthieu Lienart
Cloud Engineer, aus Ostermundigen

Datenmaskierung von AWS Lambda Funktions-Logs

Wo liegt das Problem?

Wenn man Ereignisse, Antworten auf API-Anfragen usw. innerhalb von Lambda-Funktionen in CloudWatch loggt, kann es passieren, dass sensible Informationen wie PII in den CloudWatch-Logs ersichtlich sind. Dadurch werden diese sensiblen Informationen potenziellen Personen zugänglich, welche keinen Zugriff darauf haben sollten, z. B. Entwickler und Cloud-Plattform-Administratoren. Es ist aber auch ein grosses Problem, die Datenschutzbestimmungen wie das Recht auf Löschung der Daten einzuhalten. Wie kann man sicherstellen, dass die Daten eines Kunden in allen Logs gelöscht werden?

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

Ich habe einen Python-Dekorator erstellt, welcher nach Erhalt der Nachricht die Funktion data_masker.erase() aufruft, um alle als Parameter übergebenen Felder zu löschen, bevor die dekorierte Funktion aufgerufen wird.
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

Ein Klassendekorator wird erstellt, um eine Dekoratorfunktion als Argument auf alle Logging-Methoden (z.B. Info, Error, Exceptions) der Logger-Klasse anzuwenden:
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

Schliesslich wird eine benutzerdefinierte Logger-Klasse erstellt, auf die der im vorherigen Schritt erstellte Klassendekorator angewendet wird. Der Klassendekorator nimmt als Argument die erste Funktion, die die data_masker.erase() Funktion dekoriert. Der Data-Masker-Dekorator nimmt als Argument alle JSON-Schlüssel, die PII enthalten und die gelöscht werden sollen.
    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

Durch die Instanziierung des Python-Loggers in die Lambda-Funktion als CustomLogger() statt des üblichen AWS Lambda Power Tools Logger(), werden alle Werte der JSON-Schlüssel, die im Klassendekorator-Argument aufgeführt sind, standardmässig gelöscht.
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.

*Hinweis: Das Bannerbild wurde mit dem AWS Nova Canvas-Bildgenerierungs-KI-Modell erstellt.
Image

Secured with vision: Vista relies on Rubrik for resilient IT

Ein IT-Ausfall im medizinischen Notfall? Für Vista Augenpraxen & Kliniken keine Option. Mit Amanox, Rubrik und den Schweizer Azure-Regionen ist die Datensicherung jetzt sicher, schnell und gesetzeskonform. Mehr dazu im Blog.
zum Artikel
Image

Hybrid Multi-Cloud Kubernetes mit Nutanix NKP verwalten

Container verändern nicht nur die Entwicklung, sondern auch Betrieb und Infrastruktur. Warum moderne Apps neue Antworten brauchen, erfährst du im Blog.
zum Artikel
Image

LangChain bei AWS CloudWatch protokollieren

LangChain macht LLM-Anwendungen flexibel – doch ohne sauberes Logging wird’s in der Praxis schwierig. Erfahre, wie du mit AWS CloudWatch deine KI-Systeme zuverlässig überwachst, Fehler aufdeckst und smarter analysierst.
zum Artikel
Image

Serverless Chatbot mit LangChain & AWS Bedrock

LangChain und AWS Bedrock versprechen schnelle KI-Entwicklung – doch selbst einfache Chatbots stellen hohe Anforderungen. Erfahre, wie du mit cleverer Kombination beider Tools smarte Features wie RAG, Verlaufsspeicherung und Mehrsprachigkeit meisterst.
zum Artikel