refactor(minio) — chacun son périmètre : les buckets se déclarent depuis le dépôt de l'app
Helm Charts / Detect changed charts (push) Successful in 16s
Helm Charts / Detect changed charts (pull_request) Successful in 16s
MinIO / Auth with gitea for vault (pull_request) Failing after 8m32s
MinIO / Tofu - minio IAC (pull_request) Has been skipped
MinIO / Auth with gitea for vault (push) Failing after 8m31s
MinIO / Tofu - minio IAC (push) Has been skipped
Helm Charts / Library charts tool (push) Has been cancelled
Helm Charts / Application charts pgcat (push) Has been cancelled
Hashicorp Vault / Auth with gitea for vault (push) Has been cancelled
Hashicorp Vault / Tofu - Vault IAC (push) Has been cancelled
Helm Charts / Library charts tool (pull_request) Has been skipped
Hashicorp Vault / Auth with gitea for vault (pull_request) Failing after 8m32s
Hashicorp Vault / Tofu - Vault IAC (pull_request) Has been skipped
Helm Charts / Application charts pgcat (pull_request) Has been skipped

« Les buckets sont à déclarer dans le repo de kadans-api. On ne va pas modifier
le repo tools à chaque changement d'application. Chacun son périmètre. Tools
peut proposer un module pour standardiser la déclaration de buckets à la
limite, mais c'est tout. » (fondateur, 26/07)

C'est une erreur de fond de ma part : j'avais fait de `tools` le PROPRIÉTAIRE de
déclarations qui appartiennent aux applications. À ce rythme, chaque nouveau
bucket de n'importe quelle app devenait une PR sur l'infra partagée.

CE QUI CHANGE. `consumers.tf` disparaît, et la liste de buckets du chart se vide.
À la place, un module réutilisable `iac/modules/minio_app` : une app lui donne
son nom et ses buckets, et reçoit des buckets privés, un compte de service qui
ne peut rien toucher d'autre, et ses clés dans `kvv2/minio/<app>`.

L'OBSTACLE, ET SA RÉPONSE. Déclarer ses buckets depuis son propre dépôt suppose
des droits d'ADMINISTRATION sur MinIO. Confier le root serait absurde : il lit et
écrit tous les objets de toutes les apps. `tools` fournit donc un compte
PROVISIONNEUR aux droits minimaux — créer un bucket, une politique, un compte de
service — et AUCUN droit sur les objets. Une app compromise pourrait créer des
buckets (une nuisance), pas lire les vidéos d'une autre. Le root, lui, ne sort
toujours pas de ce pipeline.

Le rôle CI de chaque app gagne la lecture de `kvv2/data/minio/provisioner` dans
`app_policy` — générique, et c'est exactement le genre de standardisation qui
appartient au dépôt commun.

⚠ CE QUE JE N'AI PAS PU PROUVER : les noms d'actions d'administration MinIO de la
politique du provisionneur viennent de la documentation, pas d'un essai — je n'ai
pas d'identifiants admin en main. Le premier `apply` les confirmera ou les
corrigera. C'est le seul point non vérifié de cette PR, et il est signalé dans le
code à l'endroit exact.

