Search code examples
amazon-web-servicesamazon-iamserverlessaws-cdkaws-serverless

AWS CDK - Cannot assume role in Lambda for fine grained authorization


I am fairly new in AWS specifics, so forgive me a stupid question. I was looking for the answers, but I did not find any question answering my issue.

I am building a project and I am trying to get the permissions done right using this AWS tutorial: https://aws.amazon.com/blogs/apn/partitioning-pooled-multi-tenant-saas-data-with-amazon-dynamodb/

I have created role and policy:

const tenantUserRole = new Role(this, "TenantUserRole", {
      assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
    });
table.grantReadWriteData(tenantUserRole);

Then, in my Lambda I am trying to:

const role = await stsClient.send(
    new AssumeRoleCommand({
      Policy: getPolicy("tenant"),
      RoleArn: process.env.TENANT_ROLE_ARN,
      RoleSessionName: "foo",
    })
  );

This does not work as I am getting an error in Lambda:

{ "errorType": "AccessDenied", "errorMessage": "User: arn:aws:sts::xxx:assumed-role/ProjectStack-createUserServiceRoleB9D8AADE-GRDI6MWXA5MY/ProjectStack-createUserC6ED88E6-K0S95UXTN9MH is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::xxx:role/ProjectStack-TenantUserRoleB79B8D3A-11N644X7UF0SR", "trace": [ "AccessDenied: User: arn:aws:sts::xxx:assumed-role/ProjectStack-createUserServiceRoleB9D8AADE-GRDI6MWXA5MY/ProjectStack-createUserC6ED88E6-K0S95UXTN9MH is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::xxx:role/ProjectStack-TenantUserRoleB79B8D3A-11N644X7UF0SR",

The target is to always resolve permissions based on the user context, so what should I do in my CDK stack to enable all my Lambdas to do what has to be done?


Solution

  • As described by @Maurice, the role should be trusted instead of the service principal(lambda.amazonaws.com). Below is the CDK code.

    TenantUserRole trusted by apiRole which is being used by aws lambda

    import * as cdk from '@aws-cdk/core';
    import * as iam from '@aws-cdk/aws-iam';
    
    export class CdkWorkshopStack extends cdk.Stack {
      constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
        super(scope, id, props);
        // aws lambda role
        const lambdaRole = new iam.Role(this, 'apiRole', {
          roleName: 'apiRole',
          assumedBy:  new iam.ServicePrincipal('lambda.amazonaws.com'),
        });
        // trusted by TenantUserRole for access
        const tenantUserRole = new iam.Role(this, 'TenantUserRole', {
          roleName: 'TenantUserRole',
          assumedBy: new iam.ArnPrincipal(lambdaRole.roleArn),
        });
    
        // policy to allow assume role TenantUserRole
        lambdaRole.addToPolicy(
          new iam.PolicyStatement({
            resources: [tenantUserRole.roleArn],
            actions: ['sts:AssumeRole'],
          })
        );
      }
    }
    

    Here it looks like in role's trust policy.

    ❯❯ aws  iam get-role --role-name TenantUserRole
    {
        "Role": {
            "Path": "/",
            "RoleName": "TenantUserRole",
            "RoleId": "AROA2WXKNDTKKGFADASD",
            "Arn": "arn:aws:iam::1234567890:role/TenantUserRole",
            "CreateDate": "2021-03-03T23:34:13+00:00",
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Principal": {
                            "AWS": "arn:aws:iam::1234567890:role/apiRole"
                        },
                        "Action": "sts:AssumeRole"
                    }
                ]
            },
            "Description": "",
            "MaxSessionDuration": 3600,
            "RoleLastUsed": {}
        }
    }
    

    Policy to allow aws lambda role to assume TenantUserRole

    ❯❯ aws iam get-role-policy  --role-name apiRole  --policy-name apiRoleDefaultPolicy771DC0DD
    {
        "RoleName": "apiRole",
        "PolicyName": "apiRoleDefaultPolicy771DC0DD",
        "PolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "sts:AssumeRole",
                    "Resource": "arn:aws:iam::1234567890:role/TenantUserRole",
                    "Effect": "Allow"
                }
            ]
        }
    }
    

    How to use trust policies with IAM roles

    In lambda you need to after assuming the TenantUserRole you need to use those credentials and create dynamodb client and then use that client for making the api call putItem

    import boto3
    
    def lambda_handler(event, context):
        sts = boto3.client('sts')
        // assume TenantUserRole
        creds = sts.assume_role(
            RoleArn='arn:aws:iam::1234567890:role/TenantUserRole',
            RoleSessionName="tempsession"
            )
        // Use Credentials from the assume call to make dynamodb client
        tenant_user_dyanmodb_client = boto3.client(
        'dynamodb',
        aws_access_key_id=creds.get('Credentials').get('AccessKeyId'),
        aws_secret_access_key=creds.get('Credentials').get('SecretAccessKey'),
        aws_session_token=creds.get('Credentials').get('SessionToken')
        )
    
       // call put item
        tenant_user_dyanmodb_client.put_item(TableName='fruitSalad', Item={'fruitName':{'S':'Banana'},'key2':{'N':'value2'}})