Aller au contenu principal

Administration du stockage client (brique E-13)

Version anglaise : storage-administration. Référence API : control-plane-v1 section "Client object storage". Page de la brique : E-13-object-storage.

La plateforme n'impose jamais de stockage. Le client déclare son propre bucket ou conteneur dans un profil de stockage ; la plateforme découvre ce que le stockage offre réellement (versioning, Object Lock, legal hold, rétention, multipart, URL présignées, chiffrement, KMS, classes de stockage, cycle de vie, réplication, intégrité) et refuse tout usage qui exige une capacité non prouvée. Le WORM n'est jamais simulé.

1. Stockages et authentifications pris en charge​

Fournisseur (provider)API utiliséeAuthentification (auth.method)URL présignées
aws_s3REST S3, URL virtual hosted (https://<bucket>.s3.<région>.amazonaws.com)sigv4 (clé d'accès, jeton de session facultatif)SigV4 en paramètres, 7 jours au plus
ibm_cosREST S3sigv4 avec clés HMAC, ou ibm_iam : clé d'API échangée sur https://iam.cloud.ibm.com/identity/token contre un jeton bearer, en-tête ibm-service-instance-id tiré de auth.service_instance_idclés HMAC uniquement (ibm_iam : capacité presign à false)
seaweedfs, s3_compatibleREST S3, URL path stylesigv4SigV4 en paramètres
azure_blobREST Blob, version 2023-11-03shared_key (clé de compte), sas (jeton SAS), entra_client_secret (identifiants client Microsoft Entra ID, auth.tenant_id, auth.client_id), workload_identity (fichier de jeton fédéré AZURE_FEDERATED_TOKEN_FILE, profils de plateforme uniquement)SAS de service (clé partagée) ou SAS de délégation utilisateur (Entra) ; aucune avec un identifiant SAS
gcsAPI JSONservice_account (assertion JWT signée par la clé, échangée sur https://oauth2.googleapis.com/token)URL signée V4 (GOOG4-RSA-SHA256), 7 jours au plus

Chaque appel passe par la garde de sortie (eai.security.egress) : aucun SDK fournisseur, aucune variable de proxy, aucune redirection, adresses de métadonnées cloud toujours refusées. Un point de terminaison privé (SeaweedFS sur site, appliance IBM COS) n'est joignable que si l'exploitant l'inscrit dans EAI_EGRESS_PRIVATE_ALLOWLIST ; la restriction du tenant egress_allowed_hosts s'applique aussi aux profils de tenant. Hors dev et test, les points de terminaison, les points de jeton et les URL présignées doivent être en https ; EAI_STORAGE_CA_BUNDLE ajoute l'autorité de certification d'un point interne.

2. Enregistrer l'identifiant, puis créer le profil​

Un profil contient une référence, jamais un secret : les profils de tenant utilisent openbao://<chemin> (secret écrit avec POST /v1/secrets), les profils de plateforme env://EAI_CRED_<NOM> (variable injectée par l'exploitant, par exemple depuis un Secret Kubernetes). La valeur du secret est un objet JSON avec les champs de la méthode, ou la valeur brute de son champ unique.

MéthodeValeur du secret
sigv4{"access_key_id": "...", "secret_access_key": "...", "session_token": "..."} (jeton de session facultatif)
ibm_iamla clé d'API, ou {"api_key": "..."}
shared_keyla clé de compte (base64), ou {"account_key": "..."}
sasle jeton SAS, ou {"sas_token": "..."}
entra_client_secretle secret client, ou {"client_secret": "..."}
service_accountle fichier de clé du compte de service Google (client_email, private_key, private_key_id, token_uri)
workload_identityaucun (credential_ref absent)
curl -X POST <control-plane-url>/v1/secrets -H "Authorization: Bearer $TOKEN" \
-d '{"path": "storage/audit", "value": "{\"access_key_id\": \"AKIA...\", \"secret_access_key\": \"...\"}"}'
curl -X POST <control-plane-url>/v1/storage/profiles -H "Authorization: Bearer $TOKEN" -d '{
"name": "audit-evidence", "provider": "aws_s3", "region": "eu-west-3", "bucket": "bank-audit-worm",
"key_prefix": "eai/", "auth": {"method": "sigv4"}, "credential_ref": "openbao://storage/audit",
"encryption": {"mode": "sse-kms", "kms_key_id": "arn:aws:kms:eu-west-3:111122223333:key/..."},
"purposes": ["audit_worm"]}'

Un champ secret dans auth, options ou encryption est refusé (422 storage_secret_in_profile). Rôles : tenant_admin gère les profils du tenant, platform_admin les profils de plateforme ("scope": "platform", invisibles des tenants).

3. Vérifier les capacités​

POST /v1/storage/profiles/{id}/verify (tenant_admin, security_officer) interroge le stockage par de vrais appels et enregistre chaque capacité avec sa méthode et son heure : supported à true (prouvée), false (absence prouvée) ou null (non déterminée : refusée comme une capacité absente). Avec probe_write (par défaut), un objet sonde est écrit sous .eai-probe/ avec le chiffrement, la classe de stockage et le legal hold configurés, relu, comparé par sha256 puis supprimé ; la sonde est omise sur un bucket doté d'une rétention par défaut ou d'une politique d'immutabilité, car elle ne pourrait pas être supprimée.

CapacitéS3 (AWS, IBM COS, SeaweedFS)Azure BlobGoogle Cloud Storage
object_lock (WORM qualifié)GetObjectLockConfiguration Enabledconteneur x-ms-immutable-storage-with-versioning-enabled (immutabilité au niveau version)bucket objectRetention.mode: Enabled ou politique de rétention verrouillée
retention, legal_holdObject Lock (legal hold posé, relu et levé par la sonde)immutabilité au niveau version, politique du conteneurpolitique de rétention ou rétention d'objet ; temporary hold posé et levé par la sonde
versioningGetBucketVersioningimpliqué par l'immutabilité au niveau version, sinon x-ms-version-id de la sondebucket versioning.enabled
multipartenvoi multipart ouvert puis annuléPut Block et Put Block List (sonde)session résumable ouverte puis annulée
presignGET présigné d'une clé absente répondu 404GET SAS répondu 404GET signé répondu 404
sse, kmsGetBucketEncryption, en-têtes de réponse de la sondex-ms-request-server-encrypted, encryption scopeclé KMS par défaut du bucket
storage_classesclasse de stockage de la sondeniveau d'accès de la sondeclasse de stockage par défaut
lifecycle, replicationGetBucketLifecycleConfiguration, GetBucketReplicationplan de gestion seulement (inconnu), SKU du compte (GRS, GZRS)règles de cycle de vie, type d'emplacement (dual-region, multi-region)
integrityaller-retour sha256 de la sonde, empreinte SigV4 et Content-MD5 vérifiés par le serveursha256 de la sonde, Content-MD5 par blocsha256 de la sonde, md5Hash vérifié par le serveur

Une vérification plus ancienne que EAI_STORAGE_CAPABILITY_MAX_AGE_SECONDS (1 jour par défaut) ne compte plus : vérifiez à nouveau, par exemple depuis une tâche planifiée de l'exploitant. Un changement de point de terminaison, de bucket, d'identifiant, de chiffrement ou d'options efface les capacités. GET /v1/storage/profiles/{id}/checks conserve chaque vérification comme preuve.

Le statut vaut verified, degraded (une capacité exigée par les usages ou par required_capabilities manque, listée dans missing_capabilities) ou failed (error : storage_credential_invalid, storage_auth_failed, storage_unreachable, storage_backend_error, egress_denied).

4. URL présignées​

POST /v1/storage/profiles/{id}/presign (tenant_admin, developer, agent) {"scope": "read" | "write", "key", "expires_seconds"} renvoie l'URL une seule fois. La clé doit commencer par l'un des presign_prefixes du profil (403 storage_key_out_of_scope) et elle est placée sous key_prefix ; l'expiration est bornée par EAI_STORAGE_PRESIGN_MAX_SECONDS (422 storage_presign_expiry_invalid) ; la capacité presign doit être vérifiée (409 storage_capability_missing). Une URL de lecture ne permet pas d'écrire, et inversement (la méthode est signée). Seuls le sha256 de l'URL, sa clé, sa portée et son expiration sont conservés (storage_presigned_grants) et audités (storage.presign.issue).

5. Exports WORM de l'audit (AUDI-011)​

Les exports signés de l'audit (POST /v1/audit/export et le job audit_worm du worker) partent vers le profil du tenant ayant l'usage audit_worm, sinon vers le profil de plateforme nommé par EAI_AUDIT_WORM_STORAGE_PROFILE. L'export est refusé en 409 storage_capability_missing tant que object_lock et retention ne sont pas vérifiées sur ce profil ; chaque objet est écrit en mode conformité jusqu'à retain_until et n'est accepté que si le stockage renvoie lui-même cette rétention (sinon 502 storage_worm_not_honoured, l'objet étant supprimé). Le manifeste porte alors worm.qualified: true, le profil, la rétention relue et l'identifiant de version ; audit_exports.worm_qualified vaut true. Sans profil, le stockage historique EAI_S3_* est utilisé et le manifeste indique worm.qualified: false : ce n'est pas une immutabilité qualifiée.

