feat: this estate's n8n deployment

The values that infra-n8n deliberately does not carry: our hostname, issuer,
Vault path and images, plus the two things that are ours alone — the lab CA the
workflows need to verify internal TLS, and the agents' Keycloak client secrets,
read from the realm's own Vault entry so there is one source per secret.

Self-managing after three applies, like the rest of the estate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Stéphane Tailland
2026-08-29 13:10:10 +02:00
parent d9c376facf
commit f9511fd15d
7 changed files with 154 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
# The agents' Keycloak client secrets, for the workflows that act as those
# agents. Read from the realm's own Vault entry rather than copied into n8n's
# path: there is one source for a client secret, and rotating it in Vault
# rotates it everywhere instead of leaving a stale twin behind.
#
# A narrow grant — this names the properties it wants, so the namespace receives
# those and nothing else of the realm.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: n8n-agent-secrets
namespace: n8n
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: vault-keycloak
target:
name: n8n-agent-secrets
data:
- secretKey: APPROVISIONNEMENT_CLIENT_SECRET
remoteRef:
key: keycloak/classic-models-realm
property: KC_SECRET_CLASSIC_MODELS_AGENT_APPROVISIONNEMENT
+20
View File
@@ -0,0 +1,20 @@
# Watches ./deploy, so everything here — including itself — reconciles from git.
#
# Three manual applies, once. Nothing can watch ./deploy until the namespace and
# the source that serves it exist:
#
# kubectl apply -f deploy/namespace.yaml
# kubectl apply -f deploy/gitrepository-self.yaml
# kubectl apply -f deploy/bootstrap-kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: n8n-deploy-bootstrap
namespace: n8n
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: agent-flows
path: ./deploy
prune: true
+36
View File
@@ -0,0 +1,36 @@
# The lab CA, mounted into n8n as NODE_EXTRA_CA_CERTS so a workflow can call
# keycloak.sttlab.local and the domain gateways with TLS verified rather than
# disabled. A CA certificate is public by construction — this is the
# certificate, never the key.
#
# Issued by the sttlab-local-ca ClusterIssuer, valid until 2026-11-24.
# Regenerate on rotation:
# kubectl -n cert-manager get secret sttlab-local-ca \
# -o jsonpath='{.data.tls\.crt}' | base64 -d
apiVersion: v1
kind: ConfigMap
metadata:
name: sttlab-ca
namespace: n8n
data:
ca.crt: |
-----BEGIN CERTIFICATE-----
MIIDPDCCAiSgAwIBAgIUd/utDf+f0yQR3v3qTjZqx+wvTZMwDQYJKoZIhvcNAQEL
BQAwGTEXMBUGA1UEAwwOKi5zdHRsYWIubG9jYWwwHhcNMjUxMTI0MTUzODQ1WhcN
MjYxMTI0MTUzODQ1WjAZMRcwFQYDVQQDDA4qLnN0dGxhYi5sb2NhbDCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALj0sryq8qXtkShT/B+/EjVJNN/TLHIn
QJYc6xuUH/f7q3dYdLm4iH0XnFgrSQLUo4iqd/Mczef5iWCYwT5FFa7P17/TcLVf
gso0j41qNLBjGxDQGQ6Z1VCxi6oan8ow98rumjVj0E0Qgs5CX/rN+gcEmZ/TZFWU
qudVoztpeiVqJ0dxCIq+KMutzd1BU7BXx4VRO9Y+t1idUc7oY7BPmS2qPAN7TtXP
j1KhH2BgLsmF5GRhYHNS578eCltcmS2sbQ3c7GwOV96lpgF9TixEvFXeN4xk43E6
1COUD7CRiWAZ0XI8FMlpZGeNZeV4bbV/MqVig1ybWNOz9MDKYj3eoXUCAwEAAaN8
MHowHQYDVR0OBBYEFDbzYWOPr4vxa+VONs7xHbcNx70YMB8GA1UdIwQYMBaAFDbz
YWOPr4vxa+VONs7xHbcNx70YMA8GA1UdEwEB/wQFMAMBAf8wJwYDVR0RBCAwHoIO
Ki5zdHRsYWIubG9jYWyCDHN0dGxhYi5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEA
tU4p5/ONXm7x4YDzSeL8NdThVLguiFur0jcTxvvPUS4NFNsnQJk4OXxBgjy1YwpL
3i56/4mCEd9OCQF/Hl0eFwGZBzNKlMoTA08AC/lBDvPrnVjuUu0qe/b/2KOkhESa
x8rQNp0o8pcj0f29nu0JfRqYGkzy0Lh2qgXhPu2iC+t2PfdleRAky1tt68wogdFL
TvHDsJbVo/ihsPpONlSyHfxG6bb/0Mkz64PH46TTt9GvJflEKMqDwivNFUA1P5S/
U4Ic8Qapnxc7h2+WbRjmYrnuygC2wMBE6rL9OF3gwCr1fkgGGRr5lVRUq2X2HZV5
5jqViv6eurq8O61N3vWGCQ==
-----END CERTIFICATE-----
+45
View File
@@ -0,0 +1,45 @@
# This estate's n8n: the template from infra-n8n, with our values.
#
# Everything specific lives here rather than in that repo, so the same template
# deploys somebody else's n8n without carrying ours.
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: n8n
namespace: n8n
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: infra-n8n
path: ./k8s
prune: true
wait: true
timeout: 5m
postBuild:
substitute:
NAMESPACE: n8n
N8N_HOSTNAME: n8n.sttlab.local
N8N_IMAGE: docker.n8n.io/n8nio/n8n:2.37.4
POSTGRES_IMAGE: postgres:16.10-alpine
N8N_STORAGE: 2Gi
POSTGRES_STORAGE: 5Gi
TIMEZONE: Europe/Paris
INGRESS_CLASS: nginx
INGRESS_BODY_SIZE: "50m"
TLS_ISSUER_NAME: sttlab-local-ca
TLS_ISSUER_KIND: ClusterIssuer
VAULT_SERVER: https://vault.sttlab.eu
VAULT_MOUNT: secrets
VAULT_SECRET_PATH: n8n/instance
VAULT_STORE_NAME: vault-n8n
VAULT_APPROLE_SECRET: vault-approle-n8n
# The agents' Keycloak client secrets, so a workflow can exchange tokens
# as the agent it represents. Created by agent-secrets.yaml beside this.
EXTRA_ENV_SECRET: n8n-agent-secrets
EXTRA_CA_CONFIGMAP: sttlab-ca
# "false" so a workflow can read $env.<AGENT>_CLIENT_SECRET. It means any
# workflow on this instance can read any variable in the pod — acceptable
# while it runs only this estate's agents, and not a moment longer.
BLOCK_ENV_ACCESS: "false"
EXECUTIONS_MAX_AGE_HOURS: "336"
+12
View File
@@ -0,0 +1,12 @@
# This repo, so ./deploy reconciles from git rather than from whoever last ran
# kubectl.
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: agent-flows
namespace: n8n
spec:
interval: 5m
url: https://gitea.sttlab.eu/stt/classic-models-agent-flows.git
ref:
branch: main
+13
View File
@@ -0,0 +1,13 @@
# The generic n8n deployment template. It carries no value specific to this
# estate — every hostname, issuer and Vault path comes from the substitutions
# in flux-kustomization.yaml beside this file.
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: infra-n8n
namespace: n8n
spec:
interval: 5m
url: https://gitea.sttlab.eu/stt/infra-n8n.git
ref:
branch: main
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: n8n