Vault Transform Engine: Format-erhaltende Verschluesselung fuer sensible Daten auf Nomad


Bicycle

In unserem vorherigen Beitrag ueber HashiCorp Nomad und Vault: Dynamic Secrets haben wir den gesamten Lebenszyklus des Secrets Managements fuer eine Python Flask-Anwendung auf Nomad beschrieben — von hartcodierten Zugangsdaten bis hin zur Vault Transit-Verschluesselung. Transit ist leistungsstark: Eine Kreditkartennummer wird zu vault:v1:abc123... — unlesbarem Ciphertext, der nicht missbraucht werden kann.

Aber genau diese Staerke ist manchmal ein Problem.

Stellen Sie sich ein Legacy-System vor, das das Format einer Sozialversicherungsnummer vor dem Speichern validiert, oder ein Data Warehouse, das erwartet, dass Kreditkartennummern auch nach der Verschluesselung noch wie Kreditkartennummern aussehen. Den Wert durch undurchsichtigen Ciphertext zu ersetzen, bricht diese Systeme. Sie benoetigen format-erhaltende Verschluesselung (FPE) — und genau das bietet die Vault Transform Secret Engine.

Transit vs Transform vs Masking

TransitTransform (FPE)Transform (Masking)
Ausgabe sieht wie Eingabe aus
Umkehrbar
AnwendungsfallAllgemeine VerschluesselungRegulierte Daten (SSN, PAN) mit FormatvorgabenEinweg-Verschleierung (Logs, Anzeige)
Vault Enginetransittransformtransform

In unserer Demo-Anwendung nutzen wir alle drei:

  • Transitbirth_date, address, salary (allgemeine Felder, Ciphertext ist akzeptabel)
  • Transform FPEssn (Sozialversicherungsnummer, muss im Format NNN-NN-NNNN bleiben)
  • Transform Maskingccn (Kreditkartennummer, wird als XXXX-XXXX-XXXX-1234 in Anzeigeansichten dargestellt)

Die Transform Engine einrichten

Die Vault Transform Engine ist von der Transit Engine getrennt. Aktivieren Sie sie an einem dedizierten Mount-Punkt.

Hinweis: Transform ist ein Vault-Enterprise-Feature (auch auf HCP Vault Dedicated verfuegbar).

1# Transform Secret Engine aktivieren
2vault secrets enable -path=dynamic-app/transform transform
3
4# Zweiten Mount fuer Masking aktivieren (haelt Policies sauber getrennt)
5vault secrets enable -path=dynamic-app/transform-masking transform

SSN — Format-erhaltende Verschluesselung

FPE fuer SSNs verwendet Vaults eingebautes Template builtin/socialsecuritynumber, das das NNN-NN-NNNN-Muster erzwingt. Der verschluesselte Wert ist ebenfalls eine gueltig aussehende SSN — nachgelagerte Format-Validatoren werden ohne Aenderungen bestanden.

 1# FPE-Transformation fuer SSN erstellen
 2vault write dynamic-app/transform/transformation/ssn \
 3    type=fpe \
 4    template=builtin/socialsecuritynumber \
 5    tweak_source=internal \
 6    allowed_roles=ssn
 7
 8# Rolle erstellen
 9vault write dynamic-app/transform/role/ssn \
10    transformations=ssn

CCN — Masking

Kreditkartennummern werden maskiert statt FPE-verschluesselt. Das Ergebnis ist nicht umkehrbar (XXXX-XXXX-XXXX-1234) und eignet sich fuer die Anzeige in Logs, Audit-Trails und Read-only-Ansichten.

1# Masking-Transformation fuer CCN erstellen
2vault write dynamic-app/transform-masking/transformation/ccn \
3    type=masking \
4    masking_character=X \
5    template=builtin/creditcardnumber \
6    allowed_roles=ccn
7
8vault write dynamic-app/transform-masking/role/ccn \
9    transformations=ccn

Vault Policy

Erweitern Sie die bestehende Policy nomad-dynamic-app um die Berechtigungen fuer Encode und Decode auf den neuen Pfaden:

 1# Bestehende Transit-Berechtigungen (gekuerzt)
 2path "dynamic-app/transit/encrypt/app" {
 3  capabilities = ["create", "update"]
 4}
 5path "dynamic-app/transit/decrypt/app" {
 6  capabilities = ["create", "update"]
 7}
 8
 9# Neue Transform-Berechtigungen
