Aller au contenu
medium

MinIO : stockage objet S3 avec chiffrement, WORM et OIDC

15 min de lecture

Logo MinIO

MinIO est un serveur de stockage objet haute performance, compatible avec l'API Amazon S3. Beaucoup de déploiements s'arrêtent à « un bucket et deux clés d'accès » et passent à côté de l'essentiel : chiffrement au repos, versioning, verrouillage WORM, cycle de vie et surtout un contrôle d'accès fin avec fédération OIDC. Ce guide déploie MinIO puis active ces fonctions une par une, chaque commande étant vérifiée sur une instance réelle (release RELEASE.2025-09-07). Public visé : administrateurs et DevOps à l'aise avec la ligne de commande et l'API S3.

  • Déployer MinIO avec le chiffrement au repos activé dès le départ (KMS)
  • Activer versioning, Object Lock WORM et cycle de vie des objets
  • Restreindre un utilisateur à un préfixe précis d'un bucket
  • Fédérer les identités via OIDC et délivrer des accès temporaires STS
  • Ajouter tagging, URL présignées, notifications et réplication
  • Un hôte Linux avec Docker (ou le binaire MinIO et un service systemd).
  • Le client mc (MinIO Client) et aws-cli pour tester l'API S3.
  • Pour la partie OIDC : un fournisseur d'identité (Keycloak, Authentik, Dex...).

Le piège classique est de démarrer MinIO sans KMS : le chiffrement côté serveur (SSE-S3) est alors refusé. On active donc le KMS dès l'installation, avec une clé maître passée en variable d'environnement. C'est la configuration qui débloque le chiffrement au repos.

Fenêtre de terminal
# Générer une clé maître de 32 octets encodée en base64
KMS_KEY=$(head -c 32 /dev/urandom | base64)
docker run -d --name minio \
-p 9000:9000 -p 9001:9001 \
-e MINIO_ROOT_USER=labadmin \
-e MINIO_ROOT_PASSWORD=labsecret123 \
-e "MINIO_KMS_SECRET_KEY=cle-maitre:${KMS_KEY}" \
-v /data/minio:/data \
quay.io/minio/minio:latest server /data --console-address ":9001"

La variable MINIO_KMS_SECRET_KEY au format nom:clé-base64 active un KMS interne à une clé, suffisant pour du chiffrement au repos. Vérifiez que le serveur répond :

Fenêtre de terminal
curl -s http://127.0.0.1:9000/minio/health/live # réponse vide = OK

mc pilote MinIO en ligne de commande. On enregistre d'abord un alias vers le serveur, puis on crée un bucket.

Fenêtre de terminal
mc alias set local http://127.0.0.1:9000 labadmin labsecret123
mc mb local/data
mc admin info local # état du serveur, version, disques

La sortie de mc admin info confirme la version et l'état des disques. À partir de là, aws-cli fonctionne aussi contre le même endpoint (--endpoint-url http://127.0.0.1:9000), utile pour tester la compatibilité S3 de vos applications.

Le KMS étant actif, on peut chiffrer les objets côté serveur. On force le chiffrement par défaut sur le bucket, pour que tout objet déposé soit chiffré sans que le client ait à le demander.

Fenêtre de terminal
# Chiffrement automatique de tout objet écrit dans le bucket
aws --endpoint-url http://127.0.0.1:9000 s3api put-bucket-encryption \
--bucket data \
--server-side-encryption-configuration \
'{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'
# Un objet déposé est alors chiffré (SSE-S3 / AES256)
echo secret > f.txt
aws --endpoint-url http://127.0.0.1:9000 s3 cp f.txt s3://data/f.txt
aws --endpoint-url http://127.0.0.1:9000 s3api head-object --bucket data --key f.txt \
--query ServerSideEncryption --output text
# -> AES256

Le champ ServerSideEncryption: AES256 dans la réponse head-object prouve que l'objet est chiffré au repos. Sans le KMS configuré à l'étape précédente, put-object --server-side-encryption AES256 renverrait une erreur.

Le versioning conserve chaque version d'un objet et transforme une suppression en delete marker réversible. C'est le filet de sécurité contre l'écrasement et la suppression accidentelle.

Fenêtre de terminal
mc version enable local/data
# Deux écritures successives = deux versions
echo v1 > o.txt; mc cp o.txt local/data/o.txt
echo v2 > o.txt; mc cp o.txt local/data/o.txt
mc ls --versions local/data/o.txt

Une suppression crée un delete marker sans effacer les versions ; on peut donc restaurer l'état précédent en ciblant un VersionId :

