Secure that Bucket! (Centralized Logging: Part 2), Resource Policies

We'll assume a role again, learn about one weird trick with resource policies, and write our first bucket policy.

Prerequisites

The Lesson

In our last lab we learned how to assume a role from one account into another. We logged in as our admin IAM user, and then assumed the OrganizationAccountAccessRole into our SecurityAudit account. Once there we created an S3 bucket, but we didn’t talk much about S3. Today we’ll cover a little of S3, and write our first resource-based policy. Specifically something called a bucket policy.

S3 stands for Simple Storage Service. It was one of the very first AWS services to launch. S3 is what we call object storage. If you’ve ever read about a big AWS data leak, 9.478 times out of 10 it’s because someone misconfigured S3.

Each major Infrastructure as a Service (IaaS) cloud provider offers at least one object storage option. Object storage like S3 is all about storing data as distinct units: objects. Each object includes data, some metadata for context, and a unique identifier. This setup enables you to store vast amounts of unstructured data — think images, videos, or backups — in a highly scalable way. The big win with object storage is its accessibility: you can grab any piece of data from anywhere over the web, not just from within the local network. And it’s designed for durability and high availability, making it a reliable choice for long-term storage.

S3 stores objects in something called a bucket, which is what we made last week to hold our CloudTrail logs. Buckets also support file paths, just like a disk drive or a traditional file directory.

Most of the biggest AWS-related data leaks and breaches happen in S3. It’s easy to keep a bucket and objects secure; it’s harder to keep buckets and objects consistently secure, at scale, in a large diversified organization, with lots of teams doing a lot of different things and sometimes needing public buckets for legitimate purposes.

Do not EVER underestimate the possibility of an S3 leak. There are a bunch of ways to misconfigure S3, and we can only scratch the surface today. Definitely rely on the Block Public Access feature that is enabled by default until we learn more about S3 — and afterwards too.

Resource-Based Policies (Bucket Policies)

S3 buckets and objects are private by default. Unless you deliberately make them public, no one on the Internet can read them. But as my big red warning box (and the endless headlines) warn… it’s easy to mess up and make buckets and objects public accidentally.

S3 buckets are the first kind of resource in AWS we will use that has an option to be accessed from outside our accounts, from the Internet at large. Think about it: S3 buckets are often used to host shared files or even entire websites. We would never want to create IAM users inside our org for all those unsavory Internet types, so S3 lets us give them a URL to access stuff directly.

This Internet-based access never touches our IAM identity-based policies, so we need another mechanism to control access. S3 has… a bunch of those, but the most common and recommended one is called a bucket policy — it’s the first kind of resource-based policy we will use.

Resource-based policies are used to control access to resources, especially when accessed from outside our account(s), and particularly when they can be accessed directly from the Internet.

Bucket policies apply to a bucket, the container that holds objects, and protect all objects in the bucket. Like IAM policies, they are written in JSON. The syntax is a little different because we are more likely to want to do things like allow access from an IP address.

Someone (or something) in your account using the AWS APIs can only access a bucket if the bucket policy, the IAM policy, and the Service Control Policy all allow access. But there’s a shortcut — if access is from outside your account, the SCP and IAM policy won’t even be evaluated! SCPs and IAM (identity-based) policies apply to API calls. But a user can access a S3 bucket with just a URL, in which case there is no API call or user identity to work with.

That’s why we have bucket policies.

Some resources can be accessed directly via a URL or other option which skips IAM. That’s one reason we need resource policies.

But there’s one key difference you need to understand about resource-based policies. As we discussed with SCPs and IAM policies, you can only do what both allow you to do. Any deny statements in either policy type will block the action.

With resource-based policies and IAM policies, though, any allow statement allows access, even if the other policy denies access. Here’s the official diagram from AWS:

Rut-Roh… Public Access (and Blocking)

S3 allows you to make buckets and objects totally public to the entire Internet. This is how most of those big data leaks happen. You can make them public with the bucket policy or in multiple other ways, which is kind of the problem — even though the default is to keep them secure.

We won’t touch it today, but keep in mind that the current default in AWS is to enable a feature called Block Public Access which… does… that. When BPA is on at the account level, you cannot make any buckets public. You can still share them privately, but they can’t ever be public to the Internet — this works across the multiple policy types that S3 supports.

Since this is our logging account, it will never share S3 objects to that nasty old Internet. You can sleep soundly knowing this is protection turned on, and we won’t turn it off for this account.

Key Lesson Points

  • Some AWS resources can be directly accessed from the Internet or other places in AWS. This means IAM policies and SCPs won’t be evaluated, because the API call originates outside your AWS account.

  • Resource-based policies enable us to protect these resources.

  • But any allow statement in a resource policy will allow access — even if you have an IAM or SCP policy which would deny access.

  • S3, the AWS object storage service, stores objects (files) in buckets.

  • We control S3 access using bucket policies, one type of resource-based policy.

The Lab

For this lab we will grab some identifiers and insert them into a bucket policy template, then apply that template to the S3 bucket we created in the prior lab.

You will need to collect the following items. I recommend pasting them into a text file. I call these cheat sheets because I provide a bunch in training classes for students to fill out — they are where I put templates and command lines for Copy and Paste magic.

  • Management Account ID:

  • Trail ARN:

  • Bucket ARN:

  • Organization ID:

