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

MFA-Anforderung für AWS-Root-Benutzer

Ab Ende März 2025 wird AWS Multi-Faktor-Authentifizierung (MFA) für alle AWS Organizations-Mitgliedskonten Root-Benutzer erzwingen. Entdecke in unserem Blog, wie du den Root-Benutzer Zugang zentral verwalten kannst.
zum Artikel
Image

Ein Beispiel für den Einsatz von GitOps zur Bereitstellung einer modernen Anwendungs­entwicklungs­plattform

In diesem Artikel erfährst du, wie GitOps mit OpenShift GitOps (ArgoCD) das Infrastrukturmanagement automatisiert, Entwickler stärkt und die Anwendungsbereitstellung beschleunigt.
zum Artikel
HYCU Teaser

Vereinfache deinen Datacenter Fussabdruck im hybriden Cloudzeitalter

Wir alle befinden uns in einer Welt, die sich rasch auf exponentielle technologische Fortschritte zubewegt. Für Unternehmen ist es von entscheidender Bedeutung, ihre IT-Plattformen nicht nur anzupassen, sondern sie auch zukunftssicher zu machen.
zum Artikel
HYCU Teaser

HYCU Data Protection bietet resilientes Backup-Management im hybriden Cloudzeitalter

Ist dir in den letzten Monaten auch schon mal der Gedanke gekommen, dass die technologische Entwicklung in atemberaubendem Tempo voranschreitet!?! Im Blog merkst du schnell, damit bist du nicht alleine.
zum Artikel