Un detalle de AEM as a Cloud Service que rompe permisos en silencio (y cómo lo resolvimos)
Un repoinit para crear un service user compila, despliega y aun así deja las ACLs sin aplicar. Tres detalles de AEMaaCS —la ruta system/cq:services, forced path y ensure en lugar de set— explican por qué, y cómo evitarlo.
Esta semana, revisando la configuración de un service user en un proyecto de AEM Forms junto a David Alonso Montesinos, me topé con uno de esos aprendizajes que no dan error de compilación… pero te dejan sin permisos en runtime. Los dos estuvimos investigando el problema hasta dar con la causa, y merece un artículo porque es fácil de reproducir y difícil de diagnosticar.
El repoinit que parece correcto
El clásico repoinit para crear un service user y darle permisos de lectura suele escribirse así:
create service user mi-servicio with path system/mi-proyecto
set principal ACL for mi-servicio
allow jcr:read on /content/dam/mi-proyecto
end
Parece correcto. La sintaxis es válida, el parser no protesta, el despliegue termina en verde. Y aun así, las ACLs pueden no aplicarse: el service user existe, pero no tiene los permisos que creías haberle dado. ¿Por qué?
La respuesta son tres detalles de AEMaaCS que, por separado, parecen menores, y que juntos convierten un script "correcto" en uno que no hace nada.
1. Los principal ACLs exigen que el usuario cuelgue de system/cq:services
En AEMaaCS, la autorización basada en principal solo funciona para usuarios ubicados bajo /home/users/system/cq:services. Es la ruta que el proveedor de autorización basado en principal tiene configurada como soportada; cualquier usuario que quede fuera de ese subárbol queda fuera del mecanismo.
Los principal ACLs guardan las ACEs en el propio principal, no en el nodo de contenido — esa es justamente su ventaja: puedes declarar permisos sin que el contenido de destino exista todavía. Pero esa comodidad tiene una condición: el principal tiene que estar en un sitio donde el sistema sepa buscar esas entradas.
Si creas el usuario en system/mi-proyecto, tu set principal ACL no tiene efecto. No hay error, no hay warning llamativo: la ACE simplemente no se aplica.
2. with forced path en lugar de with path
El segundo detalle es la ruta. Con with path, AEM no crea el usuario exactamente donde tú indicas: intercala un segmento intermedio generado (hashing) por debajo de esa ruta, de modo que la ubicación final no es determinista y puede variar entre entornos.
with forced path cambia ese comportamiento: crea el authorizable exactamente en la ruta que declaras, sin segmentos añadidos. El resultado es una ubicación predecible e idéntica en local, en dev, en stage y en producción.
Cuando además necesitas que el usuario quede bajo system/cq:services (por el punto anterior), forced path deja de ser un lujo y pasa a ser un requisito: es la única forma de garantizar que el principal aterriza justo donde el mecanismo de autorización lo espera.
3. set está deprecado → usa ensure
El tercer detalle es el más sutil, y es el que convierte el fallo en silencioso. set principal ACL está deprecado en repoinit, y la razón es exactamente el problema que estamos describiendo: según la documentación de Apache Sling, set principal ACL no falla aunque la ACL no pueda aplicarse por el motivo que sea. Se sustituyó por ensure principal ACL (SLING-10281).
La diferencia va más allá de "fallar o no fallar":
setes aditivo: acumula ACEs sobre las que ya hubiera. Repetir el despliegue va sumando entradas.ensurees declarativo y reconciliador: garantiza que el estado final sea exactamente el que declaras. Es idempotente de verdad entre despliegues.
Con ensure, si algo impide aplicar la ACL, te enteras. Con set, te quedas sin permisos y lo descubres en el peor momento.
La versión corregida
Juntando los tres detalles, el repoinit queda así:
create service user mi-servicio with forced path system/cq:services/mi-proyecto
ensure principal ACL for mi-servicio
allow jcr:read on /content/dam/mi-proyecto
end
Tres cambios respecto al original: la ruta cuelga de system/cq:services, es un forced path para que sea determinista, y set pasa a ensure para que el estado sea declarativo y cualquier fallo sea visible.
El aprendizaje de fondo
En infraestructura como código, lo peligroso no es lo que falla ruidosamente, sino lo que "funciona" sin hacer lo que crees. Un pipeline en verde y un despliegue sin errores te dan una falsa sensación de seguridad: das por hecho que el estado del sistema es el que declaraste, cuando en realidad una instrucción deprecada lo ha ignorado en silencio.
Por eso importa preferir las variantes reconciliadoras (ensure) frente a las que "intentan y callan" (set): no es solo una cuestión de idempotencia, es que trasladan el fallo del runtime —donde lo descubres tarde y a oscuras— al despliegue, donde lo ves de inmediato.
Conclusión
Un service user en la ruta equivocada no lanza una excepción: simplemente se queda sin los permisos, y lo descubres cuando un servicio no puede leer lo que debería. Los tres detalles —system/cq:services, forced path y ensure— son requisitos documentados por Adobe y Apache, pero fáciles de pasar por alto justo porque nada se queja.
Los detalles importan. Y en AEMaaCS, la ubicación del nodo y la instrucción que eliges pueden ser la diferencia entre un permiso que existe y uno que solo creías tener. Gracias a David por las horas de investigación conjunta hasta atar los tres cabos 🙂