diff --git a/minio/README.md b/minio/README.md index d237c14..a84d3cd 100644 --- a/minio/README.md +++ b/minio/README.md @@ -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. diff --git a/minio/templates/ingress-public.yaml b/minio/templates/ingress-public.yaml new file mode 100644 index 0000000..4d94fa4 --- /dev/null +++ b/minio/templates/ingress-public.yaml @@ -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 diff --git a/minio/values.yaml b/minio/values.yaml index cc6dde2..95d8989 100644 --- a/minio/values.yaml +++ b/minio/values.yaml @@ -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