Published

Runbook · F4·1 — conectar Lakekeeper a OpenFGA y poblar los permisos

Connect any source, model it as an ontology, transform it, and operationalize it, analytics, automation and machine learning, under one governed, self-hostable roof. --- Most teams stitch the...

Runbook · F4·1 — conectar Lakekeeper a OpenFGA y poblar los permisos

🏁 EJECUTADO Y VERDE — 2026-08-07

Lakekeeper autoriza con OpenFGA en producción. La ventana duró lo que la secuencia.

GateResultado
migrateModel version 4.0 written to OpenFGA store lakekeeper
reconcile --mode add-missing398 tuplas · 18 warehouse · 30 namespace · deleted=0
reopen-bootstrap + bootstrap204lakekeeper-operator como operator del servidor
lakekeeper-index-catalog (la cara)200
lakekeeper-duck-server200
lakekeeper-warehouse-writer200
lakekeeper-trino directo401 — sigue sin poder saltarse la cara
⭐ la cara sirve a TrinolistTables 200 · loadTable+vending 200 con session-token
⭐⭐ facade-smoke COMPLETOVERDE — write nativo → read/aggregate/schema + gobernanza hidratada y propagada

Permisos finales, idénticos en los 9 warehouses (5 asignaciones cada uno):
warehouse-writercreate + modify + describe · index-catalogselect ·
duck-serverselect · trino → ninguna (deny-by-default).

🔴 El gate ② falló a la primera, y por la razón exacta para la que existe

Con sólo modify en el warehouse, el writer no podía crear tablas:

NamespaceActionForbidden: Namespace action `can_create_table` forbidden
on namespace 'main.test' in warehouse f6eb4152…

modify sobre el warehouse NO otorga can_create_table en sus namespaces — hace
falta create. Es literalmente lo que el runbook avisaba: «un token que decía
read-only resultó escribir, y sólo se supo al intentarlo»
, aquí del revés. Un
health-check no lo habría visto: sólo un facade-smoke que ESCRIBE.

Y se afinó al mínimo privilegio: se concedieron y luego retiraron ownership,
manage_grants y pass_grants — los dos últimos dejarían al writer conceder
permisos a otros
. Verde igualmente sin ellos.

⚠️ reopen-bootstrap ya se retiró del preDeployCommand (queda sólo migrate,
que es idempotente). Dejarlo habría reabierto el bootstrap en cada despliegue.

✅ Y los gates DESDE TRINO, cerrados después

GateResultado
SHOW SCHEMAS FROM carboninformation_schema · main · main.default · main.my_first_project
SELECT count(*)3
SELECT *[[1,"norte",10.5],[2,"sur",20.5],[3,"norte",30.5]] — los bytes
SELECT current_usertrino-junction
NEGATIVO · schema sin grant🛑 Forbidden — falta VIEW_READ_PROPERTIES

El camino entero funciona con OpenFGA autorizando: Trino → cara → Lakekeeper
(OpenFGA) → R2, y el negativo sigue denegando.

🔴 Pero antes hubo que arreglar 132 OOMKilled, y la causa era de F1

El coordinador llevaba 132 reinicios OOMKilled / exit 137. No era la ventana:
era la configuración de memoria puesta en F1.

-Xms3276m -Xmx3276m          ← el heap se reserva ENTERO al arrancar
-XX:ReservedCodeCacheSize=512M
                              = 3.788 MB de 4.096 ⇒ 308 MB para TODO lo demás
query.max-memory=4GB          ← ⚠️ MAYOR que el heap (3,2 GB)

F1 puso 4GB razonando «que el motor corte antes que el kernel». El razonamiento
era correcto y el número estaba mal:
4GB es el límite del contenedor, no del
heap. Con el heap en 3,2 GB ese techo no se alcanza nunca — Trino sigue
reservando y el kernel mata el pod antes de que el motor considere que se pasó.

LA REGLA, y es general: query.max-memory se compara con el HEAP, no con
el contenedor. Corregido a 2GB + query.max-memory-per-node: 1GB.

⚠️ Y un intento que NO funcionó, anotado para no repetirlo: jvmArgumentOverrides
con MaxRAMPercentage=65 no bajó el heap — Stackable emite un -Xmx3276m
explícito, y un -Xmx explícito gana sobre cualquier RAMPercentage. Para tocar
el heap habría que sustituir el -Xmx, no añadir porcentajes.


Continúa openfga-standup.md (F4·0). El porqué está en catalogo-como-origen-del-grafo.md.


0 · 🔴 «Poblar antes de migrar» NO ES POSIBLE — y hay que decirlo primero

Era el plan, y Lakekeeper impone el orden contrario. Su documentación es explícita: AUTHZ_BACKEND=openfga tiene que estar puesto ANTES de correr lakekeeper migrate, y migrate es lo que instala el modelo de autorización en el store.

   flipear  →  migrate (instala modelo)  →  reconcile (jerarquía)  →  poblar permisos
      ↑                                                                      ↑
      └────────────── VENTANA: deny-by-default con el store vacío ───────────┘

No es un descuido nuestro: la discusión #1535 del repo plantea exactamente este problema —«loss of access after enabling OpenFGA (empty tuple table)»— y lleva desde diciembre de 2025 sin una sola respuesta.

La pregunta deja de ser «¿cómo lo evito?» y pasa a ser «¿cuánto dura y qué cae?».


1 · Lo verificado antes de tocar nada

Verificado
Versión de Lakekeeperv0.13.0 (infra/lakekeeper/Dockerfile, construido desde NUESTRO repo)
reconcile + reopen-bootstrapDISPONIBLES — entraron en v0.12.1. Era el riesgo nº1: la doc que los describe es de nightly, y de no estar en 0.13 no habría camino limpio
Modelo OpenFGA en v0.13.0v2.1 · v3.4 · v4.0 · v4.7 (el vigente)
OpenFGAdesplegado, preshared, gRPC en 8081
🔴 El contenedor de LakekeeperDISTROLESS — no tiene shell. railway ssh responde «your container does not have a shell»los comandos no se pueden lanzar a mano: van por preDeployCommand

2 · Qué cae durante la ventana, y qué no

Cae (todo lo que pide permisos al catálogo):

ServicioEfecto
warehouse-writerno escribe — ingestas y pipelines fallan
duck-serverno lee — el SQL Editor falla
index-catalog (la cara)no lee — la app y Trino fallan
ml-runnersin acceso

No cae:

  • El DATO. OpenFGA guarda tuplas de permisos, no filas. Nada se borra ni se mueve.
  • Los INSTANCE_ADMINS — la red de seguridad documentada: «instance admins serve as a parallel safety net while the OpenFGA store has no admin tuples». Ya hay uno configurado. ⚠️ Con un límite explícito: «do not confer data-plane access» — administran, no leen datos.
  • Trino, DuckDB, R2, Index: intactos. Sólo pierden el permiso, no la configuración.

Es una caída de disponibilidad, no de integridad. Y el rollback es una
variable
.


3 · La secuencia

Paso 1 · Variables (un solo despliegue)

LAKEKEEPER__AUTHZ_BACKEND=openfga
LAKEKEEPER__OPENFGA__ENDPOINT=http://openfga.railway.internal:8081   # ⚠️ gRPC (8081), NO 8080
LAKEKEEPER__OPENFGA__STORE_NAME=lakekeeper                            # default; explícito mejor
LAKEKEEPER__OPENFGA__API_KEY=<la preshared key de F4·0>

⚠️ Y se QUITA LAKEKEEPER__CEDAR__POLICY_SOURCES__LOCAL_FILES en el mismo cambio. Hoy convive con allowall y sugiere una protección que no existe — es la trampa que ya costó una tarde. [confirmar] si dejar el COPY policies/ del Dockerfile: las políticas Cedar siguen siendo la especificación legible de la regla, aunque nadie las lea.

Paso 2 · Los comandos, por preDeployCommand

El contenedor no tiene shell (§1), así que van aquí — y Railway los corre antes de arrancar serve:

preDeployCommand = [
  "/home/nonroot/lakekeeper migrate",
  "/home/nonroot/lakekeeper openfga reconcile --mode add-missing",
  "/home/nonroot/lakekeeper reopen-bootstrap --yes"
]
  • migrate instala el modelo (v4.7) en el store. Idempotente.
  • reconcile --mode add-missing siembra la jerarquía estructural desde el Postgres del catálogo — «purely additive; never deletes». Es lo que hace que los 9 warehouses y sus namespaces/tablas existan como objetos en OpenFGA. ⚠️ NO crea permisos: «ownership tuples, grants, role assignments … are left alone».
  • reopen-bootstrap --yes reabre el endpoint de bootstrap (que sólo corre una vez por catálogo) sin tocar server_id, datos del catálogo ni tuplas existentes.

