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.
Gate Resultado migrateModel version 4.0 written to OpenFGA store lakekeeperreconcile --mode add-missing398 tuplas · 18 warehouse · 30 namespace · deleted=0reopen-bootstrap+ bootstrap204 — lakekeeper-operatorcomo operator del servidorlakekeeper-index-catalog(la cara)200 lakekeeper-duck-server200 lakekeeper-warehouse-writer200 ⭐ lakekeeper-trinodirecto401 — sigue sin poder saltarse la cara ⭐ la cara sirve a Trino listTables200 ·loadTable+vending 200 consession-token⭐⭐ facade-smokeCOMPLETOVERDE — write nativo → read/aggregate/schema + gobernanza hidratada y propagada Permisos finales, idénticos en los 9 warehouses (5 asignaciones cada uno):
warehouse-writer→create+modify+describe·index-catalog→select·
duck-server→select·trino→ ninguna (deny-by-default).🔴 El gate ② falló a la primera, y por la razón exacta para la que existe
Con sólo
modifyen el warehouse, el writer no podía crear tablas:NamespaceActionForbidden: Namespace action `can_create_table` forbidden on namespace 'main.test' in warehouse f6eb4152…
modifysobre el warehouse NO otorgacan_create_tableen sus namespaces — hace
faltacreate. 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 unfacade-smokeque ESCRIBE.Y se afinó al mínimo privilegio: se concedieron y luego retiraron
ownership,
manage_grantsypass_grants— los dos últimos dejarían al writer conceder
permisos a otros. Verde igualmente sin ellos.⚠️
reopen-bootstrapya se retiró delpreDeployCommand(queda sólomigrate,
que es idempotente). Dejarlo habría reabierto el bootstrap en cada despliegue.
✅ Y los gates DESDE TRINO, cerrados después
Gate Resultado SHOW SCHEMAS FROM carboninformation_schema·main·main.default·main.my_first_projectSELECT count(*)3 ⭐ SELECT *[[1,"norte",10.5],[2,"sur",20.5],[3,"norte",30.5]]— los bytesSELECT 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
4GBrazonando «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-memoryse compara con el HEAP, no con
el contenedor. Corregido a2GB+query.max-memory-per-node: 1GB.⚠️ Y un intento que NO funcionó, anotado para no repetirlo:
jvmArgumentOverrides
conMaxRAMPercentage=65no bajó el heap — Stackable emite un-Xmx3276m
explícito, y un-Xmxexplícito gana sobre cualquierRAMPercentage. 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 Lakekeeper | v0.13.0 (infra/lakekeeper/Dockerfile, construido desde NUESTRO repo) |
⭐ reconcile + reopen-bootstrap | DISPONIBLES — 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.0 | v2.1 · v3.4 · v4.0 · v4.7 (el vigente) |
| OpenFGA | desplegado, preshared, gRPC en 8081 |
| 🔴 El contenedor de Lakekeeper | DISTROLESS — 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):
| Servicio | Efecto |
|---|---|
warehouse-writer | no escribe — ingestas y pipelines fallan |
duck-server | no lee — el SQL Editor falla |
index-catalog (la cara) | no lee — la app y Trino fallan |
ml-runner | sin 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"
]
migrateinstala el modelo (v4.7) en el store. Idempotente.reconcile --mode add-missingsiembra 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 --yesreabre el endpoint de bootstrap (que sólo corre una vez por catálogo) sin tocarserver_id, datos del catálogo ni tuplas existentes.
⚠️
reopen-bootstrapse QUITA delpreDeployCommanden cuanto el bootstrap esté
hecho. Dejarlo lo reabriría en cada despliegue.migratepuede 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 poder | Traducción a Lakekeeper |
|---|---|---|
21a12588… warehouse-writer | crear · commitear · soltar · leer | rol con modify sobre el warehouse |
c3219179… duck-server | sólo leer (+ forbid explícito de commit/drop/write) | select + describe |
bc8c8a9e… index-catalog (la cara) | sólo leer — reenvía lo ya autorizado arriba | select + describe |
8cd1e1be… operator | todo | admin de proyecto |
4ae04c42… trino | ⭐ NADA — forbid sobre toda acción | ninguna tupla (OpenFGA ya es deny-by-default) |
b3f95571… ml-runner (compartido, retirado) | nada | ninguna tupla |
⚠️ Y OJO con
trino: NO es una regresión — es lo mismo que ya está en vigorTrino ya no puede hablar directo con Lakekeeper: su token trae
aud: accounty
LAKEKEEPER__OPENID_AUDIENCE=lakekeeperlo 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.cedarlo 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 sí 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-onlyresultó 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.