Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7074a94d7e | ||
|
|
ec71571b98 | ||
|
|
0e1b6e1062 | ||
|
|
287e3dcf1e | ||
|
|
91a0f09b49 | ||
|
|
4ca4a05370 | ||
|
|
f49a79e393 | ||
|
|
1490f4c514 | ||
|
|
dce19141a8 |
@@ -36,6 +36,13 @@ data "vault_policy_document" "ops" {
|
|||||||
path = "kvv1/google/credentials"
|
path = "kvv1/google/credentials"
|
||||||
capabilities = ["read"]
|
capabilities = ["read"]
|
||||||
}
|
}
|
||||||
|
# Provisionneur MinIO — l'app crée ses buckets depuis son dépôt sans le root.
|
||||||
|
# ADR : factory/doc/adr/20260726-stockage-objet-minio.md
|
||||||
|
rule {
|
||||||
|
path = "kvv2/data/minio/provisioner"
|
||||||
|
capabilities = ["read"]
|
||||||
|
}
|
||||||
|
|
||||||
# read cloudflare related secrets
|
# read cloudflare related secrets
|
||||||
rule {
|
rule {
|
||||||
path = "kvv1/cloudflare/${local.name}*"
|
path = "kvv1/cloudflare/${local.name}*"
|
||||||
@@ -178,6 +185,19 @@ data "vault_policy_document" "app" {
|
|||||||
path = "postgres/creds/${local.name}*"
|
path = "postgres/creds/${local.name}*"
|
||||||
capabilities = ["read"]
|
capabilities = ["read"]
|
||||||
}
|
}
|
||||||
|
# Ses identifiants MinIO. INCONDITIONNEL : le chemin porte le nom de l'app,
|
||||||
|
# donc la règle ne peut exposer que ses propres clés ; une app sans stockage
|
||||||
|
# lit un chemin qui n'existe pas.
|
||||||
|
# ADR : factory/doc/adr/20260726-stockage-objet-minio.md
|
||||||
|
rule {
|
||||||
|
path = "kvv2/data/minio/${local.name}/*"
|
||||||
|
capabilities = ["read", "list"]
|
||||||
|
}
|
||||||
|
rule {
|
||||||
|
# Le document lui-même (la règle ci-dessus ne couvre que ses descendants).
|
||||||
|
path = "kvv2/data/minio/${local.name}"
|
||||||
|
capabilities = ["read", "list"]
|
||||||
|
}
|
||||||
# Extra shared paths this app's prod runtime may read (e.g. backup creds).
|
# Extra shared paths this app's prod runtime may read (e.g. backup creds).
|
||||||
dynamic "rule" {
|
dynamic "rule" {
|
||||||
for_each = var.kv_read_paths
|
for_each = var.kv_read_paths
|
||||||
@@ -204,6 +224,15 @@ data "vault_policy_document" "app_non_prod" {
|
|||||||
path = "postgres/creds/${each.key}*"
|
path = "postgres/creds/${each.key}*"
|
||||||
capabilities = ["read"]
|
capabilities = ["read"]
|
||||||
}
|
}
|
||||||
|
# Idem prod : chaque instance lit les identifiants MinIO portant SON nom.
|
||||||
|
rule {
|
||||||
|
path = "kvv2/data/minio/${each.key}/*"
|
||||||
|
capabilities = ["read", "list"]
|
||||||
|
}
|
||||||
|
rule {
|
||||||
|
path = "kvv2/data/minio/${each.key}"
|
||||||
|
capabilities = ["read", "list"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
resource "vault_policy" "app_non_prod" {
|
resource "vault_policy" "app_non_prod" {
|
||||||
for_each = toset(local.non_prod_instances)
|
for_each = toset(local.non_prod_instances)
|
||||||
|
|||||||
+93
-2
@@ -23,9 +23,9 @@ application vivent avec cette application.
|
|||||||
| Mode | **standalone** (1 réplique) — la donnée est dérivée et Longhorn réplique déjà le volume ; l'erasure coding distribué coûterait de la RAM que des Pi 5 n'ont pas à dépenser pour ça |
|
| Mode | **standalone** (1 réplique) — la donnée est dérivée et Longhorn réplique déjà le volume ; l'erasure coding distribué coûterait de la RAM que des Pi 5 n'ont pas à dépenser pour ça |
|
||||||
| Volume | **50 Gi** sur `longhorn` ≈ **250 h de cours** au palier « travail ». ⚠ Longhorn réplique : compter **×3** sur la capacité du cluster avant d'augmenter |
|
| Volume | **50 Gi** sur `longhorn` ≈ **250 h de cours** au palier « travail ». ⚠ Longhorn réplique : compter **×3** sur la capacité du cluster avant d'augmenter |
|
||||||
| Ressources | requests 512 Mi / 100 m · limit 2 Gi — la limite protège les voisins de `tools`, pas MinIO |
|
| Ressources | requests 512 Mi / 100 m · limit 2 Gi — la limite protège les voisins de `tools`, pas MinIO |
|
||||||
| API S3 | `s3.arcodange.lab` (Traefik) |
|
| API S3 | `s3.arcodange.lab` (interne) **et `s3.arcodange.fr`** (public, tunnel Cloudflare → entrypoint `web` + crowdsec) — voir « Pourquoi une exposition publique » |
|
||||||
| Console | `minio.arcodange.lab` (Traefik) |
|
| Console | `minio.arcodange.lab` (Traefik) |
|
||||||
| Bucket | `kadans-videos`, **privé** — l'accès passe par des URL signées (ADR-0002 du dossier produit) |
|
| Buckets | **aucun ici** — chaque app déclare les siens depuis son dépôt (module `minio_app`). Tous privés : l'accès passe par des URL signées (ADR-0002 du dossier produit) |
|
||||||
| Identifiants | **jamais dans le dépôt** : `iac/` les génère dans Vault (`kvv2/minio/config`), le Vault Secrets Operator les matérialise en secret `minio-config`, le chart les lit via `existingSecret` |
|
| Identifiants | **jamais dans le dépôt** : `iac/` les génère dans Vault (`kvv2/minio/config`), le Vault Secrets Operator les matérialise en secret `minio-config`, le chart les lit via `existingSecret` |
|
||||||
|
|
||||||
Le ServiceAccount du pod est nommé `minio` (et non le `minio-sa` par défaut du
|
Le ServiceAccount du pod est nommé `minio` (et non le `minio-sa` par défaut du
|
||||||
@@ -52,3 +52,94 @@ L'ordre compte, et il compte **deux fois** :
|
|||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> Sans le secret `minio-config`, le pod ne démarre pas. C'est voulu — mieux
|
> Sans le secret `minio-config`, le pod ne démarre pas. C'est voulu — mieux
|
||||||
> vaut un pod en attente qu'un MinIO ouvert avec des identifiants par défaut.
|
> vaut un pod en attente qu'un MinIO ouvert avec des identifiants par défaut.
|
||||||
|
|
||||||
|
|
||||||
|
## Pourquoi une exposition publique (`s3.arcodange.fr`)
|
||||||
|
|
||||||
|
La PWA Kadans est servie en `https://kadans.arcodange.fr` et téléverse ses vidéos
|
||||||
|
**directement** vers MinIO, avec des URL présignées émises par kadans-api
|
||||||
|
(kadans-api#23) : les octets ne passent jamais par l'API.
|
||||||
|
|
||||||
|
Deux raisons rendent le `.lab` inutilisable pour ça, et ce sont des faits du
|
||||||
|
navigateur, pas des préférences :
|
||||||
|
|
||||||
|
1. **Contenu mixte** — une page servie en `https` ne peut pas émettre une requête
|
||||||
|
vers `http://`. L'ingress `.lab` est en entrypoint `web` sans TLS.
|
||||||
|
2. **`.lab` n'est pas résolvable hors du LAN** — la synchronisation ne marcherait
|
||||||
|
qu'à la maison, ce qui vide de son sens « retrouver mes vidéos sur mon autre
|
||||||
|
appareil ».
|
||||||
|
|
||||||
|
**Pas de basic-auth** sur cet ingress, contrairement à `kadans-public` : une
|
||||||
|
requête S3 porte sa propre signature (SigV4). Un défi HTTP Basic casserait le PUT
|
||||||
|
présigné, auquel le navigateur ne peut pas répondre. L'autorisation vient de
|
||||||
|
l'URL signée et de sa durée de vie courte (15 min pour déposer, 1 h pour lire).
|
||||||
|
|
||||||
|
**CORS** (`MINIO_API_CORS_ALLOW_ORIGIN`) liste les origines EXACTES de la PWA —
|
||||||
|
jamais `*` : une URL présignée qui fuiterait serait sinon rejouable depuis
|
||||||
|
n'importe quel site.
|
||||||
|
|
||||||
|
### ⚠ À vérifier avant de s'y fier : la taille maximale d'une requête
|
||||||
|
|
||||||
|
Le trafic public passe par un **tunnel Cloudflare**. Les offres gratuites de
|
||||||
|
Cloudflare plafonnent la taille du corps d'une requête proxifiée (de l'ordre de
|
||||||
|
**100 Mo**) — ce plafond n'a **pas** été mesuré ici, il doit l'être avec un vrai
|
||||||
|
téléversement avant d'annoncer une limite aux utilisateurs.
|
||||||
|
|
||||||
|
Ce qu'on sait, en revanche, et qui rend le sujet peu urgent : sur le corpus réel
|
||||||
|
du fondateur (707 vidéos, ~2 ans), **la durée moyenne est de 53 secondes** et
|
||||||
|
**deux vidéos seulement dépassent 5 minutes**. Au palier « travail » de l'ADR-018
|
||||||
|
(360p ≈ 3,4 Mo/min), 100 Mo représentent ~29 minutes de cours : le corpus entier
|
||||||
|
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
|
||||||
|
|
||||||
|
**Rien à faire ici.** Chaque application déclare **ses** buckets **depuis son
|
||||||
|
propre dépôt**, avec le module que ce dépôt-ci fournit :
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# iac/main.tf de l'application
|
||||||
|
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::…/tools.git//minio/iac/modules/minio_app?depth=1&ref=main"
|
||||||
|
app = "mon-app"
|
||||||
|
buckets = ["mon-app-fichiers"]
|
||||||
|
providers = { minio = minio }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Voir `iac/modules/minio_app/README.md`. **Chacun son périmètre** : `tools`
|
||||||
|
fournit le serveur, le provisionneur et le module — pas la liste des buckets.
|
||||||
|
Sans ça, chaque bucket de chaque app deviendrait une PR sur l'infra partagée.
|
||||||
|
|
||||||
|
### Ce que `tools` fournit, et pourquoi
|
||||||
|
|
||||||
|
| Pièce | Rôle |
|
||||||
|
|---|---|
|
||||||
|
| Le serveur | le chart, son volume, ses ingress (interne + public) |
|
||||||
|
| Le **root** | généré ici, écrit dans `kvv2/minio/config`, **ne sort jamais** de ce pipeline |
|
||||||
|
| Le **provisionneur** | un compte aux droits d'administration MINIMAUX (créer bucket, politique, compte de service) et **aucun droit sur les objets** — lisible par le rôle CI de chaque app |
|
||||||
|
| Le **module** | `minio_app` : standardise la déclaration, sans la détenir |
|
||||||
|
|
||||||
|
Donner le root aux apps aurait été absurde : il lit et écrit **tous** les objets
|
||||||
|
de **toutes** les apps. Le provisionneur, lui, peut créer des buckets — une
|
||||||
|
nuisance si une app est compromise — mais **pas lire les vidéos d'une autre**.
|
||||||
|
|
||||||
|
### Rotation
|
||||||
|
|
||||||
|
Depuis l'`iac/` de l'app : détruire `module.stockage.random_password.app` et
|
||||||
|
relancer son plan. La clé change, `force_destroy = false` garde le compte, et
|
||||||
|
les objets déjà déposés conservent leur propriétaire.
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# `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 :
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# 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 ;
|
||||||
|
- 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 règle de cycle de vie, ni versioning : ces choix
|
||||||
|
appartiennent à l'app et varient d'un bucket à l'autre. À ajouter le jour où
|
||||||
|
un besoin réel apparaît, pas avant.
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# Module `minio_app` — une app déclare SES buckets depuis SON dépôt.
|
||||||
|
# Décisions : factory/doc/adr/20260726-stockage-objet-minio.md
|
||||||
|
|
||||||
|
resource "minio_s3_bucket" "app" {
|
||||||
|
for_each = toset(var.buckets)
|
||||||
|
bucket = each.key
|
||||||
|
acl = "private" # l'accès passe par des URL présignées
|
||||||
|
force_destroy = false # détruire un bucket doit être un geste explicite
|
||||||
|
}
|
||||||
|
|
||||||
|
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}/*"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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"
|
||||||
|
secret = random_password.app.result
|
||||||
|
force_destroy = false # une rotation ne recrée pas l'utilisateur : les objets gardent leur propriétaire
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "minio_iam_user_policy_attachment" "app" {
|
||||||
|
user_name = minio_iam_user.app.id
|
||||||
|
policy_name = minio_iam_policy.app.id
|
||||||
|
}
|
||||||
|
|
||||||
|
# Lu par le pod de l'app — `app_policy` lui accorde déjà ce chemin.
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
output "vault_path" {
|
||||||
|
value = "kvv2/minio/${var.app}"
|
||||||
|
description = "Où le pod lira ses identifiants (VaultStaticSecret)."
|
||||||
|
}
|
||||||
|
|
||||||
|
output "buckets" {
|
||||||
|
value = var.buckets
|
||||||
|
description = "Écho des buckets créés."
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
minio = {
|
||||||
|
source = "aminueza/minio"
|
||||||
|
configuration_aliases = [minio]
|
||||||
|
}
|
||||||
|
vault = {
|
||||||
|
source = "hashicorp/vault"
|
||||||
|
}
|
||||||
|
random = {
|
||||||
|
source = "hashicorp/random"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
variable "app" {
|
||||||
|
type = string
|
||||||
|
description = "Nom de l'app (= son rôle Vault). Décide du chemin du secret et du nom du compte de service."
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "buckets" {
|
||||||
|
type = list(string)
|
||||||
|
description = "Ses buckets, créés ici (privés). Le compte de service n'a de droits que sur eux."
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "endpoint" {
|
||||||
|
type = string
|
||||||
|
default = "s3.arcodange.fr"
|
||||||
|
description = "Hôte de l'API S3, sans schéma. Public : le runner CI n'est pas dans le LAN."
|
||||||
|
}
|
||||||
@@ -4,6 +4,14 @@ terraform {
|
|||||||
source = "vault"
|
source = "vault"
|
||||||
version = "4.4.0"
|
version = "4.4.0"
|
||||||
}
|
}
|
||||||
|
minio = {
|
||||||
|
source = "aminueza/minio"
|
||||||
|
version = "3.3.0"
|
||||||
|
}
|
||||||
|
random = {
|
||||||
|
source = "hashicorp/random"
|
||||||
|
version = "3.6.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,3 +22,11 @@ provider "vault" {
|
|||||||
role = "gitea_cicd_minio"
|
role = "gitea_cicd_minio"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Provider MinIO — crée le compte de provisionnement (provisioner.tf).
|
||||||
|
provider "minio" {
|
||||||
|
minio_server = var.minio_endpoint
|
||||||
|
minio_user = local.config.rootUser
|
||||||
|
minio_password = local.config.rootPassword
|
||||||
|
minio_ssl = true
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# Compte de PROVISIONNEMENT : crée buckets, politiques et comptes de service —
|
||||||
|
# aucun droit sur les objets. C'est lui que lisent les rôles CI des apps, pour
|
||||||
|
# qu'elles déclarent leurs buckets sans qu'on leur confie le root.
|
||||||
|
# Décisions : factory/doc/adr/20260726-stockage-objet-minio.md
|
||||||
|
#
|
||||||
|
# ⚠ Noms d'actions issus de la documentation MinIO, NON éprouvés contre le
|
||||||
|
# serveur : le premier apply les confirmera ou les corrigera.
|
||||||
|
resource "minio_iam_policy" "provisioner" {
|
||||||
|
name = "provisioner"
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = [
|
||||||
|
"admin:CreateUser",
|
||||||
|
"admin:DeleteUser",
|
||||||
|
"admin:ListUsers",
|
||||||
|
"admin:GetUser",
|
||||||
|
"admin:CreatePolicy",
|
||||||
|
"admin:DeletePolicy",
|
||||||
|
"admin:GetPolicy",
|
||||||
|
"admin:ListUserPolicies",
|
||||||
|
"admin:AttachUserOrGroupPolicy",
|
||||||
|
]
|
||||||
|
Resource = ["arn:aws:s3:::*"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# s3:GetObject / s3:PutObject volontairement ABSENTS.
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = ["s3:CreateBucket", "s3:DeleteBucket", "s3:ListAllMyBuckets", "s3:GetBucketLocation", "s3:GetBucketPolicy", "s3:PutBucketPolicy"]
|
||||||
|
Resource = ["arn:aws:s3:::*"]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "random_password" "provisioner" {
|
||||||
|
length = 40
|
||||||
|
special = false
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "minio_iam_user" "provisioner" {
|
||||||
|
name = "provisioner"
|
||||||
|
secret = random_password.provisioner.result
|
||||||
|
force_destroy = false
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "minio_iam_user_policy_attachment" "provisioner" {
|
||||||
|
user_name = minio_iam_user.provisioner.id
|
||||||
|
policy_name = minio_iam_policy.provisioner.id
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_kv_secret_v2" "provisioner" {
|
||||||
|
mount = "kvv2"
|
||||||
|
name = "minio/provisioner"
|
||||||
|
data_json = jsonencode({
|
||||||
|
MINIO_ENDPOINT = var.minio_endpoint
|
||||||
|
MINIO_ACCESS_KEY = minio_iam_user.provisioner.id
|
||||||
|
MINIO_SECRET_KEY = random_password.provisioner.result
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
variable "minio_endpoint" {
|
||||||
|
type = string
|
||||||
|
default = "s3.arcodange.fr"
|
||||||
|
description = "Hôte de l'API S3, sans schéma. Public : le runner CI n'est pas dans le LAN."
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Exposition PUBLIQUE s3.arcodange.fr — TLS terminé par le tunnel Cloudflare,
|
||||||
|
# middleware crowdsec. Le `.lab` interne reste inchangé.
|
||||||
|
#
|
||||||
|
# ⚠ PAS de basic-auth ici, contrairement à kadans-public : une requête S3 porte
|
||||||
|
# sa propre signature (SigV4), et un défi HTTP Basic casserait le PUT présigné
|
||||||
|
# auquel le navigateur ne peut pas répondre.
|
||||||
|
#
|
||||||
|
# ADR : factory/doc/adr/20260726-stockage-objet-minio.md
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: minio-public
|
||||||
|
namespace: tools
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||||
|
traefik.ingress.kubernetes.io/router.middlewares: kube-system-crowdsec@kubernetescrd
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: s3.arcodange.fr
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: minio
|
||||||
|
port:
|
||||||
|
number: 9000
|
||||||
+7
-5
@@ -61,11 +61,10 @@ minio: &minio_config
|
|||||||
# Buckets créés au déploiement. `versioning: false` assumé : ces objets sont
|
# Buckets créés au déploiement. `versioning: false` assumé : ces objets sont
|
||||||
# DÉRIVÉS et re-générables depuis le master local — versionner doublerait le
|
# DÉRIVÉS et re-générables depuis le master local — versionner doublerait le
|
||||||
# stockage pour un filet dont on n'a pas besoin.
|
# stockage pour un filet dont on n'a pas besoin.
|
||||||
buckets:
|
# AUCUN bucket ici : chaque app déclare les siens depuis son dépôt, via le
|
||||||
- name: kadans-videos
|
# module `iac/modules/minio_app`.
|
||||||
policy: none # privé : l'accès passe par des URL signées (ADR-0002 du dossier)
|
# ADR : factory/doc/adr/20260726-stockage-objet-minio.md
|
||||||
purge: false
|
buckets: []
|
||||||
versioning: false
|
|
||||||
|
|
||||||
# Métriques : Prometheus (namespace `tools`) scrape déjà la façade et le
|
# Métriques : Prometheus (namespace `tools`) scrape déjà la façade et le
|
||||||
# laptop (ADR-0014 du dossier) — MinIO rejoint la même vue.
|
# laptop (ADR-0014 du dossier) — MinIO rejoint la même vue.
|
||||||
@@ -74,6 +73,9 @@ minio: &minio_config
|
|||||||
enabled: false # pas d'opérateur Prometheus ici : scrape par annotation
|
enabled: false # pas d'opérateur Prometheus ici : scrape par annotation
|
||||||
environment:
|
environment:
|
||||||
MINIO_PROMETHEUS_AUTH_TYPE: "public"
|
MINIO_PROMETHEUS_AUTH_TYPE: "public"
|
||||||
|
# CORS : le navigateur téléverse directement (URL présignées) — origines
|
||||||
|
# EXACTES, jamais « * » : une URL qui fuite serait sinon rejouable partout.
|
||||||
|
MINIO_API_CORS_ALLOW_ORIGIN: "https://kadans.arcodange.fr,https://kadans.arcodange.lab"
|
||||||
|
|
||||||
tool:
|
tool:
|
||||||
# kind: 'SubChart' or 'HelmChart', if subchart then uncomment Chart.yaml dependency, else comment and use tool library with helm chart template
|
# kind: 'SubChart' or 'HelmChart', if subchart then uncomment Chart.yaml dependency, else comment and use tool library with helm chart template
|
||||||
|
|||||||
Reference in New Issue
Block a user