# ── Module `minio_app` — une app déclare SES buckets, depuis SON dépôt ─────── # # Chacun son périmètre (fondateur 2026-07-26) : les buckets d'une application # appartiennent au dépôt de cette application, pas à l'infra partagée. `tools` # ne fait que STANDARDISER la déclaration — il ne la détient pas. Sans ça, # chaque bucket de chaque app deviendrait une PR sur le dépôt commun. # # Usage, depuis l'`iac/` de l'app : # # module "stockage" { # source = "git::…/tools.git//minio/iac/modules/minio_app?depth=1&ref=main" # app = "kadans" # buckets = ["kadans-videos"] # providers = { minio = minio } # } # # Ce que le module garantit : # · les buckets sont PRIVÉS — l'accès passe par des URL présignées ; # · le compte de service ne peut RIEN toucher d'autre que ces buckets-là ; # · ses clés atterrissent dans `kvv2/minio/`, 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). resource "minio_s3_bucket" "app" { for_each = toset(var.buckets) bucket = each.key acl = "private" # Les objets sont DÉRIVÉS (rendus de transfert) et régénérables depuis les # originaux restés sur l'appareil : un `force_destroy` accidentel ne détruit # donc rien d'irremplaçable, mais on l'interdit quand même — la destruction # d'un bucket doit être un geste explicite, pas un effet de bord d'un plan. force_destroy = false } # La politique : SES buckets, rien d'autre. Ni les autres, ni l'administration. resource "minio_iam_policy" "app" { name = "${var.app}-app" policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"] Resource = [for b in var.buckets : "arn:aws:s3:::${b}/*"] }, { # Nécessaire pour qu'un client S3 vérifie l'existence d'un bucket et # liste SES objets — jamais ceux d'une autre app. Effect = "Allow" Action = ["s3:ListBucket", "s3:GetBucketLocation"] Resource = [for b in var.buckets : "arn:aws:s3:::${b}"] }, ] }) } resource "random_password" "app" { length = 40 special = false # les outils S3 transportent mal certains caractères en URL } resource "minio_iam_user" "app" { name = "${var.app}-app" # Le mot de passe EST la clé secrète S3 : généré ici, jamais choisi. secret = random_password.app.result # `false` : une rotation du mot de passe ne recrée pas l'utilisateur, donc les # objets déjà déposés gardent leur propriétaire. force_destroy = false } resource "minio_iam_user_policy_attachment" "app" { user_name = minio_iam_user.app.id policy_name = minio_iam_policy.app.id } # Les clés, dans l'espace Vault de MinIO — lu par le pod de l'app. # `MINIO_BUCKETS` les liste toutes pour que l'app n'ait pas à les redéclarer ; # `MINIO_BUCKET` reste le premier, pour celles qui n'en ont qu'un. resource "vault_kv_secret_v2" "app" { mount = "kvv2" name = "minio/${var.app}" data_json = jsonencode({ MINIO_ENDPOINT = var.endpoint MINIO_BUCKET = var.buckets[0] MINIO_BUCKETS = join(",", var.buckets) MINIO_ACCESS_KEY = minio_iam_user.app.id MINIO_SECRET_KEY = random_password.app.result }) }