Ajouter l'usage audit_worm à un profil vérifié sans Object Lock est refusé. Pour qualifier un bucket : AWS et IBM COS, créez le bucket avec Object Lock ; SeaweedFS, créez-le avec x-amz-bucket-object-lock-enabled: true ; Azure, activez l'immutabilité au niveau version sur le conteneur ; Google Cloud Storage, activez la rétention d'objet sur le bucket.

6. Politique, audit et isolement​

Chaque action est d'abord décidée par le moteur de politiques (storage.profile.create, storage.profile.update, storage.profile.delete, storage.profile.verify, storage.presign.read, storage.presign.write) puis auditée (storage.profile.*, storage.presign.issue, storage.capability.refused). Les tables storage_profiles, storage_capability_checks et storage_presigned_grants ont une sécurité au niveau ligne forcée : un tenant ne voit jamais le profil d'un autre tenant (404), ni les profils de plateforme.

7. Tests et émulateurs​

bash tools/emulators/fetch.sh installe dans /tmp/toolchains des émulateurs épinglés et vérifiés par sha256 : SeaweedFS weed 4.51 (Apache-2.0), Azurite 3.37.0 (MIT, npm, chaque dépendance épinglée par tools/emulators/azurite/package-lock.json, télémétrie désactivée par les tests), fake-gcs-server 1.56.1 (BSD-2-Clause, outil de test seulement, non distribué). Les tests services/control-plane/tests/test_storage_*.py les démarrent sur des ports libres et échouent avec la commande ci-dessus quand un binaire manque.

