0

We have the following code in an ECS Fargate container however it is constantly returning an error. When running identical code in a lambda with IAM authentication and the correct role setup, I am able to successfully run this.

Error

Network error: Response not successful: Received status code 403
UnrecognizedClientException
The security token included in the request is invalid.

Code

import 'isomorphic-fetch';
import AWSAppSyncClient, { AUTH_TYPE } from 'aws-appsync';
import AWS from 'aws-sdk';

// Setup variables for client
const graphqlEndpoint = process.env.GRAPHQL_ENDPOINT;
const awsRegion = process.env.AWS_DEFAULT_REGION;

const client = new AWSAppSyncClient({
    url: graphqlEndpoint,
    region: awsRegion,
    auth: {
        type: AUTH_TYPE.AWS_IAM,
        credentials: AWS.config.credentials,
    },
    disableOffline: true,
})

Cloudformation

  TaskDefinition:
    Type: "AWS::ECS::TaskDefinition"
    Properties:
      ContainerDefinitions:
        - Ommitted
      Cpu: !FindInMap [CpuMap, !Ref Cpu, Cpu]
      ExecutionRoleArn: !GetAtt "TaskExecutionRole.Arn"
      Family: !Ref "AWS::StackName"
      Memory: !FindInMap [MemoryMap, !Ref Memory, Memory]
      NetworkMode: awsvpc
      RequiresCompatibilities: [FARGATE]
      TaskRoleArn: !GetAtt "TaskRole.Arn"
  TaskRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: "ecs-tasks.amazonaws.com"
            Action: "sts:AssumeRole"
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/AWSAppSyncInvokeFullAccess" # Invoke Access for AppSync
Matt
  • 1,490
  • 2
  • 17
  • 41

2 Answers2

1

I eventually discovered that this was a result of AppSyncClient not able to load the credentials in ECS correctly.

As per AWS Docs on IAM roles in ECS, credentials are loaded different to other AWS services. Instead of credentials being populated in env vars, the Amazon ECS agent instead populated the AWS_CONTAINER_CREDENTIALS_RELATIVE_URI variable with a path to the credentials. I was able to successfully get AWSAppSyncClient working with IAM authenticaiton in an ECS container by first loading the ECS credentials manually and passing it to AWSAppSyncClient. The below example worked.

// AWSAppSyncClient needs to be provided ECS IAM credentials explicitly
const credentials = new AWS.ECSCredentials({
  httpOptions: { timeout: 50000 },
  maxRetries: 10,
});
AWS.config.credentials = credentials;

// Setup AppSync Config
const AppSyncConfig = {
  url: graphqlEndpoint,
  region: awsRegion,
  auth: {
    type: AUTH_TYPE.AWS_IAM,
    credentials: AWS.config.credentials,
  },
  disableOffline: true,
};
Matt
  • 1,490
  • 2
  • 17
  • 41
1

This is an expansion on the original answer with information regarding AWS SDK for JavaScript v3.

With v3 the fromContainerMetadata() method from the @aws-sdk/credential-providers package is used. See docs here.

Example usage:

import { fromContainerMetadata } from "@aws-sdk/credential-providers"; // ES6 import
// const { fromContainerMetadata } = require("@aws-sdk/credential-providers"); // CommonJS import

const client = new FooClient({
  credentials: fromContainerMetadata({
    // Optional. The connection timeout (in milliseconds) to apply to any remote requests.
    // If not specified, a default value of `1000` (one second) is used.
    timeout: 1000,
    // Optional. The maximum number of times any HTTP connections should be retried. If not
    // specified, a default value of `0` will be used.
    maxRetries: 0,
  }),
});
Daniel
  • 717
  • 6
  • 21