diff --git a/deploy/agent-secrets.yaml b/deploy/agent-secrets.yaml new file mode 100644 index 0000000..7d1b846 --- /dev/null +++ b/deploy/agent-secrets.yaml @@ -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 diff --git a/deploy/bootstrap-kustomization.yaml b/deploy/bootstrap-kustomization.yaml new file mode 100644 index 0000000..e1c57e4 --- /dev/null +++ b/deploy/bootstrap-kustomization.yaml @@ -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 diff --git a/deploy/ca-configmap.yaml b/deploy/ca-configmap.yaml new file mode 100644 index 0000000..84ffd22 --- /dev/null +++ b/deploy/ca-configmap.yaml @@ -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----- diff --git a/deploy/flux-kustomization.yaml b/deploy/flux-kustomization.yaml new file mode 100644 index 0000000..38a47d1 --- /dev/null +++ b/deploy/flux-kustomization.yaml @@ -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._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" diff --git a/deploy/gitrepository-self.yaml b/deploy/gitrepository-self.yaml new file mode 100644 index 0000000..1fedfda --- /dev/null +++ b/deploy/gitrepository-self.yaml @@ -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 diff --git a/deploy/gitrepository.yaml b/deploy/gitrepository.yaml new file mode 100644 index 0000000..53136a0 --- /dev/null +++ b/deploy/gitrepository.yaml @@ -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 diff --git a/deploy/namespace.yaml b/deploy/namespace.yaml new file mode 100644 index 0000000..8389695 --- /dev/null +++ b/deploy/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: n8n