Le Blog Amazon Web Services

Restreindre l’accès d’un compartiment Amazon S3 à un role IAM spécifique

Nos clients nous demandent souvent comment limiter l’accès d’un bucket (compartiment) Amazon S3 à un rôle spécifique d’ AWS Identity and Access Management (AWS IAM). En général, ils essaient d’utiliser la même méthode que pour un utilisateur AWS IAM : appliquer une stratégie de compartiment (bucket policy) refusant explicitement tous les Principals (utilisateurs et rôles) auxquels ils ne veulent pas accorder l’accès. L’inconvénient de cette approche est le maintien à jour obligatoire de la bucket policy lors de la création de nouveaux Principals. En effet, si un nouvel utilisateur AWS IAM venait à être ajouté au compte avec s3:* comme Action, l’utilisateur se verrait alors accorder l’accès au bucket. Plutôt que de spécifier la liste des utilisateurs dont on souhaite bloquer l’accès, il est préférable d’utiliser l’élément NotPrincipal dans la déclaration Deny de la bucket policy créant ainsi un refus explicite pour tout utilisateur non listé.

Cependant, cette approche s’avère problématique avec les rôles AWS IAM car la valeur du Principal du rôle est composée de deux ARNs (Amazon Resource Names) : l’ARN du rôle (role) et l’ARN du rôle assumé (assumed-role). L’ARN du role représente l’identifiant du rôle AWS IAM tandis que l’ARN du rôle assumé (assumed-role) identifie la session du rôle que l’on peut retrouver dans les logs. Lorsque vous utilisez l’élément NotPrincipal, il est nécessaire d’inclure les deux ARNs pour que cette approche fonctionne, et le second de ces ARN doit inclure un nom de variable. On serait alors tenter d’utiliser le métacaractère '*' en tant que chaîne de caractère de la variable, mais cela n’est pas autorisé pour les éléments Principal ou NotPrincipal.

Cet article montre comment restreindre l’accès au bucket S3 à un rôle ou un utilisateur AWS IAM d’un compte donné en utilisant des Conditions au lieu de l’élément NotPrincipal. De cette manière, même si un autre utilisateur dans le même compte possède une stratégie Admin ou une stratégie contenant s3:*, il se verra refuser l’accès au compartiment/bucket s’il n’est pas explicitement listé. Il est par exemple possible d’utiliser cette approche pour configurer un bucket qui ne pourra être accédé que par des instances d’un Auto Scaling Group ou encore pour limiter l’accès à un bucket ayant un besoin de sécurité d’accès élevé.

Aperçu de la solution

La solution proposée dans cet article utilise une bucket policy pour contrôler l’accès à un bucket S3. Elle peut également s’appliquer pour une entité possédant un accès à l’API complète de S3. Le schéma suivant illustre son fonctionnement pour un bucket situé dans le même compte.

Schéma de principe de la solution

  1. La user policy de l’utilisateur AWS IAM et la user policy du rôle donnent accès à “s3:*”,
  2. La bucket policy S3 limite l’accès uniquement au rôle,
  3. L’utilisateur AWS IAM et le rôle peuvent en temps normal tous deux accéder aux buckets du compte. Mais dans ce cas, le rôle peut accéder aux deux buckets existants alors que l’utilisateur ne peut accéder qu’au bucket n’ayant pas la bucket policy associée. Bien que le rôle et l’utilisateur aient tous deux des autorisations “s3:*” , la bucket policy interdit l’accès à l’espace de stockage à toute personne n’ayant pas assumé le rôle.

Dans le cas d’une approche entre comptes, la principale différence est que chaque bucket S3 doit avoir une bucket policy lui étant attachée. Le schéma suivant illustre le fonctionnement de la solution dans un scénario de déploiement entre comptes.

Cross Account solution

  1. La user policy IAM du role ainsi que la user policy des utilisateurs IAM du compte dans lequel se trouvent les bucket S3 donnent toutes deux accès à “s3:*”,
  2. La bucket policy refuse l’accès à tout utilisateur dont le user:id n’est pas égal à celui du rôle et définit les actions autorisées sur le bucket pour le role en question,
  3. La bucket policy autorise l’accès au rôle appartenant à l’autre compte,
  4. L’utilisateur et le role IAM peuvent tout deux accéder au bucket ne spécifiant pas de Deny dans sa bucket policy. Le role lui, peut accéder aux deux buckets car la directive Deny n’est appliquée que pour les Principal dont le user:id n’est pas égal à celui du rôle.