Ce qu'un émulateur ne peut pas prouver est testé contre une simulation fidèle et reste à valider sur le cloud réel du client (BLOCKED tant qu'aucun compte de test n'existe) : AWS S3 lui-même (le code S3 tourne contre SeaweedFS et les exemples de signature AWS), les jetons IBM IAM, les jetons Microsoft Entra ID (Azurite contrôle l'audience du jeton, pas sa signature), l'immutabilité Azure au niveau version, les jetons Google OAuth2, la vérification des signatures V4 et la rétention d'objet de Google Cloud Storage (fake-gcs-server ne contrôle ni l'une ni l'autre).

8. Procédures dans le portail​

L'écran est Données et connaissances > Profils de stockage (portal/src/screens/kit-data.tsx), avec les onglets Profils, Objets d'un profil et URL présignée.

  1. Enregistrer l'identifiant. Le profil ne contient qu'une référence : le secret va d'abord dans OpenBao.

    Écran : Sécurité et gouvernance > Secrets, ou POST /v1/secrets comme dans la section 2.

    Résultat attendu : le chemin du secret, par exemple storage/audit, utilisé comme openbao://storage/audit.

    En cas d'échec : 403 forbidden signale que votre rôle ne peut pas écrire de secrets.

  2. Créer le profil. Il déclare le bucket ou le conteneur du client, ses usages et son chiffrement.

    Écran : Profils de stockage > Profils > Nouveau profil : nom, fournisseur, bucket, portée, point de terminaison, région, préfixe de clé, auth, credential_ref, usages. API : POST /v1/storage/profiles.

    Résultat attendu : le profil est listé ; ses capacités ne sont pas encore vérifiées.

    En cas d'échec : 422 storage_secret_in_profile (un champ secret dans auth, options ou encryption), 409 storage_profile_exists (nom déjà pris), 422 storage_tls_required (point de terminaison en http hors dev et test), 403 storage_scope_forbidden (portée platform sans platform_admin).

  3. Vérifier les capacités. Seules les capacités vérifiées comptent : vérifiez avant tout usage.

    Écran : Profils, action verify sur le profil, cochez probe_write pour écrire l'objet sonde. API : POST /v1/storage/profiles/{id}/verify.

    Résultat attendu : statut verified ; l'action checks liste chaque vérification.

    En cas d'échec : le statut degraded liste missing_capabilities ; le statut failed donne error (voir le tableau ci-dessous).

  4. Émettre une URL présignée. Une application cliente lit ou écrit alors un objet directement, pour une durée bornée.

    Écran : Profils de stockage > URL présignée : profil, portée read ou write, clé, expiration. API : POST /v1/storage/profiles/{id}/presign.

    Résultat attendu : l'URL, affichée une seule fois.

    En cas d'échec : 403 storage_key_out_of_scope, 422 storage_presign_expiry_invalid, 409 storage_capability_missing, 422 storage_prefix_required (le profil a plusieurs presign_prefixes et la clé ne commence par aucun).

  5. Désactiver ou supprimer un profil. Un profil désactivé refuse tout usage jusqu'à sa réactivation.

    Écran : Profils, action disable, enable ou delete.

    Résultat attendu : le nouveau statut s'affiche dans la liste.

    En cas d'échec : 409 storage_profile_disabled quand un profil désactivé est utilisé ; 409 storage_object_protected quand un objet d'un usage de preuve est supprimé par l'API.

9. Dépannage​

StatutCodeCauseCorrection
422storage_secret_in_profileun champ secret dans le profildéplacez le secret dans OpenBao, donnez credential_ref
422storage_credential_invalidla valeur du secret ne correspond pas à la méthodeécrivez les champs de la méthode (section 2)
502storage_auth_failedle stockage a refusé l'identifiantrenouvelez la clé chez le fournisseur, réécrivez-la
502storage_unreachablepoint de terminaison injoignablevérifiez le réseau et EAI_EGRESS_PRIVATE_ALLOWLIST pour un point privé
422storage_tls_requiredpoint de terminaison en http hors dev et testutilisez https, ajoutez l'autorité avec EAI_STORAGE_CA_BUNDLE
409storage_capability_missingcapacité non vérifiée (présignature, Object Lock, rétention)activez-la sur le bucket, vérifiez à nouveau
502storage_worm_not_honouredle stockage n'a pas renvoyé la rétentionactivez Object Lock ou l'immutabilité (section 5)
403storage_key_out_of_scopeclé hors des presign_prefixesutilisez une clé sous un préfixe autorisé
404storage_profile_not_foundprofil inconnu ou d'un autre tenantvérifiez l'identifiant

10. Étapes suivantes​