Helm Charts / Detect changed charts (pull_request) Successful in 17s
Helm Charts / Detect changed charts (push) Successful in 16s
MinIO / Auth with gitea for vault (pull_request) Failing after 8m32s
MinIO / Tofu - minio IAC (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 (push) Has been cancelled
Helm Charts / Library charts tool (push) Has been cancelled
MinIO / Tofu - minio IAC (push) Has been cancelled
MinIO / Auth with gitea for vault (push) Has started running
Helm Charts / Library charts tool (pull_request) Has been skipped
Helm Charts / Application charts pgcat (pull_request) Has been skipped
« Pourquoi sommes-nous restreints sur les buckets ? » (fondateur, 26/07) — parce
que JE l'avais décidé, pour rendre tout dérivable d'un seul nom. Rien ne
l'imposait, et la restriction aurait mordu au pas suivant : l'ADR-018 de Kadans
prévoit DEUX paliers de transfert (aperçu 240p régénérable, travail 360p à
garder), donc deux cycles de vie, donc potentiellement deux buckets aux
politiques de purge différentes.
Chaque bucket déclare désormais son app (`app: kadans`). C'est toujours la SEULE
déclaration, au même endroit — mais le nom du bucket redevient libre, et
`kadans-videos` retrouve un nom qui dit ce qu'il contient.
Le compte de service devient par APP et non par bucket : une seule clé, autorisée
sur tous ses buckets et eux seuls. Ajouter un bucket à une app existante ne crée
donc aucune nouvelle clé — le compte existant gagne l'accès. Le secret porte
`MINIO_BUCKETS` (tous) en plus de `MINIO_BUCKET` (le premier), pour que l'app
n'ait pas à les redéclarer de son côté.
Un bucket sans `app:` est ignoré plutôt que de faire échouer le plan : il n'aura
simplement pas de compte de service, ce qui se voit immédiatement et ne casse
rien d'existant.
Vérifié sur le VRAI fichier, pas en théorie : le groupement rend bien
{"kadans" = ["kadans-videos"]}, et avec un second bucket de la même app,
{"kadans" = ["kadans-videos", "kadans-apercus"]} — le cas ADR-018 exact.
tofu fmt propre, tofu validate réussi.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01CoafGWmRVESaWX819USUUA
99 lines
4.2 KiB
Terraform
99 lines
4.2 KiB
Terraform
# ── Un compte de service par APP, sans liste à tenir ─────────────────────────
|
|
#
|
|
# Il n'y a PAS de liste de consommateurs : elle se lit dans `values.yaml` du
|
|
# chart. Chaque bucket y dit à quelle app il appartient (`app:`) — et c'est la
|
|
# SEULE déclaration (fondateur 2026-07-26 : « je préfère ne pas m'embêter avec
|
|
# consumers ou autre »). Rien à synchroniser entre deux fichiers, donc rien à
|
|
# oublier, et aucune divergence possible : c'est le fichier qu'Helm consomme.
|
|
#
|
|
# UNE APP PEUT AVOIR PLUSIEURS BUCKETS, et c'est important : rien ne justifiait
|
|
# de l'en empêcher. L'ADR-018 de Kadans prévoit déjà deux paliers de transfert
|
|
# (aperçu 240p régénérable, travail 360p à garder) — deux cycles de vie, donc
|
|
# potentiellement deux buckets aux politiques de purge différentes. Le compte de
|
|
# service est donc par APP, autorisé sur TOUS ses buckets et sur eux seuls.
|
|
#
|
|
# 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 ses propres buckets. Un compte qui fuite ne donne accès qu'aux
|
|
# objets qu'il gérait déjà.
|
|
|
|
locals {
|
|
# Le MÊME fichier que celui qu'Helm consomme : impossible de créer un bucket
|
|
# sans son compte de service, ou un compte sans ses buckets.
|
|
buckets_declares = yamldecode(file("${path.module}/../values.yaml")).minio.buckets
|
|
|
|
# app → ses buckets. `try` : un bucket sans `app:` est ignoré plutôt que de
|
|
# faire échouer tout le plan — il n'aura simplement aucun compte de service,
|
|
# ce qui se voit tout de suite et ne casse rien d'existant.
|
|
buckets_par_app = {
|
|
for app in distinct([for b in local.buckets_declares : try(b.app, null) if try(b.app, null) != null]) :
|
|
app => [for b in local.buckets_declares : b.name if try(b.app, null) == app]
|
|
}
|
|
}
|
|
|
|
# La politique d'accès : SES buckets, rien d'autre. Ni les autres buckets, ni
|
|
# l'administration.
|
|
resource "minio_iam_policy" "app" {
|
|
for_each = local.buckets_par_app
|
|
name = "${each.key}-app"
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Allow"
|
|
Action = ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"]
|
|
Resource = [for b in each.value : "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 each.value : "arn:aws:s3:::${b}"]
|
|
},
|
|
]
|
|
})
|
|
}
|
|
|
|
resource "random_password" "app" {
|
|
for_each = local.buckets_par_app
|
|
length = 40
|
|
special = false # les outils S3 transportent mal certains caractères en URL
|
|
}
|
|
|
|
resource "minio_iam_user" "app" {
|
|
for_each = local.buckets_par_app
|
|
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` : 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" {
|
|
for_each = local.buckets_par_app
|
|
user_name = minio_iam_user.app[each.key].id
|
|
policy_name = minio_iam_policy.app[each.key].id
|
|
}
|
|
|
|
# Les clés, dans l'espace Vault de MINIO. L'app y accède par la règle que
|
|
# `app_policy` accorde à toutes : `kvv2/data/minio/<son nom>`.
|
|
#
|
|
# `MINIO_BUCKETS` liste ses buckets pour que l'app n'ait pas à les redéclarer de
|
|
# son côté ; `MINIO_BUCKET` reste le premier, pour les apps qui n'en ont qu'un.
|
|
resource "vault_kv_secret_v2" "app" {
|
|
for_each = local.buckets_par_app
|
|
mount = "kvv2"
|
|
name = "minio/${each.key}"
|
|
data_json = jsonencode({
|
|
MINIO_ENDPOINT = var.minio_endpoint
|
|
MINIO_BUCKET = each.value[0]
|
|
MINIO_BUCKETS = join(",", each.value)
|
|
MINIO_ACCESS_KEY = minio_iam_user.app[each.key].id
|
|
MINIO_SECRET_KEY = random_password.app[each.key].result
|
|
})
|
|
}
|