Skip to the content.

AWS Cross Account S3 Access through Lambda

There are 2 ways to access S3 bucket from another account, using S3 bucket policy or Assume Role

I assume that the account number 111111111111 is holding the Lambda function, and the account number 222222222222 is holding the S3 bucket I ssume that the S3 bukcet is encrypted with a KMS key (this is optional if S3 bucket is not encrypted)

Bukcet Policy method

account 111111111111, set this policy for the Lambda execution role :

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::BUCKET_NAME",
                "arn:aws:s3:::BUCKET_NAME/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:DescribeKey",
                "kms:GenerateDataKey*"
            ],
            "Resource": [
              "arn:aws:kms::222222222222:key/KEY_ID"
            ]
        }
    ]
}

account 222222222222, And this for the Bucket policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111111111111:role/lambda-execution-role"
            },
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::BUCKET_NAME",
                "arn:aws:s3:::BUCKET_NAME/*"
            ]
        }
    ]
}

account 222222222222, For KMS Key Policy

{
    "Version": "2012-10-17",
    "Statement": [      
        {
            "Effect": "Allow",
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:DescribeKey",
                "kms:GenerateDataKey*",
                "kms:ReEncrypt*"
            ],
            "Resource": "*",
            "Principal": { "AWS": "arn:aws:iam::111111111111:role/lambda-execution-role" }
        }
    ]
}

Bukcet Policy

sample Lambda code

import { S3Client, GetObjectCommand, ListObjectsCommand  } from "@aws-sdk/client-s3";
export const handler = async (event, context) => {
  const s3BucketName = "BUCKET_NAME";
  const s3FileName = "123.txt";
  const s3Client = new S3Client({
        region: "us-west-2"
    }); 
    
   // ############################
   
    const listObjectsParams = {
        Bucket:s3BucketName
    };
    
    const listObjectsCommand = new ListObjectsCommand(listObjectsParams);
    
    console.log("calling ListObjectsCommand");
    const listObjectsOutput = await s3Client.send(listObjectsCommand);
    console.log(listObjectsOutput.Contents);
   // ############################
    const getObjectCommand = new GetObjectCommand({
      Bucket: s3BucketName,
      Key: s3FileName,
    });
    
    console.log("calling GetObjectCommand");
    const response = await s3Client.send(getObjectCommand);
  
    // Process the file contents
    const fileContents = await response.Body.transformToString();
    console.log(fileContents);
  
    // ############################
  
};

Assume Role

here we create a new role in account 222222222222 and assign the required policies to it then our lambda function assume that role when trying to access the S3 bucket.

account 222222222222, set this policy for the newly created role :

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::BUCKET_NAME",
                "arn:aws:s3:::BUCKET_NAME/*"
            ]
        }
    ]
}

and also we need to add this to the role Trusted entities

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111111111111:role/lambda-execution-role"
            },
            "Action": "sts:AssumeRole",
            "Condition": {}
        }
    ]
}

account 111111111111, set this policy for the Lambda execution role :

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": [
                "arn:aws:iam::222222222222:role/S3ROLE"
            ]
        }
    ]
}

assume role

sample Lambda code

import { S3Client, GetObjectCommand, ListObjectsCommand } from "@aws-sdk/client-s3";
import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";

export const handler = async (event, context) => {

  const roleArn = "arn:aws:iam::222222222222:role/S3ROLE";
  const roleSessionName = "SessionName";
  const s3BucketName = "BUCKET_NAME";
  const s3FileName = "123.txt";

  // Assume the role in the different account
  const stsClient = new STSClient({ region: "us-west-2" });
  const assumeRoleCommand = new AssumeRoleCommand({
    RoleArn: roleArn,
    RoleSessionName: roleSessionName,
  });
  const { Credentials } = await stsClient.send(assumeRoleCommand);
  
  console.log(Credentials);
  
  
   const s3Client = new S3Client({
        region: "us-west-2",
        credentials: {
            accessKeyId: Credentials.AccessKeyId,
            secretAccessKey: Credentials.SecretAccessKey,
            sessionToken: Credentials.SessionToken
        }
    });
    
   // ############################
   
    const listObjectsParams = {
        Bucket:s3BucketName
    };
    
    const listObjectsCommand = new ListObjectsCommand(listObjectsParams);
    const listObjectsOutput = await s3Client.send(listObjectsCommand);
    console.log(listObjectsOutput.Contents);

   // ############################
  
  try {
    
    const getObjectCommand = new GetObjectCommand({
      Bucket: s3BucketName,
      Key: s3FileName,
    });
    const response = await s3Client.send(getObjectCommand);
  
    // Process the file contents
    // The Body object also has 'transformToByteArray' and 'transformToWebStream' methods.

    const fileContents = await response.Body.transformToString();
    console.log(fileContents);
  
  } catch (err) {
    console.error(err);
  }
  
     // ############################
  
};

sources: