@aidalinfo/scaleway-mcp
v1.4.0
Published
Scaleway MCP server (read-only by default, optional restricted-write mode) — projects, instances, managed databases, DNS, transactional email, VPC/network, Kubernetes, scoped Object Storage access and pull-only Container Registry access, dedicated RDB dat
Maintainers
Readme
scaleway-mcp
MCP server pour explorer l'infrastructure Scaleway : projets, instances (VMs), bases de données managées (RDB), DNS, email transactionnel (TEM), réseau (VPC / private networks / load balancers) et Kubernetes (Kapsule).
Par défaut le serveur est en lecture seule : tous les appels sont des GET sur
https://api.scaleway.com authentifiés via le header X-Auth-Token (clé secrète), et aucun
tool ne crée, modifie ou supprime quoi que ce soit. Un mode restricted-write optionnel
ajoute un petit lot d'outils d'écriture ciblés (voir Mode restricted-write).
Configuration
Variables d'environnement (voir .env) :
| Variable | Requis | Description |
|---|---|---|
| SCW_SECRET_KEY | oui | Clé secrète Scaleway (envoyée en X-Auth-Token) |
| SCW_ACCESS_KEY | recommandé | Access key — sert à auto-découvrir l'organization ID |
| SCW_DEFAULT_ORGANIZATION_ID | optionnel | Court-circuite la découverte auto |
| SCW_MODE | optionnel | readonly (défaut) ou restricted-write. Toute autre valeur = readonly |
| SCW_ALLOWED_PROJECT_IDS | en écriture | Projets où les mutations sont permises (séparés par des virgules) |
| SCW_ALLOWED_TOOL_GROUPS | en écriture | Groupes d'outils d'écriture à enregistrer (voir plus bas) |
| SCW_OSA_HANDOFF_DIR | groupe object-storage-access | Répertoire absolu (0700) où sont déposés les secrets des clés créées |
| SCW_OSA_ALLOWED_BUCKETS | groupe object-storage-access | Buckets autorisés (obligatoire, sinon groupe désactivé) |
| SCW_OSA_ALLOWED_S3_PREFIXES | groupe object-storage-access | Préfixes S3 autorisés ; un préfixe demandé doit être dessous (obligatoire) |
| SCW_OSA_APPLICATION_PREFIXES | groupe object-storage-access | Préfixes de noms d'applications IAM autorisés (obligatoire) |
| SCW_OSA_POLICY_SINGLE_WRITER | groupe object-storage-access | acknowledged obligatoire : l'opérateur atteste que ce MCP est le seul à écrire les politiques des buckets autorisés (sinon groupe désactivé) |
| SCW_OSA_MAX_KEY_DAYS | optionnel | Durée de vie maximale d'une clé (1–365, défaut 365 ; valeur invalide = groupe désactivé) |
Si l'org n'est pas fournie, elle est découverte : API key → user/application → organization_id.
Build & lancement
npm install
npm run build
SCW_SECRET_KEY=... SCW_ACCESS_KEY=... npm startTest de fumée bout-en-bout en lecture seule (lit .env) : node smoke.mjs
Tests unitaires (aucun appel réseau, fetch simulé) : npm test
Enregistrement dans Claude Code
claude mcp add scaleway --scope user \
-e SCW_ACCESS_KEY=... -e SCW_SECRET_KEY=... \
-- npx -y @aidalinfo/scaleway-mcpTools
| Tool | Description |
|---|---|
| scw_organization | Vue d'ensemble org (projets, users, apps, API keys) |
| scw_projects | Liste des projets |
| scw_instances | VMs sur toutes les zones (filtre zone/projet) |
| scw_instance_info | Détail d'une VM |
| scw_flexible_ips | IPs publiques flottantes et leur rattachement |
| scw_volumes | Volumes de stockage Instance |
| scw_databases | Bases managées RDB (PostgreSQL/MySQL) toutes régions |
| scw_database_info | Détail d'une base + bases logiques + users |
| scw_dns_zones | Zones DNS |
| scw_dns_records | Enregistrements d'une zone |
| scw_domains | Noms de domaine enregistrés (registrar) |
| scw_email_domains | Domaines d'envoi TEM + statut/réputation |
| scw_email_domain_info | Détail TEM (SPF/DKIM) |
| scw_vpcs | VPCs toutes régions |
| scw_private_networks | Réseaux privés toutes régions |
| scw_load_balancers | Load balancers toutes zones |
| scw_k8s_clusters | Clusters Kubernetes |
| scw_k8s_cluster_info | Détail cluster + pools + nodes |
| scw_raw_get | Échappatoire GET arbitraire (Redis, Registry, Secret Manager, etc.) |
| scw_regions | Référence régions/zones |
Régions : fr-par, nl-ams, pl-waw. Zones : *-1, *-2, *-3.
Mode restricted-write
SCW_MODE=restricted-write
SCW_ALLOWED_PROJECT_IDS=<project-id>[,<project-id>...]
SCW_ALLOWED_TOOL_GROUPS=dns,instance-actions,snapshots,flexible-ips,rdb-backups,object-storage-access,registry-access,rdb-provisioning,object-storage-bucketsLe mode est fail-closed : valeur de SCW_MODE inconnue, SCW_ALLOWED_PROJECT_IDS vide ou
aucun groupe valide → le serveur démarre en lecture seule (avertissement sur stderr). Les
groupes inconnus sont ignorés. Seuls les groupes listés sont enregistrés.
| Groupe | Tool | Action |
|---|---|---|
| dns | scw_dns_record_create | Ajoute un enregistrement (A, AAAA, CNAME, TXT, MX, SRV, CAA) dans une zone existante |
| dns | scw_dns_record_update | Change valeur/TTL/priorité d'un enregistrement identifié par son id |
| dns | scw_dns_record_delete | Supprime un enregistrement par id, si expectedName + expectedType correspondent |
| instance-actions | scw_instance_power | poweron / poweroff |
| instance-actions | scw_instance_sensitive_action | reboot / stop_in_place (instance running uniquement) |
| snapshots | scw_volume_snapshot_create | Snapshot d'un volume Instance ou Block Storage (SBS) |
| snapshots | scw_instance_backup | Image de sauvegarde d'une instance (action backup) |
| flexible-ips | scw_flexible_ip_attach | Attache une IP flexible libre à une instance de la même zone |
| flexible-ips | scw_flexible_ip_detach | Détache une IP de l'instance attendue (serverId) |
| rdb-backups | scw_rdb_backup_create | Sauvegarde manuelle d'une base logique RDB (expiration 1–365 j, 30 par défaut) |
| rdb-provisioning | scw_rdb_database_user_provision | Base logique + utilisateur non admin + privilège exact sur cette seule base, mot de passe uniquement dans un handoff 0600 (voir plus bas) |
| rdb-provisioning | scw_rdb_database_user_rotate | Nouveau mot de passe pour un utilisateur géré, handoff remplacé atomiquement |
| rdb-provisioning | scw_rdb_database_user_revoke | Supprime un utilisateur géré (la base et ses données restent) |
| rdb-provisioning | scw_rdb_database_user_adopt | Migration : reprend un utilisateur du build local (handoff JSON) sans rotation ni écriture Scaleway |
| rdb-provisioning | scw_rdb_database_user_status | (lecture) Utilisateur, privilèges et état de gestion |
| object-storage-access | scw_object_storage_access_provision | Application IAM dédiée + clé expirante + accès limité à des préfixes d'un bucket existant, prouvé par canari (voir plus bas) |
| object-storage-buckets | scw_object_storage_bucket_baseline | Crée (si absent) un bucket autorisé avec l'identité du MCP et pose sa configuration de base : chiffrement AES256, versioning, CORS borné, lifecycle des multiparts (voir plus bas) |
| object-storage-access | scw_object_storage_access_verify | Rejoue le canari pour une clé existante |
| object-storage-access | scw_object_storage_access_key_revoke | Révoque une clé par access key exacte, si expectedApplicationId correspond |
| object-storage-access | scw_object_storage_access_status | (lecture) Clés d'une application gérée et préfixes accordés sur un bucket |
| registry-access | scw_registry_pull_access_provision | Application IAM dédiée + clé expirante avec seulement ContainerRegistryReadOnly sur un projet autorisé, prouvée par un canari en lecture (voir plus bas) |
| registry-access | scw_registry_pull_access_verify | (lecture) Rejoue le canari pour une clé existante, désignée par sa référence |
| registry-access | scw_registry_pull_access_key_revoke | Révoque une clé par référence ou access key exacte, si expectedApplicationId correspond ; supprime son handoff |
| registry-access | scw_registry_pull_access_status | (lecture) Politique et clés (référence, access key masquée, expiration) d'une application gérée |
Garde-fous
- Chaque outil mutatif est marqué
[WRITE]dans sa description. - Il n'existe aucun outil d'écriture générique : le client refuse toute mutation dont
la méthode + le chemin ne figurent pas dans une liste fermée (
WRITE_ROUTES), avec un contrôle du corps (ex. actionterminateimpossible, zone DNS jamais créée).scw_raw_getreste strictement enGET. - Région, zone, identifiants (UUID), nom de zone DNS et champs d'enregistrement sont validés avant tout appel.
- La ressource est lue avant la mutation : son projet doit figurer dans
SCW_ALLOWED_PROJECT_IDS, sinon refus sans appel d'écriture. Pour un attachement d'IP, l'IP et l'instance doivent toutes deux être dans un projet autorisé. - La ressource exacte est relue après la mutation ; si l'état attendu n'apparaît pas,
l'outil renvoie une erreur
Verification FAILED. - Idempotence : enregistrement identique déjà présent, instance déjà dans l'état cible, snapshot/image/sauvegarde de même nom, IP déjà (dé)tachée → rien n'est envoyé.
- Les erreurs Scaleway sont nettoyées : clé secrète, access key, header
X-Auth-Tokenet champssecret_key/password/tokensont masqués. - Hors périmètre : suppression d'instances, volumes, bases (hors rollback de l'appel qui l'a créée) ou clusters ; IAM en dehors des groupes
object-storage-accessetregistry-access(qui ne créent qu'une politique bornée à un projet) ; push, suppression d'images ou de tags, gestion des namespaces du Container Registry ; mutations Kubernetes (la prod reste pilotée par GitOps) ; création de ressources coûteuses.
Les confirmations humaines restent du ressort de l'agent consommateur ; le serveur reste sûr lorsqu'il est appelé directement.
Accès Object Storage dédiés (object-storage-access)
Provisionne sans console un accès S3 dédié, par exemple pour une sauvegarde RustDesk limitée à
backup-global/backup/rustdesk/** :
SCW_MODE=restricted-write
SCW_ALLOWED_PROJECT_IDS=<projet du bucket>
SCW_ALLOWED_TOOL_GROUPS=object-storage-access
SCW_OSA_HANDOFF_DIR=/srv/scaleway-mcp/handoff # mkdir -m 700
SCW_OSA_ALLOWED_BUCKETS=backup-global # obligatoire
SCW_OSA_ALLOWED_S3_PREFIXES=backup/ # obligatoire
SCW_OSA_APPLICATION_PREFIXES=backup- # obligatoire
SCW_OSA_POLICY_SINGLE_WRITER=acknowledged # obligatoire (voir « Écriture de la politique »)
SCW_OSA_MAX_KEY_DAYS=180 # optionnel
SCW_OSA_WHOLE_BUCKETS= # optionnel : exception « bucket entier » (voir plus bas)scw_object_storage_access_provision
projectId=<projet> region=fr-par bucket=backup-global
prefixes=["backup/rustdesk/**"] applicationName=backup-rustdesk keyDescription=initial expiresInDays=90Modèle d'accès Scaleway (bucket policy 2023-04-17) : une application n'accède à Object Storage
que si une politique IAM le lui permet sur le projet ; la bucket policy ne fait ensuite que
restreindre cet accès dans le bucket. Les droits IAM couvrent tous les buckets du projet : un
bucket sans politique leur est entièrement ouvert.
Déroulé :
Contrôles avant toute mutation :
- projet dans
SCW_ALLOWED_PROJECT_IDS; bucket, préfixes, nom d'application et durée dans les allowlistsSCW_OSA_*; - répertoire de handoff réel (pas de lien), à soi, en
0700; - bucket existant dans le projet (jamais créé), avec une politique déjà présente (en créer une couperait l'accès à tous les principaux non listés) ;
- isolation du projet : tous les autres buckets du projet, dans toutes les régions Object
Storage (
fr-par,nl-ams,pl-waw,it-mil), doivent avoir une politique, sans principal joker*. Sinon les droits IAM de l'application les atteindraient ; - déclarations gérées intactes : celles qui portent un Sid
scaleway-mcp-osa-<application>-*(bucket cible et autres buckets du projet) doivent être exactement deux et identiques à ce que le MCP écrit (principal, effet, actions, ressources, condition, préfixes valides). Toute altération est refusée au lieu d'être reprise. Les préfixes déjà accordés doivent rester dansSCW_OSA_ALLOWED_S3_PREFIXES; - lectures strictes : chaque page d'une liste IAM doit porter le tableau attendu et un
total_countentier stable, égal au nombre d'éléments reçus, sans troncature. Chaque élément est validé avant tout filtre de sécurité : identifiants présents et bien typés, un seul principal par politique, un seul porteur par clé, pas de doublon. Un élément incomplet fait échouer l'appel ; il n'est jamais ignoré. Les réponses S3 sont validées par un parseur XML (fast-xml-parser) : document complet et bien formé, racine unique fermée, rien hors racine.ListBucketsdoit être non tronqué, avec un seul conteneur<Buckets>et un nom valide par bucket.ListObjectsV2doit avoir un<KeyCount>unique égal au nombre de<Contents>, et jamais « vide mais tronqué » ; - une application par bucket : une application qui a déjà des déclarations gérées sur un autre bucket est refusée ;
- aucun droit parallèle : ni la politique du bucket cible ni celle d'un autre bucket du projet
ne doivent contenir de déclaration
Allownon gérée par le MCP qui vise l'application (son principal ou un joker). Ces droits échapperaient au canari ; - permission sets attendus connus d'IAM ;
- application existante gérée seulement si elle porte le tag
scaleway-mcp=object-storage-access, n'est membre d'aucun groupe IAM (pas de droits hérités), n'est pas celle du MCP et ne possède au plus que sa politique gérée. Cette politique doit contenir exactement une règle : les permission sets ci-dessous, sur ce seul projet, avecpermission_sets_scope_type=projects, sansorganization_id,account_root_user_idni condition. Le champ de métadonnéesrnest accepté uniquement au formatsrn://iam.scw.eu/rules/<uuid>; tout autre champ inconnu reste refusé. Le schéma de règle est strict : un champ inconnu est refusé. Les politiques sont lues avec le filtreapplication_idset revérifiées côté client.
- projet dans
Application IAM retrouvée par nom exact, ou créée avec le tag. Un identifiant renvoyé par un
POST(application ou politique) n'est rendu supprimable par le rollback qu'après relecture et preuve de causalité : marqueur[op:…]de l'appel, nom, tag, organisation, et application pour la politique. Sinon, il est signalé et jamais supprimé.Politique IAM gérée
scaleway-mcp-osa-<application>: une seule règle,ObjectStorageBucketsRead,ObjectStorageObjectsRead,ObjectStorageObjectsWriteetObjectStorageObjectsDelete, sur le seul projet autorisé. Jamais d'écriture ou de suppression de bucket, jamais de droit sur les politiques de bucket. Le client refuse tout autre contenu.Clé applicative expirante (projet par défaut = projet du bucket), idempotente par
keyDescription. Les clés d'une application sont listées avecbearer_id+bearer_type=application. Une clé réutilisée doit être la seule clé valide portant cette description, être une clé de cette application (pas d'un utilisateur), sur le projet demandé, et expirer dans la limite deexpiresInDayset deSCW_OSA_MAX_KEY_DAYS(sinon refus : faire une rotation). Son fichier de handoff doit décrire exactement la même clé, la même application et le même projet. L'expiration renvoyée pour une clé créée est vérifiée. Le secret est écrit uniquement dans$SCW_OSA_HANDOFF_DIR/<application>.<access key>.env: création exclusive en0600, sans suivre de lien, jamais écrasé. VariablesSCW_*,AWS_*etS3_*.S3_PREFIXESy contient le périmètre effectif, c'est-à-dire l'union des préfixes déjà accordés et demandés. Une clé n'est réutilisée que si son fichier décrit exactement le bucket, la région et ce périmètre ; sinon il faut faire une rotation. À la réutilisation, le fichier n'est lu que s'il est régulier, à soi et en0600exactement : contrôle parlstat, ouvertureO_NOFOLLOW, puisfstatsur le même inode. Le secret n'apparaît jamais dans la réponse, les erreurs ou les logs.Politique de bucket fusionnée : deux déclarations
scaleway-mcp-osa-<app>-*sont ajoutées ou mises à jour : objetsGet/Put/Delete/multipartsurbucket/prefixe/*, etListBucketconditionné às3:prefix. Les autres déclarations restent intactes et dans l'ordre, et les préfixes déjà accordés sont conservés. La politique est relue juste avant l'écriture : si elle a changé depuis la lecture initiale, refus. Après l'écriture, elle est relue et comparée structurellement en entier : déclarations, principaux, actions, ressources, conditions. Les champs S3 qui acceptent officiellement une chaîne ou une liste (Action,Resource, principal et valeurs de condition) sont comparés après normalisation scalaire/liste ; leurs valeurs restent strictement identiques et toute autre modification est refusée.Canari avec la clé de l'application :
list/put/head/deleted'un objet.mcp-canary-*dans chaque préfixe accordé, y compris ceux accordés lors d'appels précédents ;- refus (403) hors préfixe, à la racine du bucket :
list,head,putetdelete; - refus (403) des quatre mêmes opérations sur chacun des autres buckets du projet.
Après le canari, toutes les régions sont réinventoriées : l'ensemble des buckets du projet doit être identique à celui inspecté, sinon échec et rollback.
Un objet canari reste suivi tant que son absence n'est pas prouvée par une relecture : liste de cette clé avec
KeyCount=0, ouHEAD→ 404. Une réponseDELETE200/204 n'est pas une preuve.Tout objet canari éventuellement créé, y compris un
puthors préfixe accepté à tort, est supprimé dans unfinally. Le client S3 n'a aucune opération de suppression de bucket, de politique ou d'objet métier.Rollback sur toute erreur ou exception après la première mutation, y compris réseau. Il annule exactement ce que l'appel a créé : clé, politique IAM, application (suppression possible uniquement pour des ids créés par ce processus), fichier de handoff. Si le résultat d'une création IAM est ambigu (erreur de transport ou réponse sans identifiant), la ressource est recherchée avec des relectures bornées. Une erreur de relecture, ou une ressource encore invisible après la dernière tentative, n'est jamais traitée comme une absence : l'état est déclaré indéterminé. Une application ou une politique n'est adoptée par le rollback que si elle porte le marqueur unique de l'appel (
[op:…]dans sa description). Une clé ne peut porter de marqueur et l'API n'a pas de jeton d'idempotence : une clé ambiguë n'est jamais révoquée automatiquement, les nouvelles clés de l'application sont listées pour une révision explicite. Tout ce qui n'est pas adopté est signalé sans être supprimé. ChaqueDELETEest suivi d'une relecture, même après une erreur de transport (404 attendu). L'absence du fichier de handoff est vérifiée (un fichier partiellement écrit est supprimé). Un objet canari restant, non supprimé avec la clé de l'application ni avec l'identité de gestion (clés canari uniquement), rend aussi le rollback incomplet. Le résultat indique « rolled back and verified » ou « ROLLBACK INCOMPLETE ». La révocation est vérifiée par lecture directe et par la liste des clés de l'application. La politique de bucket n'est restaurée que si elle est toujours exactement celle écrite par l'appel ; sinon le rollback la laisse et le signale pour une vérification manuelle. Elle est relue après restauration.
Rotation : relancer scw_object_storage_access_provision avec une nouvelle keyDescription
(ex. rotation-2026-10). Une nouvelle clé est créée et prouvée, et les anciennes sont listées. Une
fois les consommateurs basculés, lancer
scw_object_storage_access_key_revoke accessKey=SCW… expectedApplicationId=<id>. C'est une
opération distincte : clé exacte, application attendue et gérée, projet autorisé, jamais la clé du
MCP. Elle est idempotente et relue après.
scw_object_storage_access_verify refait les mêmes contrôles (application gérée, bucket dans le
projet autorisé de la clé, isolation du projet, déclarations gérées intactes) avant toute
écriture canari. Il exige que prefixes soit exactement l'ensemble accordé par la politique et
nettoie lui aussi avec l'identité de gestion. Un résidu fait échouer la vérification. Il prend le
même verrou que le provisionnement. Après le canari, il exige que la politique relue soit identique
à celle inspectée et que l'inventaire des buckets n'ait pas changé.
Limites :
- le bucket est résolu via le projet par défaut de la clé du MCP ;
SCW_ACCESS_KEYest obligatoire et signe aussi les requêtes S3 ; - le MCP doit pouvoir lister les buckets et lire les politiques de tous les buckets du projet,
ainsi qu'écrire celle du bucket cible. Pour nettoyer et prouver l'absence des objets canari, il
doit aussi avoir
s3:ListBucket,s3:GetObjectets3:DeleteObjectdans les politiques de ces buckets (le client ne supprime que des clés.mcp-canary-*) ; - un bucket créé plus tard dans le projet sans politique serait atteint par les droits IAM de
l'application : toujours poser une politique sur les nouveaux buckets, et relancer
_verify, qui le détecte ; - Écriture de la politique : S3 n'offre aucune écriture conditionnelle (ni ETag, ni version) pour
PutBucketPolicy. Le groupe exige doncSCW_OSA_POLICY_SINGLE_WRITER=acknowledged, par lequel l'opérateur atteste que ce MCP est le seul à écrire les politiques des buckets autorisés. Un verrou local ($SCW_OSA_HANDOFF_DIR/.lock-<région>-<bucket>), pris parprovisioncomme parverify, sérialise ses propres appels. C'est un répertoire jamais vide tant qu'il est tenu : il contientowner-<jeton>. La prise se fait parrenameatomique d'un répertoire de réclamation privé ; elle échoue si le verrou est tenu. La libération supprime d'abord son propre fichierowner-<jeton>, un nom que personne d'autre n'utilise, puis fait unrmdir. Commermdirne supprime qu'un répertoire vide, il est impossible de retirer le verrou d'un successeur ; il n'y a pas de fenêtre « vérifier puis supprimer ». Un verrou perdu ou impossible à libérer fait passer le résultat en erreur. Un verrou restant n'est jamais cassé automatiquement. Toutes les instances du MCP qui écrivent ces politiques doivent partager le mêmeSCW_OSA_HANDOFF_DIR, sur un système de fichiers oùO_EXCLest atomique. La relecture juste avant l'écriture et la restauration conditionnelle restent en place pour détecter un écrivain non déclaré, sans pouvoir l'exclure.
Accès Container Registry pull-only (registry-access)
Crée sans console une identité technique qui peut uniquement récupérer les images du Container
Registry d'un projet autorisé (ex. PROD), et remet son secret dans un fichier local protégé :
SCW_MODE=restricted-write
SCW_ALLOWED_PROJECT_IDS=<projet PROD>
SCW_ALLOWED_TOOL_GROUPS=registry-access
SCW_REGISTRY_HANDOFF_DIR=/srv/scaleway-mcp/registry-handoff # mkdir -m 700 — obligatoire
SCW_REGISTRY_APPLICATION_PREFIXES=registry- # obligatoire
SCW_REGISTRY_MAX_KEY_DAYS=180 # optionnel (1–365, 365 par défaut)Toute variable absente ou invalide (répertoire relatif, préfixe hors [a-z0-9-], durée hors
1–365) désactive le groupe ; le serveur l'indique sur stderr.
scw_registry_pull_access_provision
projectId=<projet PROD> applicationName=registry-prod-pull keyDescription=initial expiresInDays=90
[region=fr-par] [canaryImage=<namespace>/<image>]Déroulé :
Contrôles avant toute mutation : projet dans
SCW_ALLOWED_PROJECT_IDS, nom d'application commençant par un préfixe deSCW_REGISTRY_APPLICATION_PREFIXES, durée ≤SCW_REGISTRY_MAX_KEY_DAYS, répertoire de handoff réel, à soi, en0700, verrou local par application (.lock-registry-<application>), permission setContainerRegistryReadOnlyconnu d'IAM. Une application existante n'est reprise que si elle porte le tagscaleway-mcp=registry-access, n'est pas celle du MCP, n'appartient à aucun groupe IAM et ne possède au plus que sa politiquescaleway-mcp-registry-<application>, dont l'unique règle est exactementContainerRegistryReadOnlysur ce seul projet. Une application de l'autre groupe (tag Object Storage) est refusée. Mêmes lectures IAM strictes que pourobject-storage-access.Application, politique et clé expirante créées comme pour
object-storage-access: marqueur[op:…], relecture et preuve de causalité, réconciliation bornée d'une création ambiguë, idempotence parkeyDescription. Le client refuse toute politique dont la règle contient un autre permission set queContainerRegistryReadOnly(aucun push, suppression ni administration de namespace), ou qui porte les deux tags gérés.Secret : écrit uniquement dans
$SCW_REGISTRY_HANDOFF_DIR/<application>.registry-<référence>.env(création exclusive0600, sans suivre de lien) :SCW_*,REGISTRY_USERNAME=nologin,REGISTRY_PASSWORD,REGISTRY_ENDPOINTS. La référence de clé est un condensat SHA-256 (16 caractères hexadécimaux) de l'access key : elle identifie la clé sans l'exposer. La réponse ne contient que des métadonnées : application, politique, projet, référence et access key masquée (SCW…ABCD), expiration, chemin du handoff.Canari en lecture seule, avec la clé de l'application (retenté le temps de la propagation IAM) :
- API Registry : liste des namespaces du projet ; une liste à l'échelle de l'organisation doit être refusée ou ne montrer que ce projet ;
- protocole Docker Registry v2 sur
rg.<région>.scw.cloud: authentificationnologin+ secret, puis liste des tags d'une image privée du projet (canaryImage, ou la première trouvée ; une image publique ne prouverait rien et est refusée). Les identifiants ne sont envoyés qu'à un point de jeton Scaleway en HTTPS (api.scaleway.comou*.scw.cloud) ; - un jeton demandé avec
pull,pushne doit accorder quepull(quand le jeton est un JWT inspectable ; sinon l'absence de push repose sur la règle IAM vérifiée) ; - sans image privée dans la région, seule l'authentification est prouvée et le résultat le dit.
Aucune image, aucun tag ni namespace n'est créé, modifié ou supprimé : il n'existe d'ailleurs aucune route d'écriture Registry dans
WRITE_ROUTES.Rollback de la clé, de la politique, de l'application et du handoff créés par l'appel, si une étape ou le canari échoue, avec relecture de chaque suppression. Une application ou une politique préexistante n'est jamais supprimée ; une clé ambiguë n'est jamais révoquée automatiquement.
Rédaction : toute sortie des quatre outils (succès comme erreur) passe par redactCredentials :
secret de la clé, jetons de registre, valeurs Authorization (Basic/Bearer), JWT, paramètres
d'URL signées (X-Amz-*, signature, token) et toute access key complète (masquée).
Rotation : relancer _provision avec une nouvelle keyDescription ; les autres clés sont listées
par référence. Puis scw_registry_pull_access_key_revoke expectedApplicationId=<id> keyRef=<référence>
(ou accessKey=SCW… exacte) : application attendue et gérée par ce groupe, projet autorisé, jamais la
clé du MCP, idempotente, relue après. Le fichier de handoff de la clé révoquée est supprimé.
Limites : le handoff est le seul mode de remise du secret (pas encore de coffre ni d'injection) ; la
politique couvre le Registry de toutes les régions du projet, le canari n'en lit qu'une (region).
Bases et utilisateurs RDB dédiés (rdb-provisioning)
Donne à une application sa propre base sur une instance Managed Database explicitement autorisée :
SCW_MODE=restricted-write
SCW_ALLOWED_PROJECT_IDS=<projet de l'instance>
SCW_ALLOWED_TOOL_GROUPS=rdb-provisioning
SCW_RDB_HANDOFF_DIR=/srv/scaleway-mcp/rdb-handoff # mkdir -m 700 — obligatoire
SCW_RDB_ALLOWED_INSTANCES=fr-par/<instance-uuid> # région/uuid, liste — obligatoire
SCW_RDB_NAME_PREFIXES=app_ # préfixes des bases ET des utilisateurs — obligatoire
SCW_RDB_LEGACY_HANDOFF_DIR=/chemin/absolu # optionnel : handoffs JSON de l'ancien build, pour _adoptToute variable absente ou invalide désactive le groupe. Aucune instance, base ou chemin n'est codé dans les sources.
scw_rdb_database_user_provision region=fr-par instanceId=<uuid> databaseName=app_billing userName=app_billing_rw [permission=all]- Avant toute mutation : instance dans
SCW_RDB_ALLOWED_INSTANCES(région comprise), noms dansSCW_RDB_NAME_PREFIXES(mêmes jeux de caractères que l'ancien build : lettres, chiffres,_, et-pour les bases), instancereadydans un projet autorisé, répertoire de handoff réel, à soi, en0700, verrou par instance (.lock-rdb-<instance>), listes strictes (total_count, éléments validés, doublons refusés). - Utilisateur existant : refusé s'il est admin ; refusé s'il n'a pas de marqueur de gestion
(
rdb.<région>.<instance>.<utilisateur>.managed,0600, sans secret) décrivant exactement cette instance et cette base : jamais de reprise d'un utilisateur créé ailleurs. Son handoff doit exister, sinon arrêt sûr. Il est réutilisé sans rotation. Un privilège sur une autre base est refusé. - Création : base logique si absente ; mot de passe de 32 caractères généré par le MCP et écrit
dans
rdb.<région>.<instance>.<utilisateur>.env(0600, exclusif) avant la création de l'utilisateur, pour qu'il ne puisse jamais être perdu ; utilisateuris_admin=false(le client refuse tout autre corps) ; marqueur ; puis privilègereadonly,readwriteouall(jamaiscustom) sur cette base. - Contrat de privilèges relu : exactement la permission demandée sur la base,
nonepartout ailleurs. - Rollback de ce que l'appel a créé : utilisateur, base (le client ne peut supprimer une base que si ce processus a créé ce chemin exact), marqueur, handoff ; privilège restauré pour un utilisateur préexistant. Chaque étape est relue. Une création ambiguë (réponse perdue) est signalée, jamais supprimée ; le handoff correspondant est conservé.
- Rotation (
_rotate) : nouveau mot de passe écrit dans un fichier temporaire0600, appliqué (PATCHlimité au mot de passe), puis renommé atomiquement sur le handoff. En cas d'échec ambigu, les deux fichiers sont conservés et signalés. - Adoption (
_adopt) : pour un utilisateur créé par l'ancien build local, lit son handoff JSON<instance>-<utilisateur>.json(0600) dansSCW_RDB_LEGACY_HANDOFF_DIR, vérifie qu'il est non admin avec exactement la permission attendue sur sa base, puis écrit handoff et marqueur officiels. Aucun appel d'écriture Scaleway, aucune rotation ; l'ancien fichier est laissé en place. - Révocation (
_revoke) : utilisateur géré exact, base attendue, suppression relue, handoff et marqueur supprimés, base et données intactes.
Le handoff contient DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD, DB_PERMISSION et
DATABASE_URL. Aucune réponse, erreur ou log ne contient le mot de passe.
Migration depuis le build local Vulcain : voir docs/scaleway-vulcain-migration.md à la racine du dépôt.
Exception « bucket entier » : les préfixes restent obligatoires par défaut. Pour un bucket dédié à
une seule application qui utilise réellement plusieurs racines de clés, wholeBucket=true (à la place de
prefixes) accorde tout le bucket, aux conditions suivantes :
- le bucket figure dans
SCW_OSA_WHOLE_BUCKETS, une sous-liste deSCW_OSA_ALLOWED_BUCKETS, faute de quoi le groupe est désactivé ; - c'est le seul bucket de son projet, toutes régions confondues ; le contrôle est refait à chaque
appel et par
_verify.
Les déclarations gérées deviennent alors bucket/* pour les objets, et ListBucket sans condition. Le
canari prouve list/put/head/delete à la racine. Un accès bucket entier déjà accordé absorbe les préfixes
demandés ensuite. Une déclaration « bucket entier » trouvée sur un bucket absent de
SCW_OSA_WHOLE_BUCKETS est refusée avant toute mutation. Le handoff porte S3_SCOPE=whole-bucket.
Création et configuration de base d'un bucket (object-storage-buckets)
SCW_MODE=restricted-write
SCW_ALLOWED_PROJECT_IDS=<projet du bucket = projet par défaut de la clé du MCP>
SCW_ALLOWED_TOOL_GROUPS=object-storage-buckets
SCW_OSB_ALLOWED_BUCKETS=helpdesk-files # obligatoire
SCW_OSB_CORS_ORIGINS=https://app.example.com # optionnel : origines HTTPS, sans chemin ni joker
SCW_OSB_ADMIN_PRINCIPALS=user_id:<uuid> # optionnel : administrateurs ajoutés à la politique de basescw_object_storage_bucket_baseline projectId=<projet> region=fr-par bucket=helpdesk-files
[corsOrigins=["https://app.example.com"]] [versioning=true] [abortMultipartDays=7]- Identité du MCP uniquement, jamais celle d'une application : aucune application ne reçoit de droit d'administration de bucket, même temporairement.
- Création si le bucket est absent du projet (projet par défaut de la clé du MCP, qui doit être le projet demandé). Un nom détenu par un autre projet est refusé.
- Politique de base, uniquement sur un bucket créé par l'appel : le MCP et
SCW_OSB_ADMIN_PRINCIPALS, personne d'autre. Elle prépareobject-storage-access. Un bucket existant sans politique n'en reçoit pas (ses utilisateurs actuels seraient coupés) ; c'est signalé. - Réglages : chiffrement AES256 par défaut ; versioning activé (
versioning=falselaisse l'état actuel ; jamais suspendu) ; règle CORSscaleway-mcp-baseline(GET/HEAD/PUT, origines de l'allowlist) sicorsOriginsest donné ; règle lifecyclescaleway-mcp-abort-multipart(abandon des multiparts incomplets). Chaque réglage est lu, écrit seulement s'il diffère, puis relu. Une configuration CORS ou lifecycle non écrite par le MCP n'est jamais remplacée : refus. - Rollback : si une étape échoue sur un bucket créé par l'appel, ce bucket, vide, est supprimé et son absence vérifiée. Le client ne peut supprimer qu'un bucket créé par cette même instance. Sur un bucket préexistant, rien n'est supprimé ; les réglages déjà vérifiés restent, et une relance est idempotente.
- Hors périmètre : création de projet, ACL publiques, suppression ou vidage d'un bucket existant.
Application IAM dédiée
Créer une application IAM propre au MCP (pas une clé utilisateur) et lui attacher une politique minimale : lecture sur l'organisation, écriture uniquement sur les projets de l'allowlist et uniquement pour les produits des groupes activés. Exemple (noms de permission sets à vérifier dans la console) :
| Règle | Portée | Permission sets |
|---|---|---|
| Lecture | Organisation | ProjectReadOnly, IAMReadOnly, InstancesReadOnly, RelationalDatabasesReadOnly, DomainsDNSReadOnly, VPCReadOnly, LoadBalancersReadOnly, KubernetesReadOnly, TransactionalEmailReadOnly, BlockStorageReadOnly |
| Écriture (dns) | Projets autorisés | DomainsDNSFullAccess |
| Écriture (instance-actions, snapshots, flexible-ips) | Projets autorisés | InstancesFullAccess, BlockStorageFullAccess |
| Écriture (rdb-backups) | Projets autorisés | RelationalDatabasesFullAccess |
| Écriture (rdb-provisioning) | Projets autorisés | RelationalDatabasesFullAccess |
| Écriture (object-storage-buckets) | Projets autorisés | ObjectStorageFullAccess (création de bucket, réglages, politique de base) |
| Écriture (object-storage-access) | Organisation | IAMManager (applications, politiques, clés) |
| Écriture (registry-access) | Organisation | IAMManager (applications, politiques, clés) |
| Écriture (object-storage-access) | Projets autorisés | ObjectStorageBucketsRead, ObjectStorageBucketPolicyFullAccess — plus une déclaration pour l'application du MCP dans la politique de chaque bucket du projet (s3:ListBucket, lecture de politique ; écriture de politique sur les buckets cibles) |
Les permission sets Scaleway sont plus larges que les outils exposés (ex. InstancesFullAccess
permet la suppression) : c'est le MCP qui restreint aux seules actions listées. N'accordez que
les règles des groupes réellement activés.
Canari d'écriture
Sur un projet non critique avec le groupe dns activé :
node canary.mjs canary.example.com # ajoute un TXT _mcp-canary, vérifie l'idempotence, le supprime