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
+22
View File
@@ -48,6 +48,7 @@ The namespace must exist first, and so must the AppRole Secret the store reads.
| `N8N_STORAGE`, `POSTGRES_STORAGE` | claim sizes, e.g. `2Gi`, `5Gi` |
| `TIMEZONE` | e.g. `Europe/Paris`; drives cron triggers |
| `INGRESS_CLASS`, `INGRESS_BODY_SIZE` | e.g. `nginx`, `"50m"` |
| `N8N_WEBHOOK_AUTH_URL` | an endpoint that answers 2xx for an authorised caller and 401/403 otherwise. **Required** — n8n's trigger endpoints carry no authentication of their own, so without this anyone who can reach the host can run any workflow. An OIDC provider's `userinfo` works as-is |
| `TLS_ISSUER_NAME`, `TLS_ISSUER_KIND` | a cert-manager issuer, e.g. `ClusterIssuer` |
| `VAULT_SERVER`, `VAULT_MOUNT`, `VAULT_SECRET_PATH` | where the two secrets live |
| `VAULT_STORE_NAME`, `VAULT_APPROLE_SECRET` | the `ClusterSecretStore` this creates, and the AppRole Secret in `external-secrets` it authenticates with |
@@ -93,3 +94,24 @@ before it holds anything that matters.
**Backups.** The Postgres claim survives a delete of the workload; it does not
survive the cluster.
## Webhooks are not open
n8n serves a workflow's trigger endpoints — `/webhook`, `/form`, `/mcp` and
their test and waiting variants — on the same host as the editor, and **they
carry no authentication of their own**. For an automation platform those
endpoints *are* the platform: whatever a workflow reads or changes is reachable
by an unauthenticated request unless something in front says otherwise. The
editor's login protects the editor, nothing else.
So they are served by a second Ingress that puts an authentication subrequest in
front of every one of them, via `${N8N_WEBHOOK_AUTH_URL}`. There is deliberately
no way to deploy this without naming one.
Callers **inside** the cluster reach the Service directly and never traverse the
Ingress, so a facade or a gateway in front of a workflow keeps working while the
outside door is shut.
If you rename an endpoint with `N8N_ENDPOINT_WEBHOOK` and friends, rename it in
`k8s/ingress/ingress-webhooks.yaml` too. A prefix left out of that list is the
hole all over again.