Search code examples
amazon-web-servicesaws-lambdaamazon-iamamazon-ecs

Why is my Lambda not allowed to RunTask on ECS resource?


I just created an AWS ECS cluster and task definition and ran it all just fine. I was able to connect to the server. The task is running on Fargate and runs on demand. I am now attempting to create a Lambda that will run the RunTask command to start the server. Here is my Lambda definition in Terraform.

data "aws_iam_policy_document" "startup_lambda_assume_role" {
  statement {
    effect = "Allow"
    actions = ["sts:AssumeRole"]
    principals {
      type = "Service"
      identifiers = ["lambda.amazonaws.com"]
    }   
  }
}

resource "aws_iam_role" "startup_lambda" {
  name = "report_lambda_role"
  assume_role_policy = data.aws_iam_policy_document.startup_lambda_assume_role.json
}

resource "aws_cloudwatch_log_group" "startup_lambda" {
  name = "/aws/lambda/${aws_lambda_function.startup.function_name}"
  retention_in_days = 14
}

data "aws_iam_policy_document" "startup_lambda" {
  statement {
    effect = "Allow"
    actions = [
      "logs:CreateLogStream",
      "logs:CreateLogGroup",
    ]
    resources = [aws_cloudwatch_log_group.startup_lambda.arn]
  }

  statement {
    effect = "Allow"
    actions = ["logs:PutLogEvents"]
    resources = ["${aws_cloudwatch_log_group.startup_lambda.arn}:*"]
  }

  statement {
    effect = "Allow"
    actions = [
      "ecs:RunTask",
    ]
    resources = [
      aws_ecs_task_definition.game.arn
    ]
  }

  statement {
    effect = "Allow"
    actions = [
      "iam:PassRole",
    ]
    resources = [
      aws_iam_role.ecs_task_execution.arn,
      aws_iam_role.game_task.arn
    ]
  }
}

resource "aws_iam_role_policy" "startup_lambda" {
  name = "startup_lambda_policy"
  policy = data.aws_iam_policy_document.startup_lambda.json
  role = aws_iam_role.startup_lambda.id
}

data "archive_file" "startup_lambda" {
  type = "zip"
  source_file = "${path.module}/startup/lambda_handler.py"
  output_path = "${path.module}/startup/lambda_handler.zip"
}

resource "aws_lambda_function" "startup" {
  function_name = "startup_lambda"
  filename = data.archive_file.startup_lambda.output_path
  handler = "lambda_handler.handler"
  source_code_hash =  data.archive_file.startup_lambda.output_base64sha256
  runtime = "python3.8"
  role = aws_iam_role.startup_lambda.arn
  environment {
    variables = {
      CLUSTER_ARN = aws_ecs_cluster.game.arn,
      TASK_ARN = aws_ecs_cluster.game.arn,
      SUBNET_IDS = "${aws_subnet.subnet_a.id},${aws_subnet.subnet_b.id},${aws_subnet.subnet_c.id}"
    }
  }
}

This is my Python code located in startup/lambda_handler.py which does appear properly as the code for the function when I checked in the AWS console.

import os
import boto3

def handler (event, callback):
    client = boto3.client("ecs")
    response = client.run_task(
        cluster = os.getenv("CLUSTER_ARN"),
        taskDefinition = os.getenv("TASK_ARN"),
        launchType = "FARGATE",
        count = 1,
        networkConfiguration = {
            "awsvpcConfiguration": {
                "subnets": os.getenv("SUBNET_IDS", "").split(","),
                "assignPublicIp": "ENABLED",
            },
        },
    )

When I run a test of the Lambda function in the console using an empty JSON object as an argument, I expect to see my ECS task spin up, but instead I get the following error.

Response
{
  "errorMessage": "An error occurred (AccessDeniedException) when calling the RunTask operation: User: arn:aws:sts::703606424838:assumed-role/report_lambda_role/startup_lambda is not authorized to perform: ecs:RunTask on resource: * because no identity-based policy allows the ecs:RunTask action",
  "errorType": "AccessDeniedException",
  "stackTrace": [
    "  File \"/var/task/lambda_handler.py\", line 6, in handler\n    response = client.run_task(\n",
    "  File \"/var/runtime/botocore/client.py\", line 386, in _api_call\n    return self._make_api_call(operation_name, kwargs)\n",
    "  File \"/var/runtime/botocore/client.py\", line 705, in _make_api_call\n    raise error_class(parsed_response, operation_name)\n"
  ]
}

Notice that I do have a statement for ecs:RunTask allowed on my task defintion in the IAM policy document attached to my Lambda. I am not sure why this doesn't give the Lambda permission to run the task.


Solution

  • The TASK_ARN you pass to your lambda container is wrong. Should probably be aws_ecs_task_definition.game.arn instead of a duplicate aws_ecs_cluster.game.arn.