Leveraging Unique Identifiers in S3 bucket policies

close up photo of mining rig

Leveraging Unique Identifiers in S3 bucket policies

The Challenge

Recently a client approached us with a need for straightforward object storage that could accommodate multiple users. Their primary requirement was to implement stringent access controls to ensure data security. In this post I’m going to talk about how we used IAM Unique Identifiers to achieve this.

Additional Constraints

The client wanted the storage solution to reside within their existing AWS account to simplify management and oversight. This setup would allow them to manage permissions efficiently and maintain a high level of security while keeping everything centralised and easy to administer.

To ensure security and proper access control, these buckets were meticulously locked down. Only specific roles, targeted for S3 access, were granted permission to list or interact with the bucket contents. All management of the buckets themselves was handled programmatically through Terraform. This access control was crucial to maintain data integrity and confidentiality.

It was imperative to implement explicit denial policies for objects within the buckets. This meant that any entity not intended to use the bucket had to be explicitly denied access. This additional layer of security ensured that only authorised parties could access the data, thereby preventing unauthorised use and potential data breaches.

This setup presented a significant challenge in managing and maintaining access controls, ensuring that the right entities had the necessary permissions while keeping unauthorised users out.

Implementation

Alternative methods

When exploring alternative methods individual roles and user accounts were considered. However, these methods required significant manual intervention and, although possible to implement programmatically, created an overhead of management of numerous unique roles.

This approach was found to be less efficient compared to using existing mechanisms. By leveraging existing federated access through permission sets, it was found that account access could be more securely linked without a need for regular audits, thus streamlining the process and enhancing security.

About IAM Unique Identifiers

Unique Identifiers, prefixed AIDA (eg. AIDACKCEVSQ6C2EXAMPLE) for User accounts, and AROA (eg. AROADBQP57FF2AEXAMPLE) for Roles, are used to manage user accounts and roles in a cloud environment. While there are many other identifiers, we won’t cover them in this article. Typically, these identifiers are hidden from plain view in the console unless a role has been deleted in the originating account. In such cases, they may still exist as placeholders in policies.

Unlike IAM Roles, referencing Unique Identifiers in a policy allows you to lock down the explicit session of the user assuming the role. This means you can use the same role with multiple users and still identify the specific user of a session to provide access.

You can only reference a Unique Identifier using a Condition key called aws:userid, unlike a standard role or user, which can also be referenced directly through a Principal statement.

For example, if you’re using AWS IAM Identity Center with permission sets across a Control Tower environment, your users’ Unique Identifiers would be suffixed with a session name that matches the email address the user uses for federation.

Caveats to leveraging Unique Identifiers include the inability to obtain Unique Identifiers easily programmatically unless they are in the local account. Additionally, if the original user or role is added or removed, the Unique Identifier would change, necessitating a policy re-deployment even if the name remains the same.

Drawbacks to using a Unique Identifier

Note that this method should only be used for IAM roles that are explicitly locked down to services like AWS Identity Center or AWS EC2 Instance Profiles, where the roleSessionName cannot be overridden by the end user. It may be unsafe to rely on this method for services that don’t have this restriction. AWS STS, for example, allows you to set the session name you would like to use.

It is also possible to control the naming of the roleSessionName by adjusting the Trust Policy to set a condition that ensures it can only be assumed based on a specific identifier such as aws:username. Roles defined in this way are safe to use with this method.

Implementing within a bucket policy

A bucket policy was set up to grant access to specific IAM roles. However, access to objects within the bucket was denied unless the aws:userid matched the expected principals exactly.

To manage this, the first statement allows access to the bucket for a specific IAM role. The second statement uses a StringNotLike condition to exclude valid users from the deny rule on s3:GetObject and s3:PutObject actions. This ensured that only users with the correct Unique Identifier and Session name could access the objects, while others were denied.

An additional initial statement should be included to configure who can manage the bucket, but this has been left out of the policy example below.

In the example below, the first statement allows the specified IAM role to list, get, and put objects in the bucket. The second statement denies these actions to all principals unless the aws:userid matches the specified unique identifier and explicit username.

This setup ensures that only the specified IAM role can perform the actions, and access is denied to everyone else unless they meet the specified condition.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::account-id:role/<ROLE-NAME>"
            },
            "Action": [
                "s3:ListBucket",
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::<BUCKET-NAME>/*",
                "arn:aws:s3:::<BUCKET-NAME>"
            ]
        },
        {
            "Effect": "Deny",
            "Principal": {
                "AWS": "*"
            },
            "Action": [
                "s3:ListBucket",
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::<BUCKET-NAME>/*",
                "arn:aws:s3:::<BUCKET-NAME>"
            ],
            "Condition": {
                "StringNotLike": {
                    "aws:userid": [
                        "<UNIQUE-IDENTIFIER>:<ROLE-SESSION-NAME>"
                    ]
                }
            }
        }
    ]
}

Try it with Terraform!

If you’re up for some hands-on fun, I’ve whipped up a code sample on GitHub. You can find a single-file setup for creating and managing an S3 bucket within an AWS account using Terraform. This setup covers the basics to test the theories discussed in this article. It defines IAM roles, sets up an S3 bucket, and applies policies to control access.

Conclusion

In this post, I’ve shown you how to implement access controls for S3 buckets within the same AWS account using IAM Unique Identifiers. While there are some caveats and drawbacks to using Unique Identifiers, such as the need for policy re-deployment when roles or users change, the benefits of streamlined access control and reduced manual intervention make this method a robust solution for secure object storage. I’ve also provided a practical code example so that you can try this yourself!

No Comments

Leave a Reply

Discover more from Shine Solutions Group

Subscribe now to keep reading and get access to the full archive.

Continue reading