AWS IAM assuming same role with session tag for tenant isolation

666 Views Asked by At

I am working on a serverless app powered by API gateway and AWS lambda. Each lambda has a separate role for least privilege access. For tenant isolation, I am working on ABAC and IAM

Example of the role that provides get object access to s3 bucket having <TenantID> as the prefix. Role Name: test-role

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
            ],
            "Resource": "arn:aws:s3:::test-bucket/${aws:PrincipalTag/TenantID}/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole",
            ],
            // Same role ARN: Ability to assume itself
            "Resource": "arn:aws:iam::<aws-account-d>:role/test-role"
        }
    ]
}

I am assuming the same role in lambda but the the session tag as

const credentials = await sts.assumeRole({
    RoleSessionName: 'hello-world',
    Tags: [{
        Key: 'TenantID',
        Value: 'tenant-1',
    }],

    RoleArn: 'arn:aws:iam::<aws-account-d>:role/test-role'
}).promise();

I am trying to achieve ABAC with a single role instead of two(one role with just assuming role permission, another role with actual s3 permission) so that it would be easier to manage the roles and also won't reach the hard limit of 5000. Is it a good practice to do so, or does this approach has security vulnerability?

2

There are 2 best solutions below

0
On

It should work, but feels a bit strange to re-use the role like this. It would make more sense to me to have a role for the lambda function, and a role for the s3 access that the lambda function uses (for a total of two roles).

Also make sure that you're not relying on user input for the TenantID value in your code, because it could be abused to access another tenant's objects.

0
On

TLDR: I would not advise you to do this.

Ability to assume itself

I think there is some confusion here. The JSON document is a policy, not a role. A policy in AWS is a security statement of who has access to what under what conditions. A role is just an abstraction of a "who".

As far as I understand the question, you don't need two roles to do what you need to do. But you will likely need two policies.

There are two types of policies in AWS, of interest to this question: Identity based policies and Resource Based policies:

  • Identity-based policies are attached to some principal, which could be a role.
  • Resource-based policies are attached to a resource - which also could be a role!

A common use case of roles & policies is for permission delegation. In this case, we have:

  • A Role, that other principals can assume, maybe temporarily
  • A trust policy, which controls who can assume the role, under what conditions, and what actions they can take in assuming it. The trust policy is a special case of a resource policy, where the resource is the role itself.
  • A permissions policy, which is granted to anyone who assumes the role. This is a special case of an identity policy, which is granted based on the assumption of a role.

Key point: both policies are associated to the same role. There is one role, two policies.

Now, let's take a look at your policy. Clearly, it's trying to be two things at once: both a permissions policy and a trust policy for the role in question.

This part of it is trying to be the trust policy:

    {
        "Effect": "Allow",
        "Action": [
            "sts:AssumeRole",
        ],
        // Same role ARN: Ability to assume itself
        "Resource": "arn:aws:iam::<aws-account-d>:role/test-role"
    }

Since the "Principal" section is missing, looks like it's allowing anyone to assume this role. Which looks a bit dodgy to me, especially since one of your stated goals was "least privilege access".

This part is trying to be the permissions policy:

    {
        "Effect": "Allow",
        "Action": [
            "s3:GetObject",
        ],
        "Resource": "arn:aws:s3:::test-bucket/${aws:PrincipalTag/TenantID}/*"
    },

This doesn't need a "Principal" section, because it's an identity policy.

Presumably you're resuing that policy as both the trust policy and the permissions policy for the given role. Seems like you want to avoid hitting the policy (not role) maximum quota limit of 5000 defined here:

Customer managed policies in an AWS account

Even if somehow it worked, it doesn't make sense and I wouldn't do it. For example, think about the trust policy. The trust policy is supposed to be a resource-based policy attached to the role. The role is the resource. So specifying a "Resource" in the policy doesn't make sense, like so:

        "Resource": "arn:aws:s3:::test-bucket/${aws:PrincipalTag/TenantID}/*"
    },

Even worse is the inclusion of this in the trust policy:

    {
        "Effect": "Allow",
        "Action": [
            "s3:GetObject",
        ],
        "Resource": "arn:aws:s3:::test-bucket/${aws:PrincipalTag/TenantID}/*"
    },

What does that even mean?

Perhaps I'm misunderstanding the question, but from what I understand my advice would be:

  • Keep your one role - that's OK
  • Create two separate policies: a trust policy & a permissions policy
  • Consider adding a "Principal" element to the trust policy
  • Attach the trust & permissions policies to the role appropriately
  • Explore other avenues to avoid exceeding the 5000 policy limit