Merge pull request 'feat(minio) — exposer l'API S3 en HTTPS public (s3.arcodange.fr) + CORS de la PWA' (#20) from arcodange/minio-public-fr into main
Helm Charts / Detect changed charts (push) Successful in 42s
Helm Charts / Library charts tool (push) Has been skipped
Helm Charts / Application charts pgcat (push) Has been skipped

Reviewed-on: #20
This commit was merged in pull request #20.
This commit is contained in:
2026-07-26 07:34:48 +02:00
3 changed files with 86 additions and 1 deletions
+41 -1
View File
@@ -23,7 +23,7 @@ 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 |
| 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 |
| 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) |
| Bucket | `kadans-videos`, **privé** — 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` |
@@ -52,3 +52,43 @@ L'ordre compte, et il compte **deux fois** :
> [!NOTE]
> 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.
## 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.
+37
View File
@@ -0,0 +1,37 @@
# Exposition PUBLIQUE s3.arcodange.fr, EN PLUS du .lab interne (qui reste
# inchangé). Même motif que `grafana/templates/ingress-public.yaml` : entrypoint
# `web` — le TLS est terminé en amont par le tunnel Cloudflare (wildcard
# *.arcodange.fr → traefik.kube-system.svc:80) — et middleware crowdsec.
#
# POURQUOI c'est nécessaire (et pas un confort) : la PWA est servie en
# https://kadans.arcodange.fr. Un navigateur REFUSE une requête vers http://
# depuis une page https (contenu mixte), et `.lab` n'est de toute façon pas
# résolvable hors du LAN. Sans cet ingress, le téléversement d'une vidéo ne peut
# marcher NI en déplacement, NI même à la maison — c'est le maillon qui manque
# à kadans-api#23 (URL présignées).
#
# ⚠ PAS de basic-auth ici, contrairement à kadans-public : une requête S3 porte
# sa propre signature (SigV4) dans ses en-têtes ou son URL. Un défi HTTP Basic
# casserait le PUT présigné du navigateur, qui n'a aucun moyen d'y répondre.
# L'autorisation vient de l'URL signée, avec sa durée de vie courte.
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
+8
View File
@@ -74,6 +74,14 @@ minio: &minio_config
enabled: false # pas d'opérateur Prometheus ici : scrape par annotation
environment:
MINIO_PROMETHEUS_AUTH_TYPE: "public"
# CORS : le navigateur téléverse DIRECTEMENT vers MinIO depuis la PWA (URL
# présignées, kadans-api#23) — donc requête inter-origines, donc préflight
# OPTIONS. Sans cette liste, le PUT est refusé par le navigateur avant même
# de partir. Origines EXACTES, jamais « * » : une URL présignée qui fuite
# serait rejouable depuis n'importe quel site.
# .fr = la PWA publique (le cas réel, en déplacement comme à la maison)
# .lab = la même app servie en interne, pour la mise au point
MINIO_API_CORS_ALLOW_ORIGIN: "https://kadans.arcodange.fr,https://kadans.arcodange.lab"
tool:
# kind: 'SubChart' or 'HelmChart', if subchart then uncomment Chart.yaml dependency, else comment and use tool library with helm chart template