CloudSecNext Presentation: Zero Trust, Building IAM with Terraform
I presented to 1.3k folks at CloudSecNext yesterday (May 3, 22) on a technical topic — using Terraform to build IAM for S3 buckets. It was…
I presented to 1.3k folks at CloudSecNext yesterday (May 3, 22) on a technical topic — using Terraform to build IAM for S3 buckets. It was super fun, and I got great feedback.
If you want to check it out, it’s here:
Based on this Article:
Transcript
Hey everyone, welcome to Zero Trust: Building IAM with Terraform. I hope you’ve all had a wonderful CloudSecNext 2022 so far, and I hope you’re all excited to become S3 IAM experts and then automate it with Terraform so we never have to write S3 IAM again!
I’m Kyler Middleton, I’m the Cloud Security Chick at IAM Pulse, where we’re working to improve the state of IAM and the education around it.
Lets first talk about what IAM is, look at a real-world IAM policy for S3, convert that policy to a Terraform module, then we’ll do a LIVE DEMO to show you how it works. I’ve sacrificed some reserved ec2 instance tokens to the demo gods, so let’s get started and see how it all goes.
(next slide)
Let’s talk about what IAM is. IAM, or Identity and Access Management, is the tool used by clouds to control who can do what, where. It is the service which you authenticate to in order to prove who you are, and the tool which checks to make sure you’re able to create an EC2 instance of that particular size or any other action within the cloud.
IAM is primarily used to control access to the control plane, but for some services like S3, it also controls access to the data plane — direct access to files.
In AWS these policies are represented as JSON documents. This permits a great deal of flexibility but a ton of challenges — JSON is finicky around commas and quotes, and the condition keys and Actions of each IAM stanza (which is a grouping of permissions) can be complicated and overlapping. Because of these traits, IAM is difficult for humans to understand and write.
We’re going to go over an S3 policy during this talk that proves this rule quite well.
(next slide)
S3 IAM is super simple, let’s look! That’s sarcasm if you can’t tell.
Let’s review a real-world policy which gives a partner company access to a home directory in an S3 bucket. This bucket has many partners with their own home folders. Each partner needs access to their home folder, but no one else’s folder. Sounds simple, right?
In fact, it’s so simple you’re going to be like, this is so easy, why are you even talking about it?
In all actuality these stanzas can be difficult to write properly, so if this all seems incredibly complicated as we go over it, you might be on to something!
(next slide)
Let’s look at the first permissions stanza each partner will need. Keep in mind that all of the stanzas we talk about will need to be created for each partner.
The first permissions stanza we’ll go over is the ListBucket permissions. This lets a partner view the bucket and files within it.
Let’s take a close look at this stanza because each will look similar.
We assign a SID, or Security ID. This is optional, but if it is set it has to be unique for each stanza within a policy.
Then the effect, either Allow or Deny. Denys take precedence, so we have to be very careful how we structure this.
Then the Principal. This can be multiple principals or a single one, and is a unique ARN for a user, group, IAM role — any principal in another AWS account that the partner will connect to us as.
Then an action or set of actions. This one is s3:ListBucket, which is a particular API call we can google and research what it does.
Then a Resource, which is the name of our bucket.
Then a condition. These are optional in each stanza, but are required for this situation. For this permission, we don’t want our partner to be able to list files across ALL folders within the bucket, we only want them to be able to list files in their own folder, so we set an s3:prefix filter of the folder name and any recursive paths with that wildcard star.
Let’s go over a few other stanzas you’ll want for each partner.
(next slide)
Next, read/delete. We want our same partner to be able to get (download) and delete file and folder objects within the bucket only within their folder path.
Notice we don’t use a condition here, we instead reference the bucket and folder name directly in the resource field. Did we need to do it differently for this stanza? I have no idea. We built it this way and it worked and now I’m too scared to touch it.
(next slide)
Next up, writing to the folder.
We want our partner to be able to put files into the folder in case they want to share stuff with us, right? So we grant them s3:PutObject on the bucket and home folder assigned to them.
There’s an interesting condition here. We match the s3:x-amz-acl, I have no idea how I’m supposed to pronounce that, for bucket-owner-full-control. Object ownership within S3 is complicated, with the uploading user retaining ownership of the object. We can use this condition to require that a client permissions setting is set during upload to permit the bucket owner (being us) to be granted read access to the files added.
Without this condition it’s feasible a partner could upload files that we can’t read, which isn’t something we want.
(next slide)
Then we deny any other paths. This is a great fallback security measure. First we grant a bunch of access, then we deny all the other access.
How this is actually achieved in IAM is pretty complicated. We Deny a StringNotLike the root or the folder name, and we also use a Null match to make sure the s3:prefix isn’t there.
If you’re nodding along like, yep, that makes perfect sense, I’m pretty sure you’re a robot.
We are denying any file listing that isn’t the home path with a false match against a null… Ya know what, let’s just agree that IAM can be super complicated, and leave it at that.
(next slide)
And don’t forget to update the S3 lifecycle configuration to make sure old files are cleaned up. You don’t want to just leave files in the partner’s folder around forever, right?
(next slide)
Sincerely, who do you trust to update this policy? Say you had a new partner, would you trust a non-senior engineer to update this policy, and would you sleep well at night if you didn’t double-check it yourself?
Imagine someone copy and pasted all those stanzas and forgetting to update one of the ARNs. Well, your company just granted a partner access to another partner’s home folder. In regulated industries your company might win the privilege of front-page news and federal regulator scrutiny because of that bad copy paste.
(next slide)
Are you scared? IAM scared. This talk was inspired by a real-life example. The company was sharing federally regulated data with 9 partners. The IAM policy that we just talked about was built by copy and pasting terraform by hand, and the total policy was more than 800 lines long.
Those facts together sufficiently scared me that I set out to automate that process.
(next slide)
Should we automate it? I mean, duh. Of course we should. That was rhetorical. We should definitely automate this very scary, potentially business-threatening thing. Let’s talk about how.
(next slide)
Terraform to the rescue! Let’s build a Terraform module that takes minimal inputs and builds that policy for us. That sounds useful, right?
And terraform is incredibly useful here. There is an AWS data resource that lets us build an IAM policy locally within Terraform.
Data resources within terraform are generally used to call data from somewhere else, like reading some attribute of a cloud provider or a file. However, they are perfectly misused here to locally build some IAM policy documents!
First of all, this might look a little complicated compared to the same stanza in IAM. However, keep in mind that we are building the Terraform version 1 time ever. We only have to be super smart for a few hours, so let’s load up on caffeine and write a module that’ll do all the hard work, forever.
Next time we need to add some IAM, we’ll send a new partner and ARN to our module, it’ll write all this yucky json for us, and we won’t have to even remember what the acronym IAM stands for. Cue the margaritas!
On line 2 we have a for_each, which means Terraform will iterate over the data we send it to potentially build many copies of this stanza, one time for each partner.
We use a Terraform reference interpolated into the SID that should be unique for each partner — their name, represented in terraform as the each.key here.
For the Principals we iterate over all the principals sent. Most partners will have a single ARN, but in the real world business case this talk is based on there was 1 that had 3 ARNs that needed access to a single folder. No big deal, we iterate over them and pass them as a list.
Even the conditions are present down at the bottom, though they look a little different in terraform.
(next slide)
Then we build the Get and DeleteObject permission.
Do you remember which conditions exist on which permissions stanzas? I already don’t, and we talked about it like 5 minutes ago.
(next slide)
Then we build the PutObject permissions for each partner, and set the s3:x-amz-acl condition again.
When you implement this pattern and empower your entire team to onboard a new partner, which conditions will they need to remember and understand? If you build this right, the answer is none!
(next slide)
Then the most twisty turny permission — the deny notString with null provider false match. I haven’t had nearly enough coffee to follow along, so let’s trust Terraform is right here.
(next slide)
Then we go all Voltron and combine our mini policies into a single mega policy that contains all the ones we just talked about. Thankfully, the same IAM policy doc data source can read over every sub policy, and with the help of some very cool flattens and FOR loops, generate a single MEGA policy.
(next slide)
And here’s how we’ll call the module. Keep in mind that this is the only part that most of your team will ever see or care about. All of that complicated JSON and IAM is now hidden behind a module that does all of the work.
We send over the name of each Partner — see AFCRichmond (because I watched too much Ted Lasso) and HiMom, and also the ARN or ARNs that partner will use to talk to us.
The module does literally all the rest of the work.
Who do you trust to update the policy now? I hope the answer is almost anyone on your team. It’s certainly more than fully understand all those IAM stanzas.
(next slide)
Let’s demo it! Please, everyone say a little prayer to the demo gods this all works.
(share screen)
This is a working Terraform stack. First we’re building some IAM users. This is solely for the sake of a demo. In real life your partners would provide their ARNs to you.
Then we have a bucket that we’ll apply this policy to.
Then we’re calling the GitHub module I’ve published. We send it the bucket name as well as the partner names and principals. The principals here are Terraform local references again because of demo, but they could as easily be strings.
Then we apply the policy by referencing the policy that’s built by our module and returned as an output.
Which is super cool, but let’s take it for a test drive! Let’s build, say, 5 more partners
(back to slides)
Thanks so much for listening to me go on about my love of Terraform and IAM. I’m Kyler Middleton, you can find me @KyMidd, at kyler.omg.lol, or in the Kyler Middleton hallway slack room immediately following this talk.
Thanks all!
kyler