# ── Les apps qui STOCKENT des objets dans MinIO ────────────────────────────── # # Le motif, générique, en trois pièces : # 1. ICI : un compte de service MinIO par app, borné à SON bucket, dont les # clés sont écrites dans `kvv2/minio/` ; # 2. côté Vault central : l'app déclare `kv_read_paths = ["kvv2/data/minio/"]` # dans `hashicorp-vault/iac/terraform.tfvars` — le mécanisme existait DÉJÀ # (même recette que `kvv2/data/longhorn/gcs-backup` pour l'ERP), donc aucun # module central n'a eu besoin d'être modifié ; # 3. côté app : une VaultStaticSecret qui matérialise ce chemin en Secret k8s. # # POURQUOI les clés vivent chez MINIO et pas chez l'app : seul ce pipeline-ci # possède les identifiants ROOT. Si chaque app créait son propre compte de # service, il faudrait donner le root de MinIO à chaque rôle CI — c'est-à-dire # à tout le monde. Ici il ne sort jamais d'ici, et l'app ne reçoit qu'une clé # qui ne peut rien lire d'autre que son bucket. # # AJOUTER UNE APP = deux lignes : une entrée dans `var.consumers` ci-dessous # (avec son bucket, qui doit exister dans `values.yaml` du chart), et un # `kv_read_paths` dans le tfvars central. # La politique d'accès : LE bucket de l'app, rien d'autre. Ni listing des autres # buckets, ni administration — un compte de service qui fuite ne donne accès # qu'aux objets qu'il gérait déjà. resource "minio_iam_policy" "app" { for_each = { for c in var.consumers : c.app => c } name = "${each.key}-app" policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"] Resource = ["arn:aws:s3:::${each.value.bucket}/*"] }, { # Nécessaire pour qu'un client S3 puisse vérifier l'existence du bucket # et lister SES objets — jamais ceux d'un autre. Effect = "Allow" Action = ["s3:ListBucket", "s3:GetBucketLocation"] Resource = ["arn:aws:s3:::${each.value.bucket}"] }, ] }) } resource "minio_iam_user" "app" { for_each = { for c in var.consumers : c.app => c } name = "${each.key}-app" # Le mot de passe EST la clé secrète S3 : généré ici, jamais choisi. secret = random_password.app[each.key].result # `false` = on ne recrée pas l'utilisateur à chaque rotation du mot de passe : # les objets déjà déposés gardent leur propriétaire. force_destroy = false } resource "random_password" "app" { for_each = { for c in var.consumers : c.app => c } length = 40 special = false # les outils S3 transportent mal certains caractères en URL } resource "minio_iam_user_policy_attachment" "app" { for_each = { for c in var.consumers : c.app => c } user_name = minio_iam_user.app[each.key].id policy_name = minio_iam_policy.app[each.key].id } # Les clés, déposées dans l'espace Vault de MINIO — l'app y accède par le # `kv_read_paths` déclaré au tfvars central (pièce 2 du motif, ci-dessus). resource "vault_kv_secret_v2" "app" { for_each = { for c in var.consumers : c.app => c } mount = "kvv2" name = "minio/${each.key}" data_json = jsonencode({ MINIO_ENDPOINT = var.minio_endpoint MINIO_BUCKET = each.value.bucket MINIO_ACCESS_KEY = minio_iam_user.app[each.key].id MINIO_SECRET_KEY = random_password.app[each.key].result }) }