Utilisation de l’élément NotPrincipal

Il est possible d’utiliser l’élément NotPrincipal dans une policy IAM ou une bucket policy afin de limiter l’accès d’une ressource à un ensemble d’utilisateurs. Cet élément permet de bloquer tout utilisateur n’étant pas présent dans le tableau de valeur associé, même si celui-ci possède un Allow explicite dans sa propre user policy IAM. Ainsi, dans le cas où un utilisateur devrait avoir accès à tous les bucket S3 à l’exception d’une seule, il est possible de créer cette définition au niveau du bucket en question sans avoir à modifier la stratégie de l’utilisateur IAM.

Cependant, comme indiqué précédemment, cela est plus compliqué pour un rôle IAM, car son Principal est définit par deux ARNs: l’ARN du role ainsi que l’ARN du assumed-role. L’ARN du role (arn:aws:iam::ACCOUNTNUMBER:role/ROLE-NAME) est statique et indépendant de qui initie la session du rôle. (N’oubliez pas de remplacer dans les exemples fournis lors de cet article les informations génériques par celles de votre propre compte). L’ARN de l’assumed-role (arn:aws:sts::ACCOUNTNUMBER:assumed-role/ROLE-NAME/ROLE-SESSION-NAME) lui varie en fonction du nom de session. Il est possible d’observer ceci en regardant par exemple l’élément Identity suivant provenant d’une entrée AWS CloudTrail enregistrée lors d’un appel API fait par un utilisateur assumant un rôle.

{
  "type": "AssumedRole",
  "principalId": "AROAJI4AVVEXAMPLE:ROLE-SESSION-NAME",
  "arn": "arn:aws:sts::ACCOUNTNUMBER:assumed-role/ROLE-NAME/ROLE-SESSION-NAME",
  "accountId": "ACCOUNTNUMBER",
  "accessKeyId": "EXAMPLEKEY",
  "sessionContext": {
    "attributes": {
      "mfaAuthenticated": "false",
      "creationDate": "XXXX-XX-XXTXX:XX:XXZ"
    },
    "sessionIssuer": {
      "type": "Role",
      "principalId": "AROAJI4AVV3EXAMPLEID",
      "arn": "arn:aws:iam::ACCOUNTNUMBER:role/ROLE-NAME",
      "accountId": "ACCOUNTNUBMER",
      "userName": "ROLE-SESSION-NAME"
    }
  }
}

On peut voir que l’ARN du role ainsi que l’ARN du assumed-role sont tout deux présents au sein de cet élément Identity. Le ROLE-SESSION-NAME peut potentiellement être amené à changer en fonction de qui assume le rôle. Le principalId qui est également présent mais son format permet une utilisation en dehors de l’élément Principal d’une bucket policy. Nous allons utiliser cette information lors de la création de la bucket policy.

Accorder l’accès à un bucket S3 à un rôle IAM spécifique au sein d’un même compte

Dans la plupart des cas, il n’est pas nécessaire d’utiliser une bucket policy pour permettre l’accès à un bucket depuis le même compte que le rôle. Cela est dû au fait que la bucket policy définit un accès étant déjà autorisé au niveau de la user policy IAM de l’utilisateur. Les bucket policy S3 sont généralement utilisées lors de la mise en place d’accès entre comptes, mais il est également possible de les utiliser pour restreindre l’accès via un Deny explicite qui serait appliqué à l’ensemble des Principals, qu’ils soient dans le même compte que le bucket ou non.

Toute entité IAM (utilisateur, groupe ou role) possède une variable aws:userid lui étant définie. Nous allons avoir besoin de cette variable dans la bucket policy afin de spécifier dans une condition le rôle ou l’utilisateur faisant exception. La valeur aws:userId d’un assumed-role est défini comme UNIQUE-ROLE-ID:ROLE-SESSION-NAME (par exemple, AROAEXAMPLEID:userdefinedsessionname).

