Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d13a8d764 | ||
|
|
1dd905dc5f | ||
|
|
9de9a9663b | ||
|
|
2bdc486ae6 | ||
|
|
901aa9a9dc | ||
|
|
e8ab19962b | ||
|
|
5c7dce96e1 |
@@ -2,12 +2,15 @@
|
|||||||
# template source: https://github.com/bretfisher/docker-build-workflow/blob/main/templates/call-docker-build.yaml
|
# template source: https://github.com/bretfisher/docker-build-workflow/blob/main/templates/call-docker-build.yaml
|
||||||
name: Crowdsec
|
name: Crowdsec
|
||||||
|
|
||||||
on: #[push,pull_request]
|
# À LA DEMANDE, et seulement à la demande — comme minio.yaml : auth Vault par
|
||||||
|
# flux OIDC (un humain doit ouvrir un lien) et apply `auto_approve` contre la prod.
|
||||||
|
#
|
||||||
|
# Note : les triggers `push`/`pull_request` retirés ici étaient de toute façon
|
||||||
|
# INERTES — ils passaient par une ancre YAML (`&`/`*`), que le parseur
|
||||||
|
# d'événements de Gitea ne résout pas (vécu sur arcodange/kadans, issues 113
|
||||||
|
# → 117). Ce workflow ne partait déjà qu'à la main ; c'est maintenant écrit.
|
||||||
|
on:
|
||||||
workflow_dispatch: {}
|
workflow_dispatch: {}
|
||||||
push: &crowdsecPaths
|
|
||||||
paths:
|
|
||||||
- 'crowdsec/**/*.tf'
|
|
||||||
pull_request: *crowdsecPaths
|
|
||||||
|
|
||||||
# cancel any previously-started, yet still active runs of this workflow on the same branch
|
# cancel any previously-started, yet still active runs of this workflow on the same branch
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|||||||
@@ -2,14 +2,32 @@
|
|||||||
# template source: https://github.com/bretfisher/docker-build-workflow/blob/main/templates/call-docker-build.yaml
|
# template source: https://github.com/bretfisher/docker-build-workflow/blob/main/templates/call-docker-build.yaml
|
||||||
name: Helm Charts
|
name: Helm Charts
|
||||||
|
|
||||||
on: [push,pull_request,workflow_dispatch]
|
# Celui-ci travaille SEUL (pas d'auth Vault, pas d'apply) : on le garde
|
||||||
# push: &helmPaths # turns out gitea don't handle well the paths filter
|
# automatique. Mais `push` sur TOUTES les branches + `pull_request` faisait
|
||||||
# paths:
|
# partir DEUX runs pour le même commit dès qu'une branche avait une PR.
|
||||||
# - '*/\.yaml'
|
#
|
||||||
# - '*/\.tpl'
|
# Même forme que la CI de kadans : la branche est couverte par `pull_request`,
|
||||||
# - '*/NOTES.txt'
|
# `main` par le `push` d'après-merge. Un run par événement, aucun angle mort.
|
||||||
# - '*/\.helmignore'
|
#
|
||||||
# pull_request: *helmPaths
|
# (Le filtre de chemins d'origine, resté en commentaire des années sous un
|
||||||
|
# « gitea don't handle well the paths filter », n'était probablement pas en
|
||||||
|
# cause : il passait par une ancre YAML, et le parseur d'événements de Gitea ne
|
||||||
|
# les résout pas — issues 113 → 117 de kadans. Le job `filter-chart` fait déjà
|
||||||
|
# ce tri au niveau job, donc on n'y retouche pas.)
|
||||||
|
#
|
||||||
|
# ⚠ Chaque clé porte un CORPS explicite : un `pull_request:` nu (valeur nulle)
|
||||||
|
# n'est pas une forme éprouvée sur ce Gitea, et son mode d'échec est le
|
||||||
|
# silencieux — aucun run, aucune erreur. On copie la forme qui tourne (kadans
|
||||||
|
# ci.yml), listes dupliquées à la main, sans ancre.
|
||||||
|
on:
|
||||||
|
workflow_dispatch: {}
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md'
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md'
|
||||||
|
|
||||||
# cancel any previously-started, yet still active runs of this workflow on the same branch
|
# cancel any previously-started, yet still active runs of this workflow on the same branch
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|||||||
+11
-12
@@ -1,20 +1,19 @@
|
|||||||
---
|
---
|
||||||
name: MinIO
|
name: MinIO
|
||||||
|
|
||||||
# ⚠ Triggers écrits EN TOUTES LETTRES, sans ancre YAML (`&`/`*`).
|
# À LA DEMANDE, et seulement à la demande. Deux raisons, chacune suffisante :
|
||||||
# Une ancre dans un trigger Gitea Actions fait taire push ET pull_request —
|
#
|
||||||
# en silence, aucun run, aucune erreur (vécu sur arcodange/kadans, issues 113
|
# 1. Ce workflow ne PEUT PAS aboutir sans un humain : l'auth Vault passe par
|
||||||
# → 117). Les autres workflows de ce repo utilisent encore des ancres : à
|
# un flux OIDC dont le lien doit être ouvert dans un navigateur connecté.
|
||||||
# vérifier séparément, c'est probablement pour ça qu'ils ne partent qu'à la
|
# Déclenché tout seul, il occupe un runner jusqu'à son timeout — et retarde
|
||||||
# main (workflow_dispatch).
|
# les runs que quelqu'un attend vraiment.
|
||||||
|
# 2. Il fait `terraform apply` en `auto_approve` CONTRE LA PROD. Se déclencher
|
||||||
|
# sur le push d'une branche, c'est appliquer du code que personne n'a relu.
|
||||||
|
#
|
||||||
|
# Au passage : `push` (toutes branches) + `pull_request` faisait partir DEUX runs
|
||||||
|
# par commit d'une branche en PR — le même SHA, deux fois.
|
||||||
on:
|
on:
|
||||||
workflow_dispatch: {}
|
workflow_dispatch: {}
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- 'minio/**/*.tf'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- 'minio/**/*.tf'
|
|
||||||
|
|
||||||
# cancel any previously-started, yet still active runs of this workflow on the same branch
|
# cancel any previously-started, yet still active runs of this workflow on the same branch
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
# template source: https://github.com/bretfisher/docker-build-workflow/blob/main/templates/call-docker-build.yaml
|
# template source: https://github.com/bretfisher/docker-build-workflow/blob/main/templates/call-docker-build.yaml
|
||||||
name: Plausible
|
name: Plausible
|
||||||
|
|
||||||
on: #[push,pull_request]
|
# À LA DEMANDE, et seulement à la demande — comme minio.yaml : auth Vault par
|
||||||
|
# flux OIDC (un humain doit ouvrir un lien) et apply `auto_approve` contre la prod.
|
||||||
|
#
|
||||||
|
# Note : les triggers `push`/`pull_request` retirés ici étaient de toute façon
|
||||||
|
# INERTES (ancre YAML non résolue par Gitea, issues 113 → 117 de kadans). Ce
|
||||||
|
# workflow ne partait déjà qu'à la main ; c'est maintenant écrit.
|
||||||
|
on:
|
||||||
workflow_dispatch: {}
|
workflow_dispatch: {}
|
||||||
push: &plausiblePaths
|
|
||||||
paths:
|
|
||||||
- 'plausible/**/*.tf'
|
|
||||||
pull_request: *plausiblePaths
|
|
||||||
|
|
||||||
# cancel any previously-started, yet still active runs of this workflow on the same branch
|
# cancel any previously-started, yet still active runs of this workflow on the same branch
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|||||||
+10
-15
@@ -2,23 +2,18 @@
|
|||||||
# template source: https://github.com/bretfisher/docker-build-workflow/blob/main/templates/call-docker-build.yaml
|
# template source: https://github.com/bretfisher/docker-build-workflow/blob/main/templates/call-docker-build.yaml
|
||||||
name: Hashicorp Vault
|
name: Hashicorp Vault
|
||||||
|
|
||||||
# ⚠ Triggers écrits EN TOUTES LETTRES, sans ancre YAML (`&`/`*`) : une ancre
|
# À LA DEMANDE, et seulement à la demande — comme minio.yaml, et pour les mêmes
|
||||||
# dans un trigger Gitea Actions fait taire push ET pull_request, en silence
|
# deux raisons : l'auth Vault exige qu'un humain ouvre un lien OIDC (sans lui, le
|
||||||
# (vécu sur arcodange/kadans, issues 113 → 117) — c'est probablement pourquoi
|
# run squatte un runner jusqu'au timeout), et l'apply se fait en `auto_approve`
|
||||||
# ce workflow ne partait qu'à la main.
|
# contre la prod.
|
||||||
# Et `*.tfvars` compte AUTANT que `*.tf` : la liste des applications (donc les
|
#
|
||||||
# rôles gitea_cicd_<app>) vit dans terraform.tfvars — l'oublier, c'est ajouter
|
# ⚠ Ce qui change AUSSI de nature : `hashicorp-vault/**/*.tfvars` compte autant
|
||||||
# une app sans jamais créer son rôle.
|
# que `*.tf` — la liste des applications (donc les rôles gitea_cicd_<app>) vit
|
||||||
|
# dans terraform.tfvars. Ce n'est plus un filtre de chemins mais ça reste vrai
|
||||||
|
# du POURQUOI on relance : ajouter une app au tfvars sans relancer ce workflow,
|
||||||
|
# c'est une app sans rôle CI.
|
||||||
on:
|
on:
|
||||||
workflow_dispatch: {}
|
workflow_dispatch: {}
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- 'hashicorp-vault/**/*.tf'
|
|
||||||
- 'hashicorp-vault/**/*.tfvars'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- 'hashicorp-vault/**/*.tf'
|
|
||||||
- 'hashicorp-vault/**/*.tfvars'
|
|
||||||
|
|
||||||
# cancel any previously-started, yet still active runs of this workflow on the same branch
|
# cancel any previously-started, yet still active runs of this workflow on the same branch
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|||||||
@@ -30,9 +30,36 @@ resource "vault_database_secret_backend_role" "role" {
|
|||||||
backend = local.vault_mount_postgres.path
|
backend = local.vault_mount_postgres.path
|
||||||
name = local.instance
|
name = local.instance
|
||||||
db_name = "postgres"
|
db_name = "postgres"
|
||||||
|
# ── Le rôle ÉPHÉMÈRE endosse le rôle STABLE, dès le login ───────────────────
|
||||||
|
#
|
||||||
|
# En PostgreSQL, un objet appartient au rôle qui l'a CRÉÉ. Comme chaque
|
||||||
|
# démarrage de pod obtient un rôle `v-kubernet-…` neuf, toute migration crée
|
||||||
|
# des objets que le pod SUIVANT ne peut plus lire. C'est ce qui a mis l'API
|
||||||
|
# kadans à terre une demi-journée le 2026-07-28 (« permission denied for table
|
||||||
|
# qualification_video »), et c'est ce que le CronJob `pg-fix-table-ownership`
|
||||||
|
# rattrape tous les jours à 03:00 — a posteriori, et pour les seules TABLES.
|
||||||
|
#
|
||||||
|
# `ALTER ROLE … SET ROLE` fait de l'endossement un DÉFAUT DE CONNEXION : plus
|
||||||
|
# rien à poser côté application, et ça vaut aussi pour les clients qui ne sont
|
||||||
|
# pas l'application (le `psql` d'un job, une console d'exploitation).
|
||||||
|
#
|
||||||
|
# AUCUN privilège nouveau : le `GRANT` de la ligne précédente rend déjà le
|
||||||
|
# rôle éphémère MEMBRE du rôle stable. Endosser une casquette qu'on porte
|
||||||
|
# déjà, ce n'est pas une élévation — et c'est pourquoi cette instruction ne
|
||||||
|
# peut pas échouer là où le `GRANT` réussit.
|
||||||
|
#
|
||||||
|
# MESURÉ (PostgreSQL 16, compte CREATEROLE non-superutilisateur, comme celui
|
||||||
|
# de Vault) : l'instruction passe, le login donne `session_user=v-test-1` /
|
||||||
|
# `current_role=proprio_v`, et un `ALTER ROLE … RESET role` la retire.
|
||||||
|
# Vérifié aussi qu'elle SURVIT à `RESET ALL` / `DISCARD ALL` — donc elle
|
||||||
|
# compose avec le `server_reset_query` de pgbouncer au lieu de s'y opposer.
|
||||||
|
#
|
||||||
|
# ⚠ Ne vaut que pour les identifiants créés APRÈS l'apply : les baux en cours
|
||||||
|
# gardent leur ancien comportement jusqu'à leur renouvellement.
|
||||||
creation_statements = [
|
creation_statements = [
|
||||||
"CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';",
|
"CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';",
|
||||||
"GRANT ${local.owner_role} TO \"{{name}}\";",
|
"GRANT ${local.owner_role} TO \"{{name}}\";",
|
||||||
|
"ALTER ROLE \"{{name}}\" SET ROLE ${local.owner_role};",
|
||||||
]
|
]
|
||||||
revocation_statements = [
|
revocation_statements = [
|
||||||
"REASSIGN OWNED BY \"{{name}}\" TO ${local.owner_role};", # reassign must be executed in the database where the reassgined objects are - TODO (one connection per database/app)
|
"REASSIGN OWNED BY \"{{name}}\" TO ${local.owner_role};", # reassign must be executed in the database where the reassgined objects are - TODO (one connection per database/app)
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
# qu'elles déclarent leurs buckets sans qu'on leur confie le root.
|
# qu'elles déclarent leurs buckets sans qu'on leur confie le root.
|
||||||
# Décisions : factory/doc/adr/20260726-stockage-objet-minio.md
|
# Décisions : factory/doc/adr/20260726-stockage-objet-minio.md
|
||||||
#
|
#
|
||||||
# ⚠ Noms d'actions issus de la documentation MinIO, NON éprouvés contre le
|
# Noms d'actions confirmés par le premier apply réel (kadans, 2026-07-26) : le
|
||||||
# serveur : le premier apply les confirmera ou les corrigera.
|
# bloc admin passe tel quel ; côté s3 il manquait `s3:ListBucket`, que le
|
||||||
|
# provider appelle AVANT de créer un bucket pour savoir s'il existe déjà.
|
||||||
resource "minio_iam_policy" "provisioner" {
|
resource "minio_iam_policy" "provisioner" {
|
||||||
name = "provisioner"
|
name = "provisioner"
|
||||||
policy = jsonencode({
|
policy = jsonencode({
|
||||||
@@ -26,9 +27,15 @@ resource "minio_iam_policy" "provisioner" {
|
|||||||
Resource = ["arn:aws:s3:::*"]
|
Resource = ["arn:aws:s3:::*"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# s3:GetObject / s3:PutObject volontairement ABSENTS.
|
# s3:GetObject / s3:PutObject volontairement ABSENTS : le provisionneur
|
||||||
|
# ne LIT ni n'ÉCRIT aucun objet, c'est ce qui rend son partage entre
|
||||||
|
# rôles CI acceptable.
|
||||||
|
#
|
||||||
|
# `s3:ListBucket` est la seule concession : MinIO le demande pour un
|
||||||
|
# HeadBucket, et le provider teste l'existence du bucket avant de le
|
||||||
|
# créer. Il donne la vue des CLÉS d'un bucket, jamais leur contenu.
|
||||||
Effect = "Allow"
|
Effect = "Allow"
|
||||||
Action = ["s3:CreateBucket", "s3:DeleteBucket", "s3:ListAllMyBuckets", "s3:GetBucketLocation", "s3:GetBucketPolicy", "s3:PutBucketPolicy"]
|
Action = ["s3:CreateBucket", "s3:DeleteBucket", "s3:ListBucket", "s3:ListAllMyBuckets", "s3:GetBucketLocation", "s3:GetBucketPolicy", "s3:PutBucketPolicy"]
|
||||||
Resource = ["arn:aws:s3:::*"]
|
Resource = ["arn:aws:s3:::*"]
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
+57
-1
@@ -14,7 +14,63 @@ pgbouncer: &pgbouncer_config
|
|||||||
auth_type: scram-sha-256
|
auth_type: scram-sha-256
|
||||||
auth_query: SELECT uname, phash FROM user_lookup($1)
|
auth_query: SELECT uname, phash FROM user_lookup($1)
|
||||||
ignore_startup_parameters: extra_float_digits # unsupported jdbc extra_float_digits=2 argument
|
ignore_startup_parameters: extra_float_digits # unsupported jdbc extra_float_digits=2 argument
|
||||||
server_reset_query: DEALLOCATE ALL # fix prepared statement already exist (crowdsec)
|
# Ce pgbouncer est PARTAGÉ (crowdsec, plausible, kadans, + le compte que
|
||||||
|
# Vault utilise sur la base `postgres`). Ce qu'un client laisse derrière
|
||||||
|
# lui sur une connexion serveur, le client SUIVANT en hérite : le reset
|
||||||
|
# est la SEULE barrière entre deux clients d'un même pool.
|
||||||
|
#
|
||||||
|
# `DEALLOCATE ALL` ne nettoie QUE les requêtes préparées — d'où sa mise en
|
||||||
|
# place (07e2c6d, « prepared statement already exists » de crowdsec).
|
||||||
|
# Tout le reste de l'état de session passait au suivant. MESURÉ contre un
|
||||||
|
# pgbouncer **1.23.1** — la version RÉELLEMENT déployée (image
|
||||||
|
# ghcr.io/icoretech/pgbouncer-docker:1.23.1-fixed, chart pgbouncer-2.3.1),
|
||||||
|
# montée avec CETTE configuration extraite du cluster (session,
|
||||||
|
# pool_size=1) : le client 2, qui n'avait rien demandé, héritait de
|
||||||
|
# `work_mem=17MB`, du `LISTEN canal_test` du client 1, de sa table TEMP —
|
||||||
|
# et de son `SET ROLE`, au point de créer des tables appartenant à un
|
||||||
|
# autre rôle que le sien.
|
||||||
|
#
|
||||||
|
# Portée de la fuite, mesurée et non supposée : les pools sont partitionnés
|
||||||
|
# par (base, utilisateur) — 210 pools, aucun ne mélange deux bases ni deux
|
||||||
|
# comptes. Le seul héritier possible d'un `SET ROLE` posé par kadans-api
|
||||||
|
# est un client de la base `kadans` avec le MÊME identifiant éphémère,
|
||||||
|
# c'est-à-dire kadans-api elle-même. Défaut d'hygiène, pas brèche
|
||||||
|
# inter-applications.
|
||||||
|
#
|
||||||
|
# `DISCARD ALL` est le défaut de pgbouncer, et c'est un SUR-ENSEMBLE strict
|
||||||
|
# des deux valeurs qui l'ont précédé ici : il contient `DEALLOCATE ALL`
|
||||||
|
# (donc le correctif crowdsec est conservé — vérifié : deux clients
|
||||||
|
# successifs préparent le même nom sans erreur) ET
|
||||||
|
# `SELECT pg_advisory_unlock_all()` (la valeur que pose le sous-chart).
|
||||||
|
#
|
||||||
|
# Il ne peut RIEN casser pour un client vivant : en `pool_mode = session`
|
||||||
|
# la connexion serveur n'est rendue qu'à la déconnexion du client, donc le
|
||||||
|
# reset ne court jamais entre deux requêtes d'une même session. Vérifié :
|
||||||
|
# table TEMP, `SET work_mem` et `LISTEN` d'un client VIVANT survivent.
|
||||||
|
#
|
||||||
|
# ⚠⚠ CE QUE CETTE LIGNE REND FRAGILE — et c'est l'inverse de ce qu'on croit.
|
||||||
|
#
|
||||||
|
# `pool_mode` n'est PAS déclaré ici, donc il vaut `session`, le défaut.
|
||||||
|
# C'est ce qui rend `DISCARD ALL` sans danger. **Le jour où quelqu'un
|
||||||
|
# écrira `pool_mode: transaction`, il cassera kadans-api en silence** :
|
||||||
|
# son `SET ROLE` est posé UNE FOIS à l'ouverture (`AfterConnect`, db.go),
|
||||||
|
# et en transaction pooling `DISCARD ALL` court ENTRE deux transactions —
|
||||||
|
# donc le rôle est effacé avant les migrations suivantes. MESURÉ, les
|
||||||
|
# quatre combinaisons, propriétaire de la table créée :
|
||||||
|
#
|
||||||
|
# session + DEALLOCATE ALL → rôle stable ✅ (mais la fuite reste)
|
||||||
|
# session + DISCARD ALL → rôle stable ✅ ← ce qu'on déploie
|
||||||
|
# transaction + DEALLOCATE ALL → rôle stable ✅ (par ACCIDENT : la fuite
|
||||||
|
# qu'on referme est ce qui le sauvait)
|
||||||
|
# transaction + DISCARD ALL → rôle ÉPHÉMÈRE ❌ le défaut du 28/07,
|
||||||
|
# qui a mis l'API à terre une demi-journée
|
||||||
|
#
|
||||||
|
# Et `poserRoleProprietaire` ne peut pas le voir : sa relecture de
|
||||||
|
# `current_role` a lieu à l'ouverture, où le rôle est encore correct.
|
||||||
|
# Ce qui couvre ce cas, c'est la ceinture Vault (`ALTER ROLE … SET ROLE`,
|
||||||
|
# module app_roles) : un défaut de rôle survit à `DISCARD ALL`. **Avant de
|
||||||
|
# passer en transaction pooling, vérifier que les baux Vault ont tourné.**
|
||||||
|
server_reset_query: DISCARD ALL # défaut pgbouncer — ⚠ ne pas réduire : voir ci-dessus
|
||||||
server_idle_timeout: 7200
|
server_idle_timeout: 7200
|
||||||
pgbouncerExporter:
|
pgbouncerExporter:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|||||||
Reference in New Issue
Block a user