**Ceci est une ancienne révision du document !**
Table des matières
Minio S3
configuration MinIO + service Graph (mode proxy)
Principe :
- MinIO expose S3 ;
- Un service écrit en Python reçoit les requêtes S3 et les traduit en appels Graph vers SharePoint
- Authentification via certificat
Correspondance S3 <=> SharePoint + gestion DELETE / RENAME
| S3 (MinIO) | SharePoint |
|---|---|
| Bucket | Dossier dans Documents |
| Objet | Fichier |
| Prefix | Sous‑dossier |
| DELETE | Suppression |
| RENAME | DELETE + CREATE |
| PUT | Upload |
| GET | Download |
S3 ne supporte pas le “rename” natif. MinIO envoie :
- ObjectRemoved
- ObjectCreated
- Le proxy traite automatiquement le renommage.
- Aucun code spécifique requis
- Conforme au comportement S3 officiel
Exemple :
s3://dossier/fichier.pdf -> Documents/dossier/fichier.pdf
- utilisation de 2 conteneurs :
- conteneur 1 : MinIO (S3)
- conteneur 2 : Un service Graph qui synchronise vers SharePoint
- certificat :
- certificat .cer
- clé privée .key au format PKCS#8
sharepoint-proxy – en Python
Ce service :
- reçoit les events S3 envoyés par MinIO (webhook)
- traduit vers Microsoft Graph
- manipule les fichiers dans SharePoint / Documents
Un webhook est un mécanisme de notification automatique entre applications.
Un webhook est :
- une URL exposée par une application
- qu’une autre application appelle automatiquement (HTTP POST/GET)
- quand un événement précis se produit
Contrairement à une API classique où l'on demande l’information, avec un webhook l’information vient à toi toute seule.
MinIO
└── webhook
└── appelle sharepoint-proxy
└── appelle Microsoft Graph
└── modifie SharePoint
- Quand un fichier est envoyé à minIO, MinIO déclenche automatiquement un webhook :
POST http://sharepoint-proxy:8080/s3event Content-Type: application/json
- Contenu envoyé (payload)
{
"EventName": "s3:ObjectCreated:Put",
"Key": "dossier/fichierr.pdf"
}
Webhook Python
- Arborescence
sharepoint-proxy/ ├── main.py ├── graph.py ├── requirements.txt └── README.md
- requirements.txt
- requirements.txt
flask requests msal
- graph.py – Authentification + appels Graph
- graph.py
import os import msal import requests TENANT_ID = os.getenv("TENANT_ID") CLIENT_ID = os.getenv("CLIENT_ID") CERT_PATH = os.getenv("CERT_PATH") KEY_PATH = os.getenv("KEY_PATH") SITE_PATH = os.getenv("SITE_PATH") AUTHORITY = f"https://login.microsoftonline.com/{TENANT_ID}" SCOPE = ["https://graph.microsoft.com/.default"] def get_token(): app = msal.ConfidentialClientApplication( CLIENT_ID, authority=AUTHORITY, client_credential={ "private_key": open(KEY_PATH).read(), "public_certificate": open(CERT_PATH).read() } ) token = app.acquire_token_for_client(scopes=SCOPE) if "access_token" not in token: raise RuntimeError(f"Token error: {token}") return token["access_token"] def graph_request(method, url, headers=None, data=None): token = get_token() h = { "Authorization": f"Bearer {token}", "Content-Type": "application/json" } if headers: h.update(headers) r = requests.request(method, url, headers=h, data=data) r.raise_for_status() return r.json() if r.text else None
- main.py – Webhook MinIO → SharePoint
- main.py
from flask import Flask, request import os from graph import graph_request app = Flask(__name__) TENANT_ID = os.getenv("TENANT_ID") SITE_PATH = os.getenv("SITE_PATH") GRAPH_BASE = "https://graph.microsoft.com/v1.0" def get_drive_id(): site = graph_request( "GET", f"{GRAPH_BASE}/sites/{SITE_PATH}" ) drive = graph_request( "GET", f"{GRAPH_BASE}/sites/{site['id']}/drive" ) return drive["id"] # INITIALISATION AU DÉMARRAGE print("Initializing SharePoint drive…") DRIVE_ID = get_drive_id() print(f"Drive ID initialized: {DRIVE_ID}") @app.route("/s3event", methods=["POST"]) def s3_event(): event = request.json record = event["Records"][0] event_name = record["eventName"] key = record["s3"]["object"]["key"] if event_name.startswith("s3:ObjectCreated"): upload_object(key) elif event_name.startswith("s3:ObjectRemoved"): delete_object(key) return "", 204 def upload_object(key): print(f"Uploading {key} to SharePoint") url = f"{GRAPH_BASE}/drives/{DRIVE_ID}/root:/{key}:/content" graph_request("PUT", url) def delete_object(key): print(f"Deleting {key} from SharePoint") url = f"{GRAPH_BASE}/drives/{DRIVE_ID}/root:/{key}" graph_request("DELETE", url) if __name__ == "__main__": app.run(host="0.0.0.0", port=8080)
Docker compose
- docker-compose.yml
services: minio: image: minio/minio:latest container_name: minio command: server /data --console-address ":9001" env_file: - .env volumes: - minio-data:/data ports: - "9000:9000" - "9001:9001" sharepoint-proxy: build: ./sharepoint-proxy container_name: sharepoint-proxy volumes: - ./sp-proxy:/app - ./certs:/certs:ro working_dir: /app command: python main.py env_file: - .env depends_on: - minio ports: - "8080:8080" volumes: minio-data:
Utiliszer gunicorn en production
- fichier .env
- .env
MINIO_ROOT_USER=admin MINIO_ROOT_PASSWORD=motdepasse TENANT_ID=<ID du tenant> CLIENT_ID=<ID de l'application dans Entra ID> SITE_PATH=tenant.sharepoint.com:/sites/site CERT_PATH=/certs/minio-sharepoint.pem KEY_PATH=/certs/minio-sharepoint.key AUTHORITY=https://login.microsoftonline.com/<ID du tenant> GRAPH_SCOPE=https://graph.microsoft.com/.default DOCUMENT_LIBRARY="Documents"
- vérifier que les variables d'environnement ont bien renseignées dans le conteneur
docker compose exec sharepoint-proxy env
- ajouter un Dockerfile dans le sous-dossier sharepoint-proxy pour inclure le module Flash à l'image Python
- Dockerfile
FROM python:3.12-slim WORKDIR /app # Installer les dépendances système minimales RUN apt-get update && apt-get install -y \ ca-certificates \ && rm -rf /var/lib/apt/lists/* # Copier les dépendances Python COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copier le code COPY . . EXPOSE 8080 CMD ["python", "main.py"]
- construire l'image
docker compose build sharepoint-proxy
Déclarer le webhook avec mc
MinIO n’active pas automatiquement le Webhook : il faut le déclarer avec mc.
Installer mc
La mc signifie MinIO Client : C’est l’outil en ligne de commande officiel de MinIO.
- installer mc dans la VM Debian qui excéture le Docker Compose de minio
curl -L https://dl.min.io/client/mc/release/linux-amd64/mc -o mc
- Rendre le binaire exécutable
chmod +x mc
- Déplacer dans un dossier du PATH
mv mc /usr/local/bin
- Vérifier l’installation
mc --version
- Définir l’alias minio
mc alias set minio http://localhost:9000 minioadmin minioadmin123 => résultat attendu Added `minio` successfully
- Vérifier les alias
mc alias list => résultat attendu gcs URL : https://storage.googleapis.com AccessKey : YOUR-ACCESS-KEY-HERE SecretKey : YOUR-SECRET-KEY-HERE API : S3v2 Path : dns Src : /root/.mc/config.json local URL : http://localhost:9000 AccessKey : SecretKey : API : Path : auto Src : /root/.mc/config.json minio URL : http://localhost:9000 AccessKey : admin SecretKey : Orpheus87 API : s3v4 Path : auto Src : /root/.mc/config.json play URL : https://play.min.io AccessKey : Q3AM3UQ867SPQQA43P2F SecretKey : zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG API : S3v4 Path : auto Src : /root/.mc/config.json s3 URL : https://s3.amazonaws.com AccessKey : YOUR-ACCESS-KEY-HERE SecretKey : YOUR-SECRET-KEY-HERE API : S3v4 Path : dns Src : /root/.mc/config.json
Créer le webhook
Dans Docker, il faut utiliser le nom du service Docker et pas le nom DNS réseau de la VM où s'éxécute le conteneur.
Si le proxy est sur un serveur distinct et non dans Docker, il faut alors utiliser le nom DNS du server qui exécute le proxy.
mc admin config set minio notify_webhook:sharepoint \ endpoint="http://sharepoint-proxy:8080/s3event" => résultat attendu : Successfully applied new settings. Please restart your server 'mc admin service restart minio'.
- sharepoint = nom logique du webhook
- sharepoint-proxy = nom Docker du conteneur (pas le nom DNS de la VM)
- Redémarrer MinIO
mc admin service restart minio
- Vérifier la configuration
- identifier le nom de ton alias MinIO
mc alias list => revoie notamment minio URL : http://localhost:9000
- afficher TOUTE la configuration MinIO
mc admin config get minio
- afficher la configuration des webhook
mc admin config get minio notify_webhook => doit renvoyer # MINIO_NOTIFY_WEBHOOK_ENABLE=on notify_webhook enable=off endpoint= auth_token= queue_limit=0 queue_dir= client_cert= client_key= notify_webhook:sharepoint endpoint=http://sharepoint-proxy:8080/s3event auth_token= queue_limit=1000 queue_dir=/tmp/notify client_cert= client_key=
Commentaires :
- le 1er notify_webhool est le wrbhook global et est désactivé.
- Le webhook nommé sp est bien configuré avec le endpoint http://sharepoint-proxy:8080/s3event.
- afficher la configuration d'un webhook
mc admin config get minio notify_webhook:sharepoint => doit renvoyer notify_webhook:sharepoint endpoint=http://sharepoint-proxy:8080/s3event auth_token= queue_limit=1000 queue_dir=/tmp/notify client_cert= client_key=
- Supprimer un webhook existant
mc admin config reset minio notify_webhook:sharepoint => doit renvoyer 'notify_webhook:sharepoint' is successfully reset. Please restart your server with `mc admin service restart minio`.
- Lier le bucket aux événements afin que MinIO envoie les données
mc event add minio/test arn:minio:sqs::sharepoint:webhook --event put,delete => doit renvoyer Successfully added arn:minio:sqs::sharepoint:webhook
Commentaires :
- minio/test → bucket concerné
- sp → webhook que tu as configuré
- put → upload / création
- delete → suppression
- Vérifier que c’est bien pris en compte
mc event list minio/test => doit renvoyer arn:minio:sqs::sharepoint:webhook s3:ObjectCreated:*,s3:ObjectRemoved:* Filter:
- supprimer l'event sharepoint du bucket test
mc event remove minio/test --force
Lancement et test
- lancer le docker compose
docker compose up -d => on doit voir Initializing SharePoint drive… Drive ID initialized: xxxx * Running on all addresses (0.0.0.0) * Running on http://127.0.0.1:8080
- vérifier les logs du conteneur sharepoint-proxy
docker compose logs -f sharepoint-proxy
Exemples de test
- Upload
mc cp devoir.pdf minio/dossier/fichier.pdf
- Vérifier que MinIO envoie réellement le webhook
docker compose exec minio curl -v http://sharepoint-proxy:8080/s3event => doit renvoyer code HTTP 204 ou 405 (selon ton proxy) : Connexion OK
- dans SharePoint apparait le fichier Documents/fichier.pdf
- Suppression
mc rm minio/dossier/fichier.pdf
- Fichier supprimé dans SharePoint
Sécuriser le webhook MinIO → Proxy
Utilisation d'une signature HMAC (RECOMMANDÉE) pour empêcher toute requête non légitime d’appeler /s3event :
- signature du webhook par MinIO,
- vérification de la signature par le proxy.
Configuration MinIO
- Créer une clé secrète partagée :
export WEBHOOK_SECRET="super-secret-minio"
- Configurer le webhook avec HMAC :
mc admin config set minio notify_webhook:sharepoint \ endpoint="http://sharepoint-proxy:8080/s3event" \ auth_token="$WEBHOOK_SECRET" mc admin service restart minio
- vérification côté proxy (Flask)
Dans main.py
import hmac, hashlib, os from flask import abort SHARED_SECRET = os.getenv("WEBHOOK_SECRET").encode() def verify_hmac(request): signature = request.headers.get("X-Minio-Signature") if not signature: abort(401) body = request.data expected = hmac.new( SHARED_SECRET, body, hashlib.sha256 ).hexdigest() if not hmac.compare_digest(signature, expected): abort(403)
- dans la route
@app.route("/s3event", methods=["POST"])
def s3_event():
verify_hmac(request)
...