Pour récupérer la valeur AROAEXAMPLEID d’un rôle IAM on peut effectuer les étapes suivantes :

  1. Assurez-vous d’avoir installé l’AWS CLI et ouvrez une invite de commande ou un shell.
  2. Executez la commande suivante: aws iam get-role --role-name ROLE-NAME.
  3. Repérez dans la sortie de la commande la chaîne de caractère du RoleId qui commence par AROA . Cette valeur sera utilisée dans le cadre de la bucket policy afin de ne donner accès au bucket qu’à ce rôle.

Dans l’exemple précédant nous avons utilisé un extrait de log AWS CloudTrail dans lequel cet identifiant est utilisé dans l’élément principalId. La valeur de cet élément est importante car il est également possible dans une policy IAM d’effectuer des opérations de comparaison de chaînes de caractères sur des variables de policy AWS. Plutôt que de spécifier les ARNs d’un role et d’un assumed-role dans un élément NotPrincipal, il est ainsi possible d’utiliser la valeur de la variable aws:userId dans une condition StringNotLike en positionnant le métacaractère '*' pour le nom de session du rôle. Il faudra également veiller à ajouter l’utilisateur root du compte dans la valeur de la variable aws:userId afin d’éviter de rendre le bucket inaccessible dans le cas où le rôle autorisé venait à être supprimé. Le userId du compte root est le numéro du compte lui même.

En utilisant l’AROAEXAMPLEID d’un rôle que l’on vient de récupéré via l’AWS CLI, il est possible de créer la logique conditionnelle permettant de limiter l’accès pour un bucket donné aux utilisateurs assumant le rôle en question. L’utilisation de cette logique conditionnelle à la place de l’élement NotPrincipal permet l’utilisation du métacaractère '*' pour accepter n’importe quel nom de session pour ce rôle.

Maintenant que nous avons l’ID du rôle dont on souhaite autoriser l’accès sur le bucket S3, il faut bloquer l’accès aux autres utilisateurs au sein du même compte que le bucket. La policy permettant de bloquer l’accès au bucket S3 et à ses objets aux utilisateurs qui ne sont pas root ou qui n’endossent pas le rôle spécifié peut être définie comme suit :

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::MyExampleBucket",
        "arn:aws:s3:::MyExampleBucket/*"
      ],
      "Condition": {
        "StringNotLike": {
          "aws:userId": [
            "AROAEXAMPLEID:*",
            "111111111111"
          ]
        }
      }
    }
  ]
}

Comme précisé précédemment, nous avons également ajouté l’identifiant de l’utilisateur root du compte (qui est le même que celui du compte, ici "111111111111") pour s’assurer de toujours pouvoir accéder au bucket S3, même en cas de suppression du rôle spécifié.

Il est également possible d’utiliser la même policy pour des utilisateurs IAM. Un utilisateur IAM possède un ID unique qui commence par AIDA que l’on peut utiliser à cet effet. Il est possible de trouver cet ID de la manière suivante :

  1. Ouvrez une invite de commande ou un shell,
  2. Exécutez la commande: aws iam get-user -–user-name USER-NAME,
  3. Repérez dans la sortie de la commande la chaîne de caractère du userId qui commence par AIDAEXAMPLEID.

Il est possible d’ajouter ce userId dans le tableau de chaînes de caractères de la variable “aws:userId” utilisée dans le block Condition de la policy comme suit :

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::MyExampleBucket",
        "arn:aws:s3:::MyExampleBucket/*"
      ],
      "Condition": {
        "StringNotLike": {
          "aws:userId": [
            "AROAEXAMPLEID:*",
            "AIDAEXAMPLEID",
            "111111111111"
          ]
        }
      }
    }
  ]
}

Accorder l’accès entre comptes à un bucket S3 pour un un rôle IAM spécifique

Dans la section précédente, nous avons vu comment restreindre l’accès à un bucket S3 pour un rôle ou utilisateur IAM au sein d’un même compte. Nous allons maintenant voir comment mettre en place une restriction d’accès similaire pour un utilisateur ou un rôle d’un compte différent. Lorsque l’on accorde un accès entre comptes à un bucket S3 pour un utilisateur ou rôle IAM, il est nécessaire de définir quel utilisateur ou rôle IAM se verra attribuer l’autorisation d’accès. Dans un autre article Jim Scharf décrit les permissions nécessaires afin de permettre à une entité IAM d’accéder à un bucket S3 via la CLI/API ou la console AWS. Réutilisons le contenu de cet article afin de donner un accès CLI/API au niveau de la bucket policy :

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111111111111:role/ROLENAME"
            },
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::MyExampleBucket"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111111111111:role/ROLENAME"
            },
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::MyExampleBucket/*"
        },
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::MyExampleBucket",
                "arn:aws:s3:::MyExampleBucket/*"
            ],
            "Condition": {
                "StringNotLike": {
                    "aws:userId": [
                        "AROAEXAMPLEID:*",
                        "111111111111"
                    ]
                }
            }
        }
    ]
}

Les actions de services nécessaires pour permettre un accès console, par exemple utilisé pour la fonctionnalité IAM Switch Role, sont explicitées dans la policy suivante :

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111111111111:role/ROLENAME"
            },
            "Action": [
                "s3:ListAllMyBuckets",
                "s3:GetBucketLocation"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111111111111:role/ROLENAME"
            },
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::*MyExampleBucket*"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111111111111:role/ROLENAME"
            },
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::MyExampleBucket/*"
        },
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::MyExampleBucket",
                "arn:aws:s3:::MyExampleBucket/*"
            ],
            "Condition": {
                "StringNotLike": {
                    "aws:userId": [
                        "AROAEXAMPLEID:*",
                        "111111111111"
                    ]
                }
            }
        }
    ]
}

Pour accorder un accès API/CLI à un utilisateur IAM d’un autre compte, dont l’identifiant sera « 222222222222 » pour l’exemple, il est nécessaire d’ajouter l’ID AIDAEXAMPLEID de l’utilisateur dans la condition utilisant la variable “aws:userId” en procédant de la même manière que dans la section précédente. En plus de cette condition, il sera également nécessaire d’ajouter l’ARN complet de l’utilisateur dans l’élément Principal de ces documents de stratégie. Il à noter qu’il n’est pas possible d’accorder un accès console entre comptes à un utilisateur IAM car cet utilisateur devrait alors assumer un rôle dans le compte destinataire. Il est cependant possible de donner un accès API/CLI de la façon suivante :

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": [
                {
                    "AWS": [
                        "arn:aws:iam::222222222222:role/ROLENAME",
                        "arn:aws:iam::222222222222:user/USERNAME"
                    ]
                }
            ],
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::MyExampleBucket"
        },
        {
            "Effect": "Allow",
            "Principal": [
                {
                    "AWS": [
                        "arn:aws:iam::222222222222:role/ROLENAME",
                        "arn:aws:iam::222222222222:user/USERNAME"
                    ]
                }
            ],
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::MyExampleBucket/*"
        },
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::MyExampleBucket",
                "arn:aws:s3:::MyExampleBucket/*"
            ],
            "Condition": {
                "StringNotLike": {
                    "aws:userId": [
                        "AROAEXAMPLEID:*",
                        "AIDAEXAMPLEID",
                        "111111111111"
                    ]
                }
            }
        }
    ]
}

En plus d’inclure les permissions du rôle dans la bucket policy, il est nécessaire de définir ces permissions dans une user policy de rôle ou d’utilisateur IAM. Les permissions peuvent êtres ajoutés à une customer managed policy puis attachées au rôle ou utilisateur souhaité via la console IAM grâce au document de stratégie suivant :

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListAllMyBuckets",
        "s3:GetBucketLocation"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::MyExampleBucket"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::MyExampleBucket/*"
    }
  ]
}

Conclusion

Dans cet article nous avons vu comment restreindre l’accès à un bucket S3 à un rôle ou utilisateur IAM spécifique se trouvant dans un même compte ou dans un autre compte, et ce même s’il possède une policy Admin ou contenant s3:*. Il y a de nombreuses applications possibles à cette logique et les prérequis peuvent changer en fonction des cas d’usage. Il est par exemple possible d’utiliser cette approche pour mettre en place un bucket qui ne sera accessible que par des instances au sein d’un Auto Scaling group. Il est également possible d’utiliser cette approche pour limiter l’accès à un bucket ayant des pré-requis de sécurité d’accès élevés, comme un bucket contenant des entrées personnelles ou encore des informations liées au compte. Il est toujours recommandé de n’accorder des permissions qu’aux ressources nécessaire pour l’accomplissement des tâches requises.

Article original publié par Chris Craig et traduit en français par Hermantino Singodiwirjo, Solutions Architect dans l’équipe AWS France.