Choose Your Own SAML Adventure: A Self-Directed Journey to AWS Identity Federation Mastery

Using SAML identities in Amazon S3 bucket policies

In this exercise, you will learn how to create an Amazon S3 bucket policy that grants access to a specific list of federated users and Amazon EC2 instances with a specific instance profile. This is an implementation of a common AWS pattern in which a bucket is created for a specific application, and needs to be restricted to the application specific instances and associated administrators.

A brief lesson about Amazon S3 objects: Amazon S3 stores data in a flat structure; you create a bucket, and the bucket stores objects. Amazon S3 doesn’t have a hierarchy of sub-buckets or folders; however, tools like the AWS Management Console can emulate a folder hierarchy to present folders in a bucket by using the names of objects (also known as keys). For simplicity, you can think of an object’s name as the full path of a file in a traditional file system. To give you an example, for an object that’s named home/common/shared.txt, the console will show the shared.txt file in the common folder that is in the home folder. The names of these folders (such as home/ or home/common/) are called prefixes.

By the way, the slash (/) in a prefix like home/ isn’t a reserved character—you could name an object (using the Amazon S3 API) with prefixes like home:common:shared.txt or home-common-shared.txt. However, the convention is to use a slash as the delimiter, and the Amazon S3 console (but not Amazon S3 itself) treats the slash as a special character for showing objects in folders. You can read more about it in our documentation Getting Started

This exercise is applicable to both variants (Open Source & Microsoft) of the workshop, just be sure to select the appropriate link at the end of the exercise to properly continue your SAML adventure.

Architecture

The following image provides a visual representation of what you are about to construct during this exercise.

Exercise architecture

Note: You should use this architecture and associated AWS CloudFormation template for demonstration and learning purposes ONLY. The infrastructure has been simplified to focus on the learning objectives and is not set up for availability, scalability, etc., and is not appropriate for production use.

Prerequisites

The following list identifies the prerequisites for this exercise. If you have not completed these tasks, please take time to do so now.

Deploy Exercise Infrastructure

To get started, you will deploy a CloudFormation stack that contains the exercise infrastructure:

Start by logging in to the AWS Management Console, and select CloudFormation.

AWS Management Console

If you aren't already there, switch to the Tokyo (ap-northeast-1) region, and then choose Create Stack.

AWS CloudFormation Console

On the first step, Select Template, choose Specify an Amazon S3 template URL, enter the following, and then choose Next.

https://s3.amazonaws.com/federationworkshopreinvent2016/cloudformation/S3BucketPolicy.json

AWS CloudFormation Create Stack

On the second step, Specify Details, enter the parameters according to the following table, and then choose Next.

Configuration Element Value
Stack name FederationWorkshopBucketPolicySample
BucketNameSuffix A string of your choice that will be appended to the end of the bucket name (lowercase only).
InstanceType t2.micro
KeyName Select an existing EC2 key pair within the ap-northeast-1 region. If you do not have an existing key pair refer to the documentation here to create a new one.
PublicSubnetId Select the same public subnet where you deployed the identity provider in the initial exercise.
SourceCidrForSSH The CIDR notation for the IP range that SSH should be restricted to. The workshop facilitators will advise how to best configure this for the conference wifi. For static environments, http://checkip.amazonaws.com/ can be used to determine your current IP address.
VPC Select the VPC that contains the public subnet above.

AWS CloudFormation Create Stack

On the third step, Options, you may proceed with the defaults by choosing Next.

AWS CloudFormation Create Stack

Finally review the information to ensure it is correct, acknowledge the creation of of IAM resources, and then choose Create.

AWS CloudFormation Create Stack

The template requires 3-4 minutes to complete. You can choose Refresh during this time to view the creation status in real time. After the stack has completed, choose the Outputs tab and make note of the BucketName and SampleAppIP for use later in this exercise.

AWS CloudFormation Create Stack

Create S3 bucket policy

Now that you have created a bucket, you will add a bucket policy so that it can only be accessed by a specific list of federated users and Amazon EC2 instances with a specific instance profile. According to the IAM policy evaluation logic, an explicit deny always supersedes an explicit allow. You will use this principle to limit access, even for IAM entities who have policy statements that would otherwise allow access (e.g. the s3:* statements attached to the FederationWorkshop-PowerUser role from the initial exercise). This combination of an explicit allow in an IAM policy, in conjunction with an explicit deny in resource policy, provides an efficient way to implement resource level permissions for services with granular access control requirements such as S3. When you use this pattern, the explicit deny policy is often formed using exclusionary statements. For example, "deny all principals access to the bucket except those in this list", as you will see in the following example.

