bayerncloud elasticsearch opensearch
Document Level Security
Kurzantwort: DLS/FLS in OpenSearch/Elasticsearch ist möglich - aber in Multi‑Tenant‑APIs mit viel Volltext, Geo & Aggregationen oft ein Performance‑, Komplexitäts‑ und Risiko‑Magnet. Für Los 1 (read‑only API, Lizenz “Open/Closed Data”, starke Filter) und Los 4 (Widgets mit vielen Suchen/Aggregationen) ist das meist die falsche Stellschraube.
Warum DLS/FLS oft schiefgeht
- Performance & Caching
- Entscheidungen passieren pro Request & Dokument → teure Filter‑Wrapper, deutlich weniger/kein Query‑Caching, hohe Latenzen unter Last.
- Besonders schmerzhaft bei Aggregationen, Geo‑Queries, Nested/Parent‑Child und
_msearch. - Effekt: schöne Dev‑Tests, aber plötzliche 99‑Percentile‑Spikes im Betrieb (kontraproduktiv zur Uptime/Performanz‑Vorgabe).
- Korrektheit von Ergebnissen
- Aggregationen (Counts, Buckets, Cardinality, Percentiles) können “komisch” wirken, weil die Sicht pro Nutzer gefiltert ist - Business erwartet “globale” Zahlen.
- Highlighting/Suggest/Terms‑Lookups u. ä. können “Spuren” verdeckter Felder/Docs hinterlassen, wenn nicht alles konsequent gehärtet ist.
- Scroll/PIT: Rechtewechsel während eines laufenden Scrolls/PIT → inkonsistente Ergebnisse oder Sicherheitslöcher, wenn IDs geteilt werden.
- Leak‑Risiken durch Nebenendpunkte
_cat/*,_field_caps, Explain/Profile, Mapping‑Blicke, Source/Stored Fields, Plugin‑APIs - ein vergessenes Loch reicht.- In JSON‑LD/@graph können verlinkte Objekte (z. B. “Place” im “Event”) ungewollt “mitrutschen”, obwohl das Hauptdokument korrekt gefiltert ist. (Gerade relevant, wenn “alles hat Basis Thing”.)
- Betrieb & Weiterentwicklung
- Policy‑Spaghetti (Rollen x Index x Felder x Abfragearten).
- Debugging-Hölle: “Warum ist das Dokument weg?” - schwer reproduzierbar, weil nutzer- und kontextabhängig.
- Upgrades/Testlast: Security‑Layer + Query‑DSL + Mappings + Plugins → hoher Test‑ und Pflegeaufwand; bei Releasewechseln gerne regressionsanfällig.
- FLS mit evolvierenden Schemata
- Euer Output ist schema.org/ODTA in JSON‑LD; Felder wachsen/ändern sich. FLS braucht allow‑/deny‑Listen - neue Properties sind dann per Default offen (oder zu restriktiv) → beides riskant/ärgerlich für ein Read‑only API mit Lizenzgrenzen.
Typische Symptome (“das hat bei uns nicht gut funktioniert”)
- Plötzliche Latenzsprünge bei Aggregations‑lastigen Widgets (Heatmaps, Kalender, Facetten).
- Unterschiedliche Zahlen zwischen API, Dashboard und Backoffice wegen gefilterter Sicht.
- Kibana/OpenSearch Dashboards wirken “kaputt”, weil Saved Searches/Aggs unerwartet leer sind.
- Edge‑Case‑Leaks über Nebenendpunkte oder falsch gehandhabte
_msearch/Scroll‑IDs.
Was stattdessen besser passt (für Los 1/3/4)
- Harte Partitionierung statt DLS
- Index‑pro‑Mandant/Org und/oder pro Lizenz (OPEN/CLOSED); Zugriff über Aliases/Routing steuern.
- Vorteile: bessere Cache‑Treffer, sauberes Monitoring, simple mentale Modelle, deutlich weniger Leck‑Risiko.
- Passt direkt zur geforderten Lizenz‑Trennung Open vs. Closed (Los 1, 2.2).
- Policy‑Durchsetzung im PEP (Gateway/Middleware)
- Wie zuletzt skizziert: OPA/Rego im Gateway oder in der Middleware → Query‑Rewrite (DLS als Filter),
_source_includes(FLS), Index‑Gating vor OpenSearch. - Vorteil: einheitlicher Schutz auch für Dashboards, Widgets, Scripte - und Auditing an einer Stelle. (Auch hilfreich für Los 3/4, die dieselben Regeln konsumieren.)
- Redaktierte Materialisierungen
- Für OPEN‑Daten einen abgeleiteten “Open‑View”‑Index (nur erlaubte Felder/Dokumente), der für Public/Widgets genutzt wird; CLOSED über gesicherte Wege.
- Ergebnis: sehr schnell, sehr schwer zu “verklicken”, simple Governance (passt zu Widgets‑Performancezielen).
- Gefilterte Aliases (leichtgewichtig)
- Wenn echte DLS zu heavy ist: Alias mit festem Filter (z. B.
license:OPEN) und nur diese Aliases exponieren. - Kein per‑User‑Kontext - aber robust für die Lizenz‑Trennung, die ihr explizit braucht.
Wenn ihr DLS/FLS dennoch einsetzen müsst - Checkliste
- Explizite Allow‑Listen für Endpunkte; alles andere 403.
_cat/*,_field_caps, Explain/Profile, Mappings, Tasks, Snapshot‑APIs: hart beschränken.- Scroll/PIT nutzergebunden, kurze TTL, keine Cross‑User‑Weitergabe.
- _msearch: Sub‑Requests per Policy einzeln prüfen (keine Misch‑Tenants).
- Aggregationen kritisch testen (Counts/Facetten) gegen “Ground Truth” ohne DLS.
- Observability: Policy‑ID & Constraints in Access‑Logs mitschreiben (Korrelation von Latenzen/Denies).
- Loadtests mit realistischen Queries (Geo, Nested, Aggs, msearch), Ziel‑P99 ans SLA koppeln.
Fazit
Für eure Anforderungen (Los 1: read‑only, Lizenz‑basiert, performant; Los 4: schnelle Widgets) sind Index‑Partitionierung + PEP (OPA) + ggf. redaktierte Views in der Praxis robuster und schneller als “alles über DLS/FLS lösen”. DLS taugt als Ergänzung für wenige Spezialfälle - aber nicht als Hauptmechanismus eurer API‑Sicherheit und Mandantentrennung.
Wenn du magst, skizziere ich dir das konkrete Schnittstellen‑Design (Index‑Layout + Aliases + OPA‑Input/Output‑Schema + Query‑Rewrite‑Regeln) passend zu euren Datentypen (Events/POIs/Touren) - sofort umsetzbar für Los 1 und anschlussfähig an Los 3/4.
OPA/rego-Integration
1) Gateway/Proxy vor OpenSearch (empfohlen)
Setup:
Client → API‑Gateway/Proxy (Envoy, NGINX, Kong, Traefik) → OPA (Sidecar/Service) → OpenSearch
- Der Proxy fängt alle OpenSearch-HTTP-Requests ab (auch
_search,_msearch,_bulk,_cat,_plugins/_securityetc.). - Vor dem Durchlassen ruft der Proxy OPA an (z. B. Envoy
ext_authz), übergibt JWT + Request (Methode, Pfad, Indizes, Query-DSL). - OPA (Rego) entscheidet: allow/deny und kann Constraints zurückgeben (z. B. erlaubte Indizes, zusätzliche Filter, erlaubte Felder).
- Der Proxy setzt die Entscheidung durch:
- Block verbotener Requests sofort.
- Query-Rewrite: fügt vom OPA gelieferte Filter (DLS) in den Query-Body ein.
- Source-Filtering: setzt
_source_includesfür FLS. - Index-Gating: limitiert auf erlaubte Index-Patterns.
Beispiel-Entscheidung in Rego (vereinfacht):
package opensearch.authz
default allow = false
# Aus Token (verifiziert im Gateway) übergeben:
# input.user = { sub, roles, org, licenses }
# input.req = { method, path, indices, body }
# indices: ["tourism-de-places", "tourism-de-events"]
# Indexfreigabe (Mandanten & Lizenzmodell)
allowed_index(idx) {
startswith(idx, sprintf("tourism-%s-", [input.user.org]))
}
# Feldfreigaben je Lizenz
allowed_fields := {
"OPEN": ["name","geo","category","summary","startDate","endDate"],
"CLOSED": ["name","geo","category","summary","startDate","endDate","contact","internalNotes"]
}[license] {
some license
license := (input.user.licenses[_])
}
# Dokument-Constraints (DLS) aus Token ableiten
dls_filter := {
"bool": {
"filter": [
{"terms": {"license": input.user.licenses}},
{"term": {"org": input.user.org}}
]
}
}
# Erlaubnisregel: read-only, Indizes erlaubt
allow {
input.req.method == "GET" # read-only API
every i in input.req.indices { allowed_index(i) }
}
# Entscheidung + Nebenprodukte zurückgeben
decision := {
"allow": allow,
"constraints": {
"dls": dls_filter,
"_source_includes": allowed_fields
}
}Gateway setzt um (Beispiele):
- Bei
_search/_msearch:- Body mit
bool.filter∪=constraints.dls. _source→{ "includes": constraints._source_includes }.
- Body mit
- Bei Pfaden wie
/{index}/_search: Indizes gegenallowed_indexprüfen; ansonsten 403. _cat- und Metadaten-Endpunkte: nur whitelisten oder per Org-Filter einschränken.- Audit: Proxy loggt OPA-Decision (allow/deny + Constraints + Subjekt).
Pluspunkte:
- Zentral durchsetzbar (auch für Tools wie Dashboards, curl, Beats).
- Kein OpenSearch-Plugin nötig.
- Rego-Policies versionierbar, testbar, Hot‑Reload.
Worauf achten?
- _msearch: jede Sub‑Query separat remappen/prüfen.
- Scroll & PIT: Tokenbindung prüfen (Scroll-ID/PIT nur für erlaubte Indizes ausgeben).
- Bulk: pro Action Item durchsetzen (Index‑ und Doc‑Prüfung).
- Aggregationen: DLS wirkt, aber Denormalisierung beachten (z. B. Terms-Aggs über gefilterten Korpus).
2) Los 1 (Middleware) als PEP
Wenn die Middleware alle Zugriffe auf OpenSearch kapselt (typisch in Los 1), kannst du OPA direkt in der Middleware aufrufen:
- Middleware validiert JWT, baut einen konformen Query‑Plan (z. B. aus REST-Parametern/GraphQL).
- Middleware ruft OPA mit
input = { user, intent, indices, query }auf. - Rückgabe wie oben (
allow,constraints), Middleware injiziert Filter in den Query‑DSL, setzt_source_includes, limitiert Indizes. - Vorteil: Kein zusätzlicher Netz-Hopf; Policies können domänennah (z. B. „Redakteur darf nur
eventsmitorg=…undlicense ∈ …“) beschrieben werden. - Für externe Tools (Kibana/OpenSearch Dashboards) brauchst du trotzdem Gateway-Absicherung, sonst umgehen sie die Middleware.
3) Rollen-Sync in OpenSearch Security (ergänzend)
OpenSearch Security (Plugin) bietet Rollen, DLS/FLS und Role Mappings. Du kannst OPA periodisch/even‑tgetrieben nutzen, um:
- Role Mappings aus Claims zu schreiben,
- DLS/FLS pro Rolle zu pflegen (statisch).
Das ist gut für grobe Policies (z. B. „Org‑Admin“), aber nicht für hochdynamische, requestkontextsensitive Regeln (z. B. „lizenzabhängige Feldliste je Benutzer“ pro Request). Daher meist als Baseline, kombiniert mit Pattern 1/2.
Konkrete Umsetzungsschritte
-
Identitäten & Claims
- Identity Provider (Keycloak/OIDC) → JWT mit
sub,roles,org,licenses(Open/Closed), evtl.purpose/consent. - Los 3 (DMS-Frontend) & Widgets (Los 4) erhalten Tokens über denselben Trust.
- Identity Provider (Keycloak/OIDC) → JWT mit
-
OPA Deployment
- Als Sidecar zum Gateway (Envoy +
ext_authz) oder als Service. - Bundle-/Policy-Distribution via OCI/Bundle-Server, Signaturen, Unit‑Tests der Rego‑Pakete.
- Als Sidecar zum Gateway (Envoy +
-
PEP (Gateway oder Middleware)
- Schema‑Valider für OpenSearch‑Bodies (minimiert Angriffsfläche).
- Query-Rewriter (Merge von
bool.filter, Setzen von_source_includes). - Index‑Resolver für Wildcards (expandieren, dann prüfen).
- Safe defaults: deny‑by‑default, allow nur mit vollständiger OPA‑Entscheidung.
-
Edge Cases absichern
_cat/*nur freigeben, wenn unkritisch oder gefiltert._cluster/*, Snapshots, Settings: i. d. R. sperren.- Rate Limits pro Subjekt/Route.
- Scroll/PIT an Identität binden (metadaten im Server, TTLs).
- Bulk/Reindex: Optional nur internen Diensten erlauben.
-
Auditing & Monitoring
- Jede OPA‑Entscheidung (Input/Outcome, redacted) auditieren.
- Dashboards: Zulassungsquote, Deny‑Ursachen, Latenz von OPA‑Calls.
- Chaos‑Tests: OPA nicht erreichbar → fail‑closed.
Mini‑Beispiele
Envoy (ext_authz) → OPA (skizziert)
http_filters:
- name: envoy.filters.http.ext_authz
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
grpc_service:
envoy_grpc:
cluster_name: opa
include_peer_certificate: false
with_request_body:
max_request_bytes: 8192
allow_partial_message: true
failure_mode_allow: false # fail-closedNGINX (lua) → OPA (HTTP)
access_by_lualiest JWT + Request, POST anhttp://opa/v1/data/opensearch/authz, wertetdecisionaus, modifiziert ggf. Body (perngx.req.set_body_data).
Rego-Unit‑Test (kurz)
test_allow_search_for_org() {
input := {
"user": {"org":"de","licenses":["OPEN"]},
"req": {"method":"GET","indices":["tourism-de-events"],"body":{}}
}
out := data.opensearch.authz.decision with input as input
out.allow
out.constraints._source_includes == ["name","geo","category","summary","startDate","endDate"]
}Wie passt das zu den Losen?
- Los 1 (Middleware): Ideal als PEP mit Rego‑Policies für Index‑/Doku‑/Feldebene, Query‑Rewrite, Audit.
- Los 3 (DMS-Frontend): Nutzt dieselben Claims; UI kann Felder dynamisch ausblenden (Defense‑in‑Depth).
- Los 2 (Datenintegration): Service‑Account‑Routen (Bulk/Index) getrennt und enger gefasst.
- Los 4 (Widgets): Leichtgewichtige Clients konsumieren abgesicherte Endpunkte, kein Geheimnis im Frontend.
TL;DR
- OPA + Gateway/Middleware ist der robuste Weg, Rego‑Policies per Request für OpenSearch durchzusetzen.
- Nutze Query‑Rewrite (DLS), _source‑Filtering (FLS) und Index‑Gating aus den OPA‑Constraints.
- Ergänzend kann OpenSearch Security für grobe Rollenzuordnung dienen, die feingranulare Logik bleibt in Rego.