How do I use AWS AppSync to access private resources in my VPC?

Last updated: 2022-07-06

I want to access Amazon Virtual Private Cloud (Amazon VPC) resources from my AWS AppSync GraphQL API, but I don't know how to do that.

Short description

To access Amazon VPC resources from an AWS AppSync GraphQL API, follow these steps:

  1. Create an AWS Lambda function to run inside the same Amazon VPC as the resources that you want to access.
  2. Create an AWS AppSync API, and then attach the Lambda function as the data source.
  3. Configure the AWS AppSync API schema.
  4. Attach either a Lambda resolver or a direct Lambda resolver to the target GraphQL field.
  5. Test the GraphQL field.

Configuring a Lambda function to connect to private subnets in an Amazon VPC lets you access the private resources in the Amazon VPC. The results of the downstream private resources are then passed back to the AWS AppSync GraphQL API and returned to the client. Private resources include databases, containers, private APIs, private Amazon OpenSearch Service domains, or other private services behind an Application or Network Load Balancer.

AWS AppSync invokes the Lambda API through Lambda public service endpoints. The invoked API is secured by the Signature Version 4 signing process. This allows AWS AppSync to assume an AWS Identity and Access Management (IAM) role with permissions to invoke the target Lambda function. For information on Lambda functions that are configured to access your Amazon VPC, see VPC networking for Lambda.

Note:

  • Only public endpoints are supported by AWS AppSync. As a workaround, use Lambda resolvers as an entry point to Amazon VPC resources.
  • Adding a Lambda function between your Amazon VPC private resources and the AWS AppSync API introduces a new application layer with a minor latency overhead.
  • If you use an Amazon VPC with Lambda functions, then you incur additional charges. For more information, see Lambda pricing.

Resolution

Note: If you receive errors when running AWS CLI commands, make sure that you're using the most recent AWS CLI version.

Create an AWS Lambda function to run inside the same Amazon VPC as the resources you want to access

In the following example scenario, an existing internal Application Load Balancer is called from a Python Lambda function. An id value is obtained from the arguments parameter that's passed in the event object that the AWS AppSync invocation sends to Lambda.

import urllib.request
import json

def lambda_handler(event, context):
  URL = 'http://XXXXXXXXXXX.us-east-1.elb.amazonaws.com/' + event['arguments']['id']  
  #Open the JSON reponse, read it, convert it to JSON object and return it
  response = urllib.request.urlopen(URL)
  raw_json = response.read()
  loaded_json = json.loads(raw_json) 
  return loaded_json

Note: For direct Lambda resolvers, the function receives a payload that consists of the entire context object.

Example response from the Lambda function:

{
  "id": "25",
  "name": "hamburger",
  "available": true
}

Create an AWS AppSync API, and attach the Lambda function as the data source

Using the AWS Management Console

To create the GraphQL API in AWS AppSync:

  1. Open the AWS AppSync console.
  2. On the dashboard, choose Create API.
  3. On the Getting Started page, under Customize your API or import from Amazon DynamoDB, choose Build from scratch.
  4. Choose Start.
  5. In the API name field, enter a name for the API.
  6. Choose Create.

Note: The preceding steps automatically create an API key for authorization that's valid for seven days. However, you can use any authorization type.

To create the Lambda data source:

  1. Open the AWS AppSync console.
  2. Choose Data Sources.
  3. Choose Create data source.
  4. Under Create new Data Source, enter the data source name that you want to define.
    For Data source type, select Amazon Lambda function.
    For Region, select the AWS Region that contains the Lambda function.
    For Function ARN, select the function that you created.
  5. Provide an existing role to allow AWS AppSync to manage the Lambda function. You can also let the wizard create one for you.
  6. Choose Create.

Using the AWS Command Line Interface (AWS CLI)

1.    Create the GraphQL API using API_KEY as the default authentication type for testing:

$ aws appsync create-graphql-api --name api-name --authentication-type API_KEY

Note: Replace api name with your API's name.

2.    Create an API key:

$ aws appsync create-api-key --api-id apiId