You should copy and paste this template onto your cheat sheet. Below I’ll show you where to get all the identifiers, which you will paste into the JSON where you see <less-than/greater-than brackets>. {} and [] are reserved characters in JSON, so I use <> to mark where to paste things in my templates.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AWSCloudTrailAclCheck20150319",
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "cloudtrail.amazonaws.com"
                ]
            },
            "Action": "s3:GetBucketAcl",
            "Resource": "<Bucket ARN>",
            "Condition": {
                "StringEquals": {
                    "aws:SourceArn": "<Trail ARN>"
                }
            }
        },
        {
            "Sid": "AWSCloudTrailWrite20150319",
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "cloudtrail.amazonaws.com"
                ]
            },
            "Action": "s3:PutObject",
            "Resource": "<bucket ARN>/AWSLogs/<Management AccountID>/*",
            "Condition": {
                "StringEquals": {
                    "s3:x-amz-acl": "bucket-owner-full-control",
                    "aws:SourceArn": "<Trail ARN>"
                }
            }
        },
        {
            "Sid": "AWSCloudTrailOrganizationWrite20150319",
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "cloudtrail.amazonaws.com"
                ]
            },
            "Action": "s3:PutObject",
            "Resource": "<Bucket ARN>/AWSLogs/<organizationID>/*",
            "Condition": {
                "StringEquals": {
                    "s3:x-amz-acl": "bucket-owner-full-control",
                    "aws:SourceArn": "<Trail ARN>"
                }
            }
        }
    ]
}

Video Walkthrough

Step-by-Step

First, go to https://console.aws.amazon.com and sign in as usual to your management account. Believe it or not, we will not be logging in this way much longer (and there’s your teaser for this week). Three of our four pieces of information are in this account. I’ll walk you through where to grab all of them for pasting into the template cheat sheet.

First let’s grab the ID for our management account. It’s in the upper-right corner and has one of those copy buttons AWS started adding to the UX a while ago to make it easier to grab identifiers.

Pop that down, then Copy the Account ID and Paste into your cheat sheet.

Next we need our CloudTrail ARN. Go to CloudTrail in the console, then click Trails and click your Default trail. From there you can grab the ARN (Amazon Resource Name):

Paste it into your cheat sheet.

The last item in this account is your Organization ID. Go back to the upper right corner, Organization, and you’ll see it in the left sidebar:

Paste that into your cheat sheet.

Okay, now you need to AssumeRole into your SecurityAudit account. If you are using the same browser as the last lab, this will still be on your pick-list (under Role history) when you click the upper right corner. If not you’ll need to get your SecurityAudit account ID from here on the Organizations page, and click Switch role in the upper right, and on the next page, paste in that account ID and OrganizationAccountAccessRole for the role name. Here’s what the pick list looks like; you can review the last lab if you need another run-through on the process:

Now navigate to S3 in the console to grab our last item: the ARN of the soon-to-be CloudTrail bucket we made last week. This will be under Properties after you click the bucket from the main page. Confirm you are in your SecurityAudit account, so you don’t pick the wrong bucket!!!

Paste that onto your cheat sheet.

As a review, you will now have these items on your cheat sheet:

  • Management Account ID:

  • Trail ARN

  • Bucket ARN:

  • Organization ID:

Now take the template from above in this post, paste it into a new text file, and start replacing the elements marked between <>. TAKE YOUR TIME!!! and watch out for the tricky ones where you have two replacements in a single line:

"Resource": "<Bucket ARN>/AWSLogs/<organizationID>/*",

So in that one you replace the bucket ARN, leave the /AWSLogs/, replace the organization ID, and leave the /*. Make sense? Here’s a screenshot of mine:

I cover what this does a bit more in the video, but the short version is that for each statement we specify two things. The first is the resource which the action applies to. In the first statement the translation is “allow the CloudTrail service to get the bucket access control list (ACL) of only the specified bucket, and only if the API call is made by the Trail from our management account.”

The second says “allow the CloudTrail service to put objects into the S3 bucket into /AWSLogs/ under the account ID of the management account path, but only if the act matches bucket-owner-full-control and only from the management account’s Trail.” This is weirdness we can’t get into now, which protects part of this setup in case the logging account leaves the organization.

The last statement says “allow the cloudtrail service to put objects into the same darn bucket, but now in a path for the organization ID and, once again, only if the log files are from the main trail in the management account”.

You might ask why we need those conditionals and can’t just trust CloudTrail? This is to help prevent something called the confused deputy problem. Imagine that someone else find out our bucket ARN (because I put it in a blog post and a YouTube video). They would be able to tell their CloudTrail to save logs to our S3, since the actual s3:PutObject API call is made by the CloudTrail service, and our policy allows access. This would be a nice economic denial of service attack if they stuffed enough in there — and imagine if we enabled read access, which would expose our data.

I deliberately worded things a little differently each time to trigger different brain cells. Also to annoy my editor. Which is mean, because he’s been fixing my writing for 20 years, often on a voluntary basis. Sorry Chris. [S’alright. I get you’re trying to stay awake too. —Chris]

And that, my friends, is our first bucket policy, the first time we’ve used conditional statements, and the most complex policy we’ve written so far. We will keep coming back to this over and over, because it takes a while to get used to the policy language, but it’s one of our most important skills. Don’t worry if you haven’t fully absorbed it all yet.

Okay, to finish up go to Permissions, scroll down to Bucket policy, click Edit, and paste in your customized policy. Then click Save.

If you managed not to mangle your paste, the top of the screen should look like this:

And it’s a wrap! Next week we’ll redirect the trail to this bucket. After that we’ll move back into IAM to enable federation, followed by a little more SCP and OU work, and then a little time jump I’m working on to show you a bit of why we do all this, with a mock attack.

Assuming my code works. Which is a big assumption.

Lab Key Points

  • Bucket policies are the resource policies for S3 buckets.

  • It’s important to copy and paste carefully because it’s easy to miss one (ask me how I know). This is a pretty common way of working with policies.

  • Conditionals are incredibly valuable to further restrict access. In this case we are making sure only our trail can save logs into our bucket.

-Rich

Join the conversation

or to participate.