IAM whatever you say IAM

Alex Chantavy
Lyft Engineering
Published in
8 min readNov 5, 2020

--

This post was co-authored with Andrew Johnson @SecPrez.

Last year in March (this was 2019 in case you forgot; doesn’t that feel like forever ago?), we open sourced Cartography, our Python tool that consolidates technical assets and the relationships between them in a graph database.

https://github.com/lyft/cartography

Using graphs helps us visualize and reason about security problems in a very powerful way. One such problem is understanding cloud permissions relationships: we needed an answer to the question “who has permission to read and write to my sensitive data resources?”

In the cloud, this is not always a straightforward problem!

How Cartography represents AWS IAM in graph form

Lyft is an AWS shop, and AWS’ access control mechanism is called IAM. It determines which Principals (e.g. users, groups, and roles) may perform which Actions on which Resources(e.g. storage buckets, compute instances, etc). IAM is powerful, highly customizable, and can work across account boundaries. IAM can be easy to configure incorrectly, and mistakes here can enable adversaries to easily move around and perform malicious actions in your environment.

Cartography ingests AWS IAM data and represents it in graph form like this:

AWS roles, users, and groups have policies attached to them, which determine the Actions they are allowed to perform or not perform against a defined set of Resources. IAM can get very complicated: you can specify advanced clauses like NotAction (which determine what a Resource can’t do) or NotResource (which determine the resources this statement does not apply to). Further, you can use the * character to have a policy apply to objects that match a given text string. A principal’s resulting access is determined by all the policy statements mapped to it.

As a motivating example, we wanted to quickly see which principals had root or “root-like” privileges in our environment. An IAM policy like this allows the equivalent of root privileges to all principals it is attached to because it allows any action to be performed on any resource:

{
"Action": "*",
"Effect": "Allow",
"Resource": "*"
}

Cartography uses a Neo4j graph database, so we can use this query to search for these principals:

MATCH (stat:AWSPolicyStatement)--(pol:AWSPolicy)--(principal:AWSPrincipal)
WHERE stat.effect = "Allow"
AND any(x IN stat.resource WHERE x="*")
AND any(x IN stat.action WHERE x="*" )
RETURN *

If you stare at the query long enough, it makes sense: we look for PolicyStatements that are attached to AWSPolicy nodes that are attached to AWSPrincipals where * is set as both a Resource and an Action.

The result can look something like this. If you try this yourself, you might be in for an unpleasant surprise if you aren’t expecting any principals to be highly privileged:

Which AWS identities have root permissions? Find the AWSPrincipals that are attached to AWSPolicies that allow `*` actions to be performed on `*` resources.

It’s great that we have the data, but it’s cumbersome to need to remember all the rules of IAM policy evaluation to answer this question. It would save us a lot of time to be able to simply ask “who has permission to read from my storage buckets?” or “who has permission to run queries on my DynamoDB tables?”

Resource Permission Relationships: Using the graph to evaluate a principal’s resulting accesses offline

With all of this data in the graph, earlier in April of this year we thought that it’d be a great idea to evaluate IAM policies offline so that we could determine a given principal’s resulting accesses (for those familiar with Windows security, this calculation might remind you a bit of RSOP). We called this feature Resource Permission Relationships.

We started by including 3 built-in RPRs with Cartography:

As seen above, our plan was for Cartography to automatically map AWS principals to the resources that they can access! These mappings would be specified in a permission_relationships.yaml file, and you can read how to configure this here. To understand how this works, we’ll walk you through the above picture’sCAN_READ example:

  1. Cartography will search allAWSPolicyStatement nodes that allow the S3:GetObject permission and find all AWSPrincipal nodes attached to these statements. Cartography also verifies that no other AWSPolicyStatement nodes deny the S3:GetObject permission.
  2. Then, Cartography will search for all S3Bucket nodes in the current AWS account (since ResourceType is specified as S3Bucket above) and draw a graph edge labeled CAN_READ from the AWSPrincipal to the S3Bucket.

In summary, we have taken the path(:AWSPrincipal)-->(:AWSPolicy)-->(:AWSPolicyStatement{effect:"Allow", resource:"s3", Action:"S3:GetObject"}), evaluated it against S3Bucket nodes, and simplified it to draw an edge from the principal to the S3 bucket like this: (:AWSPrincipal)-[:CAN_READ]->(:S3Bucket). The questions “what does this principal have permission to do?” and “who has access to my resource?” can now be queried at scale as they are precomputed and stored in the graph.

Let’s verify who has read or write privileges to a sensitive s3 bucket:

MATCH (s3:S3Bucket{name:"SensitiveS3"})<-[:CAN_READ|:CAN_WRITE]-(principal:AWSPrincipal)
RETURN *

Result:

