Put every trigger endpoint behind an authentication subrequest

n8n's webhook, form and mcp endpoints carry no authentication of their own, and
for an automation platform those endpoints are the platform: whatever a
workflow reads or changes answers an unauthenticated request. The editor's
login protects the editor and nothing else.

N8N_WEBHOOK_AUTH_URL is required rather than optional — there is no way to
deploy this without naming an authenticator.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Stéphane Tailland
2026-08-31 07:56:03 +02:00
parent f0363748b6
commit e9d7504fcd
3 changed files with 96 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
# Webhook endpoints, authenticated.
#
# n8n serves a workflow's trigger endpoints on the same host as the editor, and
# they carry **no authentication of their own** — a workflow behind one answers
# anybody who can reach the host. That is not a detail: for an automation
# platform, those endpoints are the platform. Anything a workflow can read or
# change is reachable by an unauthenticated request unless something in front
# says otherwise.
#
# So this Ingress puts every trigger path behind an authentication subrequest.
# `${N8N_WEBHOOK_AUTH_URL}` must answer 2xx for an authorised request and
# 401/403 otherwise; an OIDC provider's userinfo endpoint has exactly that
# behaviour and needs nothing built. nginx forwards the caller's own headers to
# it, so a bearer on the original request is what gets checked.
#
# The editor keeps its own path (`/`) on the main Ingress and its own session
# login. Callers inside the cluster reach the Service directly and never pass
# through here — which is how an A2A facade in front of a workflow keeps working
# while the outside door is closed.
#
# Every trigger prefix n8n can serve is listed. One left out is the whole hole
# again, and the list is configurable per deployment
# (N8N_ENDPOINT_WEBHOOK and friends) — if you rename one, rename it here too.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: n8n-webhooks
namespace: ${NAMESPACE}
annotations:
nginx.ingress.kubernetes.io/auth-url: ${N8N_WEBHOOK_AUTH_URL}
nginx.ingress.kubernetes.io/proxy-body-size: ${INGRESS_BODY_SIZE}
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-buffering: "off"
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
spec:
ingressClassName: ${INGRESS_CLASS}
tls:
- hosts:
- ${N8N_HOSTNAME}
secretName: n8n-tls
rules:
- host: ${N8N_HOSTNAME}
http:
paths:
- path: /webhook
pathType: Prefix
backend: &n8n
service:
name: n8n
port:
number: 80
- path: /webhook-test
pathType: Prefix
backend: *n8n
- path: /webhook-waiting
pathType: Prefix
backend: *n8n
- path: /form
pathType: Prefix
backend: *n8n
- path: /form-test
pathType: Prefix
backend: *n8n
- path: /form-waiting
pathType: Prefix
backend: *n8n
- path: /mcp
pathType: Prefix
backend: *n8n
- path: /mcp-test
pathType: Prefix
backend: *n8n
+1
View File
@@ -12,3 +12,4 @@ resources:
- n8n/service.yaml
- ingress/certificate.yaml
- ingress/ingress.yaml
- ingress/ingress-webhooks.yaml