Aller au contenu
Identité et accès : organisations, projets et moindre privilège

Identité et accès : organisations, projets et moindre privilège

200 Pratiquer ⏱ 1 h 15 cloudscaleway

À la fin, vous saurez

  • Décrire la hiérarchie organisation, projets et ressources, et la comparer à celle d'AWS, d'Azure, de Google Cloud et d'OVHcloud
  • Distinguer les trois sortes de principaux (utilisateur, application, groupe) et choisir le bon pour chaque besoin
  • Écrire une politique Scaleway limitée à un projet, aux seuls jeux de permissions nécessaires, avec une condition si besoin
  • Créer une clé d'API à durée de vie limitée pour une identité non humaine et vérifier ce qu'elle peut faire
  • Expliquer ce qu'une politique sans refus explicite change par rapport aux politiques d'AWS ou de Google Cloud
  • Retrouver dans le journal d'audit qui a fait quoi, et savoir ce qu'il ne voit pas

Prérequis

Testé avec scaleway-cli 2.62.0 , vérifié le 5 octobre 2026

Pourquoi

Depuis la leçon 1, toutes les commandes du cours passent par une seule clé d'API : celle que vous avez générée pour configurer scw init, avec le compte qui a ouvert l'organisation Scaleway. Cette clé appartient donc au propriétaire de l'organisation. Elle peut tout faire : créer cinquante instances à carte graphique, supprimer la base sig-db et ses sauvegardes, lire les factures, inviter de nouveaux membres, créer d'autres clés. Elle n'expire pas. Elle est écrite en clair dans ~/.config/scw/config.yaml, sur un ordinateur portable.

