Políticas de autorización del warehouse (Cedar)
Políticas Cedar para el catálogo Lakekeeper. Approach y decisión: warehouse-authorization.md. Encendido de la puerta que estas políticas cierran: runbook C5.
Por qué viven en el repo
Porque una política en un fichero es una decisión legible en un diff. Se revisa como cualquier otro cambio, se despliega con el código, y se puede razonar sin consultar el estado de un servicio. La alternativa —OpenFGA— guarda los permisos como un grafo de relaciones en un store: estado opaco que no aparece en ninguna revisión.
Es la misma disciplina que los cinco trinquetes de la plataforma: la regla se ve en la revisión.
🔴 Estado: NO SE PUEDEN ACTIVAR — Cedar no está en el build OSS de Lakekeeper
Medido el 2026-08-04 contra la v0.13.0 que corremos, tras ponerlo en producción:
- con
LAKEKEEPER__AUTHZ_BACKEND=cedary la política dentro del contenedor, el proceso arranca sin una sola queja y escribeUsing AllowAll authorizer; - en el código: el workspace sólo tiene
crates/authz-openfga, no hay crate de Cedar, y el binario hacebail!("Unsupported authz backend")para todo lo que no seaopenfga.
La documentación lo describe y las release notes lo anuncian en 0.13.0, pero la
distribución Apache-2.0 no lo lleva — encaja con el license-status que devuelve
/management/v1/info. Es de su build comercial.
⚠️ El modo de fallo es el peor posible: una configuración que parece aplicada y no lo está. Sólo lo cazó tener el gate calibrado contra el estado permisivo.
Estas políticas se quedan escritas y no se borran, por dos razones: valen tal cual el día que haya un autorizador que las lea (build comercial, o el trabajo de traducirlas a OpenFGA), y son la especificación legible de la regla — lo que cada identidad debe poder hacer en el catálogo, en un diff.
Lo que se hace mientras tanto: cerrar el camino en la autenticación con
LAKEKEEPER__OPENID_AUDIENCE — ver runbook C5 §5.
Es todo-o-nada por identidad en vez de por acción, y eso basta para lo que ⑤ persigue:
que Trino no pueda hablar con el catálogo por su cuenta.
Lo que dicen hoy (npm run check:cedar → 6 permit · 3 forbid):
| Identidad | Qué puede en el CATÁLOGO |
|---|---|
warehouse-writer | crear · commitear · soltar · leer — el único que escribe |
duck-server | leer. Y un forbid explícito sobre commit/drop/write |
index-catalog (la cara) | leer — reenvía lo que ya autorizó arriba |
operator | todo — humanos y scripts |
trino | ⭐ NADA. forbid sobre toda acción |
(el compartido) ml-runner | nada: sin permit, y Cedar es deny-by-default |
⭐ El forbid de Trino ES el paso ⑤. Desde el paso ④ Trino entra por la cara REST
de Index Catalog, pero mientras pueda seguir hablando directamente con el catálogo lo
hace porque se lo pedimos, no porque no le quede otra — y eso es una convención, no
gobernanza. Su acceso al dato no desaparece: pasa a resolverse por la puerta, donde su
grant es «listar todo el workspace, leer sólo main.default».
Cómo llegan estas políticas a la máquina
Cedar carga políticas SÓLO desde ficheros locales o ConfigMaps de Kubernetes — no hay variable de entorno con la política inline, ni fuente remota por URL (docs). Y Lakekeeper corre en Railway desde la imagen oficial, que obviamente no lleva las nuestras.
Ese hueco lo cierra ../Dockerfile: la imagen oficial + COPY policies/.
Así la política desplegada es la revisada, y no una copia que alguien subió a un
volumen y que diverge al segundo cambio.
⚠️ Si un fichero es inválido, Lakekeeper NO ARRANCA. No degrada: se apaga. Por eso existe el validador y por eso el orden de abajo separa desplegar el fichero de encender el authorizer.
npm run check:cedar # sintaxis, ANTES de desplegar
Activación · el orden, y no es negociable
0 · Validar y decidir lo que falta
-
npm run check:cedaren verde. - ⚠️ Tu usuario HUMANO (
Victor Obregon,oidc~33351116-c5ec-4e1f-ba62-78ed62b3e08a) no tiene permit. Si usas la UI de Lakekeeper, decide antes: o se le añade unpermit, o asumes que la operación se hace con la credencial deoperator. No lo descubras después de encenderlo. -
ml-runner(oidc~b3f95571-…) se queda sin acceso, y eso es el sello final de la Fase A: la identidad compartida deja de servir para algo. Comprobado que ya no la usa nadie, pero conviene saber que ésta es la línea donde muere.
1 · Desplegar el fichero, con el authorizer TODAVÍA apagado
En Railway, servicio de Lakekeeper: pasar de deploy from image a construir el
repositorio con Root Directory = infra/lakekeeper. Sin tocar AUTHZ_BACKEND.
- el servicio arranca y
/healthsigue en 200; -
facade-smokeen verde (nada ha cambiado de comportamiento: sigue enallow-all).
Este paso es reversible y no cambia ninguna decisión: sólo mete un fichero en la imagen.
2 · Encender el authorizer
LAKEKEEPER__AUTHZ_BACKEND=cedar
LAKEKEEPER__CEDAR__POLICY_SOURCES__LOCAL_FILES=/policies/warehouse.cedar
3 · Verificar en positivo Y en negativo — npx tsx scripts/governance/c5-cedar-gate.ts
En positivo (si algo de esto falla, rollback inmediato):
-
warehouse-writerescribe —facade-smokecompleto, no un health-check; -
duck-serverlee — el SQL Editor resuelve tablas; - la cara sirve a Trino:
SHOW SCHEMASyDESCRIBEenmain.defaultsiguen yendo.
En negativo — sin esto sólo has cambiado una variable:
-
duck-serverNO puede commitear; - ⭐
trinoNO puede hablar directamente con Lakekeeper… y sí por la cara.
Esa última pareja es la que convierte el paso ④ en gobernanza. Y la penúltima no es
ceremonia: en esta misma línea de trabajo, un token que decíaread-onlyresultó
escribir, y sólo se supo al intentarlo.
Rollback: LAKEKEEPER__AUTHZ_BACKEND=allow-all. Una variable, y el fichero puede
quedarse donde está.
Lo que sigue sin poder validarse antes de encender
/management/v1/permissions/cedar/schema devuelve 404 mientras el backend sea
allow-all (medido 2026-08-04): sin authorizer cargado no hay schema que publicar. Así
que un nombre de acción inexistente pasa el validador y no protege nada — ya pasó dos
veces (NamespaceSelectActions, GetTableCredentials).
En cuanto Cedar esté activo, ese endpoint responde y toca cerrar el círculo: añadir
cedar.validate({ policies, schema }) a scripts/governance/check-cedar.ts (hay un TODO
en el fichero). Hasta entonces, el gate negativo es la única validación real.
Identidades (Fase A · 2026-08-03 → 2026-08-04)
| Cliente | Id en Lakekeeper | Quién lo usa · dónde corre |
|---|---|---|
lakekeeper-warehouse-writer | oidc~21a12588-76ec-4da6-bee6-5fef4345d91a | warehouse-writer · Railway |
lakekeeper-duck-server | oidc~c3219179-5134-4f44-94e3-0afdb32f7af8 | duck-server · Railway |
lakekeeper-trino | oidc~4ae04c42-32c1-497f-8549-c9c9aa2417e2 | Trino · VM de GCP, fuera de Railway |
lakekeeper-operator | oidc~8cd1e1be-f1df-465d-8de1-466af0486d75 | humanos y scripts · .env.local |
lakekeeper-index-catalog | oidc~bc8c8a9e-2557-4d0c-9367-9948a79317ba | la cara REST · Vercel |
(el compartido, retirado) lakekeeper-ml-runner | oidc~b3f95571-eaf6-48f5-a201-378f75ffdb1b | — |
Un cliente de Keycloak es una IDENTIDAD, no un servicio. No corre en ningún
sitio: es un nombre y un secreto que algo ya existente usa para demostrar quién
es. Nunca hay que desplegar nada para dar de alta una identidad — se mete en el
entorno de lo que ya corre.
🔎 Para no perder media hora la próxima vez: un cliente recién creado en
Keycloak da 404UserNotFoundenwhoami. No es un fallo de configuración —
Lakekeeper mantiene su propio registro de principals y el alta ocurre en la
primera llamada real al catálogo ("last-updated-with": "config-call-creation").
Para forzarlo:
FASE_A_PROVISION=true LAKEHOUSE_CATALOG_CREDENTIAL=… npx tsx scripts/governance/fase-a-identities.ts
⚠️ Los service accounts tienen is-instance-admin: false. Si al activar Cedar el
operador necesita administrar a nivel de SERVIDOR (y no sólo de warehouse), eso es un flag
de Lakekeeper, aparte de estas políticas.