⚠️ reopen-bootstrap se QUITA del preDeployCommand en cuanto el bootstrap esté
hecho.
Dejarlo lo reabriría en cada despliegue. migrate puede quedarse.

Paso 3 · Bootstrap como admin

Por la UI de Lakekeeper o la Management API, con el instance admin (oidc~33351116-…, ya configurado). Es quien queda como admin del servidor.

Paso 4 · ⭐ Poblar los permisos — y la especificación YA EXISTE

No hay que inventar la política: hay que traducirla. warehouse.cedar es exactamente «lo que cada identidad debe poder hacer en el catálogo, en un diff» — se escribió para un authorizer que resultó ser comercial, y sirve tal cual como especificación:

Identidad (oidc~…)Qué debe poderTraducción a Lakekeeper
21a12588… warehouse-writercrear · commitear · soltar · leerrol con modify sobre el warehouse
c3219179… duck-serversólo leer (+ forbid explícito de commit/drop/write)select + describe
bc8c8a9e… index-catalog (la cara)sólo leer — reenvía lo ya autorizado arribaselect + describe
8cd1e1be… operatortodoadmin de proyecto
4ae04c42… trinoNADAforbid sobre toda acciónninguna tupla (OpenFGA ya es deny-by-default)
b3f95571… ml-runner (compartido, retirado)nadaninguna tupla

⚠️ Y OJO con trino: NO es una regresión — es lo mismo que ya está en vigor

Trino ya no puede hablar directo con Lakekeeper: su token trae aud: account y
LAKEKEEPER__OPENID_AUDIENCE=lakekeeper lo rechaza con 401 (medido). Con OpenFGA
se sostiene la misma regla por la vía correcta, en vez de por un efecto de la
audiencia. Su acceso al dato no desaparece: sigue resolviéndose por la cara.

🔴 Lo que NO se puede trasladar, y ya está medido

El §4 de warehouse.cedar lo dejó escrito tras validarlo contra el schema:
Lakekeeper no modela «entregar la credencial» como algo distinto de «leer la tabla»
no existe una acción de vending; va dentro de las de lectura.

Index sabe expresarlo:

loadTable                → TABLE_READ_PROPERTIES
loadTable + delegación   → TABLE_READ_PROPERTIES **y** TABLE_READ_DATA

la cara no duplica a OpenFGA: expresa una regla que la capa de abajo no puede.
Es la confirmación medida de la tesis: Index unifica y añade lo que las piezas no
alcanzan.
No es negociable moverlo abajo.


4 · Los gates — el negativo primero

# ① ⭐ EL NEGATIVO, y es el que prueba que el flip HIZO algo:
#     duck-server NO puede commitear
# ② warehouse-writer SÍ escribe    → `facade-smoke` COMPLETO, no un health-check
# ③ duck-server SÍ lee             → el SQL Editor resuelve tablas
# ④ la cara sirve a Trino          → SELECT sobre el canario de F2 = las 3 filas
# ⑤ ⭐ trino NO habla directo con Lakekeeper (hoy 401 por audiencia; ahora también por authz)

El ② no es ceremonia. En esta misma línea de trabajo, «un token que decía
read-only resultó escribir, y sólo se supo al intentarlo»
.

Rollback: LAKEKEEPER__AUTHZ_BACKEND=allowall. Una variable, y el store de OpenFGA puede quedarse poblado para el siguiente intento — no hay que deshacer nada.


5 · La decisión que este runbook NO toma

¿Cuándo se abre la ventana? Todo lo demás está preparado y verificado. Lo que falta es elegir el momento, porque durante unos minutos el Warehouse no sirve a nadie.

Y una recomendación de orden, por si ayuda: hacerlo con el canario de F2 delante (ds_e02e8ddf…, 3 filas conocidas) — es el gate ④ más barato que existe y prueba el camino entero: Trino → cara → Lakekeeper → OpenFGA → R2.


Cruza con: openfga-standup.md · f4-opa-reconocimiento.md · infra/lakekeeper/policies/README.md (la especificación) · trino-absorcion.md §6.