0
votes

Lambda cannot assume a cross-account role when triggered through a CloudFormation script.

I have a CloudFormation script which creates and triggers a Lambda function in account A. This function needs to copy an object from account B. I am using role base cross-account access.

On account B, I have the following role, For testing purposes, I'm using S3 full access.

CrossAccountAccessRole: 
    Type: 'AWS::IAM::Role'
    Properties: 
      RoleName: 'CrossAccountAccessRole'
      Path: '/'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal: 
            AWS: !Sub 'arn:aws:iam::<AccountA>:role/CustomerCrossAccountAccessRole'
          Action: sts:AssumeRole  
  CrossAccountAccessPolicy:
    Type: "AWS::IAM::ManagedPolicy"
    Properties:
      Path: '/'
      ManagedPolicyName: CrossAccountAccessPolicy
      Roles: 
        - !Ref CrossAccountAccessRole
      PolicyDocument:
        Version: "2012-10-17"
        Statement:       
        - Effect: Allow
          Action:
            - s3:*  

On account A, I have the following role with lambda execution, S3 full access and assumed role policies.

  CustomerCrossAccountAccessRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: CustomerCrossAccountAccessRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: LambdaBasicExecutionPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'logs:CreateLogGroup'
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource: '*'
        - PolicyName: LambdaSourceBucketPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              Effect: Allow
              Action: 's3:*'
              Resource: '*'
        - PolicyName: LambdaAssumeRolePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action: sts:AssumeRole
              Resource: !Sub 'arn:aws:iam::<AccountA>:role/CrossAccountAccessRole'

I run this python script inside my lambda function,

    try:
        print('Assume role of a cross account access role')
        boto_sts_client=boto3.client('sts', region_name='ap-southeast-2')
        stsresponse = boto_sts_client.assume_role(
            RoleSessionName = 'CrossAccountAccessSession',
            RoleArn = 'arn:aws:iam::<AccountA>:role/CrossAccountAccessRole',
                DurationSeconds = 3000
        )

        s3_assumed_client = boto3.client(
            's3',
            region_name='ap-southeast-2',
            aws_access_key_id = stsresponse["Credentials"]["AccessKeyId"],
            aws_secret_access_key = stsresponse["Credentials"]["SecretAccessKey"],
            aws_session_token = stsresponse["Credentials"]["SessionToken"]
        )
        s3_assumed_client.download_file(<BucketName>, <FilePath>,<FileName>)
    except:
        traceback.print_exc()

The CloudFormation script returns the following error,

botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the AssumeRole operation: Access denied

But if I run the same Lambda (created using CloudFormation script) in test mode (using "Test" button on the AWS console), it downloads the file without any error.

Thanks

2

2 Answers

1
votes

Your policy contains the following mistakes:

  • Trust policy should specify the account-A as a Principal, meaning that authorized users from this account can use the CrossAccountAccessRole role. You cannot specify ARN of users/roles in different accounts directly in policies.
  • The administrators in account-A must attach a policy that allows the role to call AssumeRole for the ARN of CrossAccountAccessRole.
  • Make sure that you assigned CustomerCrossAccountAccessRole to Lambda when creating it.
CrossAccountAccessRole: 
    Type: 'AWS::IAM::Role'
    Properties: 
      RoleName: 'CrossAccountAccessRole'
      Path: '/'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal: 
            AWS: 'arn:aws:iam::<AccountA>:root'
...
- PolicyName: LambdaAssumeRolePolicy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Action: sts:AssumeRole
          Resource: 'arn:aws:iam::<AccountB>:role/CrossAccountAccessRole'
stsresponse = boto_sts_client.assume_role(
   RoleSessionName = 'CrossAccountAccessSession',
   RoleArn = 'arn:aws:iam::<Account-B>:role/CrossAccountAccessRole',
   DurationSeconds = 3000
)

You wrote Account-A in few places (ARNs) where it should actually be Account-B but I guess these are typos.

0
votes

You don't need to manage a cross account role for this. You can simply allow access from account-A inside the target bucket's policy. Attach permission for your lambda role and you can directly access the bucket without assuming any role. Go to your S3 bucket, go to permissions tab and click on 'Bucket Policy'. Your policy will look like,

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "DelegateReadToAccountA",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::<account-A>:root"
                ]
            },
            "Action": [
                "s3:Get*",
                "s3:List*"
            ],
            "Resource": [
                "arn:aws:s3:::<bucket_name>",
                "arn:aws:s3:::<bucket_name>/*"
            ]
        }
    ]
}