n8n: cerrar el webhook del auto-reinicio con un secreto compartido
/webhook/uptime-kuma-restart es publico y sin autenticar. Mientras no reiniciaba nada daba igual; desde hoy reinicia doce servicios, los dos blogs incluidos, asi que cualquiera que diera con la ruta podia zarandear el cluster. Uptime Kuma manda ahora la cabecera X-Kuma-Token (webhookAdditionalHeaders de la notificacion "n8n Auto-Restart") y el primer nodo Code la compara con KUMA_WEBHOOK_TOKEN antes de hacer nada. La comprobacion falla en ABIERTO si el secreto no esta configurado en n8n: un despiste de configuracion degrada al comportamiento de antes en vez de dejar de avisar en silencio, que es el fallo que no se ve venir. Si esta configurado y no coincide, se devuelve vacio: ni reinicio, ni Telegram, ni fila de error que se pueda llenar a base de peticiones. El secret n8n-kuma-webhook se crea con kubectl y NO esta en git; el env va con optional: true para que la ausencia no impida arrancar el pod. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const body = $input.first().json.body || $input.first().json;\nconst monitor = body.monitor || {};\nconst heartbeat = body.heartbeat || {};\n\n// Solo lo que existe de verdad en el cluster (revisado 2026-07-30): fuera\n// OpenClaw, los tres polymarket-* y open-webui, decomisados en julio.\n// 'kind' es el plural que va en la ruta del API: Gitea es un StatefulSet,\n// no un Deployment, y con 'deployments' fijo daba 404 aunque hubiera permisos.\nconst SERVICE_MAP = {\n 'n8n': { kind: 'deployments', name: 'n8n', namespace: 'n8n' },\n 'Vaultwarden': { kind: 'deployments', name: 'vaultwarden', namespace: 'vaultwarden' },\n 'Grafana': { kind: 'deployments', name: 'kube-prometheus-stack-grafana', namespace: 'monitoring' },\n 'Uptime Kuma': { kind: 'deployments', name: 'uptime-kuma', namespace: 'monitoring' },\n 'Authentik': { kind: 'deployments', name: 'authentik-server', namespace: 'authentik' },\n 'Homarr': { kind: 'deployments', name: 'homarr', namespace: 'homarr' },\n 'ArgoCD': { kind: 'deployments', name: 'argocd-server', namespace: 'argocd' },\n 'ArgoCD repo-server': { kind: 'deployments', name: 'argocd-repo-server', namespace: 'argocd' },\n 'Ollama': { kind: 'deployments', name: 'ollama', namespace: 'ollama' },\n 'Gitea': { kind: 'statefulsets', name: 'gitea', namespace: 'gitea' },\n // Los blogs (2026-07-30). La clave es el NOMBRE EXACTO del monitor en Kuma,\n // que se busca sin distinguir mayusculas -- si se renombra alli, aqui deja de\n // encontrarse y el workflow se va por la rama de \"sin mapeo\".\n // El monitor \"www.zonadeexclusion.com (301->apex)\" queda FUERA a proposito:\n // que ese 301 falle es cosa de Traefik o del DNS, no de Ghost, y reiniciar el\n // blog no arreglaria nada.\n 'theexclusionzone.com (Ghost EN)': { kind: 'deployments', name: 'ghost-en', namespace: 'ghost-en' },\n 'zonadeexclusion.com (Ghost ES)': { kind: 'deployments', name: 'zona-exclusion', namespace: 'zona-exclusion' },\n};\n\nconst serviceName = monitor.name || 'desconocido';\nconst status = heartbeat.status;\n\nif (status !== 0) {\n return [];\n}\n\n// Busqueda case-insensitive\nconst k8s = SERVICE_MAP[serviceName] ||\n Object.entries(SERVICE_MAP).find(([k]) => k.toLowerCase() === serviceName.toLowerCase())?.[1];\n\nif (!k8s) {\n return [{ json: {\n serviceName,\n canRestart: false,\n error: 'Sin mapeo K8s para: ' + serviceName\n }}];\n}\n\n// Se sigue llamando 'deployment' aunque a veces sea un StatefulSet: el nodo IF\n// \"Tiene mapeo K8s\" comprueba ese campo, y renombrarlo lo romperia en silencio.\nreturn [{ json: {\n serviceName,\n canRestart: true,\n deployment: k8s.name,\n kind: k8s.kind,\n namespace: k8s.namespace\n}}];\n"
|
||||
"jsCode": "// Puerta de entrada (2026-07-30). Este webhook es PUBLICO y sin autenticar, y\n// desde hoy reinicia servicios de verdad, los dos blogs incluidos. Uptime Kuma\n// manda el secreto compartido en la cabecera X-Kuma-Token\n// (notificacion \"n8n Auto-Restart\" -> webhookAdditionalHeaders).\n//\n// Si el secreto NO esta configurado en n8n, se sigue adelante sin comprobar\n// nada: un despiste de configuracion debe degradar al comportamiento de antes,\n// nunca dejarte sin avisos en silencio. Si esta configurado y no coincide, se\n// devuelve vacio: ni reinicio, ni Telegram, ni fila de error que llenar a base\n// de peticiones.\nlet secreto = null;\ntry { secreto = $env.KUMA_WEBHOOK_TOKEN; } catch (e) { /* $env capado */ }\nif (!secreto && typeof process !== 'undefined') secreto = process.env.KUMA_WEBHOOK_TOKEN;\n\nconst cabeceras = $input.first().json.headers || {};\nif (secreto && cabeceras['x-kuma-token'] !== secreto) {\n return [];\n}\n\nconst body = $input.first().json.body || $input.first().json;\nconst monitor = body.monitor || {};\nconst heartbeat = body.heartbeat || {};\n\n// Solo lo que existe de verdad en el cluster (revisado 2026-07-30): fuera\n// OpenClaw, los tres polymarket-* y open-webui, decomisados en julio.\n// 'kind' es el plural que va en la ruta del API: Gitea es un StatefulSet,\n// no un Deployment, y con 'deployments' fijo daba 404 aunque hubiera permisos.\nconst SERVICE_MAP = {\n 'n8n': { kind: 'deployments', name: 'n8n', namespace: 'n8n' },\n 'Vaultwarden': { kind: 'deployments', name: 'vaultwarden', namespace: 'vaultwarden' },\n 'Grafana': { kind: 'deployments', name: 'kube-prometheus-stack-grafana', namespace: 'monitoring' },\n 'Uptime Kuma': { kind: 'deployments', name: 'uptime-kuma', namespace: 'monitoring' },\n 'Authentik': { kind: 'deployments', name: 'authentik-server', namespace: 'authentik' },\n 'Homarr': { kind: 'deployments', name: 'homarr', namespace: 'homarr' },\n 'ArgoCD': { kind: 'deployments', name: 'argocd-server', namespace: 'argocd' },\n 'ArgoCD repo-server': { kind: 'deployments', name: 'argocd-repo-server', namespace: 'argocd' },\n 'Ollama': { kind: 'deployments', name: 'ollama', namespace: 'ollama' },\n 'Gitea': { kind: 'statefulsets', name: 'gitea', namespace: 'gitea' },\n // Los blogs (2026-07-30). La clave es el NOMBRE EXACTO del monitor en Kuma,\n // que se busca sin distinguir mayusculas -- si se renombra alli, aqui deja de\n // encontrarse y el workflow se va por la rama de \"sin mapeo\".\n // El monitor \"www.zonadeexclusion.com (301->apex)\" queda FUERA a proposito:\n // que ese 301 falle es cosa de Traefik o del DNS, no de Ghost, y reiniciar el\n // blog no arreglaria nada.\n 'theexclusionzone.com (Ghost EN)': { kind: 'deployments', name: 'ghost-en', namespace: 'ghost-en' },\n 'zonadeexclusion.com (Ghost ES)': { kind: 'deployments', name: 'zona-exclusion', namespace: 'zona-exclusion' },\n};\n\nconst serviceName = monitor.name || 'desconocido';\nconst status = heartbeat.status;\n\nif (status !== 0) {\n return [];\n}\n\n// Busqueda case-insensitive\nconst k8s = SERVICE_MAP[serviceName] ||\n Object.entries(SERVICE_MAP).find(([k]) => k.toLowerCase() === serviceName.toLowerCase())?.[1];\n\nif (!k8s) {\n return [{ json: {\n serviceName,\n canRestart: false,\n error: 'Sin mapeo K8s para: ' + serviceName\n }}];\n}\n\n// Se sigue llamando 'deployment' aunque a veces sea un StatefulSet: el nodo IF\n// \"Tiene mapeo K8s\" comprueba ese campo, y renombrarlo lo romperia en silencio.\nreturn [{ json: {\n serviceName,\n canRestart: true,\n deployment: k8s.name,\n kind: k8s.kind,\n namespace: k8s.namespace\n}}];\n"
|
||||
},
|
||||
"id": "d1a2b3c4-2222-2222-2222-000000000002",
|
||||
"name": "Parse & Map Service",
|
||||
|
||||
Reference in New Issue
Block a user