Fargate Service Cannot Write to DynamoDB Table (CDK)

Viewed 31

I'm working through setting up a new infrastructure with the AWS CDK and I'm trying to get a TypeScript app running in Fargate to be able to read/write from/to a DynamoDB table, but am hitting IAM issues.

I have both my Fargate service and my DynamoDB Table defined, and both are running as they should be in AWS, but whenever I attempt to write to the table from my app, I am getting an access denied error.

I've tried the solutions defined in this post, as well as the ones it links to, but nothing seems to be allowing my container to write to the table. I've tried everything from setting table.grantReadWriteData(fargateService.taskDefinition.taskRole) to the more complex solutions described in the linked articles of defining my own IAM policies and setting the effects and actions, but I always just get the same access denied error when attempting to do a putItem:

AccessDeniedException: User: {fargate-service-arn} is not authorized to perform: dynamodb:PutItem on resource: {dynamodb-table} because no identity-based policy allows the dynamodb:PutItem action

Am I missing something, or a crucial step to make this possible?

Any help is greatly appreciated.

Thanks!


Edit (2022-09-19):

Here is the boiled down code for how I'm defining my Vpc, Cluster, Container Image, FargateService, and Table.

export class FooCdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const vpc = new Vpc(this, 'FooVpc', {
      maxAzs: 2,
      natGateways: 1
    });

    const cluster = new Cluster(this, 'FooCluster', { vpc });

    const containerImage = ContainerImage.fromAsset(
      path.join(__dirname, '/../app'),
      {
        platform: Platform.LINUX_AMD64 // I'm on an M1 Mac and images weren't working appropriately without this
      }
    );

    const fargateService = new ApplicationLoadBalancedFargateService(
      this,
      'FooFargateService',
      {
        assignPublicIp: true,
        cluster,
        memoryLimitMiB: 1024,
        cpu: 512,
        desiredCount: 1,
        taskImageOptions: {
          containerPort: PORT,
          image: containerImage
        }
      }
    );

    fargateService.targetGroup.configureHealthCheck({ path: '/health' });

    const serverTable = new Table(this, 'FooTable', {
      billingMode: BillingMode.PAY_PER_REQUEST,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      partitionKey: { name: 'id', type: AttributeType.STRING },
      pointInTimeRecovery: true
    });

    serverTable.grantReadWriteData(fargateService.taskDefinition.taskRole);
  }
}
1 Answers

Apparently either order in which the resources are defined matters, or the inclusion of a property from the table in the Fargate service is what did the trick. I moved the table definition up above the Fargate service and included an environment variable holding the table name and it's working as intended now.

export class FooCdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const vpc = new Vpc(this, 'FooVpc', {
      maxAzs: 2,
      natGateways: 1
    });

    const cluster = new Cluster(this, 'FooCluster', { vpc });

    const containerImage = ContainerImage.fromAsset(
      path.join(__dirname, '/../app'),
      {
        platform: Platform.LINUX_AMD64 // I'm on an M1 Mac and images weren't working appropriately without this
      }
    );

    const serverTable = new Table(this, 'FooTable', {
      billingMode: BillingMode.PAY_PER_REQUEST,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      partitionKey: { name: 'id', type: AttributeType.STRING },
      pointInTimeRecovery: true
    });

    const fargateService = new ApplicationLoadBalancedFargateService(
      this,
      'FooFargateService',
      {
        assignPublicIp: true,
        cluster,
        memoryLimitMiB: 1024,
        cpu: 512,
        desiredCount: 1,
        taskImageOptions: {
          containerPort: PORT,
          image: containerImage,
          environment: {
            SERVER_TABLE_NAME: serverTable.tableName
          }
        }
      }
    );

    fargateService.targetGroup.configureHealthCheck({ path: '/health' });

    serverTable.grantReadWriteData(fargateService.taskDefinition.taskRole);
  }
}

Hopefully this helps someone in the future who may come across the same issue.

Related