Start by reviewing the complete S3 bucket policy template. Then, we will break down the policy and explain how it works.

{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Sid": "AllowSpecificUsersAndInstances",
        "Effect": "Deny",
        "Principal": "*",
        "Action": "s3:*",
        "Resource": [
          "arn:aws:s3:::<YOUR_BUCKET_HERE>",
          "arn:aws:s3:::<YOUR_BUCKET_HERE>/*"
        ],
        "Condition": {
           "StringNotLike": {
              "aws:userid": [
                "<ROOT_ACCOUNT>",
                "<EC2RoleId:RoleSessionName>",
                "<FederationRoleId:RoleSessionName>"
              ]
           }
        }
    }
  ]
}

In plain English, this policy translates as: "Deny all s3 actions to the bucket, but only when the aws:userid is not the following list." The list is then implemented using the "aws:userid" IAM policy variable within a condition operator. The key here is that aws:userid takes on different forms for different types principals. For both SAML federated users and roles assigned to an Amazon EC2 instance, this variable is a composite value in the form of RoleId:RoleSessionName. For more details about this and other IAM policy variables see the documentation. RoleId is a form of an IAM unique ID that is always unique, even in cases where an IAM principal is recreated using the same name.

Note: The version number ("Version":"2012-10-17") is required whenever you use policy variables within a S3 bucket policy.

Retrieve necessary values

To further understand this policy, let's insert the actual values in place of each placeholder in the template using the AWS CLI. Start by retrieving a new federated token for use with the AWS CLI. Log in using alice's credentials, and select the FederationWorkshop-PowerUser role.

===============Open Source Variant=================================
alice@Ubuntu64:/tmp$ ./samlapi_formauth.py
Username: alice
Password: **************** (Pass@123)

Please choose the role you would like to assume:
[ 0 ]:  arn:aws:iam::012345678987:role/FederationWorkshop-ReadOnly
[ 1 ]:  arn:aws:iam::012345678987:role/FederationWorkshop-PowerUser
Selection:  1

===============Microsoft Variant===================================
alice@Ubuntu64:/tmp$ ./samlapi_formauth_adfs3.py
Username: alice@example.com
Password: **************** (Pass@123)

Please choose the role you would like to assume:
[ 0 ]:  arn:aws:iam::012345678987:role/FederationWorkshop-ReadOnly
[ 1 ]:  arn:aws:iam::012345678987:role/FederationWorkshop-PowerUser
Selection:  1

Note: Recall that alice's password has been set to Pass@123.

Next, we need to retrieve the RoleId associated with the EC2 role that was created in the CloudFormation template at the beginning of this exercise. Use the use the saml profile with the AWS CLI and a CLI filter to do so, as shown in the following screenshot.

aws --profile saml iam list-roles --query 'Roles[?RoleName==`FederationWorkshop-SampleAppRole`]'

AWS CloudFormation Create Stack

For roles assigned to an Amazon EC2 instance, the RoleSessionName is set to the instance-id. Because we want to match all of the instances launched with this role, you will use a wildcard value i-*. Concatenate the two halves together and replace the placeholder in the policy above, as shown in the following snippet.

"<EC2RoleId:RoleSessionName>",
becomes
"AROAJXDQGQT3IF5QESQUY:i-*",

Note: Be sure to substitute your actual RoleId above.

Next, retrieve the RoleId for the FederationWorkshop-PowerUser role you created in the first exercise.

aws --profile saml iam list-roles --query 'Roles[?RoleName==`FederationWorkshop-PowerUser`]'

AWS CloudFormation Create Stack

For federated users, the RoleSessionName is the value provided to AWS by your SAML identity provider. In this workshop, we configured RoleSessionName to be the uid of the user in the back end directory. Alice will play the role of the sample application administrator, so use her uid as the RoleSessionName.

"<FederationRoleId:RoleSessionName>"
becomes
"AROAIUZJXRVZV7KJY2KUG:alice"

Note: Be sure to substitute your actual RoleId above.

Note: You can append any number of users to a policy using this technique by adding additional lines in the same format.

Next, substitute in the value of your AWS account number. The root user needs to be included in bucket policies of this nature in order to avoid orphaning a bucket by accident.

"<ROOT_ACCOUNT>",
becomes
"123456789012",

Note: Be sure to substitute your actual account number above.

