From b34873f98052ac5fb4bf6731a25730075796d764 Mon Sep 17 00:00:00 2001 From: ertopogo Date: Fri, 13 Mar 2026 00:33:28 +0100 Subject: Initial commit medias platform --- .env.photoprism-secure.example | 25 +++ DECISIONS.md | 16 ++ INTEGRATIONS.md | 15 ++ MEDIA_ACCESS_API.md | 78 ++++++++ PROJET_CONTEXT.md | 23 +++ VIEWER_BFF.md | 27 +++ WINDOWS_INGESTION.md | 46 +++++ compose.photoprism-secure.dev.yml | 88 +++++++++ docs/CADDY_ARAUCARIA.md | 80 ++++++++ docs/CHANGELOG_OPERATIONS.md | 36 ++++ docs/CONFIGURATION.md | 185 ++++++++++++++++++ docs/DEPLOIEMENT_KONENPAN.md | 158 +++++++++++++++ docs/DNSMASQ_MEDIAS.md | 48 +++++ docs/INSTALLATION.md | 106 +++++++++++ docs/TEMPLATE_INCIDENT.md | 50 +++++ docs/TROUBLESHOOTING.md | 140 ++++++++++++++ docs/Untitled | 1 + docs/VM_KONENPAN_CREATION.md | 391 ++++++++++++++++++++++++++++++++++++++ sync_windows_to_minio.ps1 | 66 +++++++ viewer-bff/.dockerignore | 2 + viewer-bff/Dockerfile | 13 ++ viewer-bff/package.json | 16 ++ viewer-bff/public/app.js | 122 ++++++++++++ viewer-bff/public/index.html | 43 +++++ viewer-bff/public/styles.css | 93 +++++++++ viewer-bff/server.js | 115 +++++++++++ 26 files changed, 1983 insertions(+) create mode 100644 .env.photoprism-secure.example create mode 100644 DECISIONS.md create mode 100644 INTEGRATIONS.md create mode 100644 MEDIA_ACCESS_API.md create mode 100644 PROJET_CONTEXT.md create mode 100644 VIEWER_BFF.md create mode 100644 WINDOWS_INGESTION.md create mode 100644 compose.photoprism-secure.dev.yml create mode 100644 docs/CADDY_ARAUCARIA.md create mode 100644 docs/CHANGELOG_OPERATIONS.md create mode 100644 docs/CONFIGURATION.md create mode 100644 docs/DEPLOIEMENT_KONENPAN.md create mode 100644 docs/DNSMASQ_MEDIAS.md create mode 100644 docs/INSTALLATION.md create mode 100644 docs/TEMPLATE_INCIDENT.md create mode 100644 docs/TROUBLESHOOTING.md create mode 100644 docs/Untitled create mode 100644 docs/VM_KONENPAN_CREATION.md create mode 100644 sync_windows_to_minio.ps1 create mode 100644 viewer-bff/.dockerignore create mode 100644 viewer-bff/Dockerfile create mode 100644 viewer-bff/package.json create mode 100644 viewer-bff/public/app.js create mode 100644 viewer-bff/public/index.html create mode 100644 viewer-bff/public/styles.css create mode 100644 viewer-bff/server.js diff --git a/.env.photoprism-secure.example b/.env.photoprism-secure.example new file mode 100644 index 0000000..0d87242 --- /dev/null +++ b/.env.photoprism-secure.example @@ -0,0 +1,25 @@ +# IAM (Keycloak externe) +OIDC_ISSUER=https://kc.arauco.online/realms/chiruca +OIDC_AUDIENCE=media-access-api +OIDC_JWKS_URL=https://kc.arauco.online/realms/chiruca/protocol/openid-connect/certs +OIDC_CLIENT_ID=media-access-api +OIDC_CLIENT_SECRET=CHANGE_ME_OIDC_CLIENT_SECRET + +# RBAC cumulatif +RBAC_ROLE_PREFIX=media_reader:folder: +RBAC_ROLE_ALL=media_reader:all + +# MinIO +MINIO_ROOT_USER=minio +MINIO_ROOT_PASSWORD=CHANGE_ME_MINIO_ROOT_PASSWORD +S3_BUCKET=medias-private +S3_REGION=us-east-1 + +# API d'acces media +MEDIA_ACCESS_API_IMAGE=ghcr.io/your-org/media-access-api:latest +PRESIGN_TTL_SECONDS=120 + +# Viewer BFF +VIEWER_BFF_PORT=8082 +MEDIA_API_BASE_URL=http://media-access-api:8081 +CORS_ALLOWED_ORIGIN=https://photos.arauco.online diff --git a/DECISIONS.md b/DECISIONS.md new file mode 100644 index 0000000..100aa2e --- /dev/null +++ b/DECISIONS.md @@ -0,0 +1,16 @@ +# DECISIONS +## LDEC-0001 +- Date: 2026-03-07 +- Statut: active +- Contexte: Les changements d'installation, de configuration et de depannage ne sont pas traces de maniere systematique, ce qui augmente les risques d'erreurs et rallonge le support. +- Decision: Rendre obligatoire la documentation operationnelle pour toute action d'installation, de configuration ou de troubleshooting. La definition de termine inclut obligatoirement la mise a jour de la documentation associee. +- Impact: Amelioration de la reproductibilite, baisse du risque operationnel, meilleure autonomie de l'equipe et auditabilite des interventions. +- Promotion candidate: non + +## LDEC-0002 +- Date: 2026-03-08 +- Statut: active +- Contexte: Le projet doit permettre une visualisation web avec controle d'acces fin par roles Keycloak cumulables, sans exposition directe des objets MinIO. +- Decision: Imposer une couche `media-access-api` et un `viewer-bff` entre client web et MinIO pour valider les JWT OIDC, evaluer les ACL role->prefixe et delivrer uniquement des URLs pre-signees a TTL court. +- Impact: Reduction du risque de contournement par URL directe, meilleure tracabilite des acces, architecture plus robuste pour la segregation des droits par dossier. +- Promotion candidate: non \ No newline at end of file diff --git a/INTEGRATIONS.md b/INTEGRATIONS.md new file mode 100644 index 0000000..46bd9a0 --- /dev/null +++ b/INTEGRATIONS.md @@ -0,0 +1,15 @@ +# INTEGRATIONS +## Integrations internes +- Service/Projet: Plateforme Medias (stockage, acces API, visualisation) +- Type d'echange: Autorisation JWT/OIDC, controles ACL, URLs pre-signees, metadonnees techniques +- Contrat: Toute integration interne doit reference sa documentation d'installation, de configuration et de troubleshooting +- Flux interne cible: + - Client web -> Caddy (araucaria) -> viewer-bff (Node/Express) + - viewer-bff -> media-access-api (verification droits) + - media-access-api -> MinIO (lecture objet autorise via URL pre-signee) +## Integrations externes +- Service: Keycloak externe (`kc.arauco.online`), Caddy (edge proxy), services clients consommateurs de medias +- Auth: OIDC/OAuth2, JWT signe +- Donnees echangees: Claims d'identite/roles/groupes, decision ACL, liens pre-signes a duree limitee, journaux d'acces +- Criticite: Elevee (securite et disponibilite) +- Exigence documentaire: Chaque integration externe doit disposer d'un runbook d'installation, de configuration et de depannage maintenu a jour \ No newline at end of file diff --git a/MEDIA_ACCESS_API.md b/MEDIA_ACCESS_API.md new file mode 100644 index 0000000..ec5598e --- /dev/null +++ b/MEDIA_ACCESS_API.md @@ -0,0 +1,78 @@ +# MEDIA_ACCESS_API + +## Objectif +Definir une couche d'autorisation entre viewer-bff et MinIO afin d'appliquer les ACL Keycloak avant chaque lecture media. + +## Principes de securite +- Deny-by-default. +- Validation JWT obligatoire (issuer, audience, signature, expiration). +- Evaluation des roles cumulables pour obtenir les prefixes MinIO autorises. +- Jamais d'acces public direct au bucket prive. +- Utilisation d'URLs pre-signees courtes (TTL court) pour la lecture. + +## Variables d'environnement minimales +- `OIDC_ISSUER=https://kc.arauco.online/realms/chiruca` +- `OIDC_AUDIENCE=media-access-api` +- `OIDC_JWKS_URL=https://kc.arauco.online/realms/chiruca/protocol/openid-connect/certs` +- `RBAC_ROLE_PREFIX=media_reader:folder:` +- `RBAC_ROLE_ALL=media_reader:all` +- `S3_ENDPOINT=http://minio:9000` +- `S3_BUCKET=medias-private` +- `S3_FORCE_PATH_STYLE=true` +- `PRESIGN_TTL_SECONDS=120` + +## Contrat API propose + +### `GET /health` +- Reponse: `200` si service operationnel. + +### `GET /v1/permissions` +- Auth: `Bearer ` +- Reponse `200`: +```json +{ + "subject": "user-id", + "allowAll": false, + "allowedPrefixes": [ + "photos/equipeA/", + "photos/projetX/" + ] +} +``` + +### `POST /v1/presign` +- Auth: `Bearer ` +- Payload: +```json +{ + "objectKey": "photos/equipeA/2026/03/image-001.jpg" +} +``` +- Reponse `200`: +```json +{ + "url": "https://...signature...", + "expiresIn": 120 +} +``` +- Reponses d'erreur: + - `401`: token invalide/absent. + - `403`: role insuffisant ou `objectKey` hors prefixe autorise. + - `404`: objet introuvable. + +## Algorithme d'autorisation (reference) +1. Extraire les roles client depuis le token (`resource_access.media-access-api.roles`). +2. Si `RBAC_ROLE_ALL` present, autoriser. +3. Sinon, filtrer les roles commencant par `RBAC_ROLE_PREFIX`. +4. Convertir chaque role en prefixe MinIO (ex: `media_reader:folder:photos/equipeA` -> `photos/equipeA/`). +5. Autoriser uniquement si `objectKey` commence par un prefixe calcule. + +## Integration viewer-bff +- Le viewer-bff conserve la navigation et la recherche cote interface web. +- Les originaux proteges sont servis via `media-access-api` (URL pre-signee). +- Le bucket MinIO reste prive (pas de policy publique de lecture). + +## Journalisation et audit +- Logger `subject`, `objectKey`, decision (`allow`/`deny`), raison, `requestId`. +- Ne jamais logger un token complet ni des secrets. +- Exporter des metriques de refus ACL pour detection d'erreurs de mapping. diff --git a/PROJET_CONTEXT.md b/PROJET_CONTEXT.md new file mode 100644 index 0000000..1b1638c --- /dev/null +++ b/PROJET_CONTEXT.md @@ -0,0 +1,23 @@ +# PROJECT_CONTEXT +## Type de projet +- Domaine: infra +- Statut: actif +## Références globales +- E:/Dev/System/Araucaria/00_DOCUMENTATION/README.md +- E:/Dev/System/Araucaria/00_DOCUMENTATION/doc-regles/00_START_HERE.md +- E:/Dev/System/Pachamama-schemas (si infra/réseau) +- https://pm.arauco.online/ +## Règles d’application +- keep: appliquer +- adapt: appliquer avec justification +- skip: ignorer sauf besoin explicite +- doc-obligatoire: toute installation, configuration ou action de troubleshooting doit etre documentee (creation ou mise a jour) avant cloture de la tache +- definition-of-done: la tache est cloturee uniquement si la checklist documentaire de docs/CHANGELOG_OPERATIONS.md est complete +## Contexte local +- Objectif du projet: Mettre en place une plateforme open source de gestion et de controle d'acces aux medias via Keycloak. +- Périmètre: Deploiement self-hosted de Keycloak, stockage medias, services applicatifs et documentation operationnelle associee. +- Contraintes: + - Securite prioritaire (controle d'acces fin, moindre privilege, audit des acces) + - Open source uniquement + - Documentation obligatoire pour installation, configuration et troubleshooting + - Aucune tache n'est terminee sans preuve de mise a jour documentaire \ No newline at end of file diff --git a/VIEWER_BFF.md b/VIEWER_BFF.md new file mode 100644 index 0000000..b5b975e --- /dev/null +++ b/VIEWER_BFF.md @@ -0,0 +1,27 @@ +# VIEWER_BFF + +## Objectif +Fournir une interface web minimale de consultation des medias en s'appuyant sur `media-access-api` pour appliquer les droits Keycloak. + +## Endpoints backend +- `GET /health`: etat du service. +- `GET /api/me/permissions`: proxy vers `media-access-api /v1/permissions`. +- `POST /api/media/presign`: proxy vers `media-access-api /v1/presign`. + +## Variables +- `PORT`: port d'ecoute du BFF (defaut `8082`). +- `MEDIA_API_BASE_URL`: URL interne de `media-access-api`. +- `CORS_ALLOWED_ORIGIN`: origine frontend autorisee. + +## UI POC +- Saisie d'un token utilisateur. +- Chargement des permissions effectives. +- Saisie de cles objets MinIO. +- Chargement de previsualisations via URLs pre-signees. + +## Lancement local +```bash +cd viewer-bff +npm install +npm start +``` diff --git a/WINDOWS_INGESTION.md b/WINDOWS_INGESTION.md new file mode 100644 index 0000000..324984d --- /dev/null +++ b/WINDOWS_INGESTION.md @@ -0,0 +1,46 @@ +# WINDOWS_INGESTION + +## Objectif +Synchroniser des dossiers photos Windows vers MinIO avec une structure de prefixes compatible ACL par roles. + +## Convention de structure +- Source Windows: + - `D:\Photos\EquipeA\...` + - `D:\Photos\ProjetX\...` +- Destination MinIO: + - `medias-private/photos/EquipeA/...` + - `medias-private/photos/ProjetX/...` + +Les noms de dossiers deviennent les prefixes de securite (ex: role `media_reader:folder:photos/EquipeA`). + +## Prerequis +- AWS CLI installe sur Windows. +- Connectivite vers MinIO (`http://:9000`). +- Clefs MinIO (`MINIO_ROOT_USER`, `MINIO_ROOT_PASSWORD`) ou un compte S3 dedie. + +## Commande de sync initiale +```powershell +powershell -ExecutionPolicy Bypass -File .\sync_windows_to_minio.ps1 ` + -SourceRoot "D:\Photos" ` + -EndpointUrl "http://192.168.99.23:9000" ` + -Bucket "medias-private" ` + -AccessKey "minio" ` + -SecretKey "CHANGE_ME" +``` + +## Mode simulation +```powershell +powershell -ExecutionPolicy Bypass -File .\sync_windows_to_minio.ps1 ` + -SourceRoot "D:\Photos" ` + -EndpointUrl "http://192.168.99.23:9000" ` + -Bucket "medias-private" ` + -AccessKey "minio" ` + -SecretKey "CHANGE_ME" ` + -WhatIf +``` + +## Bonnes pratiques +- Ne pas utiliser le compte root MinIO en production; creer un compte de sync dedie. +- Figer une convention de nommage des dossiers avant mise en prod. +- Eviter les renommages massifs de prefixes pour limiter les remappings de roles. +- Planifier une sync periodique (Task Scheduler) et journaliser les erreurs. diff --git a/compose.photoprism-secure.dev.yml b/compose.photoprism-secure.dev.yml new file mode 100644 index 0000000..6cdfc9a --- /dev/null +++ b/compose.photoprism-secure.dev.yml @@ -0,0 +1,88 @@ +services: + minio: + image: minio/minio:latest + container_name: medias-minio + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: ${MINIO_ROOT_USER} + MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD} + ports: + - "9000:9000" + - "9001:9001" + volumes: + - minio_data:/data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 10s + timeout: 5s + retries: 12 + networks: + - medias_net + + minio-init: + image: minio/mc:latest + container_name: medias-minio-init + depends_on: + minio: + condition: service_healthy + entrypoint: > + /bin/sh -c " + mc alias set local http://minio:9000 ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD} && + mc mb -p local/${S3_BUCKET} || true && + mc anonymous set none local/${S3_BUCKET} || true + " + restart: "no" + networks: + - medias_net + + media-access-api: + image: ${MEDIA_ACCESS_API_IMAGE} + container_name: medias-access-api + environment: + OIDC_ISSUER: ${OIDC_ISSUER} + OIDC_AUDIENCE: ${OIDC_AUDIENCE} + OIDC_JWKS_URL: ${OIDC_JWKS_URL} + OIDC_CLIENT_ID: ${OIDC_CLIENT_ID} + OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET} + RBAC_ROLE_PREFIX: ${RBAC_ROLE_PREFIX} + RBAC_ROLE_ALL: ${RBAC_ROLE_ALL} + S3_ENDPOINT: http://minio:9000 + S3_BUCKET: ${S3_BUCKET} + S3_REGION: ${S3_REGION} + S3_ACCESS_KEY: ${MINIO_ROOT_USER} + S3_SECRET_KEY: ${MINIO_ROOT_PASSWORD} + S3_FORCE_PATH_STYLE: "true" + PRESIGN_TTL_SECONDS: ${PRESIGN_TTL_SECONDS} + PORT: "8081" + depends_on: + minio: + condition: service_healthy + minio-init: + condition: service_completed_successfully + ports: + - "8081:8081" + networks: + - medias_net + + viewer-bff: + build: + context: ./viewer-bff + dockerfile: Dockerfile + container_name: medias-viewer-bff + depends_on: + - media-access-api + environment: + PORT: "8082" + MEDIA_API_BASE_URL: ${MEDIA_API_BASE_URL} + CORS_ALLOWED_ORIGIN: ${CORS_ALLOWED_ORIGIN} + ports: + - "${VIEWER_BFF_PORT:-8082}:8082" + networks: + - medias_net + +networks: + medias_net: + name: medias_net + +volumes: + minio_data: diff --git a/docs/CADDY_ARAUCARIA.md b/docs/CADDY_ARAUCARIA.md new file mode 100644 index 0000000..975c85b --- /dev/null +++ b/docs/CADDY_ARAUCARIA.md @@ -0,0 +1,80 @@ +# CADDY_ARAUCARIA + +## Objectif +Publier la stack medias en HTTPS via Caddy sur `araucaria`, sans exposition directe des ports applicatifs vers les clients. + +## Prerequis +- Caddy installe et actif sur `araucaria`. +- `araucaria` peut joindre la VM `konenpan` sur les ports internes: + - viewer-bff `8082` + - media-access-api `8081` + - MinIO API `9000` (si necessaire) + - MinIO Console `9001` (admin uniquement) + +## Noms DNS recommandes +- `photos.arauco.online` +- `media-api.arauco.online` +- `minio-console.arauco.online` +- `minio.arauco.online` (optionnel, a limiter) + +## Caddyfile (exemple) +```caddy +photos.arauco.online { + encode zstd gzip + reverse_proxy 192.168.99.23:8082 +} + +media-api.arauco.online { + encode zstd gzip + reverse_proxy 192.168.99.23:8081 +} + +minio-console.arauco.online { + encode zstd gzip + reverse_proxy 192.168.99.23:9001 +} + +minio.arauco.online { + encode zstd gzip + reverse_proxy 192.168.99.23:9000 +} +``` + +## Application +```bash +sudo caddy validate --config /etc/caddy/Caddyfile +sudo systemctl reload caddy +sudo systemctl status caddy --no-pager +``` + +## Validation +```bash +curl -I https://photos.arauco.online +curl -I https://media-api.arauco.online/health +curl -I https://minio-console.arauco.online +``` + +## Checklist debug rapide +Utiliser cette sequence en cas de "connexion a echoue" depuis le navigateur. + +```bash +# DNS -> araucaria +dig +short photos.arauco.online + +# TLS/HTTP sur le front Caddy +curl -vkI https://photos.arauco.online + +# Etat et config Caddy +sudo caddy validate --config /etc/caddy/Caddyfile +sudo systemctl status caddy --no-pager +sudo journalctl -u caddy -n 100 --no-pager + +# Connectivite backend depuis araucaria +curl -I http://192.168.99.23:8082/health +curl -I http://192.168.99.23:8081/health +``` + +## Recommandations securite +- Exposer `minio-console` uniquement aux admins (ACL reseau/VPN/IP allowlist). +- Ne pas autoriser de lecture anonyme sur le bucket prive. +- Conserver l'enforcement ACL dans `media-access-api` (deny-by-default + URLs pre-signees). diff --git a/docs/CHANGELOG_OPERATIONS.md b/docs/CHANGELOG_OPERATIONS.md new file mode 100644 index 0000000..6cb0d85 --- /dev/null +++ b/docs/CHANGELOG_OPERATIONS.md @@ -0,0 +1,36 @@ +# CHANGELOG_OPERATIONS + +## Objectif +Conserver un journal des operations techniques et de la documentation associee. + +## Regle de tracabilite +Chaque action d'installation, configuration ou troubleshooting doit ajouter une entree ci-dessous avec references documentaires. + +## Checklist obligatoire de fin de tache +- Implementation terminee +- Documentation installation mise a jour (si applicable) +- Documentation configuration mise a jour (si applicable) +- Documentation troubleshooting mise a jour (si applicable) +- Validation technique effectuee +- Rollback defini ou confirme non necessaire +- Evidences (logs/tests) referencees + +## Entrees +| Date | Type (install/config/troubleshoot) | Composant | Resume | Docs mises a jour | Validation | Auteur | +| --- | --- | --- | --- | --- | --- | --- | +| YYYY-MM-DD | config | service-x | Exemple de changement | CONFIGURATION.md | OK | nom | +| 2026-03-07 | install | stack medias dev | Installation initiale Keycloak + stockage + API | INSTALLATION.md | OK | equipe-plateforme | +| 2026-03-07 | config | media-access-api | Activation OIDC + role requis media_reader | CONFIGURATION.md | OK | equipe-plateforme | +| 2026-03-07 | troubleshoot | media-access-api | Incident 403 resolu par correction mapping groupe-role | TROUBLESHOOTING.md | OK | equipe-plateforme | +| 2026-03-07 | install | infrastructure VM | Creation VM Konenpan avec volume de donnees 1 TB | VM_KONENPAN_CREATION.md | OK | equipe-plateforme | +| 2026-03-07 | troubleshoot | processus incidents | Ajout du template incident standard | TEMPLATE_INCIDENT.md | OK | equipe-plateforme | +| 2026-03-07 | config | VM konenpan | Ajout client NTP chrony pour synchronisation horaire vers araucaria | VM_KONENPAN_CREATION.md, TROUBLESHOOTING.md | OK | equipe-plateforme | +| 2026-03-08 | config | media-access-api | Modele ACL cumulatif role->prefixe MinIO + OIDC externe chiruca | CONFIGURATION.md, MEDIA_ACCESS_API.md, INTEGRATIONS.md | OK | equipe-plateforme | +| 2026-03-08 | install | stack photoprism securisee | Ajout compose de reference PhotoPrism+MinIO+API d'acces et exemple env | INSTALLATION.md, compose.photoprism-secure.dev.yml, .env.photoprism-secure.example | OK | equipe-plateforme | +| 2026-03-08 | install | ingestion Windows | Script de synchronisation Windows vers MinIO avec convention de prefixes ACL | WINDOWS_INGESTION.md, sync_windows_to_minio.ps1 | OK | equipe-plateforme | +| 2026-03-08 | config | dns local medias | Ajout standard dnsmasq (records A/wildcard) + validation et troubleshooting | docs/DNSMASQ_MEDIAS.md, INSTALLATION.md, CONFIGURATION.md, TROUBLESHOOTING.md | OK | equipe-plateforme | +| 2026-03-08 | config | edge proxy caddy | Alignement DNS+docs sur Caddy araucaria (HTTPS public, backends konenpan) | docs/CADDY_ARAUCARIA.md, docs/DNSMASQ_MEDIAS.md, INSTALLATION.md, CONFIGURATION.md, INTEGRATIONS.md, TROUBLESHOOTING.md, .env.photoprism-secure.example | OK | equipe-plateforme | +| 2026-03-08 | troubleshoot | acces web photos | Ajout checklist debug express DNS/Caddy/backend pour `photos.arauco.online` | docs/TROUBLESHOOTING.md, docs/CADDY_ARAUCARIA.md | OK | equipe-plateforme | +| 2026-03-08 | install | viewer-bff | Ajout backend Node/Express + UI POC pour consultation via `media-access-api` | viewer-bff/, VIEWER_BFF.md, compose.photoprism-secure.dev.yml, .env.photoprism-secure.example, INSTALLATION.md, CONFIGURATION.md, TROUBLESHOOTING.md, INTEGRATIONS.md, CADDY_ARAUCARIA.md, DECISIONS.md, MEDIA_ACCESS_API.md | OK | equipe-plateforme | +| 2026-03-08 | install | deploiement konenpan | Ajout runbook de transfert/release/rollback pour poser les fichiers sur la VM | docs/DEPLOIEMENT_KONENPAN.md, INSTALLATION.md | OK | equipe-plateforme | +| 2026-03-08 | install | git relay chillka | Standardisation du flux Antel -> chillka bare repo -> konenpan avec branches `main` + `develop` | docs/DEPLOIEMENT_KONENPAN.md, INSTALLATION.md | OK | equipe-plateforme | diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md new file mode 100644 index 0000000..6d9befa --- /dev/null +++ b/docs/CONFIGURATION.md @@ -0,0 +1,185 @@ +# CONFIGURATION + +## Objectif +Tracer tous les parametres techniques et choix de configuration. + +## Perimetre +- Composant: +- Environnement: +- Proprietaire technique: + +## Prerequis +- Installation terminee et validee +- Acces administrateur au composant +- Variables d'environnement disponibles + +## Parametres de configuration +| Cle | Valeur cible | Source | Sensible (oui/non) | Commentaire | +| --- | --- | --- | --- | --- | +| example.key | example.value | env/file | non | A adapter | + +## Procedure de configuration +1. Sauvegarder l'etat courant. +2. Appliquer les parametres. +3. Redemarrer/recharger le composant. +4. Controler les effets attendus. + +## Validation de configuration +- Parametres appliques et persistants +- Test fonctionnel reussi +- Test securite reussi (roles/scopes/groupes) + +## Rollback configuration +- Sauvegarde utilisee: +- Etapes de retour arriere: +- Verification apres retour arriere: + +## Logs et evidences +- Logs controles: +- Captures/preuves: +- Ticket/incident lie: + +## Historique des modifications +- Date: +- Auteur: +- Changement: + +## Exemple rempli: configuration OIDC Keycloak pour acces medias +### Perimetre +- Composant: media-access-api +- Environnement: dev +- Proprietaire technique: equipe plateforme + +### Parametres de configuration (exemple) +| Cle | Valeur cible | Source | Sensible (oui/non) | Commentaire | +| --- | --- | --- | --- | --- | +| OIDC_ISSUER | https://auth.local/realms/medias | env | non | URL issuer Keycloak | +| OIDC_AUDIENCE | media-access-api | env | non | Audience attendue | +| OIDC_JWKS_CACHE_TTL | 300 | env | non | Cache JWKS en secondes | +| RBAC_REQUIRED_ROLE | media_reader | env | non | Role minimal lecture | +| OIDC_CLIENT_SECRET | *** | secret store | oui | Secret client confidentiel | + +### Procedure de configuration (exemple) +1. Sauvegarder l'ancien `.env`. +2. Mettre a jour les variables OIDC et RBAC. +3. Redemarrer `media-access-api`. +4. Verifier le endpoint de sante et un endpoint media protege. + +### Validation de configuration (exemple) +- Token valide accepte (200). +- Token sans role `media_reader` refuse (403). +- Token avec mauvais `aud` refuse (401). + +### Rollback configuration (exemple) +- Sauvegarde utilisee: `.env.backup.2026-03-07` +- Etapes de retour arriere: restaurer backup puis redemarrer service. +- Verification apres retour arriere: tests 200/401/403 conformes. + +## Exemple rempli: ACL cumulatives Keycloak -> MinIO -> viewer BFF +### Perimetre +- Composant: media-access-api (enforcement) + viewer-bff (visualisation) +- Environnement: dev +- Proprietaire technique: equipe plateforme + +### Modele de roles cumulables (source Keycloak) +- Principe: deny-by-default, puis union des droits autorises par roles. +- Convention de roles client (client `media-access-api`): + - `media_reader:all` + - `media_reader:folder:photos/equipeA` + - `media_reader:folder:photos/projetX` + - `media_reader:folder:photos/partage` +- Regle d'evaluation: + - si `media_reader:all` est present, acces lecture global; + - sinon, acces limite aux prefixes declares dans les roles `media_reader:folder:*`; + - l'absence de role valide retourne 403. + +### Mapping role -> prefixe MinIO (exemple) +| Role Keycloak | Prefixe autorise | Type d'acces | +| --- | --- | --- | +| media_reader:folder:photos/equipeA | photos/equipeA/ | lecture | +| media_reader:folder:photos/projetX | photos/projetX/ | lecture | +| media_reader:folder:photos/partage | photos/partage/ | lecture | +| media_reader:all | photos/ | lecture globale | + +### Parametres de configuration (exemple) +| Cle | Valeur cible | Source | Sensible (oui/non) | Commentaire | +| --- | --- | --- | --- | --- | +| OIDC_ISSUER | https://kc.arauco.online/realms/chiruca | env | non | Realm existant | +| OIDC_AUDIENCE | media-access-api | env | non | Audience attendue | +| OIDC_CLIENT_ID | media-access-api | env | non | Client OIDC de l'API | +| OIDC_CLIENT_SECRET | *** | secret store | oui | Secret client confidentiel | +| RBAC_ROLE_PREFIX | media_reader:folder: | env | non | Prefixe de parsing des roles | +| RBAC_ROLE_ALL | media_reader:all | env | non | Role de bypass lecture globale | +| S3_BUCKET | medias-private | env | non | Bucket prive | +| PRESIGN_TTL_SECONDS | 120 | env | non | Duree de vie URL signee | + +### Procedure de configuration (exemple) +1. Creer/mettre a jour les roles client dans Keycloak (`media-access-api`). +2. Affecter les roles aux groupes/utilisateurs selon les dossiers autorises. +3. Aligner les prefixes MinIO avec la convention de roles. +4. Configurer `media-access-api` avec OIDC + S3 + TTL de signature. +5. Redemarrer API et front (`viewer-bff`/proxy) puis valider les acces. + +### Validation de configuration (exemple) +- Utilisateur avec 2 roles dossier voit la somme des 2 perimetres. +- URL signee hors prefixe autorise refusee (403). +- URL signee expiree refusee (403/401 selon implementation). + +### Rollback configuration (exemple) +- Sauvegarde utilisee: export realm Keycloak + backup `.env`. +- Etapes de retour arriere: restaurer roles precedents, recharger config API. +- Verification apres retour arriere: tests ACL et expiration conformes. + +## Exemple rempli: configuration viewer-bff (Node/Express) +### Perimetre +- Composant: viewer-bff +- Environnement: dev +- Proprietaire technique: equipe plateforme + +### Parametres de configuration (exemple) +| Cle | Valeur cible | Source | Sensible (oui/non) | Commentaire | +| --- | --- | --- | --- | --- | +| VIEWER_BFF_PORT | 8082 | env | non | Port HTTP du BFF | +| MEDIA_API_BASE_URL | http://media-access-api:8081 | env | non | URL interne de l'API ACL | +| CORS_ALLOWED_ORIGIN | https://photos.arauco.online | env | non | Origine frontend autorisee | + +### Procedure de configuration (exemple) +1. Copier `.env.photoprism-secure.example` vers `.env.dev`. +2. Renseigner `MEDIA_API_BASE_URL` et `CORS_ALLOWED_ORIGIN`. +3. Lancer `viewer-bff` via `docker compose`. +4. Verifier `GET /health` et les endpoints `/api/me/permissions`, `/api/media/presign`. + +### Validation de configuration (exemple) +- `curl -I https://photos.arauco.online/health` retourne 200. +- `GET /api/me/permissions` sans token retourne 401. +- `POST /api/media/presign` avec token autorise retourne URL signee. + +## Exemple rempli: configuration DNS local via dnsmasq (stack medias) +### Perimetre +- Composant: dnsmasq (LAN) +- Environnement: dev +- Proprietaire technique: equipe plateforme + +### Parametres de configuration (exemple) +| Cle | Valeur cible | Source | Sensible (oui/non) | Commentaire | +| --- | --- | --- | --- | --- | +| EDGE_PROXY_HOST | araucaria | infra | non | Point d'entree Caddy | +| EDGE_PROXY_IP | 192.168.99.10 | infra | non | IP Caddy (exemple) | +| BACKEND_MEDIA_VM_IP | 192.168.99.23 | infra | non | IP VM konenpan | +| DNS_RECORD_PHOTOS | photos.arauco.online -> 192.168.99.10 | dnsmasq | non | UI viewer-bff via Caddy | +| DNS_RECORD_MINIO | minio.arauco.online -> 192.168.99.10 | dnsmasq | non | API S3 via Caddy | +| DNS_RECORD_MINIO_CONSOLE | minio-console.arauco.online -> 192.168.99.10 | dnsmasq | non | Console MinIO via Caddy | +| DNS_RECORD_MEDIA_API | media-api.arauco.online -> 192.168.99.10 | dnsmasq | non | API ACL via Caddy | + +### Procedure de configuration (exemple) +1. Creer `/etc/dnsmasq.d/20-medias-araucaria.conf`. +2. Ajouter les `host-record` de `docs/DNSMASQ_MEDIAS.md` (vers IP `araucaria`). +3. Configurer les vhosts Caddy de `docs/CADDY_ARAUCARIA.md` (vers IP `konenpan`). +4. Tester `dnsmasq --test` et `caddy validate`. +5. Redemarrer `dnsmasq` puis recharger Caddy. +6. Valider la resolution DNS puis les endpoints HTTPS. + +### Validation de configuration (exemple) +- `dig +short photos.arauco.online` retourne l'IP de `araucaria`. +- `curl -I https://photos.arauco.online` retourne une reponse HTTP valide. +- La resolution de `kc.arauco.online` n'est pas surchargee localement. diff --git a/docs/DEPLOIEMENT_KONENPAN.md b/docs/DEPLOIEMENT_KONENPAN.md new file mode 100644 index 0000000..4adceb6 --- /dev/null +++ b/docs/DEPLOIEMENT_KONENPAN.md @@ -0,0 +1,158 @@ +# DEPLOIEMENT_KONENPAN + +## Objectif +Deployer les fichiers du projet sur `konenpan` de facon propre, reproductible et reversible. + +## Strategie recommandee (Git relay) +- Poste dev (Antel) -> depot bare central sur `chillka`: + - `/var/data/git/repositories/medias.git` +- `konenpan` deploie depuis ce depot Git (clone/pull). +- Branches officielles: + - `main` (stable) + - `develop` (integration) +- Aucune branche `master` exploitee. + +## Initialisation du depot central sur chillka +```bash +cd /var/data/git/repositories +git init --bare medias.git + +# Forcer HEAD du depot bare sur main (pas master) +git --git-dir=/var/data/git/repositories/medias.git symbolic-ref HEAD refs/heads/main +``` + +## Bootstrap des branches depuis Antel (Windows Git Bash) +Depuis `e:/Dev/Web-Works/Medias`: +```bash +cd /e/Dev/Web-Works/Medias +git init +git checkout -b main +git add . +git commit -m "Initial commit medias platform" + +git remote add origin toshiro@192.168.99.55:/var/data/git/repositories/medias.git +git push -u origin main + +# Creer la branche develop et la publier +git checkout -b develop +git push -u origin develop +``` + +Verification sur chillka: +```bash +git --git-dir=/var/data/git/repositories/medias.git branch -a +git --git-dir=/var/data/git/repositories/medias.git symbolic-ref HEAD +``` + +Resultat attendu: +- branches: `main`, `develop` +- HEAD: `refs/heads/main` + +## Deploiement depuis konenpan (via Git) +```bash +mkdir -p ~/src +cd ~/src +git clone toshiro@192.168.99.55:/var/data/git/repositories/medias.git +cd medias + +# Production/validation stable +git checkout main + +# Option integration +# git checkout develop +``` + +Ensuite: +```bash +cp .env.photoprism-secure.example .env.dev +nano .env.dev +docker compose --env-file .env.dev -f compose.photoprism-secure.dev.yml up -d --build +``` + +## Cycle de mise a jour +Sur Antel: +```bash +cd /e/Dev/Web-Works/Medias +git checkout develop +git add . +git commit -m "Update: " +git push +``` + +Sur konenpan: +```bash +cd ~/src/medias +git checkout develop +git pull --ff-only +docker compose --env-file .env.dev -f compose.photoprism-secure.dev.yml up -d --build +``` + +## Emplacement recommande +- Code applicatif: `/opt/medias/releases/` +- Lien actif: `/opt/medias/current` +- Secrets env: `/opt/medias/shared/.env.dev` (hors release) +- Proprietaire: utilisateur d'exploitation (ex: `toshiro`) + +Pourquoi: +- rollback facile via changement du lien `current` +- separation nette code/secrets +- evite les edits manuels disperses dans `~/` + +## Preparation sur konenpan +```bash +sudo mkdir -p /opt/medias/releases /opt/medias/shared +sudo chown -R toshiro:toshiro /opt/medias +``` + +## Transfert depuis Windows (PowerShell) +Depuis `e:\Dev\Web-Works\Medias`: +```powershell +$TS = Get-Date -Format "yyyyMMdd-HHmmss" +ssh toshiro@192.168.99.23 "mkdir -p /opt/medias/releases/$TS" +scp -r "e:\Dev\Web-Works\Medias\*" toshiro@192.168.99.23:/opt/medias/releases/$TS/ +``` + +Option recommandee (si `rsync` dispo) pour transferts incrementaux: +```bash +rsync -avz --delete \ + --exclude ".git" \ + --exclude ".env*" \ + e:/Dev/Web-Works/Medias/ \ + toshiro@192.168.99.23:/opt/medias/releases// +``` + +## Activation de la release +Sur `konenpan`: +```bash +ln -sfn /opt/medias/releases/ /opt/medias/current +cp /opt/medias/current/.env.photoprism-secure.example /opt/medias/shared/.env.dev +nano /opt/medias/shared/.env.dev +``` + +Lancer la stack depuis `current` avec env partage: +```bash +cd /opt/medias/current +docker compose --env-file /opt/medias/shared/.env.dev -f compose.photoprism-secure.dev.yml up -d --build +docker compose --env-file /opt/medias/shared/.env.dev -f compose.photoprism-secure.dev.yml ps +``` + +## Validation post-deploiement +```bash +curl -I http://127.0.0.1:8082/health +curl -I http://127.0.0.1:8081/health +docker compose --env-file /opt/medias/shared/.env.dev -f compose.photoprism-secure.dev.yml logs --since=15m +``` + +## Rollback rapide +```bash +ln -sfn /opt/medias/releases/ /opt/medias/current +cd /opt/medias/current +docker compose --env-file /opt/medias/shared/.env.dev -f compose.photoprism-secure.dev.yml up -d --build +``` + +## Bonnes pratiques +- Ne jamais transferer de secrets depuis le poste local. +- Versionner le code, pas `.env.dev`. +- Garder 2-3 releases precedentes pour rollback. +- Tracer chaque deploiement dans `docs/CHANGELOG_OPERATIONS.md`. +- Proteger `main` (pas de push direct en production, privilegier PR depuis `develop`). diff --git a/docs/DNSMASQ_MEDIAS.md b/docs/DNSMASQ_MEDIAS.md new file mode 100644 index 0000000..91b5363 --- /dev/null +++ b/docs/DNSMASQ_MEDIAS.md @@ -0,0 +1,48 @@ +# DNSMASQ_MEDIAS + +## Objectif +Publier des noms DNS stables pour la stack medias en reseau local, sans ecraser les enregistrements publics existants. + +## Recommandation +- Ne pas surcharger `kc.arauco.online` en local (risque de casser l'OIDC externe). +- Publier les noms applicatifs vers l'IP de `araucaria` (Caddy), pas directement vers `konenpan`. +- Utiliser des noms publics de service (`*.arauco.online`) termines par Caddy en HTTPS. + +## Exemple de fichier dnsmasq (avec Caddy sur araucaria) +Fichier recommande sur l'hote DNS: +- `/etc/dnsmasq.d/20-medias-araucaria.conf` + +Contenu: +```ini +# Caddy sur araucaria (remplacer par l'IP reelle) +host-record=photos.arauco.online,192.168.99.10 +host-record=media-api.arauco.online,192.168.99.10 +host-record=minio-console.arauco.online,192.168.99.10 +host-record=minio.arauco.online,192.168.99.10 +``` + +## Application +```bash +sudo dnsmasq --test +sudo systemctl restart dnsmasq +sudo systemctl status dnsmasq --no-pager +``` + +## Validation +```bash +dig +short photos.arauco.online +dig +short media-api.arauco.online +dig +short minio-console.arauco.online +``` + +Resultat attendu: IP de `araucaria` (Caddy). + +## Mapping de ports conseille +- `https://photos.arauco.online` -> Caddy -> `konenpan:8082` (`viewer-bff`) +- `https://media-api.arauco.online` -> Caddy -> `konenpan:8081` +- `https://minio-console.arauco.online` -> Caddy -> `konenpan:9001` +- `https://minio.arauco.online` -> Caddy -> `konenpan:9000` + +## Notes +- Architecture cible: DNS -> Caddy (araucaria) -> services konenpan. +- En production, preferer des enregistrements DNS authoritatifs centralises + TLS valide. diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md new file mode 100644 index 0000000..323622d --- /dev/null +++ b/docs/INSTALLATION.md @@ -0,0 +1,106 @@ +# INSTALLATION + +## Objectif +Documenter une installation reproductible de la plateforme medias. + +## Documentation liee +- Creation VM: `docs/VM_KONENPAN_CREATION.md` +- Troubleshooting: `docs/TROUBLESHOOTING.md` +- Template incident: `docs/TEMPLATE_INCIDENT.md` +- Couche acces media: `MEDIA_ACCESS_API.md` +- Backend viewer: `VIEWER_BFF.md` +- Ingestion Windows: `WINDOWS_INGESTION.md` +- DNS local stack medias: `docs/DNSMASQ_MEDIAS.md` +- Reverse proxy HTTPS: `docs/CADDY_ARAUCARIA.md` +- Deploiement sur VM: `docs/DEPLOIEMENT_KONENPAN.md` + +## Perimetre +- Services concernes: +- Version cible: +- Environnement cible: dev | test | prod + +## Prerequis +- Acces depots et secrets necessaires +- Docker / Docker Compose installes +- Certificats/TLS disponibles +- DNS et ports valides + +## Variables et secrets +- Fichier source des variables: +- Secrets requis: +- Regle: ne jamais committer les secrets + +## Procedure d'installation +1. Preparer l'environnement. +2. Recuperer la configuration necessaire. +3. Demarrer les services. +4. Initialiser les composants (DB, realm IAM, etc.). +5. Verifier l'etat de sante. + +## Validation post-installation +- Services demarres et stables +- Authentification fonctionnelle +- Acces medias testes +- Logs sans erreur bloquante + +## Rollback +- Conditions de rollback: +- Etapes de rollback: +- Verification apres rollback: + +## Logs et traces +- Emplacement des logs: +- Commandes de collecte: +- Duree de retention: + +## Historique des mises a jour +- Date: +- Auteur: +- Resume du changement: + +## Exemple rempli: stack medias avec viewer BFF (dev) +### Perimetre +- Services concernes: minio, media-access-api, viewer-bff +- Version cible: keycloak externe (`kc.arauco.online`), minio latest stable, Node.js 20 LTS (viewer-bff) +- Environnement cible: dev + +### Variables et secrets +- Fichier source des variables: `.env.dev` (non versionne) +- Secrets requis: `OIDC_CLIENT_SECRET`, `MINIO_ROOT_PASSWORD` + +### Procedure d'installation (exemple) +1. Deployer les fichiers sur `konenpan` via le depot Git relay `chillka` selon `docs/DEPLOIEMENT_KONENPAN.md`. +2. Copier `.env.photoprism-secure.example` vers `.env.dev` puis renseigner les secrets. +3. Configurer les enregistrements DNS locaux vers `araucaria` (`docs/DNSMASQ_MEDIAS.md`). +4. Configurer Caddy sur `araucaria` pour publier les services en HTTPS (`docs/CADDY_ARAUCARIA.md`). +5. Verifier l'acces OIDC externe (`OIDC_ISSUER` vers `kc.arauco.online/realms/chiruca`). +6. Demarrer la stack locale avec `compose.photoprism-secure.dev.yml`. +7. Verifier la creation du bucket prive `medias-private` et l'absence de lecture anonyme. +8. Publier `viewer-bff` via Caddy (`photos.arauco.online`) et router `media-api.arauco.online` vers `media-access-api`. +9. Lancer une ingestion initiale des dossiers Windows (`WINDOWS_INGESTION.md`). + +Commandes de reference: +```bash +cp .env.photoprism-secure.example .env.dev +docker compose --env-file .env.dev -f compose.photoprism-secure.dev.yml up -d +docker compose --env-file .env.dev -f compose.photoprism-secure.dev.yml ps +``` + +### Validation post-installation (exemple) +- Login utilisateur test via OIDC OK. +- `GET /v1/permissions` retourne les prefixes attendus. +- URL pre-signee valide permet lecture objet autorise. +- URL directe MinIO sans signature refusee. +- Utilisateur multi-roles voit la somme des dossiers autorises. +- Acces HTTPS via Caddy valide (`photos.arauco.online`, `media-api.arauco.online`). +- `GET https://photos.arauco.online/health` retourne `status=ok`. + +### Rollback (exemple) +- Conditions: echec OIDC global, erreurs 5xx persistantes API, ACL non conformes. +- Etapes: revenir au compose precedent, restaurer `.env` et mapping roles, redemarrer les services stables. +- Verification apres rollback: login OIDC + lecture media dossier autorise uniquement. + +### Logs et traces (exemple) +- Emplacement des logs: `docker compose logs`, logs `media-access-api`, logs `viewer-bff`. +- Commandes de collecte: `docker compose -f compose.photoprism-secure.dev.yml logs --since=30m > logs-install-dev.txt` +- Duree de retention: 14 jours (dev). diff --git a/docs/TEMPLATE_INCIDENT.md b/docs/TEMPLATE_INCIDENT.md new file mode 100644 index 0000000..fc49825 --- /dev/null +++ b/docs/TEMPLATE_INCIDENT.md @@ -0,0 +1,50 @@ +# TEMPLATE_INCIDENT + +## Resume rapide +- Date/heure de detection: +- Environnement: dev | test | prod +- Service impacte: +- Gravite: faible | moyenne | elevee | critique +- Statut: ouvert | en cours | resolu + +## Symptome +- Ce qui est observe: +- Message d'erreur principal: +- Portee de l'impact (utilisateurs/systemes): + +## Verification initiale +- Healthcheck service: +- Disponibilite dependances (DB, IAM, stockage): +- Verification reseau/DNS: +- Verification authentification/autorisation: + +## Diagnostic +- Hypothese 1: +- Hypothese 2: +- Cause racine retenue: + +## Correctif applique +- Action 1: +- Action 2: +- Rollback necessaire: oui | non +- Detail rollback (si oui): + +## Validation +- Test fonctionnel: +- Test securite: +- Resultat final: OK | NOK + +## Prevention +- Action preventive 1: +- Action preventive 2: +- Ticket de suivi: + +## Pieces de preuve +- Logs: +- Captures: +- Liens dashboard/monitoring: + +## Cloture +- Date/heure: +- Responsable: +- Documents mis a jour: TROUBLESHOOTING.md | CHANGELOG_OPERATIONS.md | autre diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 0000000..58d72a2 --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -0,0 +1,140 @@ +# TROUBLESHOOTING + +## Objectif +Fournir une procedure de diagnostic et resolution rapide des incidents. + +## Symptomes frequents +- Echec connexion Keycloak +- Refus d'acces media (403) +- Erreur token/JWT +- Indisponibilite stockage media +- Decalage horaire VM / erreurs de validation temporelle (token, TLS) + +## Checklist de diagnostic initial +1. Identifier l'environnement impacte. +2. Verifier l'etat des services. +3. Consulter les logs applicatifs et IAM. +4. Reproduire le probleme avec un cas minimal. + +## Checklist express: `photos.arauco.online` inaccessible +Executer dans cet ordre pour isoler rapidement le point de blocage. + +```bash +# 1) DNS: le domaine doit pointer vers l'IP de araucaria (Caddy) +dig +short photos.arauco.online + +# 2) Reachability 443 depuis client +curl -vkI https://photos.arauco.online + +# 3) Caddy actif et sans erreur de config sur araucaria +sudo caddy validate --config /etc/caddy/Caddyfile && sudo systemctl status caddy --no-pager + +# 4) Port 443 ecoute sur araucaria +sudo ss -lntp | grep :443 + +# 5) Backend viewer-bff reachable depuis araucaria +curl -I http://192.168.99.23:8082/health +``` + +Interpretation rapide: +- (1) vide ou mauvaise IP: corriger dnsmasq. +- (2) timeout/refused: route/firewall/Caddy KO. +- (3) echec validate: corriger Caddyfile. +- (4) rien sur 443: Caddy non demarre ou bind incorrect. +- (5) backend KO: service viewer-bff/route VM a corriger. + +## Matrice cause probable -> action +| Symptom | Cause probable | Verification | Action corrective | +| --- | --- | --- | --- | +| 401/403 | Role/groupe manquant | Inspecter claims JWT | Corriger mapping roles/groupes | +| Timeout | Service indisponible | Healthcheck et reseau | Redemarrer service / corriger reseau | +| Token invalide | Mauvaise config OIDC | Verifier issuer/audience | Corriger configuration client | +| 401 sur viewer-bff | Token absent/non transfere | Verifier header Authorization | Rejouer avec Bearer token valide | +| Heure incoherente | NTP non synchronise | `timedatectl`, `chronyc tracking` | Configurer chrony vers `araucaria` | +| Nom DNS local non resolu | Record dnsmasq absent/invalide | `dig +short .arauco.online` | Corriger fichier dnsmasq puis restart service | +| HTTPS KO via domaine | Caddy non charge / vhost invalide | `caddy validate`, `journalctl -u caddy` | Corriger Caddyfile puis reload Caddy | + +## Procedure de resolution +1. Isoler la cause racine. +2. Appliquer la correction minimale. +3. Valider la resolution. +4. Documenter l'incident et la correction. + +## Escalade +- Niveau 1: +- Niveau 2: +- Niveau 3: + +## Post-mortem court +- Impact: +- Cause racine: +- Correctif: +- Prevention: + +## Template d'incident +- Utiliser `docs/TEMPLATE_INCIDENT.md` pour chaque incident. +- Copier le template, le remplir, puis referencer l'entree dans `docs/CHANGELOG_OPERATIONS.md`. + +## Historique incidents resolus +- Date: +- Incident: +- Resolution: + +## Cas pratique: erreur 403 sur media protege +### Contexte +- Date: 2026-03-07 +- Environnement: dev +- Symptome: utilisateur authentifie, mais acces refuse sur `GET /media/{id}` (403) + +### Diagnostic +1. Token JWT decode: role `media_reader` absent. +2. Mapping Keycloak verifie: groupe `consultants` non mappe vers role applicatif. +3. Logs API confirment: `missing required role media_reader`. + +### Correction appliquee +1. Ajouter mapper de groupe dans Keycloak (`consultants` -> `media_reader`). +2. Forcer reconnexion utilisateur pour emettre un nouveau token. +3. Rejouer le test d'acces sur le meme media. + +### Validation +- Avant correctif: 403 confirme. +- Apres correctif: 200 confirme. +- Aucun impact detecte sur les autres roles. + +### Prevention +- Ajouter test automatique de non-regression RBAC (cas 200/403). +- Exiger verification des mappers lors de creation de nouveau groupe. + +## Cas pratique: DNS local medias non resolu +### Contexte +- Symptome: `photos.arauco.online` ne repond pas ou resolve une mauvaise IP. + +### Diagnostic +1. Verifier fichier `/etc/dnsmasq.d/20-medias-araucaria.conf`. +2. Verifier syntaxe: `sudo dnsmasq --test`. +3. Verifier service: `sudo systemctl status dnsmasq --no-pager`. +4. Verifier resolution: `dig +short photos.arauco.online`. + +### Correction appliquee +1. Corriger les records `host-record` vers l'IP de `araucaria` (Caddy). +2. Redemarrer `dnsmasq`. +3. Vider cache DNS poste client si necessaire. + +### Validation +- `dig +short photos.arauco.online` retourne l'IP de `araucaria`. +- Acces URL de service valide. + +## Cas pratique: domaine HTTPS indisponible via Caddy +### Contexte +- Symptome: `https://photos.arauco.online` indisponible alors que le service local repond. + +### Diagnostic +1. Verifier Caddy: `sudo caddy validate --config /etc/caddy/Caddyfile`. +2. Verifier etat: `sudo systemctl status caddy --no-pager`. +3. Verifier logs: `sudo journalctl -u caddy -n 100 --no-pager`. +4. Verifier backend: `curl -I http://192.168.99.23:8082/health`. + +### Correction appliquee +1. Corriger le bloc de vhost dans Caddyfile. +2. Recharger: `sudo systemctl reload caddy`. +3. Rejouer le test HTTPS. diff --git a/docs/Untitled b/docs/Untitled new file mode 100644 index 0000000..498098d --- /dev/null +++ b/docs/Untitled @@ -0,0 +1 @@ +gKmen2026 \ No newline at end of file diff --git a/docs/VM_KONENPAN_CREATION.md b/docs/VM_KONENPAN_CREATION.md new file mode 100644 index 0000000..6ba9f28 --- /dev/null +++ b/docs/VM_KONENPAN_CREATION.md @@ -0,0 +1,391 @@ +# VM_KONENPAN_CREATION + +## Objectif +Documenter la creation complete de la VM `Konenpan` (de A a Z) sur infrastructure KVM/libvirt, avec un volume LVM de `1T`. + +## Specification demandee +- Nom de la VM: `konenpan` +- Taille stockage: `1T` +- Hyperviseur: KVM/QEMU via libvirt (`virsh`, `virt-install`) +- Usage: base d'hebergement de la plateforme medias (IAM, stockage, services applicatifs) + +## Parametres cibles +- RAM: `8192` MB (8 GB, a ajuster) +- vCPU: `4` (a ajuster) +- Bridge reseau: `br0` +- Groupe de volumes LVM: `vgarauco0` +- Volume LVM: `vgarauco0-konenpan` +- OS variant: `ubuntu24.04` (Ubuntu Server 24.04.1) +- ISO: `/var/lib/libvirt/images/ubuntu-24.04.1-live-server-amd64.iso` + +## Procedure complete (A a Z) + +### 1) Verification des prerequis sur l'hote +```bash +# Verifier les commandes necessaires +command -v virsh +command -v virt-install +command -v lvcreate + +# Installer les dependances si manquantes (commande issue du projet Araucaria) +apt-get update && apt-get install -y libvirt-clients libvirt-daemon-system qemu-kvm virtinst + +# Verifier le service libvirt +systemctl status libvirtd +systemctl enable --now libvirtd +``` + +### 2) Verification de l'existant (VMs, reseau, stockage) +```bash +# VMs existantes +virsh list --all +virsh list --all --name + +# Reseaux/bridges +virsh net-list --all +brctl show +ip link show type bridge + +# Stockage LVM +vgs vgarauco0 +lvs vgarauco0 --units g -o lv_name,lv_size +virsh domblklist --all | grep -E "/dev/mapper" || true +``` + +### 3) Creation du volume LVM 1T pour Konenpan +```bash +# Creation du LV de 1 To +lvcreate -L 1T -n konenpan vgarauco0 + +# Verifications +lvs vgarauco0 | grep konenpan +ls -lh /dev/mapper/vgarauco0-konenpan +``` + +### 4) Verification de l'ISO Ubuntu +```bash +# ISO imposee pour ce runbook +ISO_UBUNTU="/var/lib/libvirt/images/ubuntu-24.04.1-live-server-amd64.iso" +echo "ISO selectionnee: $ISO_UBUNTU" + +# Verifier acces en lecture +test -r "$ISO_UBUNTU" && echo "ISO OK" || echo "ISO manquante/inaccessible" +``` + +### 5) Verifier que la VM konenpan n'existe pas deja +```bash +virsh list --all | grep konenpan || true + +# Si necessaire (attention: supprime la definition libvirt, pas le LV) +# virsh destroy konenpan 2>/dev/null +# virsh undefine konenpan +``` + +### 6) Creation de la VM (nouvelle installation depuis ISO) +```bash +virt-install \ + --name konenpan \ + --memory 8192 \ + --vcpus 4 \ + --os-variant ubuntu24.04 \ + --disk path=/dev/mapper/vgarauco0-konenpan,bus=virtio \ + --network bridge=br0,model=virtio \ + --graphics vnc,listen=0.0.0.0 \ + --cdrom /var/lib/libvirt/images/ubuntu-24.04.1-live-server-amd64.iso \ + --noautoconsole +``` + +### 6.1) Connexion VNC pour installer le systeme +```bash +# Sur l'hote hyperviseur: recuperer l'ecran VNC attribue +virsh vncdisplay konenpan +# Exemple de sortie: :0 (port TCP 5900), :1 (5901), etc. + +# Option pratique: calculer automatiquement le port VNC +VNC_DISPLAY=$(virsh vncdisplay konenpan | tr -d ':') +VNC_PORT=$((5900 + VNC_DISPLAY)) +echo "Display :$VNC_DISPLAY -> Port $VNC_PORT" +``` + +```bash +# Si vous etes connecte directement a l'hote (avec interface graphique) +vncviewer 127.0.0.1:$VNC_PORT +``` + +```bash +# Si vous etes sur un poste distant: creer un tunnel SSH +# (a lancer depuis votre poste local) +ssh -L 5900:127.0.0.1:$VNC_PORT @ + +# Puis, depuis le poste local: +vncviewer 127.0.0.1:5900 +``` + +### 7) Alternative: creation en mode import (si OS deja present sur disque) +```bash +virt-install \ + --name konenpan \ + --memory 8192 \ + --vcpus 4 \ + --os-variant ubuntu24.04 \ + --disk path=/dev/mapper/vgarauco0-konenpan,bus=virtio,cache=none \ + --network bridge=br0,model=virtio \ + --graphics vnc,listen=0.0.0.0 \ + --import \ + --noautoconsole +``` + +### 8) Verification immediate apres creation +```bash +virsh list --all | grep konenpan +virsh dominfo konenpan +virsh dumpxml konenpan +virsh domblklist konenpan +virsh domiflist konenpan +``` + +### 9) Demarrage, console et acces VNC +```bash +# Demarrer +virsh start konenpan +virsh list --all +virsh domstate konenpan + +# Console texte +virsh console konenpan +# Quitter la console: Ctrl+] + +# Afficher l'ecran VNC attribue +virsh vncdisplay konenpan + +# Option tunnel SSH depuis un poste local +# ssh -L 5900:127.0.0.1:5900 araucaria +# puis: vncviewer localhost:5900 +``` + +### 10) Partitionnement recommande pendant l'installation (ecran VNC) +Pour `konenpan` (1T), la meilleure approche est de separer le systeme et les donnees avec LVM: + +- `root (/)`: `120G` (OS + paquets) +- `/var`: `200G` (logs, cache, services) +- `/data`: `reste du disque` (applications/donnees medias) +- `swap`: pas de partition dediee (utiliser un swapfile apres installation) + +#### Procedure dans l'ecran "Storage configuration" +1. Selectionner `/dev/vda` puis valider pour initialiser le disque (table GPT). +2. Choisir `Create volume group (LVM)` et creer un VG (exemple: `vg_konenpan`) sur `/dev/vda`. +3. Creer les volumes logiques (LV): + - `lv_root` taille `120G`, format `ext4`, montage `/` + - `lv_var` taille `200G`, format `ext4`, montage `/var` + - `lv_data` taille `restant` (ou environ `670G`), format `ext4`, montage `/data` +4. Verifier qu'un point de montage `/` existe (obligatoire pour continuer). +5. Valider avec `Done`, confirmer l'ecriture des changements, puis poursuivre l'installation Ubuntu. + +#### Si le bouton "Done" est grise (cas frequent) +Cause: l'installateur n'a pas de disque/partition de boot selectionne. + +Dans certains ecrans Subiquity, `fat32` n'apparait pas dans la liste des formats. +Dans ce cas, suivre le mode de boot detecte: + +**Cas A - UEFI (recommande)** +1. Selectionner `/dev/vda` puis `Add GPT partition` de `512M` a `1G`. +2. Si `fat32` est disponible: format `fat32`, mount `/boot/efi`. +3. Si `fat32` n'est pas disponible: choisir `Leave unformatted`, mount `/boot/efi` (l'installateur traitera cette partition comme ESP). +4. Marquer `/dev/vda` comme boot device (`Use as boot device`) si l'option est proposee. + +**Cas B - BIOS (legacy)** +1. Selectionner `/dev/vda` puis `Add GPT partition` de `1M`. +2. Choisir `Leave unformatted` et affecter le role `bios_grub` (si propose par l'UI). +3. Ne pas utiliser `/boot/efi` en mode BIOS. + +Puis, dans les 2 cas: +1. Creer le LVM sur le reste du disque (`vg_konenpan` + `lv_root`, `lv_var`, `lv_data`). +2. Verifier au minimum: + - un montage `/` existe + - un boot target existe (`/boot/efi` en UEFI, ou `bios_grub` en BIOS) +3. Revenir sur `Done`: le bouton doit devenir actif. + +Remarque: si tout le disque a deja ete consomme par le PV LVM, recreer le schema en reservant d'abord la partition de boot, puis utiliser le reste pour le PV LVM. + +#### Cas reel: l'installation est lancee sans selection explicite "boot" +Si l'installateur a accepte la configuration et a demarre, c'est souvent valide. +Dans ce cas, faire une verification apres le premier redemarrage: + +```bash +# Dans la VM +test -d /sys/firmware/efi && echo "Boot UEFI" || echo "Boot BIOS" +lsblk -f +df -h +``` + +Verifier ensuite la presence de GRUB: + +```bash +# Dans la VM +sudo grub-install --version +sudo update-grub +``` + +Si la VM ne boote pas apres retrait de l'ISO: +- remettre temporairement l'ISO, +- booter en mode rescue/live, +- chroot sur le systeme installe, +- reinstaller GRUB (UEFI ou BIOS selon le mode detecte). + +#### Option plus simple (si tu veux aller plus vite) +- Utiliser "Use an entire disk" + LVM automatique (un seul grand `/`). +- Cette option est rapide, mais moins pratique a long terme pour isoler logs/donnees. + +#### Swap (recommande en swapfile, apres installation) +Oui, ici on ne cree pas de partition swap dediee. Sur Ubuntu Server, un swapfile est plus simple a gerer. + +Taille conseillee pour `konenpan` (RAM 8G): `8G` de swap. + +```bash +# Dans la VM, apres installation +sudo fallocate -l 8G /swapfile +sudo chmod 600 /swapfile +sudo mkswap /swapfile +sudo swapon /swapfile + +# Rendre persistant +echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab + +# Verifier +swapon --show +free -h +``` + +### 11) Fin d'installation OS: retirer le CD-ROM puis redemarrer +```bash +# Identifier les disques et le lecteur CD-ROM +virsh domblklist konenpan + +# Retirer le CD-ROM persistant (exemple device sda, adapter si besoin) +virsh detach-disk konenpan sda --persistent + +# Verifier puis redemarrer +virsh domblklist konenpan +virsh reboot konenpan +# ou: +# virsh shutdown konenpan && virsh start konenpan +``` + +### 12) Configuration reseau dans la VM (netplan) +```bash +# Dans la VM +ip addr show +ls /etc/netplan/ +sudoedit /etc/netplan/00-installer-config.yaml + +# Appliquer +sudo netplan try +sudo netplan apply + +# Verifier DNS +cat /etc/resolv.conf +nslookup araucaria.local +nslookup google.com +``` + +### 13) Synchronisation de l'heure vers araucaria (NTP client) +```bash +# Dans la VM +sudo apt-get update +sudo apt-get install -y chrony + +# Sauvegarder la configuration par defaut +sudo cp /etc/chrony/chrony.conf /etc/chrony/chrony.conf.bak.$(date +%F-%H%M%S) + +# Declarer araucaria comme source NTP principale +echo "server araucaria iburst prefer" | sudo tee /etc/chrony/sources.d/araucaria.sources + +# Redemarrer et activer le service +sudo systemctl enable --now chrony +sudo systemctl restart chrony + +# Verifier la synchronisation +chronyc sources -v +chronyc tracking +timedatectl status +``` + +Notes: +- Si `araucaria` n'est pas resolu en DNS, utiliser temporairement son IP dans le fichier source Chrony. +- Quand le DNS sera finalise, revenir a `server araucaria iburst prefer`. + +### 14) Configuration hostname, SSH et mises a jour dans la VM +```bash +# Dans la VM +sudo hostnamectl set-hostname konenpan +hostnamectl + +sudo apt-get update +sudo apt-get upgrade -y +sudo apt-get install -y openssh-server qemu-guest-agent + +sudo systemctl enable --now ssh +sudo systemctl enable --now qemu-guest-agent +``` + +### 15) Validation finale +```bash +# Sur l'hote +virsh dominfo konenpan +virsh domblklist konenpan +virsh domiflist konenpan + +# Dans la VM +hostnamectl +free -h +df -h +ip addr show +timedatectl status +chronyc sources -v +ping -c 3 8.8.8.8 +``` + +## Checklist de fin +- [ ] LV `vgarauco0-konenpan` cree en `1T` +- [ ] VM `konenpan` creee avec `virt-install` +- [ ] VM demarree (`virsh start konenpan`) +- [ ] Partitionnement valide (`/`, `/var`, `/data`) +- [ ] Swap actif (`swapon --show`) +- [ ] Installation OS terminee et CD-ROM detache +- [ ] Reseau/DNS operationnels +- [ ] Synchronisation horaire active vers araucaria (chrony) +- [ ] SSH operationnel +- [ ] VM enregistree dans l'inventaire infra +- [ ] Sauvegarde planifiee + +## Rollback (si echec) +```bash +# Arreter puis supprimer la definition VM +virsh destroy konenpan 2>/dev/null || true +virsh undefine konenpan + +# Supprimer le volume LVM (attention: destructif) +lvremove -y /dev/vgarauco0/konenpan + +# Verifier qu'il ne reste rien +virsh list --all | grep konenpan || true +lvs vgarauco0 | grep konenpan || true +``` + +## Troubleshooting rapide +- VM non accessible: `virsh dominfo konenpan`, verifier bridge `br0`, firewall, NIC +- Disque absent: `virsh domblklist konenpan`, `lvs vgarauco0`, `ls -lh /dev/mapper/vgarauco0-konenpan` +- VNC indisponible: `virsh vncdisplay konenpan`, verifier tunnel SSH +- DNS KO dans la VM: verifier netplan, `resolv.conf`, `nslookup` +- Heure non synchronisee: verifier `systemctl status chrony`, `chronyc sources -v`, resolution DNS de `araucaria` + +## Sources Araucaria utilisees +- `E:\Dev\System\Araucaria\01_SKVM\docs\DOCUMENTATION_SKVM.md` +- `E:\Dev\System\Araucaria\07_VMS\docs\GUIDE_COMPLET_VM_DOCKER.md` +- `E:\Dev\System\Araucaria\migration_chillka.sh` + +## Historique +- Date: 2026-03-07 +- Auteur: equipe-plateforme +- Changement: ajout procedure complete de creation VM `konenpan` de A a Z (commandes completes) +- Changement: ajout de la configuration NTP client (chrony) pour synchronisation vers `araucaria` diff --git a/sync_windows_to_minio.ps1 b/sync_windows_to_minio.ps1 new file mode 100644 index 0000000..43e0871 --- /dev/null +++ b/sync_windows_to_minio.ps1 @@ -0,0 +1,66 @@ +param( + [Parameter(Mandatory = $true)] + [string]$SourceRoot, + + [Parameter(Mandatory = $true)] + [string]$EndpointUrl, + + [Parameter(Mandatory = $true)] + [string]$Bucket, + + [Parameter(Mandatory = $true)] + [string]$AccessKey, + + [Parameter(Mandatory = $true)] + [string]$SecretKey, + + [string]$ProfileName = "minio-sync", + [string]$PrefixRoot = "photos", + [switch]$WhatIf +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +if (-not (Test-Path -Path $SourceRoot)) { + throw "Le chemin source '$SourceRoot' est introuvable." +} + +$aws = Get-Command aws -ErrorAction SilentlyContinue +if (-not $aws) { + throw "AWS CLI est requis. Installer AWS CLI puis relancer." +} + +Write-Host "Configuration du profil AWS CLI '$ProfileName'..." +aws configure set aws_access_key_id $AccessKey --profile $ProfileName | Out-Null +aws configure set aws_secret_access_key $SecretKey --profile $ProfileName | Out-Null +aws configure set default.region us-east-1 --profile $ProfileName | Out-Null + +$normalizedSource = (Resolve-Path $SourceRoot).Path +$folders = Get-ChildItem -Path $normalizedSource -Directory + +if ($folders.Count -eq 0) { + Write-Warning "Aucun sous-dossier detecte. Synchronisation du dossier source complet." + $target = "s3://$Bucket/$PrefixRoot/" + $cmd = "aws s3 sync `"$normalizedSource`" `"$target`" --endpoint-url `"$EndpointUrl`" --profile `"$ProfileName`" --delete" + if ($WhatIf) { + Write-Host "[WhatIf] $cmd" + } else { + Invoke-Expression $cmd + } + exit 0 +} + +foreach ($folder in $folders) { + $logicalPrefix = "$PrefixRoot/$($folder.Name)/" + $target = "s3://$Bucket/$logicalPrefix" + $cmd = "aws s3 sync `"$($folder.FullName)`" `"$target`" --endpoint-url `"$EndpointUrl`" --profile `"$ProfileName`" --delete" + if ($WhatIf) { + Write-Host "[WhatIf] $cmd" + } else { + Write-Host "Sync '$($folder.FullName)' -> '$target'" + Invoke-Expression $cmd + } +} + +Write-Host "Synchronisation terminee." diff --git a/viewer-bff/.dockerignore b/viewer-bff/.dockerignore new file mode 100644 index 0000000..3abe3d4 --- /dev/null +++ b/viewer-bff/.dockerignore @@ -0,0 +1,2 @@ +node_modules +npm-debug.log diff --git a/viewer-bff/Dockerfile b/viewer-bff/Dockerfile new file mode 100644 index 0000000..af28598 --- /dev/null +++ b/viewer-bff/Dockerfile @@ -0,0 +1,13 @@ +FROM node:20-alpine + +WORKDIR /app + +COPY package.json ./ +RUN npm install --omit=dev + +COPY . . + +ENV PORT=8082 +EXPOSE 8082 + +CMD ["npm", "start"] diff --git a/viewer-bff/package.json b/viewer-bff/package.json new file mode 100644 index 0000000..489d044 --- /dev/null +++ b/viewer-bff/package.json @@ -0,0 +1,16 @@ +{ + "name": "viewer-bff", + "version": "0.1.0", + "private": true, + "description": "BFF Node/Express pour viewer medias securise", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "engines": { + "node": ">=20" + }, + "dependencies": { + "express": "^4.21.2" + } +} diff --git a/viewer-bff/public/app.js b/viewer-bff/public/app.js new file mode 100644 index 0000000..ecbe3fb --- /dev/null +++ b/viewer-bff/public/app.js @@ -0,0 +1,122 @@ +const tokenInput = document.getElementById("tokenInput"); +const objectKeysInput = document.getElementById("objectKeysInput"); +const permissionsOutput = document.getElementById("permissionsOutput"); +const gallery = document.getElementById("gallery"); + +const loadPermissionsBtn = document.getElementById("loadPermissionsBtn"); +const buildGalleryBtn = document.getElementById("buildGalleryBtn"); + +let currentPermissions = null; + +function getToken() { + return tokenInput.value.trim(); +} + +function parseObjectKeys() { + return objectKeysInput.value + .split("\n") + .map((line) => line.trim()) + .filter(Boolean); +} + +function isAllowedByPermissions(objectKey, permissions) { + if (!permissions) return false; + if (permissions.allowAll) return true; + const prefixes = permissions.allowedPrefixes || []; + return prefixes.some((prefix) => objectKey.startsWith(prefix)); +} + +async function callJson(url, options = {}) { + const token = getToken(); + const headers = { + "Content-Type": "application/json", + ...(options.headers || {}) + }; + if (token) { + headers.Authorization = `Bearer ${token}`; + } + + const response = await fetch(url, { ...options, headers }); + const payload = await response.json().catch(() => ({})); + if (!response.ok) { + throw new Error(payload.message || `HTTP ${response.status}`); + } + return payload; +} + +loadPermissionsBtn.addEventListener("click", async () => { + try { + const perms = await callJson("/api/me/permissions"); + currentPermissions = perms; + permissionsOutput.textContent = JSON.stringify(perms, null, 2); + } catch (error) { + permissionsOutput.textContent = `Erreur: ${error.message}`; + } +}); + +buildGalleryBtn.addEventListener("click", async () => { + gallery.innerHTML = ""; + const keys = parseObjectKeys(); + + if (!currentPermissions) { + permissionsOutput.textContent = "Charger d'abord les permissions."; + return; + } + + if (!keys.length) { + permissionsOutput.textContent = "Ajouter au moins une objectKey."; + return; + } + + for (const objectKey of keys) { + const card = document.createElement("article"); + card.className = "card"; + + const img = document.createElement("img"); + img.className = "thumb"; + img.alt = objectKey; + + const keyP = document.createElement("p"); + keyP.className = "key"; + keyP.textContent = objectKey; + + const openBtn = document.createElement("button"); + openBtn.textContent = "Ouvrir"; + openBtn.disabled = !isAllowedByPermissions(objectKey, currentPermissions); + + openBtn.addEventListener("click", async () => { + try { + const presign = await callJson("/api/media/presign", { + method: "POST", + body: JSON.stringify({ objectKey }) + }); + const signedUrl = presign.url; + img.src = signedUrl; + window.open(signedUrl, "_blank", "noopener,noreferrer"); + } catch (error) { + alert(`Presign refuse: ${error.message}`); + } + }); + + if (!openBtn.disabled) { + // Previsualisation opportuniste pour les objets autorises. + callJson("/api/media/presign", { + method: "POST", + body: JSON.stringify({ objectKey }) + }) + .then((presign) => { + img.src = presign.url; + }) + .catch(() => { + img.alt = "Previsualisation indisponible"; + }); + } else { + img.alt = "Acces refuse (roles)"; + } + + card.appendChild(img); + card.appendChild(keyP); + card.appendChild(openBtn); + gallery.appendChild(card); + } +}); diff --git a/viewer-bff/public/index.html b/viewer-bff/public/index.html new file mode 100644 index 0000000..8112bfa --- /dev/null +++ b/viewer-bff/public/index.html @@ -0,0 +1,43 @@ + + + + + + Viewer Medias (POC) + + + +
+