Who has read and write access to my sensitive S3 bucket?

Since group expansion has already been calculated, this is the resulting set of roles and users that can access the sensitive S3 bucket. Only identity-based policies are evaluated at the moment, we plan to add resource policies in a future update.

Finding out an individual user has access to is also simple, as Cartography syncs Okta identity data:

MATCH (h:Human{name:”Andrew Johnson”})-[:CAN_ASSUME_ROLE]-(r:AWSRole)-[:CAN_READ]-(s3:S3Bucket)
RETURN *

You can also query for aggregate data over your entire graph. For example, who in your organization has access to the greatest number of S3 buckets?

MATCH (h:Human)-[:CAN_ASSUME_ROLE]-(r:AWSRole)-[:CAN_READ]-(s3:S3Bucket)
RETURN DISTINCT h.name, h.email, count(distinct s3)
ORDER BY count(distinct s3) desc

Related Work

At the time of development, we knew of other great open-source projects that dealt with IAM like policyuniverse and Cloudsplaining, but we did not know of anything that performed offline evaluation in the way we wanted. We found that the closest project to our idea was NCC Group’s PMapper (neat!), but PMapper focuses on returning a single query answer and doesn’t yield as much of a full, explorable picture to the extent that Cartography does. There are more benefits to having this logic in Cartography itself, which we will cover in the next scenario.

Extending and customizing Resource Permission Relationships

You might have noticed that we only included 3 RPRs in the previous section regarding S3 buckets and DynamoDB tables, and you might be wondering why we didn’t perform this policy expansion logic for all principals and for all resources so that the graph would be as complete as possible. This is infeasible because AWS has a lot of built-in IAM policies that may not even apply to any principals in your environment. Running this calculation for everything is wasteful so we opted instead to include some sample RPRs in the default permission_relationships.yaml file and allow you to customize this to your needs. You can copy the process described in the next section to do this.

Adding new permission relationships

More recently, we had a task to monitor the IAM principals that have admin access to AWS Redshift instances. This was a simple 6 line change to our permission_relationships.yaml file (and you can see the PR here):

- target_label: RedshiftCluster
permissions:
- redshift:*
- redshift:CreateClusterUser
- redshift:GetClusterCredentials
- redshift:JoinGroup
relationship_name: CAN_ADMINISTER

This short config searches the graph for policy statements that allow any one of the four above actions (redshift:*, redshift:CreateClusterUser, etc). Then, we find which AWS principals in the current account are attached to these statements. Finally, we draw a link from each principal to all RedshiftCluster nodes in the current account of the form (:AWSPrincipal)-[:CAN_ADMINISTER]->(:RedshiftCluster).

Our rationale here is that if an identity is able to perform any one of those four actions on a Redshift cluster, then we consider that a so-called “Redshift admin” and we want to draw a relationship from the identity to the cluster so that we can quickly query for them.

The result looks like this where the yellow AWSGroup below has the redshift:* action attached to it, which allows it to perform any action on all Redshift clusters.

You can draw your own resource permission relationships by copying our examples to your own yaml file and specifying its absolute path in the Cartography command-line interface’s --permission-relationships-file argument.

Tracking IAM changes over time with Drift Detection

We have established mappings from AWS principal to sensitive Redshift resources, but as mentioned above in our Related Work section, this is still slightly duplicative of PMapper’s functionality. Why even build this into Cartography?

Now that we have enriched the IAM data in the graph, we can use Cartography’s Drift Detection feature to let us know via Slack alerts whenever the list of Redshift admins changes, and that we should investigate why this list changed. We’ll blog on the details of Drift Detection in a future post if there’s interest (and once we dig ourselves out from under of the pile of other wonderful ideas we want to build), but as a teaser, the result looks like this:

Where Cartography is going next

We’ve shared Cartography at several security conferences over our first year and a half as an open source project, but this is the first time that we’ve blogged about it here on the Lyft Engineering blog. The problem of understanding IAM permissions lends itself well to a graph-based solution, and we’re just scratching the surface of what we have planned.

There are lots of ideas we have around engineering a more reliable data sync, speeding up the sync process, making our plugin framework even more newcomer-friendly, and making the data useful for more people. More than just providing data, we’re excited about using the graph for both offensive and defensive automated actions: imagine having a robot army running around your environment using the graph as a map and fixing all the security problems that it finds. There are many possibilities to use this idea to its full potential and we look forward to building out this platform together with the open source infosec community.

We hope that you’ve found this post informative and that you feel inspired to check out our tool at https://github.com/lyft/cartography.

If you have questions or want to reach out, please say hi in channel #cartography on the Lyft OSS Slack or at our monthly public community meeting.

Join us building open source tools to solve security problems at scale; Lyft Security is hiring!

--

--