Finally, substitute in the value of your bucket name, using the value you noted earlier.

"arn:aws:s3:::<YOUR_BUCKET_HERE>",
"arn:aws:s3:::<YOUR_BUCKET_HERE>/*"
becomes
"arn:aws:s3:::federationworkshopsampleappbucket-vandeman",
"arn:aws:s3:::federationworkshopsampleappbucket-vandeman/*"

Your final policy should look similar to the following.

{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Sid": "AllowSpecificUsersAndInstances",
        "Effect": "Deny",
        "Principal": "*",
        "Action": "s3:*",
        "Resource": [
          "arn:aws:s3:::federationworkshopsampleappbucket-vandeman",
          "arn:aws:s3:::federationworkshopsampleappbucket-vandeman/*"
        ],
        "Condition": {
           "StringNotLike": {
              "aws:userid": [
                "123456789012",
                "AROAJXDQGQT3IF5QESQUY:i-*",
                "AROAIUZJXRVZV7KJY2KUG:alice"
              ]
           }
        }
    }
  ]
}

Apply the S3 bucket policy to your S3 bucket

Now that you have assembled the appropriate policy statement, you will apply the S3 bucket policy to the bucket you created earlier with CloudFormation. Start by logging in to the AWS Management Console, and choose S3.

AWS Management Console

Note: As a best practice, you should not normally modify resources provisioned by CloudFormation outside of CloudFormation. This is only being done here to focus on the learning objectives.

Select the bucket that you created above, which starts with federationworkshopsampleappbucket. Choose Properties, then expand Permissions, and finally choose Add bucket policy.

AWS Management Console

Once the S3 policy editor pops up, paste your assembled S3 bucket policy and choose Save.

AWS Management Console

Note: Make sure that if you are copying the bucket policy in the S3 bucket editor, you check for the whitespaces and json syntax. If you want to validate your policy, you can use Json Validator

Testing

Now that the configuration is done, it is time to test things out. To do so you will confirm the following:

Log in to the SampleApp instance via SSH

Log in using SSH to your newly provisioned SampleApp instance using the public IP address that you noted in the previous step. See directions for how to use SSH according to your client platform.

SSH

Use the following command sequence to create a test file and upload it to the protected bucket, as shown in the following screenshot.

cd /tmp
echo "FooBar" > foo.txt
aws s3api put-object --bucket <YOUR_BUCKET_HERE> --key foo.txt --body foo.txt

SSH

Test using an authorized federated user

Return to your local workstation. Retrieve a new federated token for use with the AWS CLI. Log in using alice's credentials, and select the FederationWorkshop-PowerUser role.

===============Open Source Variant=================================
alice@Ubuntu64:/tmp$ ./samlapi_formauth.py
Username: alice
Password: **************** (Pass@123)

Please choose the role you would like to assume:
[ 0 ]:  arn:aws:iam::012345678987:role/FederationWorkshop-ReadOnly
[ 1 ]:  arn:aws:iam::012345678987:role/FederationWorkshop-PowerUser
Selection:  1

===============Microsoft Variant===================================
alice@Ubuntu64:/tmp$ ./samlapi_formauth_adfs3.py
Username: alice@example.com
Password: **************** (Pass@123)

Please choose the role you would like to assume:
[ 0 ]:  arn:aws:iam::012345678987:role/FederationWorkshop-ReadOnly
[ 1 ]:  arn:aws:iam::012345678987:role/FederationWorkshop-PowerUser
Selection:  1

Note: Recall that alice's password has been set to Pass@123.

Retrieve the object that you uploaded to S3 above using the following command sequence, as shown in the following screenshot.

aws --profile saml s3api get-object --bucket federationworkshopsampleappbucket-vandeman --key foo.txt /tmp/foo.txt  
cat /tmp/foo.txt

Test alice

If time permits

If time permits, you may wish to test the negative condition as well. To do so, refresh your credentials by either logging in as bob (any role) or as alice using the FederationWorkshop-ReadOnly role. In either case, you are not be able to retrieve the object from the bucket. Instead, you receive an unauthorized operation message.

Note: For federated users with multiple roles, they will only be authorized when operating as the role explicitly listed in the S3 bucket policy.

Key take-aways

In summary, there are two key take-aways from this use case:

Exercise complete

Congratulations! You've successfully completed the using SAML identities in Amazon S3 bucket policies advanced use case.

With this use case complete, you are now ready to continue your journey through more of the advanced use cases. To continue, select the appropriate link according to your technology stack to return to the index of advanced use cases.