Fenêtre de terminal
aws --endpoint-url http://127.0.0.1:9000 s3api delete-object --bucket data --key o.txt
# l'objet "disparait" (delete marker) mais les versions restent
aws --endpoint-url http://127.0.0.1:9000 s3api list-object-versions --bucket data --prefix o.txt \
--query 'Versions[].VersionId'
# récupération d'une version précise
aws --endpoint-url http://127.0.0.1:9000 s3api get-object --bucket data --key o.txt \
--version-id <VersionId> restaure.txt

Pour la conformité (sauvegardes inaltérables, journaux légaux), MinIO propose Object Lock en mode WORM (write once, read many) : un objet verrouillé ne peut pas être supprimé avant la fin de sa rétention. Object Lock exige un bucket créé avec le verrouillage activé (le versioning est alors automatique).

Fenêtre de terminal
aws --endpoint-url http://127.0.0.1:9000 s3api create-bucket \
--bucket coffre --object-lock-enabled-for-bucket
# Déposer un objet avec une rétention de 30 jours en mode GOVERNANCE
aws --endpoint-url http://127.0.0.1:9000 s3api put-object \
--bucket coffre --key rapport.pdf --body rapport.pdf \
--object-lock-mode GOVERNANCE \
--object-lock-retain-until-date 2026-08-16T00:00:00Z

Toute tentative de suppression de la version verrouillée est refusée tant que la rétention court. Le mode COMPLIANCE est encore plus strict : même le compte root ne peut pas lever la rétention avant son terme, là où GOVERNANCE autorise un contournement par un utilisateur habilité (s3:BypassGovernanceRetention).

Le cycle de vie (lifecycle) automatise l'expiration des objets et le nettoyage des versions anciennes ou des uploads multipart incomplets. Indispensable pour maîtriser le coût de stockage d'un bucket versionné.

Fenêtre de terminal
aws --endpoint-url http://127.0.0.1:9000 s3api put-bucket-lifecycle-configuration \
--bucket data --lifecycle-configuration '{
"Rules": [
{ "ID": "expire-30j", "Status": "Enabled", "Filter": {"Prefix": "logs/"},
"Expiration": {"Days": 30} },
{ "ID": "purge-multipart", "Status": "Enabled", "Filter": {},
"AbortIncompleteMultipartUpload": {"DaysAfterInitiation": 7} }
] }'

La première règle expire les objets sous logs/ après 30 jours ; la seconde purge les uploads multipart abandonnés après 7 jours. Le contrôleur de cycle de vie de MinIO applique ces règles en arrière-plan.

MinIO embarque un moteur de politiques au format IAM. On crée un utilisateur (ou un compte de service) et on lui attache une politique. Les politiques prédéfinies readonly, readwrite, writeonly couvrent les cas simples ; les vraies restrictions passent par une politique sur mesure.

Fenêtre de terminal
mc admin user add local alice AliceSecret123
mc admin policy attach local readonly --user alice # exemple simple

C'est le besoin fréquent en multi-équipes : chaque équipe ne voit que son préfixe dans un bucket partagé. La politique combine une autorisation ciblée sur data/team-a/* pour les objets et une condition s3:prefix pour le listage.

{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::data"],
"Condition": { "StringLike": { "s3:prefix": ["team-a/*"] } } },
{ "Effect": "Allow", "Action": ["s3:GetObject","s3:PutObject","s3:DeleteObject"],
"Resource": ["arn:aws:s3:::data/team-a/*"] }
]
}
Fenêtre de terminal
mc admin policy create local team-a-only team-a-policy.json
mc admin policy attach local team-a-only --user alice

Résultat vérifié : avec ces droits, alice lit et écrit sous team-a/ mais reçoit un AccessDenied (403) sur team-b/, y compris pour lister le préfixe voisin. C'est le cloisonnement par préfixe dans un bucket unique.