10path "dynamic-app/transform/encode/ssn" {
11  capabilities = ["create", "update"]
12}
13path "dynamic-app/transform/decode/ssn" {
14  capabilities = ["create", "update"]
15}
16path "dynamic-app/transform-masking/encode/ccn" {
17  capabilities = ["create", "update"]
18}

Fuer CCN wird keine decode-Berechtigung benoetigt, da Masking irreversibel ist.

Die Python-Anwendung — db_client_transform.py

Der DbClient in db_client_transform.py erweitert den Transit-Client. Er erbt das gesamte Transit-Verhalten fuer birth_date, address und salary und fuegt die SSN- und CCN-Behandlung hinzu.

Das Vault Python SDK (hvac) bietet Transform-API-Support. In dieser Implementierung verwenden wir fuer Transform-Aufrufe dennoch hvacs zugrunde liegenden HTTP-Adapter, damit die Request-Verarbeitung explizit bleibt und ueber alle Sprachvarianten konsistent ist. Dies integriert sich sauber mit hvacs Session-Management und SSL-Konfiguration.

Den Transform-Client initialisieren

 1def init_transform(
 2    self,
 3    transform_path,
 4    transform_masking_path,
 5    ssn_role,
 6    ccn_role,
 7):
 8    self.transform_mount_point = transform_path
 9    self.transform_masking_mount_point = transform_masking_path
10    self.ssn_role = ssn_role
11    self.ccn_role = ccn_role
12    logger.debug(f"Initialized transform: {self.vault_client}")

transform_mount_point und transform_masking_mount_point werden aus config.ini gelesen — die Anwendung ist vollstaendig konfigurationsgesteuert ohne hartcodierte Pfade.

Eine SSN kodieren

 1def encode_ssn(self, value):
 2    try:
 3        url = (
 4            self.vault_client.url
 5            + "/v1/"
 6            + self.transform_mount_point
 7            + "/encode/"
 8            + self.ssn_role
 9        )
10        headers = {
11            "X-Vault-Token": self.vault_client.token,
12            "X-Vault-Namespace": super().get_namespace(),
13            "Content-Type": "application/json",
14            "cache-control": "no-cache",
15        }
16        # hvac Adapter fuer HTTP-Aufrufe verwenden
17        response = self.vault_client.adapter.post(
18            url=url,
19            json={"value": value, "transformation": self.ssn_role},
20            headers=headers,
21            timeout=300,
22        )
23        logger.debug(f"Response: {response.text}")
24        return response.json()["data"]["encoded_value"]
25    except Exception as e:
26        logger.error(f"There was an error encrypting the data: {e}")
27    return ""

Der Eingabewert 123-45-6789 wird zu etwas wie 987-65-4321 — numerisch verschluesselt, strukturell identisch. Beachten Sie, dass wir self.vault_client.adapter.post() mit json={} verwenden — dies ist sauberer als rohes String-Building von Payloads.

Eine SSN dekodieren

 1def decode_ssn(self, value):
 2    logger.debug(f"Decoding {value}")
 3    try:
 4        url = (
 5            self.vault_client.url
 6            + "/v1/"
 7            + self.transform_mount_point
 8            + "/decode/"
 9            + self.ssn_role
10        )
11        headers = {
12            "X-Vault-Token": self.vault_client.token,
13            "X-Vault-Namespace": self.get_namespace(),
14            "Content-Type": "application/json",
15            "cache-control": "no-cache",
16        }
17        response = self.vault_client.adapter.post(
18            url=url,
19            json={"value": value, "transformation": self.ssn_role},
20            headers=headers,
21            timeout=300,
22        )
23        logger.debug(f"Response: {response.text}")
24        return response.json()["data"]["decoded_value"]
25    except Exception as e:
26        logger.error(f"There was an error decoding the data: {e}")
27    return None

Das Dekodieren ist die exakte Umkehrung. Nur Anwendungen mit dem richtigen Vault-Token und der richtigen Policy koennen den Originalwert abrufen.

Einen Kundendatensatz verarbeiten

Die Methode process_customer wendet selektiv die Entschluesselung je nach Feldtyp an — Transit fuer birth_date, address und salary; Transform-Decode fuer ssn; CCN wird nie dekodiert (Masking ist irreversibel):

1def process_customer(self, row, raw=None):
2    r = { ... }
3    if self.vault_client is not None and not raw:
4        r["birth_date"] = self.decrypt(r["birth_date"])       # Transit
5        r["ssn"]        = self.decode_ssn(r["ssn"])           # Transform FPE
6        r["address"]    = self.decrypt(r["address"])          # Transit
7        r["salary"]     = self.decrypt(r["salary"])           # Transit
8        # ccn bleibt maskiert — kein Decode
9    return r

Nomad Job-Konfiguration

Die Nomad Job-Datei fuegt den Transform-Konfigurationsabschnitt zur gerenderten config.ini hinzu. Am vault-Stanza sind keine Aenderungen erforderlich — die bestehende Policy nomad-dynamic-app deckt die oben hinzugefuegten neuen Pfade bereits ab.

 1task "dynamic-app" {
 2  driver = "docker"
 3
 4  config {
 5    image = "quay.io/infralovers/nomad-vault-mysql"
 6    volumes = ["local/config.ini:/usr/src/app/config/config.ini"]
 7    ports   = ["web"]
 8  }
 9
10  template {
11    destination = "local/config.ini"
12    data        = <<EOF
13[DEFAULT]
14LogLevel = DEBUG
15Port = 8080
16
17[DATABASE]
18{{ range service "mysql-server" }}
19Address = {{ .Address }}
20Port    = {{ .Port }}
21{{ end }}
22{{ with secret "dynamic-app/kv/database" }}
23Database = {{ .Data.data.database }}
24{{ end }}
25{{ with secret "dynamic-app/db/creds/app" }}
26User     = {{ .Data.username }}
27Password = {{ .Data.password }}
28{{ end }}
29
30[VAULT]
31Enabled     = True
32InjectToken = True
33Namespace   =
34Address     = {{ env "VAULT_ADDR" }}
35KeyPath     = dynamic-app/transit
36KeyName     = app
37
38[VAULT_TRANSFORM]
39Enabled              = True
40TransformPath        = dynamic-app/transform
41TransformMaskingPath = dynamic-app/transform-masking
42SsnRole              = ssn
43CcnRole              = ccn
44EOF
45  }
46}

So sehen die Daten aus

Nach dem Einfuegen eines Kundendatensatzes zeigt die folgende Tabelle, wie die rohen Datenbankzeilen im Vergleich zur Anwendungsansicht aussehen:

FeldRoher DB-WertDargestellt (App-Ansicht)
birth_datevault:v1:AbC123...1985-04-12
ssn987-65-4321123-45-6789
ccnXXXX-XXXX-XXXX-1234XXXX-XXXX-XXXX-1234
addressvault:v1:XyZ456...Hauptstrasse 42
salaryvault:v1:PqR789...85000

Die SSN-Spalte sieht in der Datenbank wie eine gueltige Sozialversicherungsnummer aus — sie besteht Format-Validatoren, kann indiziert werden und ist nur ueber Vault durch eine autorisierte Anwendung wiederherstellbar.

Zusammenfassung

Die Vault Transform Engine fuellt eine wichtige Luecke im Secrets-Management-Toolkit:

  • Transit verwenden, wenn Sie nur Vertraulichkeit benoetigen und das nachgelagerte System nicht auf die Datenform angewiesen ist.
  • Transform FPE verwenden, wenn der verschluesselte Wert weiterhin wie der Originaltyp aussehen muss — SSNs, Kreditkarten-PANs, Steuer-IDs.
  • Transform Masking verwenden, wenn der Wert niemals wiederherstellbar sein soll — Audit-Logs, Read-only-Anzeigefelder.

Alle drei Methoden arbeiten mit dem gleichen Nomad + Consul Template-Muster und erfordern nur eine kleine Erweiterung der config.ini und eine aktualisierte Vault Policy. Die Aenderung am Anwendungscode ist minimal und auf db_client_transform.py beschraenkt.

Der vollstaendige Quellcode ist unter github.com/infralovers/nomad-vault-mysql verfuegbar.

Zurück Unsere Trainings entdecken

Wir sind für Sie da

Sie interessieren sich für unsere Trainings oder haben einfach eine Frage, die beantwortet werden muss? Sie können uns jederzeit kontaktieren! Wir werden unser Bestes tun, um alle Ihre Fragen zu beantworten.

Hier kontaktieren