tofu fmt propre · tofu validate réussi sur minio/iac ET hashicorp-vault/iac.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01CoafGWmRVESaWX819USUUA
This commit is contained in:
2026-07-26 10:07:05 +02:00
co-authored by Claude Opus 5
parent 0e1b6e1062
commit ec71571b98
10 changed files with 314 additions and 155 deletions
+39 -42
View File
@@ -25,7 +25,7 @@ application vivent avec cette application.
| Ressources | requests 512 Mi / 100 m · limit 2 Gi — la limite protège les voisins de `tools`, pas MinIO |
| API S3 | `s3.arcodange.lab` (interne) **et `s3.arcodange.fr`** (public, tunnel Cloudflare → entrypoint `web` + crowdsec) — voir « Pourquoi une exposition publique » |
| Console | `minio.arcodange.lab` (Traefik) |
| Buckets | **privés**, chacun déclarant son app (`app:`) l'accès passe par des URL signées (ADR-0002 du dossier produit). Le bucket est la SEULE déclaration : compte de service et droits Vault en découlent. Une app peut en avoir plusieurs |
| Buckets | **aucun ici** chaque app déclare les siens depuis son dépôt (module `minio_app`). Tous privés : l'accès passe par des URL signées (ADR-0002 du dossier produit) |
| Identifiants | **jamais dans le dépôt** : `iac/` les génère dans Vault (`kvv2/minio/config`), le Vault Secrets Operator les matérialise en secret `minio-config`, le chart les lit via `existingSecret` |
Le ServiceAccount du pod est nommé `minio` (et non le `minio-sa` par défaut du
@@ -96,53 +96,50 @@ une phrase claire, qu'échouer au milieu d'un téléversement.
## Donner à une app l'accès au stockage
**Une seule chose à faire** : déclarer son bucket dans `values.yaml`, en disant
à quelle app il appartient.
**Rien à faire ici.** Chaque application déclare **ses** buckets **depuis son
propre dépôt**, avec le module que ce dépôt-ci fournit :
```yaml
buckets:
- name: mon-app-fichiers
app: mon-app # ← la seule déclaration
policy: none # privé : l'accès passe par des URL signées
```hcl
# iac/main.tf de l'application
data "vault_kv_secret_v2" "minio_provisioner" {
mount = "kvv2"
name = "minio/provisioner"
}
provider "minio" {
minio_server = "s3.arcodange.fr"
minio_user = data.vault_kv_secret_v2.minio_provisioner.data["MINIO_ACCESS_KEY"]
minio_password = data.vault_kv_secret_v2.minio_provisioner.data["MINIO_SECRET_KEY"]
minio_ssl = true
}
module "stockage" {
source = "git::…/tools.git//minio/iac/modules/minio_app?depth=1&ref=main"
app = "mon-app"
buckets = ["mon-app-fichiers"]
providers = { minio = minio }
}
```
Tout le reste en découle, sans rien écrire ailleurs :
Voir `iac/modules/minio_app/README.md`. **Chacun son périmètre** : `tools`
fournit le serveur, le provisionneur et le module — pas la liste des buckets.
Sans ça, chaque bucket de chaque app deviendrait une PR sur l'infra partagée.
- `iac/consumers.tf` **lit ce même fichier**, groupe les buckets par app, et crée
**un** compte de service `mon-app-app` autorisé sur **tous ses buckets et eux
seuls**. Ses clés atterrissent dans `kvv2/minio/mon-app` ;
- le module Vault central `app_policy` accorde **déjà** à toute app la lecture de
`kvv2/data/minio/<son nom>` — inconditionnellement, parce que le chemin porte
le nom de l'app et ne peut donc jamais exposer que ses propres clés. Une app
qui ne stocke rien y lit un chemin qui n'existe pas : une règle inerte.
### Ce que `tools` fournit, et pourquoi
Côté app, il reste à écrire une `VaultStaticSecret` sur `kvv2/minio/<app>` et à
injecter les variables dans son Deployment (voir `kadans-api` pour l'exemple).
Le secret porte `MINIO_ENDPOINT`, `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`,
`MINIO_BUCKET` (le premier) et `MINIO_BUCKETS` (tous, séparés par des virgules).
| Pièce | Rôle |
|---|---|
| Le serveur | le chart, son volume, ses ingress (interne + public) |
| Le **root** | généré ici, écrit dans `kvv2/minio/config`, **ne sort jamais** de ce pipeline |
| Le **provisionneur** | un compte aux droits d'administration MINIMAUX (créer bucket, politique, compte de service) et **aucun droit sur les objets** — lisible par le rôle CI de chaque app |
| Le **module** | `minio_app` : standardise la déclaration, sans la détenir |
### Plusieurs buckets pour une même app
C'est prévu, et c'est le cas courant : deux contenus aux **cycles de vie
différents** méritent deux buckets aux politiques de purge différentes. Kadans y
viendra avec les deux paliers de l'ADR-018 — l'aperçu 240p, régénérable, et le
rendu de travail 360p, à garder. Il suffit d'une seconde entrée avec le même
`app:` ; le compte de service existant gagne l'accès, sans nouvelle clé.
> **Il n'y a volontairement AUCUNE liste de consommateurs.** Une liste de plus
> serait une liste à tenir synchronisée avec les buckets — donc une liste à
> oublier. Le bucket fait foi.
### Pourquoi les clés vivent ICI et pas chez l'app
Seul ce pipeline possède les identifiants **root** de MinIO. Si chaque app créait
son propre compte de service, il faudrait donner ce root à chaque rôle CI —
c'est-à-dire à tout le monde. Ici il ne sort jamais, et l'app ne reçoit qu'une
clé qui **ne peut rien lire d'autre que son bucket**. Un compte de service qui
fuite ne donne accès qu'aux objets qu'il gérait déjà.
Donner le root aux apps aurait été absurde : il lit et écrit **tous** les objets
de **toutes** les apps. Le provisionneur, lui, peut créer des buckets — une
nuisance si une app est compromise — mais **pas lire les vidéos d'une autre**.
### Rotation
Détruire `random_password.app["<app>"]` et relancer le plan suffit : la clé
change, `force_destroy = false` garde le compte, et les objets déjà déposés
conservent leur propriétaire.
Depuis l'`iac/` de l'app : détruire `module.stockage.random_password.app` et
relancer son plan. La clé change, `force_destroy = false` garde le compte, et
les objets déjà déposés conservent leur propriétaire.