Front-End Web & Mobile

Building cross-account AWS Lambda Resolvers for AWS AppSync

This article was written by Lucas Schejtman, Principal Solutions Architect, AWS

 

One of the biggest value propositions of GraphQL is that it’s not prescriptive as to where its data comes from and with AWS AppSync it’s no different. AppSync enables you to choose from six different data source types to resolve any GraphQL field.

One of the most common AWS architecture best practices is to separate resources across multiple accounts, whether it’s by environment, teams, products, projects, or something else. The ability to aggregate them into one API while keeping the powerful querying capabilities of GraphQL, allows products and applications to have a single point of entry to all of your data.

In this post, we look at how to set up AWS Lambda functions deployed on different AWS accounts to resolve data in AppSync GraphQL APIs.

The GraphQL schema used in this example AppSync API looks like this:

schema {
  query: Query
}

type Response {
  message: String
}

type Query {
  crossAccount: Response
  sameAccount: Response
}

The objective is to have an AppSync API capable of calling Lambda data sources in different accounts.

 

 

Creating a data source

We create two identical Lambda functions (called sameaccount and crossaccount) to be used as the data sources. One is deployed in the same account as the AppSync API account (Account#1) and the other on a different account  (Account#2).

The function replies with its account number and the code looks like this:

const AWS = require("aws-sdk");

exports.handler = async (event) => {
  const sts = new AWS.STS();
  const { Account } = await sts.getCallerIdentity({}).promise();
  
  return {
    message: `hello from acc#${Account}`,
  };
};

When creating each data source, define the corresponding function and let AppSync create the role for you. In the following screenshot, we select a role that was previously created.

 

Once completed for both Lambda functions, the two data sources look something like this:

 

 

If you prefer the programmatic approach, you can use the AWS CLI to create the data sources:

aws appsync create-data-source
--api-id <api#id> \
--name crossaccount \
--type AWS_LAMBDA \
--service-role-arn <role#arn> \
--lambda-config '{ "lambdaFunctionArn": "<function#arn>" }'

 

Managing permissions

The right permissions must be set in both accounts. If you delegated the data source role creation to AppSync, the work required in Account#1 is done!

In case you’re creating your own, all we need is to ensure that the role in Account#1 allows the lambda:InvokeFunction action and that it targets the right Lambda function in Account#2. Here’s an example policy:

 

 

The second step is for the function in Account#2 to allow invocation from our newly created role in Account#1. The mechanism to do that is through Resource-based Policies.

The following command adds the necessary permissions to the Lambda function. See how we’re defining the principal entity invoking the function to the role used in the data source.

aws lambda add-permission \
--function-name <function#name> \
--statement-id cross_account \
--action lambda:InvokeFunction \
--principal <role#arn>

You can verify the command above worked by going to the Permissions section of the Lambda function’s console. If it worked as intended, it should look something like this:

 

 

In summary, making use of the AWS IAM cross-account role delegation we’re able to allow the AppSync API in Account#1 to assume the required permissions to invoke a specific Lambda function in Account#2. While in Account#2, we allow invocations from the specific role via Resource Policy.

Here’s a diagram to visualize the set of policies created and how they interact with one another:

 

 

 

Resolving the fields

Now that we’ve defined where the data comes from and the necessary permissions have been set, it’s time to let the AppSync GraphQL API know how to resolve the fields defined in the schema.

For this example we use Direct Lambda Resolvers, which enable us to connect the AppSync API to the Lambda functions directly without the need of writing VTL code. By default Lambda resolvers have VTL mapping templates disabled so there’s nothing to change.

 

 

And again, the programmatic approach:

aws appsync create-resolver \
--api-id <api#id> \
--type-name Query \
--field-name crossAccount \
--data-source-name crossaccount

 

Putting it all together

There’s nothing left to do but to test the API. We leverage the GraphQL querying capabilities to retrieve data from both sources in a single API call.

 

 

 

 

Conclusion

In this article we demonstrated how you have full flexibility to define where your GraphQL data sources reside, even if in a different AWS account. You can leverage AppSync’s seamless integration with IAM to communicate across accounts.

You can try AppSync and Direct Lambda Resolvers today with AppSync, for more information refer to our documentation.

Go build with GraphQL, AWS AppSync, AWS Lambda, and no servers!