Bucket Policies & cloisonnement des accès
Par défaut, chaque credential EC2 d'un projet dispose d'un accès complet à chaque bucket de ce projet. C'est pratique : un nouveau bucket fonctionne avec les credentials existants sans que personne n'ait à toucher aux permissions.
Pour certains buckets, ce comportement par défaut est trop large, par exemple un bucket dans lequel exactement une application peut écrire et depuis lequel exactement un consommateur peut lire. Plutôt que de déplacer un tel bucket dans son propre projet, vous pouvez inverser le comportement par défaut à l'aide d'une bucket policy : tout refuser à tout le monde, sauf aux credentials qui portent l'un des deux rôles nommés. Tous les buckets et tous les credentials restent dans le même projet.
Quels credentials une bucket policy peut cibler
Une bucket policy cible des rôles, pas des clés d'accès individuelles. Le rôle d'un credential est défini dans le portail au moment de la création du credential (Credential role), voir Create EC2 Credentials.
Fonctionnement de l'accès par défaut
| Sujet | Comportement |
|---|---|
| Périmètre | Un projet est le conteneur : tous les buckets et tous les credentials EC2 qu'il contient. |
| Par défaut | Sans bucket policy, chaque credential du projet peut lire et écrire dans chaque bucket du projet, qu'il soit lié au projet ou à un utilisateur. |
| Restriction | L'accès étant autorisé par défaut, l'attribution d'un droit ne change rien. Seul un Deny explicite dans une bucket policy restreint l'accès. |
| Granularité | Une policy par bucket, par région. Le même bucket dans l'autre région nécessite sa propre policy, voir Multi Region. |
| Visibilité | Le cloisonnement bloque l'accès, pas la visibilité : aws s3 ls sans bucket liste toujours le nom du bucket. |
Prérequis
- Un projet avec le profil de quota object-storage et au moins un bucket, voir Configuration.
- Une AWS CLI configurée pour l'endpoint et la région S3, voir AWS CLI & IAM Features.
- Deux credentials EC2 dans le projet qui portent un rôle, voir étape 1.
- L'ID du projet, voir étape 2.
Étape 1 : créer des credentials avec un rôle
Dans le portail : Object Storage → S3 → sélectionnez le projet → Credentials → + Create Credentials, puis renseignez Credential role :
| Credential | Credential role |
|---|---|
| Accès en écriture | <bucket>-readwrite |
| Accès en lecture | <bucket>-readonly |
Le nom du rôle constitue votre périmètre d'autorisation
- Le nom du rôle est un texte libre et appartient au credential. Un nom différent signifie un nouveau credential.
- Pour la policy, chaque credential du projet qui porte le même nom de rôle est interchangeable, y compris les credentials liés à un utilisateur appartenant à d'autres utilisateurs.
- N'utilisez pas de noms génériques comme
readwritesi différents buckets nécessitent des accès différents : un seul credential ouvrirait alors chaque bucket dont la policy liste ce rôle. - Notez les noms exacts. Vous en aurez besoin pour modifier ou révoquer la policy.
Étape 2 : déterminer l'ID du projet
La policy référence les credentials sous forme d'ARN :
arn:aws:iam::<PROJECT_ID>:role/<ROLE>
<PROJECT_ID> est l'ID de votre projet, pas son nom. Vous pouvez le lire avec le client OpenStack (openstack ec2 credentials list l'affiche dans la colonne Project ID, voir EC2 Credentials), ou le reprendre depuis la vue d'ensemble du projet dans le portail.
Étape 3 : écrire la policy
Définissez vos valeurs dans des variables shell et laissez le shell les insérer. Ne modifiez pas les ARN à la main : un seul caractère erroné dans un ARN refuse l'accès à tout le monde, y compris au portail.
export bucket=my-bucket
export project=<PROJECT_ID>
# Note the unquoted EOF: ${bucket} and ${project} must be expanded.
cat <<EOF > bucket-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyAllWithoutRole",
"Effect": "Deny",
"NotPrincipal": {
"AWS": [
"arn:aws:iam::${project}:role/${bucket}-readonly",
"arn:aws:iam::${project}:role/${bucket}-readwrite"
]
},
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::${bucket}",
"arn:aws:s3:::${bucket}/*"
]
},
{
"Sid": "DenyNonReadActionsForReadonlyRole",
"Effect": "Deny",
"Principal": {
"AWS": "arn:aws:iam::${project}:role/${bucket}-readonly"
},
"NotAction": [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:ListBucket",
"s3:ListBucketVersions",
"s3:GetBucketLocation"
],
"Resource": [
"arn:aws:s3:::${bucket}",
"arn:aws:s3:::${bucket}/*"
]
}
]
}
EOF
# Control: no ${...} may be left in the file
grep -n '\$' bucket-policy.json || echo "OK: all variables expanded"
Ce que font les deux instructions :
DenyAllWithoutRole: tout ce qui n'est pas l'un des deux rôles se voit refuser toutes les actions sur ce bucket et ses objets. Cela inclut tous les autres credentials du projet, le portail et les requêtes anonymes.DenyNonReadActionsForReadonlyRole: le rôle en lecture seule ne peut que lire (GetObject,ListBucket, ainsi que les recherches de version et d'emplacement dont certains clients ont besoin). Les écritures et suppressions lui sont refusées.
Un Deny explicite l'emporte toujours sur n'importe quel Allow. Si vous n'avez pas besoin d'un accès en lecture seule, supprimez la deuxième instruction et l'entrée -readonly de la première.
Étape 4 : répéter sur un bucket de test
Une inadéquation entre la policy et les credentials verrouille le bucket pour tout le monde. Faites d'abord un essai sur un bucket jetable dans le même projet et la même région, cela prend deux minutes.
export prod_bucket=my-bucket
export bucket=${prod_bucket}-fence-test
aws s3 mb s3://${bucket}
# Run the heredoc from step 3 again (it uses ${bucket} and ${project}),
# then apply the policy to the scratch bucket:
aws s3api put-bucket-policy --bucket="${bucket}" --policy=file://bucket-policy.json
# 1) Read-write credential: export its keys first
export AWS_ACCESS_KEY_ID=<readwrite-access-key>
export AWS_SECRET_ACCESS_KEY=<readwrite-secret-key>
AWS_MAX_ATTEMPTS=1 aws s3 cp - s3://${bucket}/hello.txt <<<"hello"
# 2) Read-only credential: switch to its keys
export AWS_ACCESS_KEY_ID=<readonly-access-key>
export AWS_SECRET_ACCESS_KEY=<readonly-secret-key>
AWS_MAX_ATTEMPTS=1 aws s3 cp s3://${bucket}/hello.txt -
# 3) A credential without a role: switch to its keys (expect 403)
export AWS_ACCESS_KEY_ID=<roleless-access-key>
export AWS_SECRET_ACCESS_KEY=<roleless-secret-key>
AWS_MAX_ATTEMPTS=1 aws s3api head-bucket --bucket "${bucket}"
Le contrôle 3 constitue la véritable preuve. Si un credential sans rôle réussit encore, arrêtez et contactez le support : n'appliquez pas la policy à un bucket de production.
Nettoyage :
aws s3api delete-bucket-policy --bucket="${bucket}"
aws s3 rm s3://${bucket} --recursive
aws s3 rb s3://${bucket}
Si l'essai se verrouille lui-même
Si le contrôle 1 échoue avec Access Denied, le principal de la policy ne correspond pas aux credentials : ID de projet erroné, autre projet ou faute de frappe dans le nom du rôle. Laissez le bucket de test tranquille et n'appliquez pas la policy au bucket de production. Sans credential correspondant, ni vous ni le portail ne pouvez supprimer la policy, voir Revoke and lockout.
Étape 5 : appliquer au bucket
export bucket=${prod_bucket}
# Run the heredoc from step 3 again, then:
aws s3api put-bucket-policy --bucket="${bucket}" --policy=file://bucket-policy.json
# Control: the stored document should match your file
aws s3api get-bucket-policy --bucket="${bucket}" --query Policy --output text | jq .
Étape 6 : vérifier
Exécutez les trois contrôles de l'étape 4 sur le bucket de production, chacun avec son propre credential comme indiqué. Attendez-vous à : écriture et lecture avec le credential en écriture, lecture seule avec le credential en lecture seule, et 403 pour tous les autres credentials du projet.
Comportement au quotidien
- Portail : le panneau S3 fonctionne avec le credential sélectionné sous
Select Credentials. Si ce credential ne porte aucun rôle, la navigation, le téléversement et l'éditeur de policy échouent avecAccess Denied. Attribuez au credential utilisé par vos administrateurs le rôle<bucket>-readwrite, ou travaillez avec la CLI. - Autre région : répétez les étapes 3 à 5 pour le bucket dans la seconde région.
- Nouveaux buckets : il n'y a pas d'héritage. Un nouveau bucket est ouvert à tout le projet jusqu'à ce qu'il reçoive sa propre policy.
- Versioning : le rôle en lecture seule peut lire les versions actuelles et anciennes des objets, rien d'autre.
- Changement de rôle : créez le nouveau credential, ajoutez le nouveau rôle à la policy, basculez les applications, puis retirez l'ancien rôle de la policy. Jamais dans l'ordre inverse.
Révocation et verrouillage
Pour révoquer, supprimez la policy. Cela nécessite un credential qui porte <bucket>-readwrite :
aws s3api delete-bucket-policy --bucket="${bucket}"
Ensuite, le bucket revient au comportement par défaut du projet : chaque credential du projet retrouve un accès complet.
Verrouillage : plus personne n'a accès
Symptôme : chaque credential du projet, y compris le portail, reçoit Access Denied sur le bucket. Cause : les noms de rôle ou l'ID de projet dans la policy ne correspondent à aucun credential existant, ou le dernier credential portant le rôle a été supprimé.
La récupération sans support fonctionne tant que vous connaissez les noms de rôle de la policy. Créez un nouveau credential dans le même projet avec exactement ce nom de rôle (<bucket>-readwrite). L'instruction de refus exclut ce principal, le nouveau credential a donc de nouveau accès. Utilisez-le pour supprimer la policy, puis recréez le credential perdu.
Récupération avec support : si les noms de rôle ne sont plus connus, ouvrez un ticket avec l'ID de projet, le nom du bucket, la région et la mention qu'une bucket policy DenyAllWithoutRole verrouille le bucket. Le support peut supprimer la policy.
Prévention : créez les deux credentials avant d'appliquer la policy, faites un essai sur un bucket de test et ne supprimez jamais le credential en écriture tant que la policy est en place.
Dépannage
| Symptôme | Cause et solution |
|---|---|
Les requêtes bloquent environ une minute, puis renvoient 403 |
La CLI réessaie les requêtes échouées. export AWS_MAX_ATTEMPTS=1 échoue rapidement, utile pour les refus attendus ci-dessus. |
MalformedPolicy lors de l'application |
${...} n'a pas été développé : le heredoc doit commencer par <<EOF, et non par <<'EOF'. |
aws s3 rb échoue |
Le bucket n'est pas vide. Supprimez d'abord les objets (aws s3 rm --recursive, nécessite le rôle). |
Le bucket apparaît toujours dans aws s3 ls |
Normal. Le cloisonnement refuse l'accès au contenu, pas le nom du bucket. |
| Un second credential avec le même rôle fonctionne | Normal. Le nom du rôle est le périmètre, pas la clé. |