From 7c76658f600e3abec229b58071ff77ef64d549f9 Mon Sep 17 00:00:00 2001 From: Ozioma Uzoegwu <31167303+Tessot@users.noreply.github.com> Date: Tue, 9 Jul 2024 10:08:24 +0100 Subject: [PATCH 1/2] appsync-private-api-sam first draft --- appsync-private-api-sam/Ouzoegwu.jpeg | Bin 0 -> 45018 bytes appsync-private-api-sam/README.md | 95 +++++++ appsync-private-api-sam/example-pattern.json | 57 ++++ .../graphql/schema.graphql | 57 ++++ .../resolvers/addRestaurant.js | 10 + .../resolvers/deleteRestaurant.js | 4 + .../resolvers/getRestaurant.js | 5 + .../resolvers/listRestaurants.js | 11 + .../resolvers/updateRestaurant.js | 16 ++ appsync-private-api-sam/template.yaml | 249 ++++++++++++++++++ 10 files changed, 504 insertions(+) create mode 100644 appsync-private-api-sam/Ouzoegwu.jpeg create mode 100644 appsync-private-api-sam/README.md create mode 100644 appsync-private-api-sam/example-pattern.json create mode 100644 appsync-private-api-sam/graphql/schema.graphql create mode 100644 appsync-private-api-sam/resolvers/addRestaurant.js create mode 100644 appsync-private-api-sam/resolvers/deleteRestaurant.js create mode 100644 appsync-private-api-sam/resolvers/getRestaurant.js create mode 100644 appsync-private-api-sam/resolvers/listRestaurants.js create mode 100644 appsync-private-api-sam/resolvers/updateRestaurant.js create mode 100644 appsync-private-api-sam/template.yaml diff --git a/appsync-private-api-sam/Ouzoegwu.jpeg b/appsync-private-api-sam/Ouzoegwu.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..379c05ee65d24d777293f60d55b2da4f998c9fc9 GIT binary patch literal 45018 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%0DFq-9H;=;M43oQ09TH~cS2;gCr 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 for further guidance + +-- Using AppSync GraphQL API URL (enabled by Private DNS settings = Yes) + +```curl {AppSyncGraphQLAPIURL} \ + -H "Content-Type:application/graphql" \ + -H "x-api-key:da2-{AppSyncApiKey}" \ + -d '{"query": "query MyQuery {listRestaurants {items {name state restaurantId zip cuisine }}}","variables":"{}"}' +``` + +-- Using AppSync VPC Interface Endpoint DNS (you will need to pass the`AppSyncGraphQLAPIURL` in the host header, remember to remove suffix `www.`) + +```curl https://{AppSyncVPCEndpointDNS}/graphql \ + -H "Host:{AppSyncGraphQLAPIURL}" \ + -H "Content-Type:application/graphql" \ + -H "x-api-key:da2-{AppSyncApiKey}" \ + -d '{"query": "query MyQuery {listRestaurants {items {name state restaurantId zip cuisine }}}","variables":"{}"}' +``` + +4. Refer to the blog [Architecture Patterns for AWS AppSync Private APIs]() for further examples on how to test out GraphQL subscriptions. + +## Cleanup + +1. Delete the stack + ```bash + sam delete + ``` + +--- + +Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/appsync-private-api-sam/example-pattern.json b/appsync-private-api-sam/example-pattern.json new file mode 100644 index 000000000..bbd4a6c14 --- /dev/null +++ b/appsync-private-api-sam/example-pattern.json @@ -0,0 +1,57 @@ +{ + "title": "AWS AppSync Private API ", + "description": "Create an AWS AppSync Private API with a sample API to demonstrate how you can invoke Private API from resources in your private network", + "language": "Yaml", + "level": "200", + "framework": "SAM", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern shows how you can deploy an AWS AppSync Private API which can only be invoked by resources within your private network.", + "The SAM application will create AppSync Interface VPC Endpoint and a sample AppSync Private API backed with a DynamoDB data source.", + "Requests to AppSync Private APIs will go through AWS’s private network without going over the internet.", + "GraphQL requests from your application are routed via the interface VPC endpoint to AppSync Private API." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/appsync-private-api-sam", + "templateURL": "serverless-patterns/appsync-private-api-sam", + "projectFolder": "appsync-private-api-sam", + "templateFile": "template.yaml" + } + }, + "resources": { + "bullets": [ + { + "text": "AppSync Private API documentation:", + "link": "https://docs.aws.amazon.com/appsync/latest/devguide/using-private-apis.html" + } + ] + }, + "deploy": { + "text": [ + "sam deploy --guided" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "Delete the stack: sam delete." + ] + }, + "authors": [ + { + "name": "Ozioma Uzoegwu", + "image": "./Ouzoegwu.jpeg", + "bio": "I am a Principal Solutions Architect working at AWS", + "linkedin": "ouzoegwu", + "twitter": "iam_tessot" + } + ] +} + diff --git a/appsync-private-api-sam/graphql/schema.graphql b/appsync-private-api-sam/graphql/schema.graphql new file mode 100644 index 000000000..f24f5f2ff --- /dev/null +++ b/appsync-private-api-sam/graphql/schema.graphql @@ -0,0 +1,57 @@ +input AddRestaurantInput { + name: String! + state: String + zip: String + cuisine: CuisineType! +} + +enum CuisineType { + Multi + Indian + Chinese + Italian + Thai + American + Continental +} + +input DeleteRestaurantInput { + restaurantId: ID! +} + +type Restaurant { + restaurantId: ID! + name: String! + state: String + zip: String + cuisine: CuisineType +} + +type RestaurantConnection { + items: [Restaurant] + nextToken: String +} + +input UpdateRestaurantInput { + restaurantId: ID! + name: String + state: String + zip: String + cuisine: CuisineType +} + +type Mutation { + addRestaurant(input: AddRestaurantInput!): Restaurant + updateRestaurant(input: UpdateRestaurantInput!): Restaurant + deleteRestaurant(input: DeleteRestaurantInput!): Restaurant +} + +type Query { + listRestaurants(limit: Int, nextToken: String): RestaurantConnection + getRestaurant(restaurantId: ID!): Restaurant +} + +schema { + query: Query + mutation: Mutation +} diff --git a/appsync-private-api-sam/resolvers/addRestaurant.js b/appsync-private-api-sam/resolvers/addRestaurant.js new file mode 100644 index 000000000..4b2704e1a --- /dev/null +++ b/appsync-private-api-sam/resolvers/addRestaurant.js @@ -0,0 +1,10 @@ +import * as ddb from '@aws-appsync/utils/dynamodb'; + +export function request(ctx) { + const key = { restaurantId: util.autoId() }; + const item = ctx.args.input; + const condition = { restaurantId: { attributeExists: false } }; + return ddb.put({ key, item, condition }); +} + +export const response = (ctx) => ctx.result; \ No newline at end of file diff --git a/appsync-private-api-sam/resolvers/deleteRestaurant.js b/appsync-private-api-sam/resolvers/deleteRestaurant.js new file mode 100644 index 000000000..a52ebc6c8 --- /dev/null +++ b/appsync-private-api-sam/resolvers/deleteRestaurant.js @@ -0,0 +1,4 @@ +import * as ddb from '@aws-appsync/utils/dynamodb'; + +export const request = (ctx) => ddb.remove({ key: { restaurantId: ctx.args.input.restaurantId } }); +export const response = (ctx) => ctx.result; \ No newline at end of file diff --git a/appsync-private-api-sam/resolvers/getRestaurant.js b/appsync-private-api-sam/resolvers/getRestaurant.js new file mode 100644 index 000000000..2175c9f92 --- /dev/null +++ b/appsync-private-api-sam/resolvers/getRestaurant.js @@ -0,0 +1,5 @@ +import * as ddb from '@aws-appsync/utils/dynamodb'; + +export const request = (ctx) => ddb.get({ key: { restaurantId: ctx.args.restaurantId } }); + +export const response = (ctx) => ctx.result; \ No newline at end of file diff --git a/appsync-private-api-sam/resolvers/listRestaurants.js b/appsync-private-api-sam/resolvers/listRestaurants.js new file mode 100644 index 000000000..d4fbfdfc1 --- /dev/null +++ b/appsync-private-api-sam/resolvers/listRestaurants.js @@ -0,0 +1,11 @@ +import * as ddb from '@aws-appsync/utils/dynamodb'; + +export function request(ctx) { + const { limit = 10, nextToken } = ctx.args; + return ddb.scan({ limit, nextToken }); +} + +export function response(ctx) { + const { items, nextToken } = ctx.result; + return { items: items ?? [], nextToken }; +} \ No newline at end of file diff --git a/appsync-private-api-sam/resolvers/updateRestaurant.js b/appsync-private-api-sam/resolvers/updateRestaurant.js new file mode 100644 index 000000000..a68251294 --- /dev/null +++ b/appsync-private-api-sam/resolvers/updateRestaurant.js @@ -0,0 +1,16 @@ +import { util } from '@aws-appsync/utils'; +import * as ddb from '@aws-appsync/utils/dynamodb'; + +export function request(ctx) { + const { restaurantId, ...values } = ctx.args.input; + const condition = { restaurantId: { attributeExists: true } }; + return ddb.update({ key: { restaurantId }, update: values, condition }); +} + +export function response(ctx) { + const { error, result } = ctx; + if (error) { + return util.error(error.message, error.type); + } + return result; +} \ No newline at end of file diff --git a/appsync-private-api-sam/template.yaml b/appsync-private-api-sam/template.yaml new file mode 100644 index 000000000..7ff1b4f70 --- /dev/null +++ b/appsync-private-api-sam/template.yaml @@ -0,0 +1,249 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: Deploy AppSync Private API in a VPC, provide the VPC ID and Subnet ID to deploy the AppSync VPC Interface Endpoints + +Parameters: + #### Parameter for VPC ID of the subnets to deploy the AppSync API Interface Endpoint #### + VpcId: + Description: VPC ID + Type: AWS::EC2::VPC::Id + ConstraintDescription: Must be a valid VPC ID. + + #### Parameter for SubnetIds to deploy the AppSync API Interface Endpoint #### + SubnetIds: + Description: Comma-separated list of Subnet IDs + Type: List + ConstraintDescription: Must be a comma-separated list of valid Subnet IDs. + +Resources: + #### AppSync VPC Interface Endpoint #### + AppSyncVPCEndpoint: + Type: AWS::EC2::VPCEndpoint + Properties: + ServiceName: !Sub "com.amazonaws.${AWS::Region}.appsync-api" + VpcEndpointType: Interface + VpcId: !Ref VpcId + SubnetIds: !Ref SubnetIds + SecurityGroupIds: + - !Ref AppSyncVPCEndpointSecurityGroup + PrivateDnsEnabled: true + + #### AppSync VPC Interface Endpoint Security Group, you can further restrict this security group to only the VPC or Subnet CIDR range #### + AppSyncVPCEndpointSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Security group for AppSync VPC Endpoint + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 443 + ToPort: 443 + CidrIp: 0.0.0.0/0 # Update for VPC CIDR range + VpcId: !Ref VpcId + + #### DynamoDb Table #### + RestaurantTable: + Type: AWS::Serverless::SimpleTable + Properties: + PrimaryKey: + Name: restaurantId + Type: String + + #### Appsync API #### + AppsyncGraphQLApi: + Type: AWS::AppSync::GraphQLApi + Properties: + Name: !Sub "RestaurantAPI-${AWS::StackName}" + AuthenticationType: API_KEY + LogConfig: + ExcludeVerboseContent: false + FieldLogLevel: "ALL" + CloudWatchLogsRoleArn: !GetAtt AppsyncPushToCloudWatchLogsRole.Arn + XrayEnabled: true + Visibility: PRIVATE + + AppsyncGraphQLApiKey: + Type: AWS::AppSync::ApiKey + Properties: + ApiId: !GetAtt AppsyncGraphQLApi.ApiId + + AppsyncGraphQLApiSchema: + Type: AWS::AppSync::GraphQLSchema + Properties: + ApiId: !GetAtt AppsyncGraphQLApi.ApiId + DefinitionS3Location: "./graphql/schema.graphql" + + #### Appsync API Logging #### + + AppsyncGraphQLApiLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub "/aws/appsync/apis/${AppsyncGraphQLApi.ApiId}" + RetentionInDays: 7 + + AppsyncPushToCloudWatchLogsRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - appsync.amazonaws.com + Action: + - sts:AssumeRole + + AppsyncPushToCloudWatchLogsRolePolicy: + Type: AWS::IAM::Policy + Properties: + PolicyName: !Sub ${AWS::StackName}-AppsyncPushToCloudWatchLogs-Policy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + Resource: !GetAtt AppsyncGraphQLApiLogGroup.Arn + Roles: + - !Ref AppsyncPushToCloudWatchLogsRole + + #### Appsync DynamoDB Datasource #### + + AppsyncDynamoDBDatasourceRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - sts:AssumeRole + Principal: + Service: + - appsync.amazonaws.com + + AppsyncDynamoDBDatasourceRolePolicy: + Type: AWS::IAM::Policy + Properties: + PolicyName: !Sub ${AWS::StackName}-AppsyncDynamoDB-Policy + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - dynamodb:DeleteItem + - dynamodb:GetItem + - dynamodb:PutItem + - dynamodb:Query + - dynamodb:Scan + - dynamodb:UpdateItem + Resource: + - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${RestaurantTable}" + - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${RestaurantTable}/*" + Roles: + - !Ref AppsyncDynamoDBDatasourceRole + + AppsyncGraphQLApiDataSource: + Type: AWS::AppSync::DataSource + Properties: + ApiId: !GetAtt AppsyncGraphQLApi.ApiId + Name: "Restaurant_Api_Datasource" + Type: "AMAZON_DYNAMODB" + ServiceRoleArn: !GetAtt AppsyncDynamoDBDatasourceRole.Arn + DynamoDBConfig: + AwsRegion: !Sub "${AWS::Region}" + TableName: !Ref RestaurantTable + + #### Appsync Resolvers #### + GetRestaurantResolver: + Type: AWS::AppSync::Resolver + DependsOn: + - AppsyncGraphQLApiSchema + Properties: + ApiId: !GetAtt AppsyncGraphQLApi.ApiId + TypeName: Query + FieldName: getRestaurant + DataSourceName: !GetAtt AppsyncGraphQLApiDataSource.Name + Runtime: + Name: APPSYNC_JS + RuntimeVersion: "1.0.0" + CodeS3Location: resolvers/getRestaurant.js + + ListRestaurantsResolver: + Type: AWS::AppSync::Resolver + DependsOn: + - AppsyncGraphQLApiSchema + Properties: + ApiId: !GetAtt AppsyncGraphQLApi.ApiId + TypeName: Query + FieldName: listRestaurants + DataSourceName: !GetAtt AppsyncGraphQLApiDataSource.Name + Runtime: + Name: APPSYNC_JS + RuntimeVersion: "1.0.0" + CodeS3Location: resolvers/listRestaurants.js + + AddRestaurantResolver: + Type: AWS::AppSync::Resolver + DependsOn: + - AppsyncGraphQLApiSchema + Properties: + ApiId: !GetAtt AppsyncGraphQLApi.ApiId + TypeName: Mutation + FieldName: addRestaurant + DataSourceName: !GetAtt AppsyncGraphQLApiDataSource.Name + Runtime: + Name: APPSYNC_JS + RuntimeVersion: "1.0.0" + CodeS3Location: resolvers/addRestaurant.js + + DeleteRestaurantResolver: + Type: AWS::AppSync::Resolver + DependsOn: + - AppsyncGraphQLApiSchema + Properties: + ApiId: !GetAtt AppsyncGraphQLApi.ApiId + TypeName: Mutation + FieldName: deleteRestaurant + DataSourceName: !GetAtt AppsyncGraphQLApiDataSource.Name + Runtime: + Name: APPSYNC_JS + RuntimeVersion: "1.0.0" + CodeS3Location: resolvers/deleteRestaurant.js + + UpdateRestaurantResolver: + Type: AWS::AppSync::Resolver + DependsOn: + - AppsyncGraphQLApiSchema + Properties: + ApiId: !GetAtt AppsyncGraphQLApi.ApiId + TypeName: Mutation + FieldName: updateRestaurant + DataSourceName: !GetAtt AppsyncGraphQLApiDataSource.Name + Runtime: + Name: APPSYNC_JS + RuntimeVersion: "1.0.0" + CodeS3Location: resolvers/updateRestaurant.js + +Outputs: + AppSyncGraphQLAPIURL: + Description: "AppSync GraphQL API URL" + Value: !GetAtt AppsyncGraphQLApi.GraphQLUrl + + AppSyncApiKey: + Description: "AppSync API Key" + Value: !GetAtt AppsyncGraphQLApiKey.ApiKey + + AppSyncVPCEndpointDNS: + Description: AppSync VPC Endpoint DNS + Value: !Select + - 1 + - !Split + - ":" + - !Select + - 0 + - !GetAtt + - AppSyncVPCEndpoint + - DnsEntries From 2f63894e4361c6eebf4580e7f73f3e36280ff767 Mon Sep 17 00:00:00 2001 From: Ozioma Uzoegwu <31167303+Tessot@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:04:36 +0100 Subject: [PATCH 2/2] updated comments from review --- appsync-private-api-sam/README.md | 8 +++++--- appsync-private-api-sam/example-pattern.json | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/appsync-private-api-sam/README.md b/appsync-private-api-sam/README.md index 320f15ad0..b3cff75b7 100644 --- a/appsync-private-api-sam/README.md +++ b/appsync-private-api-sam/README.md @@ -40,6 +40,8 @@ aws ec2 describe-subnets --filters "Name=vpc-id,Values=vpc-xxxxxxxxxxx" --query - Enter a stack name - Enter the desired AWS Region + - Enter the VpcID where to deploy the Private AppSync API + - Enter a comma-separated list of SubnetIds in the VPC to deploy theA AppSync API Interface Endpoint - Allow SAM CLI to create IAM roles with the required permissions. Once you have run `sam deploy --guided` mode once and saved arguments to a configuration file (samconfig.toml), you can use `sam deploy` in future to use these defaults. @@ -50,11 +52,11 @@ aws ec2 describe-subnets --filters "Name=vpc-id,Values=vpc-xxxxxxxxxxx" --query This patterns creates and AppSync Interface VPC Endpoint and a sample AppSync Private API backed with a DynamoDB data source. Requests to AppSync Private APIs will go through AWS’s private network without going over the internet. GraphQL requests from your application are routed via the interface VPC endpoint to AppSync Private API. Interface VPC endpoint is powered by [AWS PrivateLink](https://aws.amazon.com/privatelink/), a highly available, scalable technology that enables you to privately connect your VPC to AWS services like AWS AppSync as if the services were in your VPC. -API Key is used as the authorization mode for the AppSync API however it is not recommended to use API Key for production application, kindly refer to other authorization modes supported by AppSync in the [documentation](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html) +API Key is used as the authorization mode for the AppSync API. However it is not recommended to use API Key for production application, please refer to other authorization modes supported by AppSync in the [documentation](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html) ## Testing -You can easily test this pattern using any command prompt that supports the `curl` command. Refer to the outputs `AppSyncApiUrl`, `AppSyncApiKey` and `AppSyncVPCEndpointDNS` from deploying the SAM application which will be used for testing. +You can test this pattern using any command prompt that supports the `curl` command. Refer to the outputs `AppSyncApiUrl`, `AppSyncApiKey` and `AppSyncVPCEndpointDNS` from deploying the SAM application which will be used for testing. 1. Create a resource (for example EC2 instance) within your private network to invoke the AppSync API 2. Open your command prompt where you can run a `curl` commands @@ -70,7 +72,7 @@ Note: You can either use the `AppSync GraphQL API URL` or `AppSync VPC Interface -d '{"query": "query MyQuery {listRestaurants {items {name state restaurantId zip cuisine }}}","variables":"{}"}' ``` --- Using AppSync VPC Interface Endpoint DNS (you will need to pass the`AppSyncGraphQLAPIURL` in the host header, remember to remove suffix `www.`) +-- Using AppSync VPC Interface Endpoint DNS (you will need to pass the`AppSyncGraphQLAPIURL` in the host header, remember to remove prefix `www.`) ```curl https://{AppSyncVPCEndpointDNS}/graphql \ -H "Host:{AppSyncGraphQLAPIURL}" \ diff --git a/appsync-private-api-sam/example-pattern.json b/appsync-private-api-sam/example-pattern.json index bbd4a6c14..b1ee293c0 100644 --- a/appsync-private-api-sam/example-pattern.json +++ b/appsync-private-api-sam/example-pattern.json @@ -1,7 +1,7 @@ { "title": "AWS AppSync Private API ", "description": "Create an AWS AppSync Private API with a sample API to demonstrate how you can invoke Private API from resources in your private network", - "language": "Yaml", + "language": "YAML", "level": "200", "framework": "SAM", "introBox": {