Tant que vous êtes seul à faire des essais, c'est tolérable. Trois événements, banals, rendent la situation intenable :

  • Le pipeline de Signalements doit publier l'image de l'application dans le registre de conteneurs de Scaleway. Il lui faut une clé. Copier la vôtre dans les secrets de GitHub, c'est confier les clés de toute l'organisation à chaque personne qui peut modifier un workflow (le cours GitHub Actions l'a montré en détail).
  • Une développeuse arrive dans l'équipe. Elle doit voir les instances et lire les journaux, pas supprimer la base. Lui donner votre clé, c'est perdre la trace de qui a fait quoi, et s'interdire de lui retirer l'accès le jour où elle part sans tout renouveler.
  • Une clé fuit. Ce n'est pas une hypothèse d'école : le rapport State of Secrets Sprawl 2026 de GitGuardian a recensé environ 29 millions de secrets nouveaux poussés en clair sur GitHub public en 2025, en hausse de 34 % sur un an, et constate que 64 % des secrets valides découverts en 2022 l'étaient encore en janvier 2026. Une clé de propriétaire qui fuit, c'est l'organisation entière qui change de mains. Le scénario classique, que la leçon 8 reprend sous l'angle de la facture, est le minage de cryptomonnaie sur des instances créées en quelques minutes avec la clé volée.

La réponse tient en un principe énoncé en 1975 par Jerome Saltzer et Michael Schroeder, dans un article fondateur de la sécurité informatique : chaque programme et chaque utilisateur doit fonctionner avec le plus petit ensemble de privilèges nécessaire à sa tâche. C'est le moindre privilège. Le service d'IAM (Identity and Access Management, gestion des identités et des accès) d'un fournisseur cloud est l'outil qui permet de l'appliquer : il répond, pour chaque appel d'API, à la question « qui demande quoi, sur quelle ressource, et en a-t-il le droit ? ».

Cette leçon donne à Signalements des identités séparées et limitées : une pour le pipeline, une pour les instances, un groupe pour les développeurs. Votre clé de propriétaire, elle, retourne au coffre.

Les concepts

Authentifier, puis autoriser

Deux questions distinctes se posent à chaque requête :

  • L'authentification établit qui parle. Une personne prouve son identité à la console par un mot de passe et un second facteur, ou par un fournisseur d'identité d'entreprise ; un programme la prouve en présentant une clé d'API.
  • L'autorisation décide si cette identité a le droit de faire ce qu'elle demande, sur cette ressource-là. C'est le rôle des politiques.

Une erreur d'authentification (clé inconnue, expirée) se traduit en HTTP par un code 401 ; un refus d'autorisation (clé valide, droits insuffisants) par un code 403. Distinguer les deux fait gagner beaucoup de temps au diagnostic.

La hiérarchie : organisation, projets, ressources

Chez Scaleway, tout commence par l'organisation. Elle est créée avec le compte, et la personne qui crée le compte en devient le propriétaire (Owner). L'organisation porte ce qui est commun : la facturation, l'IAM, le support, la gestion des projets.

Une organisation contient un ou plusieurs projets, qui regroupent les ressources (instances, réseaux privés, bases de données, buckets). Chaque organisation a un projet par défaut, et en accepte 25 par défaut selon la page des quotas. Chaque ressource appartient à un projet et un seul.

    flowchart TB
  O["Organisation Lyneko<br/>(facturation, IAM, support)"]
  O --> P1["Projet signalements"]
  O --> P2["Projet default"]
  O --> P3["Projet lyneko-apps"]
  P1 --> R1["sig-app-1, sig-app-2"]
  P1 --> R2["sig-db"]
  P1 --> R3["pn-signalements, lb-signalements"]
  P1 --> R4["bucket, registre"]
  

Le projet est la frontière de droits la plus utile au quotidien : une politique peut donner à une identité tous les droits sur les instances du projet signalements, et aucun sur celles des autres projets. C'est pourquoi la leçon 1 a créé un projet dédié plutôt que de tout mettre dans le projet par défaut.

Tous les fournisseurs ont une hiérarchie de ce genre, avec des noms et des frontières différents :

FournisseurNiveau communNiveau intermédiaireConteneur de ressources
ScalewayOrganisation(aucun)Projet
AWSOrganisation (AWS Organizations)Unités d'organisation (OU)Compte AWS
AzureGroupe d'administration (management group)Abonnement (subscription)Groupe de ressources (resource group)
Google CloudOrganisationDossiers (folders)Projet
OVHcloudCompte client(aucun)Projet Public Cloud

Deux différences méritent d'être retenues. Chez AWS, l'unité d'isolement recommandée entre environnements est le compte entier, avec ses propres quotas et sa propre facture, regroupé avec d'autres dans une organisation : c'est une frontière plus épaisse qu'un projet Scaleway. Chez Azure, la documentation de l'autorisation par rôles (Azure RBAC) décrit quatre niveaux où l'on peut attribuer un droit (groupe d'administration, abonnement, groupe de ressources, ressource), et chaque niveau hérite des droits du niveau supérieur.

Les principaux : utilisateurs, applications, groupes

Un principal est une entité à qui l'on attribue des droits. Scaleway en connaît trois sortes.

Les utilisateurs sont des personnes. Le propriétaire a tous les droits sur l'organisation, sans politique. Les membres (Members) sont créés dans l'organisation par le propriétaire ou par un administrateur IAM ; ils n'existent que dans cette organisation et n'ont, au départ, aucun droit. Scaleway a longtemps proposé aussi des « invités » (Guests), des comptes extérieurs invités dans l'organisation ; leur création n'est plus possible depuis juin 2025, au profit des membres.

Les applications sont des identités non humaines : un pipeline, un script, un fournisseur Terraform, un service. Elles n'ont pas accès à la console, seulement à des clés d'API. La documentation de Scaleway insiste sur leur intérêt : une clé attachée à une personne disparaît avec elle quand elle quitte l'organisation, et arrête au passage tout ce qui en dépendait ; une clé attachée à une application ne dépend de personne. Chez les autres fournisseurs, le même besoin est couvert par les rôles IAM (AWS), les comptes de service (Google Cloud), les identités managées et principaux de service (Azure).

Les groupes réunissent des utilisateurs et des applications, pour leur attribuer une politique commune. Depuis septembre 2026, deux groupes gérés par Scaleway existent dans toutes les organisations : All Users et All Applications, dont le contenu suit automatiquement les membres et les applications de l'organisation.

Note

Une politique Scaleway a au plus un principal : un utilisateur, une application ou un groupe. Pour donner les mêmes droits à trois personnes, on crée un groupe et une politique, pas trois politiques.

Les politiques : règles, jeux de permissions, périmètres, conditions

Une politique (policy) relie un principal à une ou plusieurs règles. Chaque règle se compose de trois parties.

  1. Un périmètre (scope) : un ou plusieurs projets, ou l'organisation entière. Les fonctions qui vivent au niveau de l'organisation (IAM, facturation, gestion des projets, tickets de support) ne s'atteignent qu'avec un périmètre « organisation ».
  2. Un ou plusieurs jeux de permissions (permission sets) : des paquets de permissions élémentaires, nommés de façon explicite. InstancesReadOnly donne la lecture des instances, InstancesFullAccess toutes les actions sur les instances, ContainerRegistryFullAccess toutes les actions sur le registre. Pour certains produits, le découpage est fin (les instances ont des jeux séparés pour créer, démarrer, arrêter, lire un serveur) ; pour d'autres, il n'existe que deux jeux, lecture et accès complet (c'est le cas du registre de conteneurs).
  3. Une condition facultative, écrite en CEL (Common Expression Language, un petit langage d'expressions conçu par Google), qui restreint encore la règle.

Les conditions sont de deux familles :

  • Sur la requête : request.ip (l'adresse IP publique d'origine ; les adresses privées ne sont pas prises en charge), request.time (l'heure de la requête) et request.user_agent (le logiciel client). Par exemple, request.user_agent.contains("terraform/") n'autorise que les appels venus de Terraform.
  • Sur la ressource : resource.name, resource.id et resource.locality. En octobre 2026, elles ne fonctionnent que pour trois produits : IAM, Key Manager et Secret Manager. Elles permettent par exemple de n'ouvrir qu'un « dossier » de secrets (resource.name.startsWith("/signalements/")).

Trois règles de fonctionnement découlent de la documentation, et elles comptent plus que la syntaxe :

  • Tout est refusé par défaut, sauf pour le propriétaire. Un membre ou une application sans politique ne peut rien faire.
  • Les politiques ne font qu'ajouter. La documentation est explicite : une règle ne peut que donner un accès, on ne peut pas écrire de règle qui refuse. Les politiques d'un même principal (les siennes et celles de ses groupes) s'additionnent, sans ordre de priorité. Une conséquence moins intuitive concerne les conditions : si deux politiques donnent le même droit, l'une le lundi et l'autre le mardi, le droit est accordé les deux jours ; une condition non remplie dans une politique ne retire rien à une autre.
  • Les changements ne sont pas instantanés : un nouveau jeu de permissions peut mettre jusqu'à une minute à s'appliquer, à cause de la réplication et des caches.

Certains jeux de permissions sont plus dangereux que leur nom ne le laisse croire. La documentation avertit que toute identité qui détient IAMManager ou OrganizationManager peut se créer à elle-même une politique lui donnant n'importe quel autre droit. Ces deux jeux équivalent donc, en pratique, à un accès de propriétaire, et se réservent à très peu de personnes.

Ailleurs : des refus explicites

La comparaison avec AWS éclaire ce que l'absence de refus implique. Une politique AWS est un document JSON fait de déclarations, chacune avec un effet Allow ou Deny, des actions, des ressources et des conditions :

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["ecr:PutImage", "ecr:InitiateLayerUpload", "ecr:UploadLayerPart",
                 "ecr:CompleteLayerUpload", "ecr:BatchCheckLayerAvailability"],
      "Resource": "arn:aws:ecr:eu-west-3:111122223333:repository/signalements"
    },
    {
      "Effect": "Deny",
      "Action": "ecr:*",
      "Resource": "*",
      "Condition": {"NotIpAddress": {"aws:SourceIp": "203.0.113.0/24"}}
    }
  ]
}

La logique d'évaluation d'AWS part d'un refus implicite, accorde ce qu'une politique autorise, et fait primer tout refus explicite sur toutes les autorisations. Elle superpose plusieurs types de politiques : celles des identités, celles des ressources, et, dans une organisation, les politiques de contrôle des services (SCP), qui fixent un plafond que même un administrateur de compte ne peut dépasser. Google Cloud a des politiques de refus (deny policies) attachables à l'organisation, à un dossier ou à un projet, et vérifiées avant les politiques d'autorisation. OVHcloud propose dans ses politiques IAM une liste deny, qui l'emporte sur toute autre politique, et une liste except, limitée à la politique où elle figure.

Ces refus servent de garde-fous : « personne, quelles que soient ses autres autorisations, ne supprime de sauvegarde en production ». Chez Scaleway, faute de refus, il n'existe pas de garde-fou de ce type. Le moindre privilège s'obtient par construction : n'accorder que ce qui est nécessaire, à la bonne portée, et utiliser la frontière des projets (ou, pour un isolement plus fort, des organisations distinctes) pour ce qui ne doit jamais se mélanger.

Les clés d'API

Une clé d'API Scaleway est faite de deux parties :

  • la clé d'accès (access key), qui identifie la clé. Le SDK Go de Scaleway la valide avec l'expression ^SCW[A-Z0-9]{17}$ : SCW suivi de 17 caractères. Elle n'est pas secrète, et apparaît dans les journaux ;
  • la clé secrète (secret key), un UUID, qui prouve que l'on détient la clé. Elle n'est affichée qu'une seule fois, à la création.

Une clé appartient à un porteur (bearer), un utilisateur ou une application, et en hérite exactement les droits : elle n'a pas de droits propres. Elle n'est valable que dans une organisation. Elle peut porter une date d'expiration ; par défaut, elle n'en a pas. Depuis janvier 2026, une organisation peut imposer une durée maximale à toutes les nouvelles clés, dans ses réglages de sécurité (la commande scw iam security-settings update expose l'option max-api-key-expiration-duration). La documentation précise que ce réglage ne touche pas les clés existantes, et que l'on ne peut pas modifier l'expiration d'une clé existante depuis la console : on la révoque et on en crée une autre.

Une particularité vient du stockage objet. L'API S3, que Scaleway implémente, n'a pas de notion de projet. Chaque clé porte donc un projet préféré (preferred project, default-project-id) que les outils S3 utilisent pour créer et lister les buckets. Une clé destinée à manipuler le bucket des pièces jointes de la leçon 5 doit avoir le projet signalements comme projet préféré.

Les personnes : second facteur et authentification unique

Pour les humains, l'organisation peut imposer l'authentification multifacteur (MFA) à tous ses membres : chacun dispose alors d'un délai de grâce pour l'activer, faute de quoi son compte est verrouillé. Elle peut aussi choisir les méthodes d'authentification permises (mot de passe, code, connexion OAuth2, SAML), limiter la durée des sessions de console (30 jours par défaut) et activer l'authentification unique (SSO) :

  • par OAuth2 avec Google ou GitHub, disponible sans configuration ;
  • par SAML avec le fournisseur d'identité de l'entreprise (Entra ID, Okta, Google Workspace, Authentik...), généralement disponible depuis novembre 2025. La documentation précise que SAML ne s'applique pas au propriétaire, qui garde son propre mode de connexion ;
  • avec, en complément, le provisionnement automatique des comptes par SCIM, pour que la création et la suppression des membres suivent l'annuaire de l'entreprise.

Le journal d'audit

Deux journaux répondent à la question « qui a fait quoi ».

Les journaux IAM (scw iam log list) tracent les créations, modifications et suppressions d'objets IAM : clés d'API, utilisateurs, applications, groupes, politiques.

Audit Trail va plus loin : pour chaque produit intégré, il enregistre les appels d'API, réussis ou refusés, avec le principal, la méthode, l'adresse IP d'origine et le statut (200 ou 403). Il est gratuit. Les événements sont conservés dans la région où l'action a eu lieu (ceux des produits globaux, comme IAM, à Paris), et peuvent être exportés chaque jour dans un bucket pour une conservation plus longue que les 90 jours que couvre l'export.

Sa limite se lit dans la page des produits intégrés : en octobre 2026, les instances, les réseaux, Kubernetes, les bases PostgreSQL, Secret Manager, les répartiteurs de charge et IAM y figurent, mais le stockage objet, le registre de conteneurs, le stockage bloc et la facturation n'y figurent pas encore. Une image écrasée dans le registre ou un fichier supprimé d'un bucket n'y laisse donc aucune trace. Il faut le savoir avant d'en avoir besoin.

En pratique

Vous allez créer, avec votre profil de propriétaire (une dernière fois pour un usage courant), trois identités pour Signalements :

IdentitéSorteBesoinJeux de permissionsPérimètre
sig-deploiementapplicationle pipeline publie l'imageContainerRegistryFullAccessprojet signalements
sig-instancesapplicationles instances tirent l'imageContainerRegistryReadOnlyprojet signalements
signalements-devgroupeles développeurs consultentAllProductsReadOnlyprojet signalements

Les commandes ont été vérifiées avec l'aide de la ligne de commande scw 2.62 ; les identifiants montrés sont des exemples.

Préparer les variables

$ ORGA=$(scw config get default-organization-id)
$ PROJET=$(scw account project list name=signalements -o json | jq -r '.[0].id')
$ echo "$ORGA $PROJET"

Vérifiez que PROJET n'est pas vide ni null : toutes les règles qui suivent en dépendent, et une règle mal ciblée est précisément ce que l'on cherche à éviter.

Un registre privé pour l'image

Le pipeline va publier l'image dans le registre de conteneurs de Scaleway, dans un espace de noms (namespace) du projet. Le nom d'un espace de noms doit être unique pour tous les clients de Scaleway, d'où un suffixe :

$ scw registry namespace create name=signalements-<suffixe> \
    project-id="$PROJET" is-public=false region=fr-par

is-public=false rend l'espace privé : tirer une image demande une clé. L'adresse des images sera rg.fr-par.scw.cloud/signalements-<suffixe>/signalements:<version>.

L'application du pipeline et sa politique

$ APP_DEPLOIEMENT=$(scw iam application create name=sig-deploiement \
    description="Pipeline de Signalements : publication de l'image" \
    -o json | jq -r .id)
$ scw iam policy create name=sig-deploiement-registre \
    description="Publier les images de Signalements, projet signalements uniquement" \
    application-id="$APP_DEPLOIEMENT" \
    rules.0.project-ids.0="$PROJET" \
    rules.0.permission-set-names.0=ContainerRegistryFullAccess

Lisez la politique comme une phrase : l'application sig-deploiement (le principal, application-id) a l'accès complet au registre (permission-set-names) dans le projet signalements (project-ids), et nulle part ailleurs. Les arguments indexés (rules.0..., .0) sont la façon dont scw exprime des listes : rules.1.project-ids.0=... ajouterait une seconde règle.

Pourquoi l'accès complet, et pas la lecture ? Parce que publier une image est une écriture, et que le registre n'a que deux jeux de permissions. C'est un compromis à connaître : une clé volée pourrait aussi supprimer ou écraser des images du projet. Elle ne pourrait en revanche ni toucher aux instances, ni à la base, ni lire la facture, ni agir sur les autres projets.

Une clé qui expire

$ scw iam api-key create application-id="$APP_DEPLOIEMENT" \
    description="GitHub Actions, depot signalements" \
    expires-at=+90d default-project-id="$PROJET" \
    -o json > .tmp/cle-deploiement.json
$ chmod 600 .tmp/cle-deploiement.json
$ jq -r '.access_key, .expires_at' .tmp/cle-deploiement.json
  • expires-at=+90d utilise la syntaxe de dates relatives de scw (voir scw help date) : la clé expire dans 90 jours. Une date absolue au format RFC 3339 fonctionne aussi.
  • default-project-id fixe le projet préféré pour le stockage objet ; il n'a pas d'effet sur le registre, mais le renseigner évite une surprise si la clé sert un jour à S3.
  • La sortie JSON contient secret_key en clair, et c'est la seule fois où vous la verrez. Elle va dans un fichier aux droits restreints, hors de tout dépôt Git (ici un répertoire .tmp/ ignoré par Git), le temps de la déposer dans les secrets de GitHub :
$ jq -r .secret_key .tmp/cle-deploiement.json | gh secret set SCW_SECRET_KEY --env production
$ jq -r .access_key .tmp/cle-deploiement.json | gh secret set SCW_ACCESS_KEY --env production
$ shred -u .tmp/cle-deploiement.json

La leçon 9 du cours GitHub Actions explique pourquoi un secret d'environnement (--env production) vaut mieux qu'un secret de dépôt. Dans le workflow, la connexion au registre suit la documentation de Scaleway : l'utilisateur est le mot fixe nologin, le mot de passe est la clé secrète.

$ docker login rg.fr-par.scw.cloud/signalements-<suffixe> -u nologin --password-stdin <<< "$SCW_SECRET_KEY"

L'identité des instances

Les instances sig-app-1 et sig-app-2 doivent tirer l'image depuis l'espace privé. Elles n'ont besoin que de la lecture, et n'ont aucune raison de partager l'identité du pipeline :

$ APP_INSTANCES=$(scw iam application create name=sig-instances \
    description="Instances de Signalements : lecture du registre" -o json | jq -r .id)
$ scw iam policy create name=sig-instances-registre \
    application-id="$APP_INSTANCES" \
    rules.0.project-ids.0="$PROJET" \
    rules.0.permission-set-names.0=ContainerRegistryReadOnly
$ scw iam api-key create application-id="$APP_INSTANCES" \
    description="sig-app-1 et sig-app-2" expires-at=+90d -o json > .tmp/cle-instances.json

Si cette clé fuit, l'attaquant peut lire vos images, rien de plus. Deux identités séparées, c'est aussi deux révocations indépendantes : renouveler la clé du pipeline n'interrompt pas les instances.

Warning

Une clé transmise à une instance par les données utilisateur de cloud-init (leçon 4) reste lisible depuis l'instance par le service de métadonnées, par tout processus de la machine. C'est acceptable pour une clé de lecture seule à expiration courte ; ce ne l'est pas pour une clé qui écrit. Les cours Gestion des secrets et GitOps avec Argo CD présentent des solutions plus robustes.

Le groupe des développeurs

$ GROUPE=$(scw iam group create name=signalements-dev \
    description="Equipe Signalements : consultation" -o json | jq -r .id)
$ scw iam policy create name=signalements-dev-lecture \
    group-id="$GROUPE" \
    rules.0.project-ids.0="$PROJET" \
    rules.0.permission-set-names.0=AllProductsReadOnly
$ scw iam user list type=member -o json | jq -r '.[] | [.id, .email] | @tsv'
$ scw iam group add-member "$GROUPE" user-id=<id-de-la-developpeuse>

AllProductsReadOnly donne la lecture de tous les produits du projet. Un membre du groupe voit les instances, les réseaux, la base, mais ne peut rien créer ni rien modifier. Les droits suivent le groupe : quand une personne change d'équipe, on la retire du groupe, sans toucher à aucune politique.

Vérifier ce qu'une clé peut faire

La commande get d'une clé affiche les politiques qui s'appliquent à son porteur :

$ scw iam api-key get <SCWXXXXXXXXXXXXXXXXX> with-policies=true

Pour tester un refus, utilisez la clé du pipeline à la place de votre profil. Les variables d'environnement SCW_ACCESS_KEY et SCW_SECRET_KEY l'emportent sur le fichier de configuration :

$ SCW_ACCESS_KEY=<cle-d-acces-sig-deploiement> SCW_SECRET_KEY=<cle-secrete> \
    scw instance server list zone=fr-par-1 project-id="$PROJET"

La clé est valide, mais son porteur n'a aucun droit sur les instances. La page de dépannage de Scaleway décrit les formes que prend ce refus selon le produit et l'outil : un message Insufficient permissions ou Access Denied, une erreur 403, ou, plus déroutant, une réponse vide, sans erreur. Une liste vide n'est donc pas une preuve qu'il n'existe aucune ressource : c'est peut-être que vous n'avez pas le droit de les voir. La même clé, en revanche, peut se connecter au registre et y pousser une image.

Le refus, lui, laisse une trace. Les instances étant intégrées à Audit Trail, la commande suivante (avec votre profil, qui a le droit de lire les journaux) retrouve les appels refusés de la dernière heure, avec le principal et l'adresse IP d'origine :

$ scw audit-trail event list status=403 recorded-after=-1h region=fr-par -o json \
    | jq '.events[] | {recorded_at, principal, source_ip, method_name}'

Contrairement aux autres commandes de liste, celle-ci renvoie la réponse complète de l'API, avec un champ events et un jeton de page suivante : d'où le filtre .events[]. Chaque événement contient aussi le corps de la requête (request_body) et le logiciel client (user_agent).

Ajouter une condition

Si le pipeline tourne sur un runner auto-hébergé à l'adresse publique fixe, une condition peut limiter la clé à cette adresse. Une clé volée devient alors inutilisable depuis ailleurs :

$ scw iam policy create name=sig-deploiement-registre-ip \
    application-id="$APP_DEPLOIEMENT" \
    rules.0.project-ids.0="$PROJET" \
    rules.0.permission-set-names.0=ContainerRegistryFullAccess \
    rules.0.condition='request.ip == "203.0.113.10"'

Deux pièges : la condition ne sert à rien si l'ancienne politique sans condition reste en place, puisque les politiques s'additionnent (supprimez-la avec scw iam policy delete) ; et elle ne convient pas aux runners hébergés par GitHub, dont les adresses changent d'une exécution à l'autre. L'adresse 203.0.113.10 appartient à une plage réservée à la documentation : remplacez-la.

Sous le capot

Le chemin d'une requête. Quand le pipeline pousse une couche d'image, le client Docker envoie la clé secrète au registre. Le service identifie la clé, donc son porteur (sig-deploiement), puis rassemble les règles de toutes les politiques qui visent ce porteur, directement ou par ses groupes. Il cherche une règle dont le périmètre contient le projet de la ressource visée, dont un jeu de permissions contient la permission élémentaire requise, et dont la condition, s'il y en a une, est vraie pour cette requête. Une seule règle qui convient suffit ; s'il n'y en a aucune, c'est un refus. C'est l'union pure : il n'y a pas d'étape « chercher un refus explicite », contrairement à AWS ou Google Cloud.

Pourquoi une minute de délai. L'évaluation des droits ne se fait pas en interrogeant une base centrale à chaque requête, ce qui serait trop lent pour des milliers d'appels par seconde : les politiques sont répliquées et mises en cache près des services. D'où le délai d'application d'une minute annoncé par la documentation, et le même ordre de grandeur pour les conditions horaires. Pour vos tests : après un changement de politique, attendez une minute avant de conclure qu'il ne marche pas.

Les conditions de ressource et les listes. Une condition sur resource.id s'évalue sur la ressource visée. Une requête de liste n'en vise aucune : la condition échoue, et la liste devient vide. La documentation recommande d'ajouter || !has(resource.id) à l'expression pour préserver les listes, ce qui revient à autoriser la liste de toutes les ressources du type, noms compris. Restreindre l'accès à un secret ne cache donc pas son existence.

Les identités qu'on ne crée pas soi-même. Certains produits créent leurs propres identités. La documentation cite Kapsule : à la création d'un cluster, Scaleway crée un groupe contenant les nœuds sous forme d'applications invisibles, et une politique par défaut, que l'on peut supprimer mais pas modifier. Elles apparaissent dans les journaux IAM avec Scaleway comme auteur. Dans une revue d'accès, il faut savoir les reconnaître plutôt que de les supprimer par excès de zèle.

Pièges courants

« Ça marche avec ma clé, pas avec celle du pipeline. » Normal : la vôtre est celle du propriétaire, qui n'est soumis à aucune politique. Testez toujours avec la clé de l'identité réelle, jamais avec la vôtre.

La variable d'environnement oubliée. scw lit SCW_ACCESS_KEY et SCW_SECRET_KEY avant le fichier de configuration. Une variable restée exportée dans un terminal fait agir une autre identité que celle que vous croyez ; la page de dépannage de Scaleway le cite parmi les causes de refus inattendus. env | grep ^SCW_ lève le doute.

La règle au mauvais périmètre. Un jeu de permissions d'organisation (BillingReadOnly, IAMReadOnly, ProjectManager) placé dans une règle à périmètre de projet, ou l'inverse, ne donne pas le droit attendu. La page des jeux de permissions les classe en deux tableaux, « Scoped by Organization » et « Scoped by Project ».

La liste vide prise pour une absence. Voir plus haut : un manque de droits peut produire une réponse vide plutôt qu'une erreur.

IAMManager donné « pour dépanner ». Il permet de s'attribuer tous les autres droits. Le donner à un pipeline revient à lui donner la clé du propriétaire.

Une condition qui ne restreint rien. Une politique conditionnée ajoutée à côté d'une politique sans condition ne retire aucun droit : l'union l'emporte. Avant d'ajouter une condition, listez les politiques du principal (scw iam policy list application-ids.0=<id>).

La clé expirée un vendredi soir. Une clé à 90 jours arrête le pipeline le 91e jour. L'expiration n'est une protection que si son renouvellement est planifié : notez la date (expires_at) dans un ticket récurrent ou un calendrier d'équipe, ou automatisez la rotation.

Sécurité

Toute cette leçon parle de sécurité ; quelques points restent à ajouter.

Le propriétaire n'est pas un compte de travail. Il a tous les droits, n'est soumis à aucune politique, et SAML ne s'applique pas à lui. Protégez-le par un second facteur robuste, ne créez pas de clé d'API à son nom pour l'usage courant (supprimez celle de la leçon 1 dès que vos identités de travail existent), et travaillez au quotidien avec un compte membre. Gardez une procédure de bris de glace (break glass) : qui détient les moyens de connexion du propriétaire, où, et dans quelles circonstances on s'en sert, avec un contrôle a posteriori dans les journaux.

Les fuites de clés. La liste des motifs de détection de secrets de GitHub (secret scanning) ne mentionne pas Scaleway en octobre 2026 : contrairement à une clé AWS, une clé Scaleway poussée dans un dépôt public n'est pas signalée automatiquement au fournisseur par ce programme. Vous pouvez définir un motif personnalisé à partir du format de la clé d'accès (SCW[A-Z0-9]{17}), et utiliser un détecteur de secrets en local avant chaque commit. En cas de fuite, la séquence est toujours la même : révoquer d'abord (scw iam api-key delete <cle-d-acces>), rechercher ensuite dans Audit Trail ce que la clé a fait, puis nettoyer l'historique Git ; nettoyer Git sans révoquer ne sert à rien, la clé a déjà pu être copiée.

L'expiration comme filet. Imposez une durée maximale aux clés de l'organisation (max-api-key-expiration-duration). Le rapport de GitGuardian rappelle que la plupart des secrets fuités restent valides des années : une expiration de 90 jours borne la fenêtre d'exploitation, même si personne ne remarque la fuite.

Pas de fédération pour les pipelines. AWS, Azure et Google Cloud acceptent qu'un pipeline échange un jeton OIDC signé par GitHub contre des identifiants temporaires, sans aucune clé stockée. Scaleway propose la fédération des personnes (OAuth2, SAML, SCIM), mais sa documentation IAM ne décrit pas de fédération des charges de travail à la date de vérification. La leçon OIDC du cours GitHub Actions détaille le mécanisme et les deux palliatifs : des clés dédiées et courtes, comme ici, ou un coffre intermédiaire qui, lui, accepte les jetons OIDC.

Les journaux sont une ressource à protéger. Lire Audit Trail se donne avec AuditTrailReadOnly ; les exports vont dans un bucket dont la politique doit empêcher la suppression par ceux-là mêmes dont on trace l'activité.

En production

Un projet par environnement. signalements-recette et signalements-production plutôt qu'un seul projet : les politiques du pipeline de recette ne peuvent alors pas toucher à la production, et la facture se lit par environnement (leçon 8). Pour un isolement encore plus fort, par exemple entre clients, des organisations séparées, au prix d'une facturation et d'une administration séparées.

Les droits en code. Les commandes de cette leçon se rejouent mal et se relisent mal. En production, applications, groupes et politiques se décrivent dans Terraform ou OpenTofu, revus par demande de fusion comme le reste du code ; le cours Terraform et OpenTofu : les fondamentaux le fera.

Les revues d'accès. Une fois par trimestre, quelqu'un répond à trois questions, outils à l'appui :

$ scw iam user list type=member -o json | jq -r '.[] | [.email, .mfa, .last_login_at] | @tsv'
$ scw iam api-key list -o json | jq -r '.[] | [.access_key, .description, .expires_at] | @tsv'
$ scw iam policy list -o json | jq -r '.[] | .name'

Qui n'a pas de second facteur ? Qui ne s'est pas connecté depuis trois mois ? Quelles clés n'expirent jamais ?

Les commandes de liste de scw parcourent toutes les pages de résultats et renvoient, en JSON, un simple tableau ; d'où les filtres .[]. Une clé sans expiration a un champ expires_at vide.

Les départs. Le jour où une personne quitte l'équipe, on la retire de ses groupes et on verrouille son compte (scw iam user lock), qui ne peut plus ni se connecter ni utiliser ses clés ; avec SCIM, la désactivation dans l'annuaire suffit. Les clés techniques n'étant pas attachées à des personnes, rien ne casse.

Chez Lyneko, le pipeline qui publie ce site utilise une application dédiée, limitée au registre de conteneurs, avec une clé à expiration ; External Secrets Operator, qui alimente les applications déployées par Argo CD, lit Secret Manager avec une autre application, limitée à la lecture des secrets d'un projet. Deux besoins, deux identités, deux politiques.

Exercices

1. Lire une politique (niveau 100). Une application sauvegarde-nocturne a deux politiques. La première : périmètre projet signalements, RelationalDatabasesReadOnly, condition request.time.getHours("Europe/Paris") < 6. La seconde : périmètre projet signalements, RelationalDatabasesReadOnly et ObjectStorageFullAccess, sans condition. L'application peut-elle lire la configuration de la base à 14 h ? Que faudrait-il changer pour que la lecture de la base ne soit possible que la nuit ?

Solution

Oui : les politiques s'additionnent, et la seconde donne la lecture des bases sans condition. La condition de la première ne retire rien. Pour limiter la lecture à la nuit, il faut retirer RelationalDatabasesReadOnly de la seconde politique (ou mettre la même condition sur toutes les règles qui donnent ce droit). Au passage, ObjectStorageFullAccess mérite un examen : une sauvegarde a besoin d'écrire des objets, rarement d'en supprimer ou de modifier la configuration des buckets ; des jeux plus fins existent (ObjectStorageObjectsWrite, par exemple).

2. Choisir le principal (niveau 100). Pour chacun des besoins suivants, dites s'il faut un utilisateur, une application ou un groupe, et pourquoi : (a) un script Terraform lancé par la CI ; (b) trois développeurs qui doivent lire les ressources du projet ; (c) une consultante externe pour une mission de deux semaines ; (d) un outil de supervision qui lit la consommation.

Solution

(a) Une application : identité non humaine, clé qui ne dépend d'aucune personne. (b) Un groupe contenant trois membres, et une seule politique attachée au groupe. (c) Un membre, ajouté au groupe adéquat, avec une date de fin notée et un verrouillage du compte à l'issue (les invités ne se créent plus depuis juin 2025). (d) Une application, avec BillingReadOnly dans une règle à périmètre organisation, puisque la facturation vit à ce niveau.

3. Concevoir les identités de la production (niveau 200). Signalements passe en production dans un projet signalements-production. Le pipeline doit publier l'image dans le registre et redémarrer les deux instances après publication. Proposez les applications, leurs politiques (jeux de permissions et périmètres) et les mesures autour des clés. Indiquez ce qu'un attaquant pourrait faire avec chaque clé volée.

Solution

Une solution raisonnable sépare les deux besoins, pour que chaque clé reste petite. sig-prod-publication : ContainerRegistryFullAccess sur signalements-production ; volée, elle permet d'écraser ou supprimer des images de la production, ce qui est grave, d'où une expiration courte et, si le runner a une adresse fixe, une condition request.ip. sig-prod-redemarrage : les jeux élémentaires des instances nécessaires au redémarrage (lecture et liste des serveurs, InstancesServerStart, InstancesServerStop) plutôt que InstancesFullAccess, qui permettrait de créer des machines ; volée, elle permet d'interrompre le service, pas de créer de ressources ni d'accéder aux données. Vérifiez dans la page des jeux de permissions lesquels couvrent l'action exacte que vous utilisez, et testez avec la clé elle-même. Dans les deux cas : secrets d'environnement GitHub limités à la branche principale, expiration de 90 jours au plus, renouvellement planifié, durée maximale imposée au niveau de l'organisation. Une autre voie évite la seconde clé : le pipeline ne fait que publier, et les instances (ou, mieux, un outil comme Argo CD) détectent la nouvelle version elles-mêmes.

4. Fuite (niveau 200). Un collègue vous prévient qu'une clé SCW... figure dans un commit poussé il y a deux heures sur un dépôt public. Écrivez, dans l'ordre, les commandes et vérifications que vous faites.

Solution
  1. Révoquer immédiatement : scw iam api-key delete <cle-d-acces>. 2. Identifier le porteur et ses droits (avant la suppression si possible, avec scw iam api-key get <cle-d-acces> with-policies=true, sinon dans les journaux IAM). 3. Chercher ce que la clé a fait : scw audit-trail event list filtré sur le principal (principal-id=) et sur les dernières heures, dans chaque région utilisée, en se rappelant que le registre et le stockage objet n'y apparaissent pas ; regarder aussi la consommation du mois (leçon 8) pour repérer des ressources inattendues. 4. Supprimer toute ressource créée par l'attaquant. 5. Créer une nouvelle clé pour le porteur et la déposer là où l'ancienne servait. 6. Seulement ensuite, réécrire l'historique Git, et ajouter un détecteur de secrets avant commit pour que cela ne se reproduise pas.

Récapitulatif

  • Une organisation porte la facturation et l'IAM ; ses projets regroupent les ressources et sont la frontière de droits du quotidien. AWS isole par comptes, Azure par abonnements et groupes de ressources, Google Cloud par projets sous des dossiers.
  • Trois sortes de principaux : les utilisateurs (propriétaire, membres), les applications (identités non humaines, sans console), les groupes. Une politique a au plus un principal.
  • Une politique contient des règles : un périmètre (projets ou organisation), des jeux de permissions, une condition CEL facultative. Tout est refusé par défaut, les politiques s'additionnent, et Scaleway n'a pas de refus explicite, contrairement à AWS, Google Cloud ou OVHcloud.
  • Une clé d'API hérite des droits de son porteur ; la clé secrète ne s'affiche qu'une fois. Donnez-lui une expiration, imposez une durée maximale, et réservez les clés longues aux applications.
  • IAMManager et OrganizationManager équivalent à un accès de propriétaire.
  • Les journaux IAM et Audit Trail disent qui a fait quoi, y compris les refus, mais ne voient pas encore le registre ni le stockage objet.
  • Le propriétaire ne sert pas au quotidien ; les personnes passent par le second facteur et, si possible, l'authentification unique.

Pour aller plus loin

  • Les pages Understanding IAM Policies et Permission sets de Scaleway, à relire chaque fois que vous écrivez une politique : la liste des jeux de permissions évolue avec les produits.
  • La page Policy evaluation logic d'AWS, pour voir un modèle avec refus explicites, plafonds d'organisation et politiques de ressources, et mesurer ce qu'il apporte.
  • L'article de Saltzer et Schroeder (1975), dont les huit principes de conception, moindre privilège compris, n'ont pas pris une ride.
  • La leçon suivante, où les alertes de facturation deviennent aussi un détecteur de clé volée.
Voir ma constellation →

Sources