Note: Replace apiId with you API's ID.

3.    Create the IAM role that's used by the data source, and then attach the trust policy that allows AWS AppSync to assume the role:

$ aws iam create-role --role-name Test-Role-for-AppSync --assume-role-policy-document file://trustpolicy.json

Trust policy JSON:

{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Effect": "Allow",
          "Principal": {
              "Service": "appsync.amazonaws.com"
          },   
         "Action": "sts:AssumeRole"
      }
  ]
}

4.    Embed the permissions policy to the role:

$ aws iam put-role-policy --role-name Test-Role-for-AppSync --policy-name Permissions-Policy-For-AppSync --policy-document file://permissionspolicy.json

Permissions policy JSON:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowFunctionInvocation",
      "Effect": "Allow",
      "Action": "lambda:InvokeFunction",
      "Resource": [
        "lambda-ARN"
      ]
    }
  ]
}

Note: Replace lambda-ARN with your Lambda ARN.

5.    Create the data source, and indicate the ARN of the IAM role and the Lambda function:

$ aws appsync create-data-source --api-id apiId \
--name LambdaDataSourceName \
--type AWS_LAMBDA \
--service-role-arn IAM-role-ARN \
--lambda-config lambdaFunctionArn=Lambda-function-ARN

Note: Replace LambdaDataSourceName with the name of your data source, apiId with your API's ID, IAM-role-ARN with your IAM role's ARN, and Lambda-function-ARN with your Lambda function's ARN.

Configure the AWS AppSync API schema

Using the AWS Management Console

Configure the GraphQL schema definition to access the Lambda function response:

1.    Open the AWS AppSync console.

2.    On the left panel, choose Schema.

3.    Copy and paste the provided schema into the editor.

The following example schema has a response query that returns data with the Product type structure:

type Product {
    id: ID!
    name: String
    available: Boolean
}

type Query {
    response(id: ID!): Product
}

schema {
    query: Query
}

4.    Choose Save Schema.

Using the AWS CLI

1.    Save the preceding GraphQL schema as schema.graphql.

2.    Create the schema in AWS AppSync:

$ aws appsync start-schema-creation --api-id "apiId" --definition fileb://schema.graphql

Note: Replace apiId with your API's ID.

Attach either a Lambda resolver or a direct Lambda resolver to a GraphQL field

Using the AWS Management Console

Attach the resolver:

  1. Open the AWS AppSync console.
  2. On the Schema page of your API, under Resolvers, scroll to the response query. Or, filter by query in the resolver types.
  3. Next to the Response field, choose Attach.
  4. On the Create new Resolver page, for Data source name, select the name of the Lambda data source. Note: Lambda direct integration is used for the example scenario, so configuring the mapping templates isn't necessary.
  5. Choose Save Resolvers.

Using the AWS CLI

Create the direct Lambda resolver, and specify the data source name from the preceding steps:

$ aws appsync create-resolver \
--field-name response \
--type-name Query \
--api-id "apiId" \
--data-source-name LambdaDataSourceName

Note: Replace apiId with you API's ID and LambdaDataSourceName with the name of the created data source.

Test the GraphQL field

To test the GraphQL field:

1.    Open the AWS AppSync console.

2.    In the left navigation pane, choose Queries.

3.    In the Query editor, design your GraphQL query.

Example GraphQL query:

query MyQuery {
  response(id: "1") {
    available
    name
    id
  }
}

The preceding query gets the product ID 1 from the Application Load Balancer.

4.    To run the test query, choose Play.

Example response from the API:

{
  "data": {
    "response": {
      "available": false,
      "id": "1",
      "name": "pizza"
    }
  }
}

Using the terminal

Run the query by calling the POST method. The following example uses the curl command:

$ curl --request POST 'https://<Grapqhl-API-endpoint>/graphql' \
--header 'Content-Type: application/graphql' \
--header 'x-api-key: api-key' \
--data-raw '{"query":"query MyQuery {\n  response(id: \"1\") {\n available\n    id\n    name\n  }\n}\n","variables":{}}'

Note: Replace api-key with your API key.