GCP’s IAM syntax is better than AWS’s
Google’s GCP and Amazon’s AWS both have something called IAM (Identity & Access Management) at their heart. AWS’s is definitely easier to understand than GCP’s, largely because GCP’s identity system is tied to Google-at-large’s identity system, meaning GCP users are also YouTube users; AWS is blissfully untied to Amazon.com in comparison. However, GCP gets something right that AWS didn’t, and it’s something everyone who eventually builds out their own permission system should copy. It’s a tiny thing, really: the syntax for operations within the IAM system.
To clarify terminology here, by “operation” I mean “a fine-grained thing you can do”. OAuth calls these “scopes”. Almost no two systems in the world agree on what to call these things, but I think “operation” is the best word for the job.
In AWS, an operation is called an action, and they look like this:
s3:GetObject
dynamodb:Query
iam:CreateRole
In GCP, an operation is called a permission, and they look like this:
storage.objects.get
spanner.databases.select
iam.roles.create
If you ever find yourself building a fine-grained permission system, copy GCP’s syntax, not AWS’s. In almost every other category AWS made a simpler to understand system, but GCP got this one right. Here’s why:
-
GCP permissions have a more consistent structure. They are always of the form:
service.resource.verb
This makes the policies easier to parse mentally.
-
As a result of their structure, GCP permissions always give away what sort of resources they grant access to.
By contrast, in AWS, can you guess what sorts of resources it’s useful to have
dynamodb:Query
on? The answer isn’t just DynamoDB tables, it’s also GSIs. You will almost certainly mess this up the first time you do IAM with DynamoDB, and will get an error when you try to read from a GSI for the first time.If AWS had followed GCP’s lead, they would have probably split the permission into two:
dynamodb.table.query
anddynamodb.gsi.query
. Or they would have come up with a pseudo-resource that unifies table and GSIs, call it a “table-ish”, and have it bedynamodb.tableish.query
.In any case: lack of structure here means that what should be a “caught on the first pass of reading the docs” sort of bug instead becomes a “you’ll only figure this out by running into an error after launching it” kind of bug.
-
GCP could introduce wildcards more clearly than AWS could. GCP doesn’t actually let you do wildcards at all today, but if they did they would have a clearer version of widely-used AWS patterns.
This is because in practice, wildcards are for two things:
- Let someone do any sort of operation on a specific sort of resource.
- Let someone do a particular operation on any resource within a service.
In GCP-land if there were wildcards, you’d express these as:
storage.objects.*
storage.*.create
In AWS-land, you typically see:
s3:*Object
s3:Create*
The GCP examples are considerably easier to understand.
-
There is a subtle, mostly-theoretical problem with the glob-style permissions in AWS.
If your intention was to make sure some sort of worker has append-only access to S3, and you give them
s3:Create*
, then you run into the risk that S3 has, or will add at any point in the future, some action calleds3:CreateOrUpdateObject
or some equivalent.This isn’t pure theory. There’s already a permission called
autoscaling:CreateOrUpdateTags
. Soautoscaling:Create*
is already not just append-only. AWS could add more actions like this, at least in theory. -
AWS’s syntax is tightly bound to English pluralization rules, and English defies glob patterns. For instance, consider these actions, taken from a policy in AWS’s reference documentation for writing IAM policies:
{ "Effect": "Allow", "Action": [ "iam:*AccessKey*", "iam:ChangePassword", "iam:GetUser", "iam:*ServiceSpecificCredential*", "iam:*SigningCertificate*" ], "Resource": ["arn:aws:iam::*:user/${aws:username}"] }
The first, fourth, and fifth actions in that policy are an example of what I mean. Take
iam:*AccessKey*
. The spirit of that action is to let someone do anything to IAM access keys, but not access anything else. You would probably have expected this would be done withiam:*AccessKey
, but the issue is that this doesn’t coveriam:ListAccessKeys
. The trailing*
is trying to capture English pluralization suffixes.But English pluralization isn’t that simple. If instead of IAM access keys you wanted to let someone manage IAM policies, you would have to write:
iam:*Polic*
That’s pretty hard to make sense of. You’d have to do it this way because of how English pluralizes “policy”, namely by replacing “y” with “ies”. If you did
iam:*Policy*
, you would have missediam:ListPolicies
.By contrast, GCP’s style handles this without difficulty. We go from AWS’s:
iam:*AccessKey* iam:*Polic*
To GCP’s:
iam.access_keys.* iam.policies.*
Consider that some English plurals are utterly irregular, and if your IAM system manipulates them, your users may struggle to implement their IAM requirements with your system: “man / men”, “mouse / mice”, “person / people”, “datum / data” (this one differs between American and British English), “genus” / “genera” … the list goes on.
Finally, and I think this is the most important point: many of your users may not be English speakers, and so these quirks can be very confusing to them. Chinese, in particular, does not typically use a grammatical plural form, so many native Chinese speakers struggle with English plurals. Accessibility doesn’t just mean making your system work with the blind, it also requires being usable for people of various reading levels.
I know this is a tiny thing. AWS’s action syntax really isn’t so bad. But given how much time a lot of cloudsec and other engineers spend looking at IAM policies, you might as well just use the nicer practice. There’s essentially no downside.