feat(minio) — un compte de service par app consommatrice, borné à son bucket #21
@@ -24,5 +24,13 @@ applications = [
|
||||
service_account_namespaces = ["tools"]
|
||||
},
|
||||
{ name = "prospection" },
|
||||
{ name = "kadans" },
|
||||
{
|
||||
name = "kadans"
|
||||
# Le pod lit les clés de SON compte de service MinIO, écrites par le pipeline
|
||||
# `minio` dans son propre espace (minio/iac/consumers.tf). Le mécanisme est
|
||||
# celui qui existait déjà pour l'ERP et les creds GCS de Longhorn — aucun
|
||||
# module central n'a eu à changer. Le root de MinIO, lui, ne sort jamais du
|
||||
# pipeline minio : l'app ne reçoit qu'une clé bornée à son bucket.
|
||||
kv_read_paths = ["kvv2/data/minio/kadans"]
|
||||
},
|
||||
]
|
||||
|
||||
@@ -92,3 +92,41 @@ du fondateur (707 vidéos, ~2 ans), **la durée moyenne est de 53 secondes** et
|
||||
passe très largement. Si la limite se confirme, le plafond de 200 Mio annoncé
|
||||
côté API mérite d'être ramené sous celle du tunnel — mieux vaut refuser tôt, avec
|
||||
une phrase claire, qu'échouer au milieu d'un téléversement.
|
||||
|
||||
|
||||
## Donner à une app l'accès au stockage
|
||||
|
||||
Le motif tient en **trois pièces**, et il est générique — ajouter une app ne
|
||||
demande aucune modification des modules Vault centraux.
|
||||
|
||||
1. **Le bucket** — dans `values.yaml` de ce chart (bloc `buckets`), privé.
|
||||
2. **Le compte de service** — une entrée dans `var.consumers` de `iac/consumers.tf` :
|
||||
```hcl
|
||||
{ app = "mon-app", bucket = "mon-app-fichiers" }
|
||||
```
|
||||
Le plan crée une politique MinIO **bornée à ce bucket**, un compte de service,
|
||||
et écrit ses clés dans `kvv2/minio/mon-app`.
|
||||
3. **La lecture** — dans `hashicorp-vault/iac/terraform.tfvars`, l'app déclare :
|
||||
```hcl
|
||||
{ name = "mon-app", kv_read_paths = ["kvv2/data/minio/mon-app"] }
|
||||
```
|
||||
`kv_read_paths` **existait déjà** (l'ERP s'en sert pour les creds GCS de
|
||||
Longhorn) : rien à changer dans `app_policy` ni `app_roles`.
|
||||
|
||||
Puis, côté app : une `VaultStaticSecret` sur `kvv2/minio/<app>` et l'injection
|
||||
des variables dans le Deployment.
|
||||
|
||||
### 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 son bucket** : ni les autres buckets, ni
|
||||
l'administration. Un compte de service qui fuite ne donne accès qu'aux objets
|
||||
qu'il gérait déjà.
|
||||
|
||||
### Rotation
|
||||
|
||||
Détruire `random_password.app["<app>"]` et relancer le plan suffit : la clé
|
||||
change, `force_destroy = false` garde le compte et les objets déjà déposés
|
||||
conservent leur propriétaire.
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
# ── 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/<app>` ;
|
||||
# 2. côté Vault central : l'app déclare `kv_read_paths = ["kvv2/data/minio/<app>"]`
|
||||
# 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
|
||||
})
|
||||
}
|
||||
@@ -4,6 +4,14 @@ terraform {
|
||||
source = "vault"
|
||||
version = "4.4.0"
|
||||
}
|
||||
minio = {
|
||||
source = "aminueza/minio"
|
||||
version = "3.3.0"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = "3.6.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,3 +22,13 @@ provider "vault" {
|
||||
role = "gitea_cicd_minio"
|
||||
}
|
||||
}
|
||||
|
||||
# Provider MinIO — pour créer les COMPTES DE SERVICE des apps consommatrices.
|
||||
# Il parle à l'API S3 publique (s3.arcodange.fr, exposée par le chart) : le
|
||||
# runner CI n'est pas dans le LAN, et `.lab` ne s'y résout pas.
|
||||
provider "minio" {
|
||||
minio_server = var.minio_endpoint
|
||||
minio_user = local.config.rootUser
|
||||
minio_password = local.config.rootPassword
|
||||
minio_ssl = true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
variable "minio_endpoint" {
|
||||
type = string
|
||||
default = "s3.arcodange.fr"
|
||||
description = "Hôte de l'API S3, SANS schéma (le provider ajoute https via minio_ssl). Public : le runner CI n'est pas dans le LAN et `.lab` ne s'y résout pas."
|
||||
}
|
||||
|
||||
variable "consumers" {
|
||||
type = list(object({
|
||||
app = string # nom de l'app — doit correspondre à son nom Vault (kvv2/<app>) et à son rôle k8s
|
||||
bucket = string # son bucket, qui doit exister dans `values.yaml` du chart (bloc `buckets`)
|
||||
}))
|
||||
default = [
|
||||
{ app = "kadans", bucket = "kadans-videos" },
|
||||
]
|
||||
description = "Apps autorisées à stocker des objets. Chacune reçoit un compte de service borné à SON bucket, dont les clés atterrissent dans kvv2/minio/<app>. Ajouter une app ici ne suffit pas : elle doit aussi déclarer kv_read_paths = [\"kvv2/data/minio/<app>\"] dans hashicorp-vault/iac/terraform.tfvars."
|
||||
}
|
||||
Reference in New Issue
Block a user