I've been working on implementing IAM roles to manage S3 access in my application, but I seem to be missing a crucial step. While running my code in AWS, I encountered a "missing credentials" exception, indicating that something is not configured correctly.
I initially thought that the SDK would default to using the container's identity if credentials were not specified, but that doesn't appear to be the case. It seems like either my code is incorrect or the Assume Role policy I set up is not specifying the correct principal. Unfortunately, I haven't been able to find clear guidance in the documentation.
One possibility I'm considering is creating a separate role specifically for accessing the S3 bucket and then writing code to assume that role and use those credentials with the S3 client.
Any assistance would be greatly appreciated.
I'm utilizing Terraform for infrastructure setup and have included relevant extracts below. Towards the end of this post, you'll find my Typescript code for interacting with S3.
Current setup
- I've defined an ECS Fargate Task role:
resource "aws_iam_role" "ecs_task_execution_role" {
name = "ecsTaskExecutionRole"
assume_role_policy = data.aws_iam_policy_document.ecs_task_execution_role.json
}
- I've established an Assume Role policy for this task:
data "aws_iam_policy_document" "ecs_task_execution_role" {
version = "2012-10-17"
statement {
sid = ""
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
- I've defined an IAM Policy for managing access to the specific S3 bucket:
resource "aws_iam_policy" "read_write_image_storage" {
name = "${var.environment}-read-write-image-storage"
description = "Allows read access to s3 bucket in the ${var.environment} environment"
policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Effect = "Allow",
Action = [
"s3:*"
],
Resource = [
aws_s3_bucket.image_storage.arn,
]
}]
})
}
- I've attached the policy to the role under which the containers are operating:
resource "aws_iam_role_policy_attachment" "read_write_image_storage" {
role = aws_iam_role.ecs_task_execution_role.name
policy_arn = aws_iam_policy.read_write_image_storage.arn
}
- Within the node.js code executed in the associated container, I initiate a default S3 client and attempt to interact with the bucket:
const s3config = {
region: process.env.AWS_REGION,
};
const s3 = new S3(s3config);
export const putBlob = async (key: string, blob: Buffer) => {
// save buffer to an AWS S3 bucket
const params = {
Bucket: process.env.AWS_ATTACHMENT_STORAGE_BUCKETNAME,
Key: key,
Body: blob,
ContentType: "image/png",
};
try {
console.debug("Saving image to S3");
const result = await s3.upload(params).promise();
} catch(e) {
console.error(e.message) // MissingCredentials
throw 2
}
}
- The Fargate task has been configured to utilize the role as defined in step 1:
resource "aws_ecs_task_definition" "webapp" {
family = "webapp-task"
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "256"
memory = "512"
container_definitions = jsonencode([
{
"name" : "MyContainerDefinition",
"image" : "MyContainerImage",
"cpu" : 256,
"memory" : 512,
"essential" : true,
"portMappings" : [
{
"containerPort" : 80,
"hostPort" : 80,
"protocol" : "tcp"
}
],
}
])
}