Files
tools/minio/iac/modules/minio_app
arcodangeandClaude Fable 5.1 56d0421943 minio_app : le compte de service peut refermer et abandonner un téléversement en parts (kadans#1033)
`ListObjectParts` et `AbortMultipartUpload` rendaient 403 AccessDenied avec les
identifiants de kadans-api — mesuré le 2026-09-07 sur le homelab, en rejouant les
appels de contrôle exacts de l'API depuis un poste. Ouvrir et déposer les parts
sont des PutObject et passaient ; l'assemblage n'a jamais pu avoir lieu, et toute
vidéo de plus d'une part échouait à la confirmation derrière « stockage injoignable ».

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-07 08:22:44 +02:00
..

minio_app — déclarer ses buckets depuis SON dépôt

Chacun son périmètre : les buckets d'une application appartiennent au dépôt de cette application. Ce module standardise la déclaration, il ne la détient pas — sans lui, chaque bucket de chaque app deviendrait une PR sur tools.

Usage

Dans l'iac/ de l'app :

# Le provisionneur MinIO : des droits d'administration MINIMAUX (créer un
# bucket, un compte de service, une politique), jamais le root — qui, lui, ne
# sort pas du pipeline `minio`.
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::ssh://[email protected]:2222/arcodange-org/tools.git//minio/iac/modules/minio_app?depth=1&ref=main"
  app       = "mon-app"          # = le nom de son rôle Vault
  buckets   = ["mon-app-fichiers"]
  providers = { minio = minio }
}

Puis, côté chart : une VaultStaticSecret sur kvv2/minio/<app> et l'injection des variables dans le Deployment.

Ce que le module garantit

  • les buckets sont privés — l'accès passe par des URL présignées ;
  • les téléversements abandonnés sont ramassés au bout d'un jour (voir plus bas : c'est de l'hygiène de protocole, pas un choix de l'app) ;
  • le compte de service ne peut rien toucher d'autre que ces buckets-là ;
  • ses clés vont dans kvv2/minio/<app>, que le module Vault central autorise déjà l'app à lire (règle inconditionnelle : le chemin porte le nom de l'app, donc il ne peut exposer que ses propres clés).

Plusieurs buckets

C'est le cas courant : deux contenus aux cycles de vie différents méritent deux buckets. Il suffit de les lister — le compte de service existant gagne l'accès, sans nouvelle clé.

Ce que le module ne fait PAS

Il ne pose ni quota, ni expiration de contenu, ni versioning : ces choix appartiennent à l'app et varient d'un bucket à l'autre. « Ces vidéos se purgent à 90 jours » est une décision de produit, elle se prend dans le dépôt du produit.

⚠ L'exception, et la ligne qu'elle trace

Ce module pose une seule règle de cycle de vie : AbortIncompleteMultipartUpload à 1 jour, sur chaque bucket qu'il crée.

Elle a l'air de contredire le paragraphe ci-dessus. Elle ne le contredit pas — elle en précise la frontière, et c'est la question « à QUOI cette connaissance appartient-elle ? » qui tranche :

  • une expiration de contenu porte sur des objets que l'app a voulus, qu'elle montre, et dont elle seule sait combien de temps ils valent. Elle varie d'une app à l'autre : elle est chez l'app ;
  • un téléversement en plusieurs parts jamais refermé n'est le contenu de personne. Ce sont des morceaux qu'aucune API ne montre — ni mc ls, ni la console — laissés par un navigateur qui a fermé l'onglet. Aucune app ne veut les garder, et aucune ne peut les voir depuis son propre code. C'est un déchet de PROTOCOLE, produit par le mécanisme même du bucket : il est chez celui qui crée les buckets.

Le test pratique : si la réponse à « combien de temps ? » demande de connaître le produit, c'est chez l'app. Ici, la réponse ne demande que de connaître S3 — passé l'expiration des URL signées, un téléversement en cours ne peut plus rien recevoir, il occupe seulement.

⚠ Le délai n'est pas paramétrable, et c'est voulu (YAGNI) : un seul cas existe. Le jour où une app signe des parts pour plus de 24 h, ce sera le déclencheur pour en faire une variable — pas avant.

Plancher de version

⚠ Ce module exige aminueza/minio >= 3.10.0 : abort_incomplete_multipart_upload y est apparu (mesuré en interrogeant le schéma du provider version par version — 3.9.0 ne l'a pas). Un appelant resté plus bas se verra dire, dès tofu init :

no available releases match the given constraints 3.3.0, >= 3.10.0

… ce qui NOMME la version à atteindre, au lieu d'un « unsupported block type » au plan, qui ne l'aurait pas dite.