Plutôt que de distribuer des clés statiques, on fédère l'authentification à un fournisseur d'identité (Keycloak, Authentik, Dex...). L'utilisateur se connecte à l'IdP, obtient un jeton OIDC, puis l'échange contre des identifiants temporaires via STS AssumeRoleWithWebIdentity. Ces identifiants portent la politique par préfixe vue plus haut : l'accès est scopé et expire tout seul.

  1. Déclarer le fournisseur OIDC dans MinIO, en pointant sur une politique existante via role_policy :

    Fenêtre de terminal
    mc admin config set local identity_openid \
    config_url="https://idp.example.com/.well-known/openid-configuration" \
    client_id="minio" client_secret="<secret>" \
    scopes="openid" role_policy="team-a-only"
    mc admin service restart local

    Au redémarrage, MinIO génère un ARN de rôle (arn:minio:iam:::role/<hash>) visible dans les logs : c'est lui que l'échange STS ciblera.

  2. Obtenir un jeton OIDC auprès de l'IdP (ici via le flux d'authentification de votre application ou, pour tester, le grant approprié de l'IdP), puis l'échanger contre des identifiants temporaires. L'appel STS se fait en POST :

    Fenêtre de terminal
    curl -s -X POST http://127.0.0.1:9000/ \
    --data-urlencode "Action=AssumeRoleWithWebIdentity" \
    --data-urlencode "Version=2011-06-15" \
    --data-urlencode "DurationSeconds=3600" \
    --data-urlencode "RoleArn=arn:minio:iam:::role/<hash>" \
    --data-urlencode "WebIdentityToken=<jeton-oidc>"

    La réponse XML contient un AccessKeyId, un SecretAccessKey et un SessionToken temporaires.

  3. Utiliser les identifiants temporaires (avec le SessionToken) : l'accès est limité au préfixe de la politique et expire au bout de la durée demandée.

    Fenêtre de terminal
    export AWS_ACCESS_KEY_ID=<temp> AWS_SECRET_ACCESS_KEY=<temp> AWS_SESSION_TOKEN=<temp>
    aws --endpoint-url http://127.0.0.1:9000 s3 cp s3://data/team-a/f.txt . # OK
    aws --endpoint-url http://127.0.0.1:9000 s3 cp s3://data/team-b/f.txt . # AccessDenied

Une fois le socle en place, quelques activations complètent le déploiement.

  • Tagging d'objets : classez et pilotez le cycle de vie par tag.

    Fenêtre de terminal
    aws --endpoint-url http://127.0.0.1:9000 s3api put-object-tagging --bucket data --key f.txt \
    --tagging 'TagSet=[{Key=classe,Value=confidentiel}]'
  • URL présignée : partager un objet en lecture pour une durée limitée, sans exposer de clé.

    Fenêtre de terminal
    mc share download --expire 1h local/data/f.txt
  • Notifications d'événements : déclencher un traitement à chaque dépôt d'objet (webhook, file d'attente). On enregistre une cible (par exemple un webhook) dans la configuration, puis on l'associe au bucket.

  • Réplication : MinIO propose la site replication (mc admin replicate add) pour synchroniser plusieurs sites MinIO, et la réplication de bucket vers une cible S3. Elle s'appuie sur le versioning activé plus haut.

  • Activez TLS : placez private.key et public.crt dans ~/.minio/certs/ (ou /root/.minio/certs/ en conteneur) ; MinIO les détecte au démarrage. Le SSE-C (clés fournies par le client) exige d'ailleurs TLS.
  • Changez la clé root après le bootstrap et créez des comptes de service dédiés par application, jamais la clé root dans un pipeline.
  • Moindre privilège : préférez les politiques par préfixe et les identifiants temporaires STS aux clés statiques à large portée.
  • Sauvegardez la configuration (mc admin cluster bucket export, export des politiques et des identités) avant toute montée de version.
SymptômeCause probableSolution
put-object --server-side-encryption refuséKMS non configuréDémarrer avec MINIO_KMS_SECRET_KEY (ou KES)
STS renvoie des identifiants videsAppel AssumeRoleWithWebIdentity en GETEnvoyer la requête en POST
OIDC ignoré après config setServeur non redémarrémc admin service restart puis vérifier mc admin config get identity_openid
Suppression d'un objet verrouillé impossibleObject Lock actif (attendu)Attendre la fin de rétention ou lever en GOVERNANCE avec le droit de bypass
Access Denied sur un préfixePolitique scopée trop stricte ou s3:prefix manquantVérifier la condition s3:prefix et le Resource de la politique
  • Sans KMS (MINIO_KMS_SECRET_KEY ou KES), pas de chiffrement SSE-S3 : activez-le dès l'installation.
  • Le versioning transforme les suppressions en delete markers réversibles ; l'Object Lock WORM rend les objets inaltérables pour la conformité.
  • Le cycle de vie maîtrise le coût d'un bucket versionné (expiration, purge multipart).
  • Une politique par préfixe (Resource ciblé + condition s3:prefix) cloisonne les équipes dans un bucket unique.
  • La fédération OIDC + STS AssumeRoleWithWebIdentity délivre des accès temporaires et scopés ; l'appel STS se fait en POST.
  • L'édition communautaire reste AGPLv3 et fonctionnelle ; l'administration se pilote en CLI/IaC depuis le retrait de la console.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn