Search code examples
amazon-iamamazon-ecsaws-appsyncaws-fargateappsync-apollo-client

Using AWSAppSyncClient inside an ECS Container (Fargate) with AWS_IAM auth mode - Returns 403 UnrecognizedClientException


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

Solution

  • 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,
    };