Commit Graph
12 Commits
Author SHA1 Message Date
arcodangeandClaude Opus 5 6d09adab0c feat(minio) — un téléversement abandonné laisse des parts que RIEN ne montre, et le plafond du tunnel est enfin mesuré
Helm Charts / Detect changed charts (pull_request) Successful in 26s
Helm Charts / Library charts tool (pull_request) Skipped
Helm Charts / Application charts pgcat (pull_request) Skipped
## Le plafond, mesuré — la section du README qui disait « à vérifier » ne le dit plus

`minio/README.md` portait : « 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 ».

Sonde par PUT NON SIGNÉ vers le bucket : rien ne s'écrit, et les deux réponses se
distinguent proprement — un 403 vient de MinIO (le corps a donc traversé le
tunnel), un 413 vient du tunnel.

    100 Mio → 403   corps passé
    101 Mio → 413   refusé par le tunnel

⚠ Le plafond est EXACTEMENT 100 Mio, alors que kadans-api annonçait 200 Mio.

⚠ Et la parade retenue n'est PAS celle que le README recommandait. Ramener le
gabarit sous le plafond aurait aussi fermé les cours longs (~29 min au palier
« travail »). C'est le téléversement en PARTS qui a été livré (kadans-api #188).

## Ce que ça crée comme déchet, et à qui il appartient

Un téléversement en parts jamais refermé laisse ses parts dans le bucket :
`mc ls` n'en dit rien, la console non plus, aucun objet ne les montre. Une fuite
qui ne se voit qu'à la facture — ou à la saturation d'un volume de 50 Gi.

L'app abandonne ce qu'elle ouvre quand elle échoue en route. Elle ne peut PAS
rattraper le navigateur qui ferme l'onglet.

⚠ LE README DU MODULE DISAIT « il ne pose ni quota, ni règle de cycle de vie » —
et cette règle-ci ne le contredit pas, elle en précise la frontière. La question
qui tranche est « à QUOI cette connaissance appartient-elle ? » :

  - une EXPIRATION DE CONTENU (« ces vidéos se purgent à 90 jours ») demande de
    connaître le produit. Elle est chez l'app ;
  - un téléversement incomplet n'est le contenu de PERSONNE. Aucune app ne veut
    le garder, aucune ne peut le voir depuis son code. C'est un déchet de
    PROTOCOLE, produit par le mécanisme même du bucket : il est chez celui qui
    crée les buckets.

Test pratique écrit au README : si répondre à « combien de temps ? » exige de
connaître le produit, c'est chez l'app. Ici la réponse n'exige que de connaître
S3 — passé l'expiration des URL signées (2 h côté kadans-api), un téléversement
ne peut plus RIEN recevoir. D'où 1 jour, douze fois la marge, et pas 7.

⚠ Non paramétrable (YAGNI) : un seul cas. Le déclencheur pour en faire une
variable est écrit — une app qui signerait des parts au-delà de 24 h.

## ⚠ Ce réglage-ci fonctionne, contrairement au CORS par bucket

MinIO communautaire stubbe `PutBucketCors` en 501 (`cmd/dummy-handlers.go`), ce
qui avait déjà coûté une tentative d'IaC — c'est écrit dans le `iac/main.tf` du
dépôt front. Les handlers de CYCLE DE VIE, eux, n'y figurent PAS : vérifié à la
source avant d'écrire une ligne, pour ne pas répéter exactement cette erreur.

## Le plancher de version, et pourquoi il est là

`abort_incomplete_multipart_upload` est apparu en **3.10.0** — mesuré en
interrogeant le schéma du provider version par version (3.9.0 ne l'a pas,
3.10.0 l'a). Le module déclare donc `>= 3.10.0` LUI-MÊME.

⚠ Sans ce plancher, un appelant resté sur 3.3.0 échouerait au plan sur un
« unsupported block type » qui ne dit pas qu'il faut monter de version. Avec, il
lit dès `tofu init` : « no available releases match the given constraints 3.3.0,
>= 3.10.0 ».

## ⚠ ORDRE DE FUSION

Cette PR fait passer tout appelant du module sous le plancher 3.10.0. Le dépôt
`kadans` épingle encore 3.3.0 : **son bump doit atterrir AVANT celle-ci**, sinon
son apply casse entre les deux fusions.

## Preuve

`tofu validate` contre le schéma RÉEL du provider 3.10.0, module instancié depuis
un bac à sable (aucun backend, aucun appel à MinIO ni Vault) : « Success! The
configuration is valid. »

Et le plancher a été éprouvé plutôt que relu : épinglé à 3.3.0, `tofu init` rend
bien le refus cité ci-dessus.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-20 20:06:02 +02:00
arcodangeandClaude Opus 5 2cb19809c6 doc(reseau) — le confort LAN ne se règle pas dans Traefik : mesure, pièges, voie retenue
Helm Charts / Detect changed charts (pull_request) Successful in 1m6s
Helm Charts / Library charts tool (pull_request) Has been skipped
Helm Charts / Application charts pgcat (pull_request) Has been skipped
Demande : « ne pas avoir le basic auth Traefik quand on est sur le Wi-Fi partagé
avec le homelab ». La réponse intuitive — une règle `ClientIP(192.168.1.0/24)` —
ne peut pas marcher, et la variante « je regarde CF-Connecting-IP » est un
contournement d'authentification. Ce commit écrit la mesure pour qu'on ne
re-découvre ni l'une ni l'autre.

Mesuré (2026-07-28, homelab) :

- `kadans.arcodange.fr` résout vers Cloudflare MÊME depuis le LAN, et redescend
  par le tunnel cloudflared. L'adresse de socket vue par Traefik est donc
  toujours celle d'un pod, en 10.42.x.x.
- Traefik SAIT qui est le vrai client (journal d'accès : l'IPv6 de la maison,
  `2a01:cb04:dff:cf00::/64` — pas l'IPv4 qu'`ipify` injecte dans `localIp`), car
  l'entrypoint `web` fait confiance aux en-têtes venant de 10.42.0.0/16.
- Mais le matcher de routeur `ClientIP()`, lui, juge la SOCKET. Prouvé par trois
  routeurs temporaires vers un Service sans endpoint (503 = règle matchée,
  401 = repli sur le routeur normal) : témoin 503, `ClientIP(<IPv6 maison>)` 401,
  `ClientIP(10.42.0.0/16)` 503. Sondes retirées après mesure.

D'où deux pièges, écrits là où on les rencontrerait :

- `localIp@file` sur un routeur `.fr` laisserait entrer Internet entier, parce
  que `10.42.0.0/16` est dans son `sourceRange` et qu'`ipAllowList` juge lui
  aussi la socket. Vérifié : aucun routeur `.fr` ne le porte aujourd'hui — le
  piège est latent, et les cinq endroits d'où on pourrait le recopier
  (2 gabarits `.fr`, 3 values `.lab`) portent désormais l'avertissement.
- Un en-tête posé par le client ne peut pas piloter une exemption d'auth :
  Cloudflare ne retire pas les en-têtes inconnus, et Traefik reste joignable en
  direct sur 192.168.1.201.

Voie retenue, et pourquoi celle-là : la décision « suis-je à la maison ? » doit
être prise là où la vraie IP est native et non falsifiable, donc au bord, chez
Cloudflare. Une règle de transformation y pré-remplit l'en-tête `Authorization`
pour les IP du foyer. Traefik ne bouge pas, aucun certificat public à produire,
aucun changement DNS. Surface d'attaque ajoutée : AUCUNE — `kadans:kkadans` est
déjà en clair dans le chart de kadans, Cloudflare ne fait que le taper à notre
place. Et quand l'IP du foyer dérive, la règle cesse de matcher : le navigateur
redemande le mot de passe. Dégradation douce, pas de panne.

Le doc écrit l'action Cloudflare mot pour mot (expression, en-tête, valeur, et
l'équivalent Terraform pour `cms/cloudflare`) plutôt que de la supposer faite,
avec les deux commandes qui la vérifient — et nomme le repli si Cloudflare
refuse de modifier `Authorization` : Cloudflare Access avec une politique de
bypass, PAS un en-tête secret.

Enfin, un contre-exemple utile : le bouncer CrowdSec, lui, juge la VRAIE IP
(journal à l'appui). Son `clientTrustedIPs` contient `10.42.0.0/16` sans que ce
soit un trou — noté pour que personne ne « corrige » cette ligne en croyant y
reproduire le piège.

⚠ Aucun des réglages en cause ne vit dans ce dépôt : les valeurs Helm de
Traefik, `dynamic.yaml`/`localIp` et le Middleware crowdsec sont dans `factory`
(ansible), le tunnel et le DNS dans `cms`. Ce dépôt reçoit la mesure et les
garde-fous parce que c'est lui qui héberge les gabarits `.fr` et les
consommateurs de `localIp@file`.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GdUCA5Uz8QyMwa2P4Pg2hK
2026-07-28 14:29:35 +02:00
arcodangeandClaude Opus 5 e8ab19962b fix(minio) — le provisionneur ne pouvait pas créer de bucket : s3:ListBucket manquait
Helm Charts / Detect changed charts (push) Successful in 15s
Helm Charts / Detect changed charts (pull_request) Successful in 16s
Helm Charts / Library charts tool (push) Has been skipped
MinIO / Auth with gitea for vault (push) Failing after 8m31s
MinIO / Tofu - minio IAC (push) Has been skipped
Helm Charts / Library charts tool (pull_request) Has been skipped
Helm Charts / Application charts pgcat (push) Has been skipped
MinIO / Auth with gitea for vault (pull_request) Failing after 8m32s
MinIO / Tofu - minio IAC (pull_request) Has been skipped
Helm Charts / Application charts pgcat (pull_request) Has been skipped
Le premier apply réel (kadans) a créé la politique, le compte de service,
l'attachement et le secret Vault — puis a échoué sur le bucket lui-même :

    Error: [FATAL] unable to check bucket (kadans-videos): Access Denied.

Le provider teste l'existence du bucket AVANT de le créer (HeadBucket), et
MinIO exige `s3:ListBucket` pour ça. C'est le seul point que l'ADR annonçait
comme non éprouvé ; il l'est maintenant.

`s3:ListBucket` donne la vue des CLÉS d'un bucket, pas leur contenu :
GetObject / PutObject restent absents, donc le provisionneur — partagé entre
les rôles CI — ne peut toujours pas lire les vidéos d'une autre application.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01CoafGWmRVESaWX819USUUA
2026-07-26 11:43:04 +02:00
arcodangeandClaude Opus 5 7074a94d7e docs(minio) — le raisonnement part dans l'ADR, le code garde ce qui protège
Helm Charts / Detect changed charts (push) Successful in 22s
Helm Charts / Detect changed charts (pull_request) Successful in 16s
MinIO / Auth with gitea for vault (push) Failing after 8m33s
MinIO / Tofu - minio IAC (push) Has been skipped
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 / Library charts tool (push) Has been skipped
Helm Charts / Library charts tool (pull_request) Has been skipped
Hashicorp Vault / Auth with gitea for vault (push) Failing after 8m31s
Hashicorp Vault / Tofu - Vault IAC (push) Has been skipped
Helm Charts / Application charts pgcat (push) Has been skipped
Helm Charts / Application charts pgcat (pull_request) Has been skipped
Les décisions de conception (qui déclare, qui détient, pourquoi les octets ne
passent pas par l'API) vivent désormais dans
`factory/doc/adr/20260726-stockage-objet-minio.md`.

Le code ne garde que ce qu'un relecteur ne peut pas deviner et qui l'empêcherait
de casser quelque chose : l'absence VOLONTAIRE de s3:GetObject/PutObject dans la
politique du provisionneur, l'absence VOLONTAIRE de basic-auth sur l'ingress
S3, le caractère inconditionnel de la règle Vault, et l'avertissement sur les
noms d'actions MinIO non éprouvés. Chaque fichier pointe l'ADR.

283 → 230 lignes sur minio/iac. tofu fmt propre, tofu validate réussi sur les
deux racines.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01CoafGWmRVESaWX819USUUA
2026-07-26 10:19:28 +02:00
arcodangeandClaude Opus 5 ec71571b98 refactor(minio) — chacun son périmètre : les buckets se déclarent depuis le dépôt de l'app
Helm Charts / Detect changed charts (push) Successful in 16s
Helm Charts / Detect changed charts (pull_request) Successful in 16s
MinIO / Auth with gitea for vault (pull_request) Failing after 8m32s
MinIO / Tofu - minio IAC (pull_request) Has been skipped
MinIO / Auth with gitea for vault (push) Failing after 8m31s
MinIO / Tofu - minio IAC (push) Has been skipped
Helm Charts / Library charts tool (push) Has been cancelled
Helm Charts / Application charts pgcat (push) Has been cancelled
Hashicorp Vault / Auth with gitea for vault (push) Has been cancelled
Hashicorp Vault / Tofu - Vault IAC (push) Has been cancelled
Helm Charts / Library charts tool (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 (pull_request) Has been skipped
« Les buckets sont à déclarer dans le repo de kadans-api. On ne va pas modifier
le repo tools à chaque changement d'application. Chacun son périmètre. Tools
peut proposer un module pour standardiser la déclaration de buckets à la
limite, mais c'est tout. » (fondateur, 26/07)

C'est une erreur de fond de ma part : j'avais fait de `tools` le PROPRIÉTAIRE de
déclarations qui appartiennent aux applications. À ce rythme, chaque nouveau
bucket de n'importe quelle app devenait une PR sur l'infra partagée.

CE QUI CHANGE. `consumers.tf` disparaît, et la liste de buckets du chart se vide.
À la place, un module réutilisable `iac/modules/minio_app` : une app lui donne
son nom et ses buckets, et reçoit des buckets privés, un compte de service qui
ne peut rien toucher d'autre, et ses clés dans `kvv2/minio/<app>`.

L'OBSTACLE, ET SA RÉPONSE. Déclarer ses buckets depuis son propre dépôt suppose
des droits d'ADMINISTRATION sur MinIO. Confier le root serait absurde : il lit et
écrit tous les objets de toutes les apps. `tools` fournit donc un compte
PROVISIONNEUR aux droits minimaux — créer un bucket, une politique, un compte de
service — et AUCUN droit sur les objets. Une app compromise pourrait créer des
buckets (une nuisance), pas lire les vidéos d'une autre. Le root, lui, ne sort
toujours pas de ce pipeline.

Le rôle CI de chaque app gagne la lecture de `kvv2/data/minio/provisioner` dans
`app_policy` — générique, et c'est exactement le genre de standardisation qui
appartient au dépôt commun.

⚠ CE QUE JE N'AI PAS PU PROUVER : les noms d'actions d'administration MinIO de la
politique du provisionneur viennent de la documentation, pas d'un essai — je n'ai
pas d'identifiants admin en main. Le premier `apply` les confirmera ou les
corrigera. C'est le seul point non vérifié de cette PR, et il est signalé dans le
code à l'endroit exact.

tofu fmt propre · tofu validate réussi sur minio/iac ET hashicorp-vault/iac.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01CoafGWmRVESaWX819USUUA
2026-07-26 10:07:05 +02:00
arcodangeandClaude Opus 5 0e1b6e1062 refactor(minio) — lever la restriction « un seul bucket par app »
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
2026-07-26 09:46:32 +02:00
arcodangeandClaude Opus 5 287e3dcf1e refactor(minio) — le BUCKET est la seule déclaration : plus de liste à tenir
Helm Charts / Detect changed charts (push) Successful in 14s
Helm Charts / Detect changed charts (pull_request) Successful in 14s
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 been cancelled
MinIO / Auth with gitea for vault (pull_request) Failing after 8m31s
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 / Library charts tool (pull_request) Has been skipped
Helm Charts / Application charts pgcat (pull_request) Has been skipped
« Je ne vois pas le mal à donner la permission de lire sur un chemin qui n'existe
pas. Je préfère ne pas m'embêter avec consumers ou autre. » (fondateur, 26/07)

`var.consumers` disparaît. Le plan LIT `values.yaml` du chart — le même fichier
qu'Helm consomme — et provisionne un compte de service par bucket. Créer un
bucket EST la déclaration : il devient impossible d'avoir un bucket sans son
compte, ou un compte sans son bucket. Une liste de plus aurait été une liste à
tenir synchronisée, donc une liste à oublier.

La convention qui rend ça possible : UN BUCKET PAR APP, NOMMÉ COMME ELLE. Le
bucket passe donc de `kadans-videos` à `kadans`. Il est VIDE aujourd'hui — le
renommer maintenant ne coûte rien ; dans un mois ce serait une migration.

Tout en découle sans être écrit ailleurs : le compte `<app>-app` borné à ce seul
bucket, le secret `kvv2/minio/<app>`, et la lecture que `app_policy` accorde
déjà à toute app sur `kvv2/data/minio/<son nom>`.

Vérifié plutôt que supposé : `yamldecode` lit bien ce values.yaml, ancres YAML
comprises (testé en isolation avant d'écrire le plan). 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
2026-07-26 09:38:24 +02:00
arcodangeandClaude Opus 5 91a0f09b49 refactor(vault) — lire ses identifiants MinIO devient une propriété de la plateforme
Helm Charts / Detect changed charts (pull_request) Successful in 15s
Helm Charts / Detect changed charts (push) Successful in 15s
Helm Charts / Library charts tool (push) Has been cancelled
Helm Charts / Application charts pgcat (push) Has been cancelled
Hashicorp Vault / Auth with gitea for vault (push) Failing after 8m35s
Hashicorp Vault / Tofu - Vault IAC (push) Has been skipped
MinIO / Auth with gitea for vault (pull_request) Failing after 8m32s
MinIO / Tofu - minio IAC (pull_request) Has been skipped
Helm Charts / Library charts tool (pull_request) Has been skipped
Hashicorp Vault / Auth with gitea for vault (pull_request) Failing after 8m36s
Hashicorp Vault / Tofu - Vault IAC (pull_request) Has been skipped
Helm Charts / Application charts pgcat (pull_request) Has been skipped
Retour fondateur : « je pensais que tools#21 contribuerait à app_policy pour une
policy kvv2/minio/<app name> ». Il a raison, et mon choix initial était le plus
faible des deux.

Mon objection — ne donner le droit qu'aux apps qui en ont besoin — ne tient pas
à l'examen : la règle porte le NOM de l'app, donc elle ne peut jamais exposer
que ses propres clés. Il n'y a aucun privilège à préserver. Une app qui ne
stocke rien lit un chemin qui n'existe pas : une règle inerte, pas un droit.

Son argument, lui, porte : savoir lire ses propres identifiants de stockage est
une propriété de la PLATEFORME, pas une exception par application. Et
`kv_read_paths` est documenté comme la trappe pour un secret appartenant à une
AUTRE app (les creds GCS de Longhorn pour l'ERP) — y ranger un motif standard
l'aurait rendu invisible et aurait obligé à le redéclarer à chaque app.

La règle passe donc dans `app_policy`, en prod ET pour chaque instance non-prod
(symétrie stricte), sur deux chemins : le document `kvv2/data/minio/<app>` et
ses descendants.

Un cran plus loin que la demande : la règle est INCONDITIONNELLE, sans drapeau.
Conséquence — déclarer un consommateur MinIO se fait désormais à UN SEUL
endroit, `var.consumers` du pipeline minio. Aucune synchronisation à tenir entre
deux fichiers, donc rien à oublier. Le `kv_read_paths` que j'avais ajouté à
kadans est retiré : il faisait double emploi.

tofu fmt propre · tofu validate réussi sur hashicorp-vault/iac.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01CoafGWmRVESaWX819USUUA
2026-07-26 09:26:05 +02:00
arcodangeandClaude Opus 5 4ca4a05370 feat(minio) — un compte de service par app consommatrice, borné à son bucket
Helm Charts / Detect changed charts (push) Successful in 57s
MinIO / Auth with gitea for vault (push) Failing after 8m34s
MinIO / Tofu - minio IAC (push) Has been skipped
Hashicorp Vault / Auth with gitea for vault (push) Failing after 8m33s
Hashicorp Vault / Tofu - Vault IAC (push) Has been skipped
Helm Charts / Detect changed charts (pull_request) Successful in 1m1s
Helm Charts / Library charts tool (push) Has been cancelled
Helm Charts / Application charts pgcat (push) Has been cancelled
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 / Library charts tool (pull_request) Has been skipped
Helm Charts / Application charts pgcat (pull_request) Has been skipped
« On peut adopter ce pattern à toutes les apps qui pourraient avoir besoin de
MinIO, et ainsi modifier les app roles / app policy générique. » (fondateur, 26/07)

Bonne nouvelle : les modules centraux n'ont PAS eu à changer. `kv_read_paths`
existe déjà dans `app_policy` et fait exactement ça — l'ERP s'en sert pour lire
les creds GCS de Longhorn. Le motif générique était donc à moitié construit ;
il manquait la moitié MinIO.

CE QUI EST AJOUTÉ : `minio/iac/consumers.tf` provisionne, pour chaque app
déclarée dans `var.consumers`, une politique MinIO bornée à SON bucket
(GetObject/PutObject/DeleteObject sur les objets, ListBucket sur le bucket seul
— ni les autres buckets, ni l'administration), un compte de service, et écrit
ses clés dans `kvv2/minio/<app>`.

POURQUOI LES CLÉS VIVENT CHEZ MINIO ET PAS CHEZ L'APP : seul ce pipeline possède
les identifiants ROOT. 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 d'ici, et l'app ne reçoit qu'une clé qui ne peut rien lire
d'autre que son bucket. Un compte de service qui fuite ne donne accès qu'aux
objets qu'il gérait déjà.

Ajouter une app = trois lignes déclaratives, documentées au README : son bucket
dans values.yaml, son entrée dans `var.consumers`, son `kv_read_paths` au tfvars
central. Aucun module à toucher.

Le provider parle à `s3.arcodange.fr` — l'endpoint PUBLIC, parce que le runner
CI n'est pas dans le LAN et que `.lab` ne s'y résout pas. C'est l'exposition
HTTPS mergée ce matin qui rend ce plan applicable.

⚠ Erreur attrapée par le validateur, et corrigée : je relisais `kvv2/minio/config`
alors que `local.config` de main.tf porte déjà les identifiants root — une
lecture qui aurait créé une dépendance circulaire avec l'écriture faite par ce
même plan.

Vérifié : `tofu fmt` propre, `tofu validate` réussi (avec TERRAFORM_VAULT_AUTH_JWT
posé — sans lui, le provider Vault échoue déjà sur main, ce n'est pas mon fait).
Je n'ai lancé AUCUN apply : c'est le workflow qui le fera, et la revue t'appartient.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01CoafGWmRVESaWX819USUUA
2026-07-26 09:12:09 +02:00
arcodangeandClaude Opus 5 1490f4c514 feat(minio) — exposer l'API S3 en HTTPS public (s3.arcodange.fr) + CORS de la PWA
Helm Charts / Detect changed charts (push) Successful in 1m6s
Helm Charts / Detect changed charts (pull_request) Successful in 1m5s
Helm Charts / Library charts tool (push) Has been skipped
Helm Charts / Library charts tool (pull_request) Has been skipped
Helm Charts / Application charts pgcat (push) Has been skipped
Helm Charts / Application charts pgcat (pull_request) Has been skipped
Sans ça, la synchronisation des vidéos ne peut pas marcher, et ce sont des faits
du navigateur : une page servie en https ne peut pas émettre de requête vers
http:// (contenu mixte), et `.lab` n'est pas résolvable hors du LAN. L'ingress
existant est en entrypoint `web` sans TLS, sur un domaine interne — donc
inutilisable depuis kadans.arcodange.fr, en déplacement COMME à la maison.

Même motif que grafana/templates/ingress-public.yaml : entrypoint `web`, TLS
terminé par le tunnel Cloudflare (wildcard *.arcodange.fr), middleware crowdsec.
Le `.lab` interne reste inchangé.

PAS de basic-auth, 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. L'autorisation vient de l'URL signée et de sa
durée de vie courte.

CORS déclaré sur les origines EXACTES de la PWA (.fr et .lab), jamais « * » :
une URL présignée qui fuiterait serait sinon rejouable depuis n'importe quel
site. Sans cette liste, le préflight OPTIONS échoue et le PUT ne part même pas.

⚠ Signalé, NON mesuré : le tunnel Cloudflare plafonne probablement le corps
d'une requête (~100 Mo sur les offres gratuites). À éprouver avec un vrai
téléversement. Contexte utile : sur le corpus réel du fondateur (707 vidéos,
~2 ans), la durée MOYENNE est de 53 s et deux vidéos seulement dépassent 5 min —
au palier « travail » de l'ADR-018, 100 Mo valent ~29 min de cours.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01CoafGWmRVESaWX819USUUA
2026-07-26 01:56:35 +02:00
arcodangeandClaude Opus 5 d146affbcd fix(minio) — déclarer minio dans la liste centrale des applications Vault
Helm Charts / Application charts pgcat (pull_request) Has been skipped
Helm Charts / Detect changed charts (push) Successful in 1m6s
Helm Charts / Detect changed charts (pull_request) Successful in 24s
Hashicorp Vault / Auth with gitea for vault (push) Failing after 8m38s
Hashicorp Vault / Tofu - Vault IAC (push) Has been skipped
Helm Charts / Library charts tool (pull_request) Has been skipped
Helm Charts / Library charts tool (push) Has been skipped
Hashicorp Vault / Tofu - Vault IAC (pull_request) Has been skipped
Hashicorp Vault / Auth with gitea for vault (pull_request) Failing after 8m40s
Helm Charts / Application charts pgcat (push) Has been skipped
CI en échec sur le workflow MinIO : role "gitea_cicd_minio" could not be found.

Diagnostic : les rôles CI gitea_cicd_<app> ne naissent PAS dans l'IaC de
l'application — ils viennent du module app_policy, appliqué centralement par
hashicorp-vault/iac pour chaque entrée de terraform.tfvars. J'avais posé
minio/iac (qui s'authentifie AVEC ce rôle) sans alimenter la liste : le run
tentait donc de s'authentifier avec un rôle que personne n'avait créé. Amorçage
circulaire, entièrement de mon fait.

- hashicorp-vault/iac/terraform.tfvars : minio ajouté aux applications, avec
  service_account_namespaces = ["tools"] comme crowdsec et plausible ;
- .gitea/workflows/vault.yaml : les triggers couvrent désormais *.tfvars en
  plus de *.tf. C'est là que vit la liste des applications : sans ça, ajouter
  une app ne déclenchait jamais la création de son rôle — le piège qui vient
  de mordre. Les ancres YAML des triggers sont retirées au passage (une ancre
  dans un trigger Gitea Actions fait taire push ET pull_request en silence,
  vécu sur arcodange/kadans, issues 113→117 ; c'est probablement pourquoi ce
  workflow ne partait qu'à la main) ;
- minio/README.md : l'ordre de mise en service dit maintenant les DEUX étapes
  et nomme l'erreur exacte à laquelle on s'expose en les inversant.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CoafGWmRVESaWX819USUUA
2026-07-25 18:23:02 +02:00
arcodangeandClaude Opus 5 84edfc8640 feat(minio) — stockage objet S3 du homelab (brique partagée de tools)
Helm Charts / Detect changed charts (push) Successful in 16s
Helm Charts / Detect changed charts (pull_request) Successful in 15s
Helm Charts / Library charts tool (push) Has been skipped
Helm Charts / Library charts tool (pull_request) Has been skipped
MinIO / Tofu - minio IAC (pull_request) Has been skipped
MinIO / Auth with gitea for vault (pull_request) Failing after 8m32s
Helm Charts / Application charts pgcat (pull_request) Has been skipped
Helm Charts / Application charts pgcat (push) Has been skipped
Le fondateur : « on déploie MinIO dans le repo tools du homelab non ? » — oui,
c'est bien le pattern : le dossier tools/ porte les briques PARTAGÉES
(pgbouncer, clickhouse, grafana…) et le namespace tools les fait tourner ; les
charts applicatifs vivent dans le repo de leur app.

Décidé de longue date côté produit, jamais déployé : ADR-012 « MinIO local
d'abord » (bascule R2 à 100+ utilisateurs / 10 To par mois), ADR-013 (le gratuit
reste local-first, MinIO sert les paliers payants). Vérifié avant d'écrire :
aucun pod ni service MinIO dans le cluster.

Le chart suit la recette du repo (dépendance à la library "tool" + chart amont
en SubChart, deux gardes dans templates/) :

- mode STANDALONE, 1 réplique : ce qui transite est DÉRIVÉ (le master d'une
  vidéo reste sur l'appareil de son propriétaire, ADR-018) 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 ;
- 50 Gi sur longhorn ≈ 250 h de cours au palier « travail » (360p, 3,4 Mo/min) ;
  ⚠ Longhorn réplique : compter ×3 sur la capacité avant d'augmenter ;
- ressources bornées (512 Mi / 2 Gi) : la limite protège les voisins de tools ;
- API s3.arcodange.lab + console minio.arcodange.lab (Traefik) ;
- bucket kadans-videos PRIVÉ — l'accès passera par des URL signées (ADR-0002) ;
- identifiants JAMAIS au dépôt : iac/ les génère dans Vault (kvv2/minio/config),
  le Vault Secrets Operator les matérialise, le chart les lit via existingSecret.
  Le SA du pod est nommé "minio" (pas le "minio-sa" amont) car le module
  app_roles borne l'authentification au SA portant le nom de l'app.

⚠ Le workflow minio.yaml écrit ses triggers EN TOUTES LETTRES : une ancre YAML
dans un trigger Gitea Actions fait taire push ET pull_request en silence (vécu
sur arcodange/kadans, issues 113→117). Les workflows plausible/crowdsec/vault de
ce repo en utilisent encore — à vérifier séparément, c'est probablement
pourquoi ils ne partent qu'à la main.

Vérifié : helm dependency update + helm template (11 ressources rendues, SA et
VaultAuth cohérents) + helm lint ✓.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CoafGWmRVESaWX819USUUA
2026-07-25 16:49:34 +02:00