docs(adr) — stockage objet MinIO : qui déclare quoi, et qui détient quoi #45

Merged
arcodange merged 1 commits from arcodange/adr-stockage-objet into main 2026-07-26 10:46:35 +02:00
2 changed files with 112 additions and 0 deletions
Showing only changes of commit aabedb0f3f - Show all commits
+111
View File
@@ -0,0 +1,111 @@
[← ADRs](.) · [factory](../..) · **20260726 — stockage objet (MinIO) : qui déclare quoi**
> **Cross-references** (bidirectionnel : chaque fichier listé doit citer cette ADR en tête)
>
> - **Infra partagée** (repo `arcodange-org/tools`) :
> [`minio/iac/modules/minio_app/`](https://gitea.arcodange.lab/arcodange-org/tools/src/branch/main/minio/iac/modules/minio_app) ·
> [`minio/iac/provisioner.tf`](https://gitea.arcodange.lab/arcodange-org/tools/src/branch/main/minio/iac/provisioner.tf) ·
> [`minio/values.yaml`](https://gitea.arcodange.lab/arcodange-org/tools/src/branch/main/minio/values.yaml) ·
> [`hashicorp-vault/iac/modules/app_policy/main.tf`](https://gitea.arcodange.lab/arcodange-org/tools/src/branch/main/hashicorp-vault/iac/modules/app_policy/main.tf)
> - **Premier consommateur** (repo `arcodange/kadans`) :
> [`iac/main.tf`](https://gitea.arcodange.lab/arcodange/kadans/src/branch/main/iac/main.tf)
> - **API consommatrice** (repo `arcodange/kadans-api`) :
> [`stockage.go`](https://gitea.arcodange.lab/arcodange/kadans-api/src/branch/main/stockage.go) ·
> [`chart/values.yaml`](https://gitea.arcodange.lab/arcodange/kadans-api/src/branch/main/chart/values.yaml)
> - **Related ADR** :
> [`04_tool_hashicorp_vault.md`](04_tool_hashicorp_vault.md) (rôles et politiques Vault) ·
> [`20260407-network-architecture.md`](20260407-network-architecture.md) (Cloudflare / Traefik / CrowdSec)
# ADR 20260726 : stockage objet (MinIO) — qui déclare quoi, et qui détient quoi
## Status
Proposed
## Context
MinIO est déployé dans le namespace `tools` (chart officiel, standalone, volume Longhorn). Le premier consommateur est Kadans, qui doit téléverser des rendus vidéo depuis le navigateur pour qu'ils suivent l'utilisateur d'un appareil à l'autre.
Trois questions se posaient, et elles sont indépendantes :
1. **Qui déclare les buckets** d'une application ?
2. **Qui détient les identifiants** capables de les créer ?
3. **Comment l'application lit** les siens à l'exécution ?
Une première version faisait tout porter par `tools` : une liste de consommateurs dans son Terraform, les buckets dans son chart. Elle a été rejetée — à ce rythme, chaque bucket de chaque application devient une PR sur l'infra partagée, et le dépôt commun devient le goulot de tout le monde.
## Decision
### 1. Chacun son périmètre
**Une application déclare ses buckets depuis son propre dépôt.** `tools` fournit le serveur, un module de standardisation, et un compte de provisionnement — **pas la liste**.
```hcl
# iac/main.tf de l'application
module "stockage" {
source = "git::…/tools.git//minio/iac/modules/minio_app?depth=1&ref=main"
app = "kadans"
buckets = ["kadans-videos"]
providers = { minio = minio }
}
```
Le module crée les buckets (privés), une politique bornée à ces buckets, un compte de service, et écrit ses clés dans `kvv2/minio/<app>`.
### 2. Trois identités, trois portées
| Identité | Peut | Ne peut pas | Qui la lit |
|---|---|---|---|
| **root** MinIO | tout | — | le seul pipeline `minio` (`kvv2/minio/config`) |
| **provisionneur** | créer bucket, politique, compte de service | lire ou écrire un objet | le rôle **CI** de chaque app (`kvv2/minio/provisioner`) |
| **compte de service** d'une app | lire/écrire dans **ses** buckets | tout le reste | le **pod** de l'app (`kvv2/minio/<app>`) |
C'est la pièce qui rend le point 1 possible. Provisionner demande des droits d'administration ; confier le **root** aurait donné à chaque application la lecture des objets de **toutes** les autres. Le provisionneur, lui, peut créer des buckets — une nuisance si une app est compromise — mais **pas lire les vidéos d'une autre**.
### 3. La lecture est une propriété de la plateforme
Le module Vault central `app_policy` accorde à **toute** application la lecture de `kvv2/data/minio/<son nom>`, **inconditionnellement**.
Pas de drapeau, pas de déclaration par app : le chemin porte le nom de l'application, donc la règle **ne peut 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, pas un privilège.
Conséquence pratique : déclarer un consommateur se fait à **un seul endroit**, son propre `iac/`. Rien à synchroniser, donc rien à oublier.
### 4. Les octets ne passent pas par l'API
L'application signe des **URL présignées** ; le navigateur téléverse **directement** vers MinIO. Faire transiter 50 à 200 Mo par un pod applicatif doublerait le transit et exposerait l'API à un seul gros fichier.
Corollaires :
- l'endpoint signé doit être **joignable par le navigateur**, donc **public** (`s3.arcodange.fr`) — une page servie en HTTPS ne peut pas téléverser vers `http://` (contenu mixte), et un TLD interne ne se résout pas hors du LAN ;
- **CORS** liste les origines **exactes** de l'application, jamais `*` : une URL présignée qui fuiterait serait sinon rejouable depuis n'importe quel site ;
- l'ingress public ne porte **pas** de basic-auth, contrairement aux autres : une requête S3 porte sa propre signature, et un défi HTTP Basic casserait un PUT présigné auquel le navigateur ne peut pas répondre.
### 5. Un bucket par cycle de vie, pas par application
Une application peut avoir plusieurs buckets. Deux contenus aux durées de vie différentes méritent deux politiques de purge — Kadans en aura deux (un rendu de travail à garder, un aperçu régénérable).
Le compte de service est **par application** : ajouter un bucket ne crée aucune clé, le compte existant gagne l'accès.
## Consequences
- **Le dépôt `tools` n'est plus modifié** quand une application change ses buckets. C'était l'objet de la décision.
- **Ordre de déploiement contraint** : le module doit exister sur `main` de `tools` avant qu'une application l'appelle (`?ref=main`), et le provisionneur doit exister avant le premier plan d'application.
- **Le provisionneur est un secret partagé** entre les rôles CI. Sa compromission permet de créer des buckets et des comptes, pas de lire des objets. Si ce risque devient inacceptable, la suite est une identité de provisionnement **par application**, bornée par préfixe de bucket — MinIO ne le permet pas simplement aujourd'hui.
- **Non vérifié à la rédaction** : les noms d'actions d'administration MinIO de la politique du provisionneur viennent de la documentation, pas d'un essai. Le premier `apply` les confirmera ou les corrigera.
## Alternatives Considered
| Option | Pourquoi non |
|---|---|
| `tools` détient la liste des consommateurs | Chaque bucket de chaque app devient une PR sur l'infra partagée — rejeté par le fondateur, et c'est le cœur de cette ADR |
| Les buckets déclarés dans le chart de MinIO (`values.yaml`) | Même défaut : la déclaration vit chez l'infra, pas chez l'application |
| Chaque app crée son compte de service avec le **root** | Le root lit et écrit tous les objets de toutes les apps : le distribuer à chaque rôle CI revient à ne plus avoir de cloisonnement |
| Déclarer la lecture Vault par app (`kv_read_paths`) | Mécanisme réel, mais c'est la trappe pour lire un secret appartenant à une **autre** app (creds GCS de Longhorn pour l'ERP). Y ranger un motif standard le rend invisible et oblige à le redéclarer partout |
| Un drapeau `object_storage = true` par app | Une déclaration de plus à tenir synchronisée avec le `iac/` de l'app — donc une à oublier. La règle inerte ne coûte rien |
| Une identité de provisionnement par app | Souhaitable, mais MinIO ne borne pas simplement les actions d'administration par préfixe. À reconsidérer si le modèle de menace change |
## Success Metrics
- Ajouter une application consommatrice ne touche **aucun** fichier de `tools`.
- Un compte de service compromis ne donne accès qu'aux objets qu'il gérait déjà.
- Le root de MinIO n'apparaît dans aucune politique Vault en dehors du pipeline `minio`.
+1
View File
@@ -15,6 +15,7 @@
- [x] gitea packages
- [ ] devsecops tools
- [x] [hashicorp vault](./04_tool_hashicorp_vault.md)
- [x] [stockage objet MinIO — qui déclare quoi](./20260726-stockage-objet-minio.md)
- [ ] terrakube
- [ ] prometheus/grafana
- [ ] ansible AWX