Viewer medias securise (POC)

+ +
+

1) Token utilisateur

+ + +
Aucune permission chargee.
+
+ +
+

2) Cles objet a visualiser

+

+ Une cle par ligne, exemple: photos/equipeA/2026/03/img001.jpg +

+ +
+ +
+
+ +
+

3) Galerie

+ +
+
+ + + + diff --git a/viewer-bff/public/styles.css b/viewer-bff/public/styles.css new file mode 100644 index 0000000..5417318 --- /dev/null +++ b/viewer-bff/public/styles.css @@ -0,0 +1,93 @@ +* { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: Arial, sans-serif; + background: #0f172a; + color: #e2e8f0; +} + +.container { + max-width: 1100px; + margin: 0 auto; + padding: 24px; +} + +h1 { + margin: 0 0 20px; +} + +.panel { + background: #111827; + border: 1px solid #334155; + border-radius: 8px; + padding: 16px; + margin-bottom: 16px; +} + +textarea { + width: 100%; + background: #0b1220; + color: #e2e8f0; + border: 1px solid #334155; + border-radius: 6px; + padding: 10px; +} + +button { + margin-top: 10px; + background: #2563eb; + border: none; + color: white; + border-radius: 6px; + padding: 8px 12px; + cursor: pointer; +} + +button:hover { + background: #1d4ed8; +} + +.hint { + margin-top: 0; + color: #94a3b8; +} + +#permissionsOutput { + white-space: pre-wrap; + background: #0b1220; + border: 1px solid #334155; + border-radius: 6px; + padding: 10px; + min-height: 50px; +} + +.gallery { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 14px; +} + +.card { + background: #0b1220; + border: 1px solid #334155; + border-radius: 8px; + padding: 10px; +} + +.thumb { + width: 100%; + height: 140px; + object-fit: cover; + border-radius: 6px; + background: #1e293b; +} + +.key { + margin: 8px 0; + font-size: 12px; + word-break: break-all; + color: #cbd5e1; +} diff --git a/viewer-bff/server.js b/viewer-bff/server.js new file mode 100644 index 0000000..30ef81a --- /dev/null +++ b/viewer-bff/server.js @@ -0,0 +1,115 @@ +const express = require("express"); +const path = require("path"); + +const app = express(); + +const PORT = process.env.PORT || 8082; +const MEDIA_API_BASE_URL = + process.env.MEDIA_API_BASE_URL || "http://media-access-api:8081"; +const CORS_ALLOWED_ORIGIN = process.env.CORS_ALLOWED_ORIGIN || "*"; + +app.use(express.json({ limit: "1mb" })); + +app.use((req, res, next) => { + res.setHeader("Access-Control-Allow-Origin", CORS_ALLOWED_ORIGIN); + res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS"); + res.setHeader("Access-Control-Allow-Headers", "Authorization,Content-Type"); + if (req.method === "OPTIONS") { + return res.sendStatus(204); + } + return next(); +}); + +app.use(express.static(path.join(__dirname, "public"))); + +app.get("/health", (_req, res) => { + res.json({ + status: "ok", + service: "viewer-bff", + mediaApiBaseUrl: MEDIA_API_BASE_URL + }); +}); + +function getBearerToken(req) { + const authHeader = req.headers.authorization || ""; + if (!authHeader.startsWith("Bearer ")) { + return null; + } + return authHeader.slice("Bearer ".length).trim(); +} + +async function proxyJson(req, res, targetPath, method = "GET", body) { + const token = getBearerToken(req); + if (!token) { + return res.status(401).json({ + error: "missing_token", + message: "Header Authorization Bearer requis" + }); + } + + try { + const response = await fetch(`${MEDIA_API_BASE_URL}${targetPath}`, { + method, + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json" + }, + body: body ? JSON.stringify(body) : undefined + }); + + const text = await response.text(); + let payload = {}; + if (text) { + try { + payload = JSON.parse(text); + } catch (_err) { + payload = { raw: text }; + } + } + + if (!response.ok) { + return res.status(response.status).json({ + error: "media_api_error", + status: response.status, + message: payload.message || "Erreur retour media-access-api", + details: payload + }); + } + + return res.status(response.status).json(payload); + } catch (error) { + return res.status(502).json({ + error: "media_api_unreachable", + message: "Impossible de joindre media-access-api", + details: error.message + }); + } +} + +app.get("/api/me/permissions", async (req, res) => { + return proxyJson(req, res, "/v1/permissions", "GET"); +}); + +app.post("/api/media/presign", async (req, res) => { + const objectKey = req.body?.objectKey; + if (!objectKey || typeof objectKey !== "string") { + return res.status(400).json({ + error: "invalid_payload", + message: "Champ objectKey requis" + }); + } + + return proxyJson(req, res, "/v1/presign", "POST", { objectKey }); +}); + +app.get("*", (req, res) => { + if (req.path.startsWith("/api/")) { + return res.status(404).json({ error: "not_found" }); + } + return res.sendFile(path.join(__dirname, "public", "index.html")); +}); + +app.listen(PORT, () => { + // eslint-disable-next-line no-console + console.log(`viewer-bff listening on port ${PORT}`); +}); -- cgit v1.2.3