KB-INFRA-005: GLPI 11 subcaminho root_doc="" quebra rotas Symfony (form builder)

Salvar/criar formulario no form builder nativo falhava com 404 do Traefik
porque root_doc vazio faz o JS chamar /GenericAjaxCrud na raiz, fora do
allowlist. Documenta causa-raiz, correcao (allowlist Traefik+nginx) e fix de raiz.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Gemini 2026-07-15 21:22:50 -03:00
parent a0c30bfc65
commit 5e5a467f07
2 changed files with 131 additions and 1 deletions

View file

@ -1,6 +1,6 @@
{
"version": "1.0.0",
"last_updated": "2026-07-13",
"last_updated": "2026-07-15",
"records": [
{
"id": "KB-INFRA-001",
@ -69,6 +69,27 @@
"path": "records/infrastructure/KB-INFRA-004-forgejo-onmind-dev-origin.md",
"summary": "id: KB-INFRA-004"
},
{
"id": "KB-INFRA-005",
"title": "GLPI 11 em subcaminho com root_doc vazio quebra rotas Symfony (form builder nao salva)",
"domain": "infrastructure",
"tags": [
"glpi",
"glpi11",
"docker",
"swarm",
"traefik",
"nginx",
"reverse-proxy",
"root_doc",
"symfony-routes",
"allowlist"
],
"status": "active",
"severity": "critical",
"path": "records/infrastructure/KB-INFRA-005-glpi11-subpath-root_doc-symfony-routes-allowlist.md",
"summary": "id: KB-INFRA-005"
},
{
"id": "KB-PLUGIN-001",
"title": "Instalacao de plugin GLPI requer callbacks install/uninstall",

View file

@ -0,0 +1,109 @@
---
id: KB-INFRA-005
title: GLPI 11 em subcaminho com root_doc vazio quebra rotas Symfony (form builder nao salva)
domain: infrastructure
tags:
- glpi
- glpi11
- docker
- swarm
- traefik
- nginx
- reverse-proxy
- root_doc
- symfony-routes
- allowlist
status: active
severity: critical
created_at: 2026-07-15
updated_at: 2026-07-15
applies_to:
- GLPI 11.0.x atras de reverse proxy em subcaminho (ex.: /glpi)
- Stack devportal (Traefik + nginx glpi-proxy) em /opt/mindtek-dev
- Qualquer deploy que sirva o GLPI sob um path prefix removendo o prefixo antes do PHP
related_records:
- KB-INFRA-001
- KB-INFRA-002
---
# Contexto
O GLPI 11 e exposto em subcaminho (`https://host/glpi`), mas o proxy remove o prefixo
antes de entregar ao PHP. O construtor de formularios nativo passou a nao salvar nem
criar nenhum formulario.
# Sintoma observavel
- Salvar/criar QUALQUER formulario no form builder falha silenciosamente.
- Nada nos logs do servidor (`php-errors.log`, `sql-errors.log` vazios) — indicio de erro que nem chega no PHP.
- No console do browser: AJAX de `common_ajax_controller.js` (`#handleFormSubmit`) com
`status: 404` e `responseText: "404 page not found\n"`.
- O texto `404 page not found` (minusculo, plain text) e o 404 padrao do **Go/Traefik**,
NAO a pagina 404 (HTML) do GLPI. Ou seja: o request nao foi roteado, morreu no proxy.
# Causa raiz
Cadeia: browser -> Traefik -> nginx (glpi-proxy) -> GLPI.
1. O nginx faz `proxy_pass http://glpi/` (com barra final) na `location /glpi/`, o que
**remove** o prefixo `/glpi`. O GLPI recebe o request na raiz.
2. `Config::loadLegacyConfiguration()` computa, no contexto web,
`root_doc = Request::createFromGlobals()->getBasePath()` (`src/Config.php` ~1398).
Sem o prefixo no request, `getBasePath()` retorna `""` -> **`root_doc = ""`**.
3. Com `root_doc=""`, o JS do GLPI monta as URLs de AJAX na RAIZ do dominio:
`${CFG_GLPI.root_doc}/GenericAjaxCrud` = `/GenericAjaxCrud` (sem `/glpi`).
4. O GLPI NAO honra `X-Forwarded-Prefix` (nao ha `Request::setTrustedProxies()` no codigo),
entao mesmo o nginx enviando o header, `root_doc` continua vazio.
5. O acesso ao GLPI e liberado por **allowlist de paths de topo** em DOIS lugares que
precisam casar (ver KB-INFRA-002): a regra Traefik `dev-glpi-root.rule` (labels do
servico `glpi-proxy`) e a regex `location ~ ^/(...)` em `nginx.conf`. Rotas legadas
(`/front`, `/ajax`, ...) estavam no allowlist e funcionavam; as rotas **novas de topo
do GLPI 11 (Symfony)** — em especial `GenericAjaxCrud`, usada pelo save do form builder
— NAO estavam -> 404 do Traefik.
Por isso o form builder (controlador Symfony generico) quebrava, enquanto o resto do GLPI
(AJAX legado sob `/ajax`, `/front`) funcionava.
# Correcao aplicada
Adicionar as rotas de topo do GLPI 11 ao allowlist nos DOIS lugares.
Enumerar as rotas de topo:
```bash
grep -rhoE "#\[Route\(\s*[\"']/[A-Za-z0-9_]+" src/Glpi/Controller/ \
| sed -E "s/.*['\"]\/([A-Za-z0-9_]+).*/\1/" | sort -u
# -> Central Form GenericAjaxCrud Helpdesk Install Inventory front progress
```
1. `nginx.conf` — regex do `location`:
```
location ~ ^/(index\.php|front|ajax|...|build|Central|Form|GenericAjaxCrud|Helpdesk|Inventory|progress)(/|$) {
```
2. `stack.yml` — label `traefik.http.routers.dev-glpi-root.rule`, acrescentar:
```
|| PathPrefix(`/Central`) || PathPrefix(`/Form`) || PathPrefix(`/GenericAjaxCrud`)
|| PathPrefix(`/Helpdesk`) || PathPrefix(`/Inventory`) || PathPrefix(`/progress`)
```
3. Aplicar: `sudo docker stack deploy -c stack.yml devportal` (atualiza labels Traefik) +
`docker exec <glpi-proxy> nginx -s reload` (recarrega o bind-mount do nginx.conf).
`Install` deixado de fora por seguranca (nao expor o instalador na raiz).
# Validacao padrao
```bash
# Antes: 404 "404 page not found" (Traefik). Depois: 403/302 com HTML do GLPI (chegou no GLPI).
curl -sk -o /dev/null -w "%{http_code}\n" -X POST https://host/GenericAjaxCrud # -> 403
curl -sk -o /dev/null -w "%{http_code}\n" https://host/Form/Render/1 # -> 302
curl -sk https://host/rota-inexistente | head -c 40 # -> "404 page not found" (controle: Traefik OK)
```
No browser: recarregar (Ctrl+Shift+R) e salvar um formulario -> deve funcionar.
# Regra para agentes
- 404 com corpo `404 page not found` (plain text) = proxy/Traefik nao roteou; 404 HTML = GLPI roteou mas nao achou a rota. Distinga SEMPRE pelo corpo antes de culpar a app ou um plugin.
- GLPI 11 em subcaminho depende de allowlist manual das rotas de topo (Symfony) em Traefik + nginx. A cada upgrade do GLPI, reenumerar as rotas de `src/Glpi/Controller` e ressincronizar as duas allowlists.
- Isto NAO e problema de plugin. Ao ver "form nao salva" + logs de servidor limpos, checar o console do browser e o roteamento do proxy primeiro.
# Fix de raiz (recomendado a longo prazo)
Eliminar o subcaminho: servir o GLPI num subdominio proprio na raiz (ex.: `glpi.host`),
onde `root_doc=""` passa a ser correto e nao ha allowlist a manter. Alternativa: wire de
trusted proxies para honrar `X-Forwarded-Prefix` e ter `root_doc=/glpi` consistente.
# Classificacao
- Tipo: Armadilha de deploy do GLPI 11 atras de reverse proxy em subcaminho.
- Reutilizacao: obrigatoria em qualquer ambiente que sirva GLPI 11 sob path prefix.