From df415e847c625ce35d442476bd702cc736f66f96 Mon Sep 17 00:00:00 2001 From: bboure Date: Sun, 13 Feb 2022 01:21:02 +0100 Subject: [PATCH 01/16] Update Doc --- README.md | 731 ++------------------------------------------- doc/dataSources.md | 134 +++++++++ 2 files changed, 157 insertions(+), 708 deletions(-) create mode 100644 doc/dataSources.md diff --git a/README.md b/README.md index 12a25063..2ca09948 100644 --- a/README.md +++ b/README.md @@ -5,732 +5,47 @@ Deploy [AppSync](https://aws.amazon.com/appsync) API's in minutes using this [Serverless](https://www.serverless.com/) plugin. -# Getting Started - -Be sure to check out all that [AWS AppSync](https://aws.amazon.com/appsync) has to offer. Here are a few resources to help you understand everything needed to get started! - -- [Mapping Templates](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference.html) - Not sure how to create Mapping Templates for **DynamoDB**, **Lambda** or **Elasticsearch**? Here's a great place to start! -- [Data Sources and Resolvers](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials.html) - Get more information on what data sources are supported and how to set them up! -- [Security](https://docs.aws.amazon.com/appsync/latest/devguide/security.html) - Checkout this guide to find out more information on securing your API endpoints with AWS_IAM or Cognito User Pools! - # Minimum requirements -- [Node.js v8 or higher](https://nodejs.org) -- [Serverless v1.30.0 or higher](https://github.com/serverless/serverless) - -# Installation & Configuration - -Install the plugin via [Yarn](https://yarnpkg.com/lang/en/docs/install/) - -``` -yarn add serverless-appsync-plugin -``` +- [Node.js v14 or higher](https://nodejs.org) +- [Serverless v3.0.0 or higher](https://github.com/serverless/serverless) -or via [NPM](https://docs.npmjs.com/cli/install) +# Installation ``` npm install serverless-appsync-plugin ``` -## Configuring the plugin - -Add `serverless-appsync-plugin` to the plugins section of `serverless.yml` - -``` -plugins: - - serverless-appsync-plugin -``` - -Add the following config to the custom section of `serverless.yml` and update it accordingly to your needs +# Quick start ```yaml -custom: - appSync: - name: # defaults to api - # apiKey # only required for update-appsync/delete-appsync - # apiId # if provided, will update the specified API. - authenticationType: API_KEY or AWS_IAM or AMAZON_COGNITO_USER_POOLS or OPENID_CONNECT or AWS_LAMBDA - schema: # schema file or array of files to merge, defaults to schema.graphql (glob pattern is acceptable) - # Caching options. Disabled by default - # read more at https://aws.amazon.com/blogs/mobile/appsync-caching-transactions/ - # and https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-apicache.html - caching: - behavior: FULL_REQUEST_CACHING # or PER_RESOLVER_CACHING. Required - ttl: 3600 # The TTL of the cache. Optional. Default: 3600 - atRestEncryption: # Bool, Optional. Enable at rest encryption. disabled by default. - transitEncryption: # Bool, Optional. Enable transit encryption. disabled by default. - type: 'T2_SMALL' # Cache instance size. Optional. Default: 'T2_SMALL' - # if AMAZON_COGNITO_USER_POOLS - userPoolConfig: - awsRegion: # defaults to provider region - defaultAction: # required # ALLOW or DENY - userPoolId: # required # user pool ID - appIdClientRegex: # optional - # if AWS_LAMBDA - lambdaAuthorizerConfig: - functionName: # The function name in your serverless.yml. Ignored if lambdaFunctionArn is provided. - functionAlias: # optional, used with functionName - lambdaFunctionArn: # required if functionName is not defined - identityValidationExpression: # optional - authorizerResultTtlInSeconds: # optional - # if OPENID_CONNECT - openIdConnectConfig: - issuer: - clientId: - iatTTL: - authTTL: - - apiKeys: - - name: john # name of the api key - description: 'My api key' - expiresAfter: 30d # api key life time - - name: jane - description: "Jane's api key" - expiresAt: '2021-03-09T16:00:00+00:00' - # Array of additional authentication providers - additionalAuthenticationProviders: - - authenticationType: API_KEY - - authenticationType: AWS_IAM - - authenticationType: OPENID_CONNECT - openIdConnectConfig: - issuer: - clientId: - iatTTL: - authTTL: - - authenticationType: AMAZON_COGNITO_USER_POOLS - userPoolConfig: - awsRegion: # defaults to provider region - userPoolId: # required # user pool ID - appIdClientRegex: # optional - - authenticationType: AWS_LAMBDA - lambdaAuthorizerConfig: - functionName: # The function name in your serverless.yml. Ignored if lambdaFunctionArn is provided. - functionAlias: # optional, used with functionName - lambdaFunctionArn: # required if functionName is not defined - identityValidationExpression: # optional - authorizerResultTtlInSeconds: # optional - logConfig: - loggingRoleArn: { Fn::GetAtt: [AppSyncLoggingServiceRole, Arn] } # Where AppSyncLoggingServiceRole is a role with CloudWatch Logs write access - level: ERROR # Logging Level: NONE | ERROR | ALL - excludeVerboseContent: false - defaultMappingTemplates: # default templates. Useful for Lambda templates that are often repetitive. Will be used if the template is not specified in a resolver - request: my.request.template.tpl # or, e.g: false for Direct lambdas - response: my.response.template.tpl # or e.g.: false for Direct lambdas - mappingTemplatesLocation: # defaults to mapping-templates - mappingTemplates: - - dataSource: # data source name - type: # type name in schema (e.g. Query, Mutation, Subscription, or a custom type e.g. User) - field: getUserInfo - # kind: UNIT (default, not required) or PIPELINE (required for pipeline resolvers) - functions: # array of functions if kind === 'PIPELINE' - - # function name - request: # request mapping template name | defaults to `defaultMappingTemplates.request` or {type}.{field}.request.vtl - response: # response mapping template name | defaults to `defaultMappingTemplates.response` or {type}.{field}.response.vtl - # When caching is enaled with `PER_RESOLVER_CACHING`, - # the caching options of the resolver. - # Disabled by default. - # Accepted values: - # - `true`: cache enabled with global `ttl` and default `keys` - # - an object with the following keys: - # - ttl: The ttl of this particular resolver. Optional. Defaults to global ttl - # - keys: The keys to use for the cache. Optionnal. Defaults to a hash of the - # $context.arguments and $context.identity - caching: - keys: # array. A list of VTL variables to use as cache key. - - '$context.identity.sub' - - '$context.arguments.id' - ttl: 1000 # override the ttl for this resolver. (default comes from global config) - # When versioning is enabled with `versioned` on the datasource, - # the datasync options of the resolver. - # Disabled by default. - # Accepted values: - # - `true`: sync enabled with default ConflictDetection VERSION - # - an object with the following keys: - # - conflictDetection: The Conflict Detection strategy to use. - # - functionName: The function name in your serverless.yml. Ignored if lambdaFunctionArn is provided. - # - lambdaFunctionArn: The Arn for the Lambda function to use as the Conflict Handler. - # - conflictHandler: The Conflict Resolution strategy to perform in the event of a conflict. - sync: - conflictDetection: VERSION # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appsync-resolver-syncconfig.html - conflictHandler: OPTIMISTIC_CONCURRENCY # when not using lambda conflict handler choose The Conflict Resolution strategy to perform in the event of a conflict. OPTIMISTIC_CONCURRENCY / AUTOMERGE / LAMBDA - functionName: graphql # The function name in your serverless.yml. Ignored if lambdaFunctionArn is provided. - lambdaFunctionArn: 'arn:aws:lambda:{REGION}:{ACCOUNT_ID}:myFunction' - - - ${file({fileLocation}.yml)} # link to a file with arrays of mapping templates - functionConfigurationsLocation: # defaults to mappingTemplatesLocation (mapping-templates) - functionConfigurations: - - name: # function name - dataSource: # data source name - request: # request mapping template name | defaults to {name}.request.vtl - response: # reponse mapping template name | defaults to {name}.response.vtl - dataSources: - - type: NONE - name: none - - type: AMAZON_DYNAMODB - name: # data source name - description: # DynamoDB Table Description - config: - tableName: { Ref: MyTable } # Where MyTable is a dynamodb table defined in Resources - serviceRoleArn: { Fn::GetAtt: [AppSyncDynamoDBServiceRole, Arn] } # Where AppSyncDynamoDBServiceRole is an IAM role defined in Resources - iamRoleStatements: # custom IAM Role statements for this DataSource. Ignored if `serviceRoleArn` is present. Auto-generated if both `serviceRoleArn` and `iamRoleStatements` are omitted - - Effect: 'Allow' - Action: - - 'dynamodb:GetItem' - Resource: - - 'arn:aws:dynamodb:{REGION}:{ACCOUNT_ID}:myTable' - - 'arn:aws:dynamodb:{REGION}:{ACCOUNT_ID}:myTable/*' - # Versioned DataSource configuration - versioned: false # (default, not required) - # When you enable versioning on a DynamoDB data source, you specify the following fields - # read more at https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appsync-datasource-deltasyncconfig.html - # deltaSyncConfig: - # baseTableTTL: 0 # (default, not required) # The amount of time (in minutes) items should be kept in the base table when deleted. Set to 0 to delete items in the base table immediately - # deltaSyncTableName: { Ref: MyTableDelta } # required # The Delta Sync table name - # deltaSyncTableTTL: 60 # (default, not required) # The amount of time (in minutes) the delta sync table will keep track of changes - - region: # Overwrite default region for this data source - - type: RELATIONAL_DATABASE - name: # data source name - description: # data source description - config: - dbClusterIdentifier: { Ref: RDSCluster } # The identifier for RDSCluster. Where RDSCluster is the cluster defined in Resources - awsSecretStoreArn: { Ref: RDSClusterSecret } # The RDSClusterSecret ARN. Where RDSClusterSecret is the cluster secret defined in Resources - serviceRoleArn: { Fn::GetAtt: [RelationalDbServiceRole, Arn] } # Where RelationalDbServiceRole is an IAM role defined in Resources - databaseName: # optional database name - schema: # optional database schema - iamRoleStatements: # custom IAM Role statements for this DataSource. Ignored if `serviceRoleArn` is present. Auto-generated if both `serviceRoleArn` and `iamRoleStatements` are omitted - - Effect: 'Allow' - Action: - - 'rds-data:DeleteItems' - - 'rds-data:ExecuteSql' - - 'rds-data:ExecuteStatement' - - 'rds-data:GetItems' - - 'rds-data:InsertItems' - - 'rds-data:UpdateItems' - Resource: - - 'arn:aws:rds:{REGION}:{ACCOUNT_ID}:cluster:mydbcluster' - - 'arn:aws:rds:{REGION}:{ACCOUNT_ID}:cluster:mydbcluster:*' - - Effect: 'Allow' - Action: - - 'secretsmanager:GetSecretValue' - Resource: - - 'arn:aws:secretsmanager:{REGION}:{ACCOUNT_ID}:secret:mysecret' - - 'arn:aws:secretsmanager:{REGION}:{ACCOUNT_ID}:secret:mysecret:*' - - region: # Overwrite default region for this data source - - type: AMAZON_ELASTICSEARCH - name: # data source name - description: 'ElasticSearch' - config: - domain: # a reference to a resource of type `AWS::Elasticsearch::Domain` - endpoint: # required if `domain` not provided. Ex: "https://{XXX}.{REGION}.es.amazonaws.com" - serviceRoleArn: { Fn::GetAtt: [AppSyncESServiceRole, Arn] } # Where AppSyncESServiceRole is an IAM role defined in Resources - iamRoleStatements: # custom IAM Role statements for this DataSource. Ignored if `serviceRoleArn` is present. Auto-generated if both `serviceRoleArn` and `iamRoleStatements` are omitted - - Effect: 'Allow' - Action: - - 'es:ESHttpGet' - Resource: - - 'arn:aws:es:{REGION}:{ACCOUNT_ID}:{DOMAIN}' - - type: AWS_LAMBDA - name: # data source name - description: 'Lambda DataSource' - config: - functionName: graphql # The function name in your serverless.yml. Ignored if lambdaFunctionArn is provided. - lambdaFunctionArn: { Fn::GetAtt: [GraphqlLambdaFunction, Arn] } # Where GraphqlLambdaFunction is the lambda function cloudformation resource created by serverless for the serverless function named graphql - serviceRoleArn: { Fn::GetAtt: [AppSyncLambdaServiceRole, Arn] } # Where AppSyncLambdaServiceRole is an IAM role defined in Resources - iamRoleStatements: # custom IAM Role statements for this DataSource. Ignored if `serviceRoleArn` is present. Auto-generated if both `serviceRoleArn` and `iamRoleStatements` are omitted - - Effect: 'Allow' - Action: - - 'lambda:invokeFunction' - Resource: - - 'arn:aws:lambda:{REGION}:{ACCOUNT_ID}:myFunction' - - 'arn:aws:lambda:{REGION}:{ACCOUNT_ID}:myFunction:*' - - type: HTTP - name: # data source name - description: 'Http endpoint' - config: - endpoint: # required # "https://{DOMAIN}/{PATH}" - - ${file({dataSources}.yml)} # link to a file with an array or object of datasources - substitutions: # allows to pass variables from here to velocity templates - # ${exampleVar1} will be replaced with given value in all mapping templates - exampleVar1: '${self:service.name}' - exampleVar2: { 'Fn::ImportValue': 'Some-external-stuff' } - xrayEnabled: true # Bool, Optional. Enable X-Ray. disabled by default. - wafConfig: - enabled: true - name: AppSyncWaf - defaultAction: Allow # or Block. Defaults to Allow - description: 'My AppSync Waf rules' - rules: - - throttle: 100 - - disableIntrospection - - name: UsOnly - action: Block # Allow, Block, or Count - statement: - NotStatement: - Statement: - GeoMatchStatement: - CountryCodes: - - US - - tags: # Tags to be added to AppSync - key1: value1 - key2: value2 -``` - -> Be sure to replace all variables that have been commented out, or have an empty value. - -### Working with existing APIs - -If you already have an API created in AppSync through the UI or from a different CF stack -and want to manage it via Serverless then the plugin can also support that. - -There is an optional _apiId_ parameter that you can use to specify the ID of an existing AppSync API: - -```yaml -custom: - appSync: - # ... - apiId: 1234abcd - # ... -``` - -Without _apiId_ parameter the plugin will create a different endpoint with the same name alongside the original one. - -You can find the _apiId_ value in the AppSync console, just open your existing AppSync API -and go to Settings. - -In that case, the plugin will not attempt to create a new endpoint for you, instead, it will attach all newly configured resources to the -existing endpoint. - -The following configuration options are only associated with the creation of a new AppSync endpoint -and will be ignored if you provide _apiId_ parameter: - -- name -- authenticationType -- userPoolConfig -- openIdConnectConfig -- additionalAuthenticationProviders -- logConfig -- tags -- xrayEnabled -- apiKeys -- wafConfig - -So later, if you wanted to change the name of the API, or add some tags, or change the logging configuration, -anything from the list above you would have to do that via a different method, for example from the UI. - -If the existing API already contains schema and resolvers those will be completely replaced by the new -schema and resolvers from the code. - -If the existing API already contains data sources, those data sources will remain untouched unless they have the same -names as the data sources in the code, in which case they will be replaced with the ones from the code. - -> **Note:** You should never set the apiId of an API that was previously deployed with the same serverless stack, otherwise, it would be deleted. That is because the resource would be removed from the stack. -> -> Only use the apiId parameter if you know what you are doing. - -### Multiple APIs - -If you have multiple APIs and do not want to split this up into another CloudFormation stack, simply change the `appSync` configuration property from an object into an array of objects: - -```yaml -custom: - appSync: - - name: private-appsync-endpoint - schema: AppSync/schema.graphql # or something like AppSync/private/schema.graphql - authenticationType: OPENID_CONNECT - openIdConnectConfig: - ... - serviceRole: AuthenticatedAppSyncServiceRole - dataSources: - ... - mappingTemplatesLocation: ... - mappingTemplates: - ... - - name: public-appsync-endpoint - schema: AppSync/schema.graphql # or something like AppSync/public/schema.graphql - authenticationType: API_KEY - serviceRole: PublicAppSyncServiceRole - dataSources: - ... - mappingTemplatesLocation: ... - mappingTemplates: - ... -``` - -> **Note:** CloudFormation stack outputs and logical IDs will be changed from the defaults to api name prefixed. This allows you to differentiate the APIs on your stack if you want to work with multiple APIs. - -### Direct Lambda Resolvers - -Amazon supports [direct lambda resolvers](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html) - -With a direct lambda resolver, no VTL mapping template is required for either request or response. This can be an option if you would like to avoid usage of the Apache VTL langauge or require a complex resolver. You can enable direct Lambda resolvers by setting `false` as the `request` and/or `response` value. - -Example: - -```yml -custom: - appsync: - mappingTemplates: - - type: Query - request: false - response: false - dataSource: myLambdaSource - field: getMyData -``` - -Furthermore, direct resolution can be enabled separately for the request and response templates. - -### Pipeline Resolvers - -Amazon supports [pipeline resolvers](https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html) - -They allow you to perform more than one mapping template in sequence, so you can do multiple queries to multiple sources. -These queries are called function configurations ('AWS::AppSync::FunctionConfiguration') and are children of a resolver. - -Here is an example of how to configure a resolver with function configurations. -The key here is to provide a 'kind' of 'PIPELINE' to the mapping template of the parent resolver. -Then provide the names of the functions in the mappingTemplate to match the names of the functionConfigurations. - -```yml -custom: - appSync: - mappingTemplates: - - type: Query - field: testPipelineQuery - request: './mapping-templates/before.vtl' # the pipeline's "before" mapping template, defaults to {type}.{field).request.vtl - response: './mapping-templates/after.vtl' # the pipeline's "after" mapping template, defaults to {type}.{field}.response.vtl - kind: PIPELINE - functions: - - authorizeFunction - - fetchDataFunction - functionConfigurations: - - dataSource: graphqlLambda - name: 'authorizeFunction' - request: './mapping-templates/authorize-request.vtl' # defaults to {name}.request.vtl - response: './mapping-templates/common-response.vtl' # defaults to {name}.response.vtl - - dataSource: dataTable - name: 'fetchDataFunction' - request: './mapping-templates/fetchData.vtl' # defaults to {name}.request.vtl - response: './mapping-templates/common-response.vtl' # defaults to {name}.response.vtl -``` - -### Managing API keys - -Since v1.5.0, api keys management is supported. You can pass one or more api keys configuration as an array in the `appSync.apiKeys` property. - -The keys can either be a string (name of the key with defaults) or an object of the following shape: - -| property | default | description | -| ------------ | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| name | _auto-generated_ | Name of the key. This is used under the hood to differentiate keys in the deployment process.

Names are used in the Cfn resource name. Please, keep them short and without spaces or special characters to avoid issues. Key names are case sensitive. | -| description | _name of the key_ | A short description for that key | -| expiresAfter | 365d | Expiration time for the key.
Can be expressed in hours or in "human" format (As in momentjs [add](https://momentjscom.readthedocs.io/en/latest/moment/03-manipulating/01-add/)).
eg: `24`, `30d`, `1M`, `2w`, `1y`
Min: 1d, max: 1y | -| expiresAt | _one year from now_ | A specific expiration date in ISO 8601 format. Or as a unix timestamp | -| apiKeyId | `undefined` | the id if the api to update. Useful for when an api key has been created manually in the AWS console. | - -If both `expiresAfter` and `expiresAt` are specified, `expiresAfter` takes precedence. - -When naming keys, you need to be aware that changing the value will require the **replacement** of the api key. - -Unnamed keys are named automatically sequentially Key1, Key2, Key3 and so forth. - -:warning: **Be careful when removing unnamed keys!!!**. For exemple, if you have 3 unnamed keys and you remove the second one in your list, Key3 will become Key2. As a result, it is former Key3 that **will be removed**. To workaround that, you could specify their auto-generated names before removing any unnamed keys (Key1, Key2 and Key3 in our example. Then remove Key2). As a rule of thumb, all keys should be named to avoid issues. - -:bulb: If you have already deployed and an api key was previously auto-generated for you (either in version <1.5.0 or if you deployed without specifying the `apiKeys` property), you can add it to your yml template by naming it `Default` (case sensitive!!). Starting from there, you can add additional API keys. - -:bulb: If you want to revoke a key, delete it, or rename it. - -:bulb: If a key expires, or you have manually deleted it from the cosole, subsequent deployments will fail (after 60 days in the case it expires). You can fix that by simply removing the key from your yml file, or by renaming it (in which case, a new key will be generated). - -Example: - -```yml -apiKeys: - - name: Default # default API key. Use this name if you already have an auto-generated API key - description: Default api key - expires: 1y # 1 year timelife - - Mark # inline named key, with defaults (1 year duration) - - name: John - description: John api key - expires: 30d - - name: Jane - expires: 2d - - description: Unnamed key # first unnamed key (Key1) - - expires: 30d # second unnamed key (Key2) - - name: ThrottledAPIKey - wafRules: - - throttle # throttle this API key to 100 requests per 5 min - - - name: GeoApiKey - description: Us Only - # Disallow this Api key outsite the US - wafRules: - - action: Block - name: UsOnly - statement: - NotStatement: - Statement: - GeoMatchStatement: - CountryCodes: - - US -``` - -:bulb: Finally, if you dont't want serverless to handle keys for you, just pass an empty array: - -```yml -# Handle keys manually in the aws console. -apiKeys: [] -``` - -### WAF Web ACL - -AppSync [supports WAF](https://aws.amazon.com/blogs/mobile/appsync-waf/). WAF is an Application Firewall that helps you protect your API against common web exploits. - -This plugin comes with some handy pre-defined rules that you can enable in just a few lines of code. - -### Throttling - -Throttling will disallow requests coming from the same ip address when a limit is reached within a 5-minutes period. +service: my-app -There are several ways to enable it. Here are some examples: - -```yml -wafConfig: - enabled: true - rules: - - throttle # limit to 100 requests per 5 minutes period - - throttle: 200 # limit to 200 requests per 5 minutes period - - throttle: - limit: 200 - priority: 10 - aggregateKeyType: FORWARDED_IP - forwardedIPConfig: - headerName: 'X-Forwarded-For' - fallbackBehavior: 'MATCH' -``` - -### Disable Introspection - -Sometimes, you want to disable introspection to disallow untrusted consumers to discover the structure of your API. - -```yml -wafConfig: - enabled: true - rules: - - disableIntrospection # disables introspection for everyone -``` - -### Using AWS Managed Rules - -You can use -[AWS Managed Rules](https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups.html) -using this configuration: - -```yml -wafConfig: - enabled: true - rules: - - name: MyRule1 # this is your rule name - overrideAction: - none: {} - statement: - managedRuleGroupStatement: - vendorName: AWS - name: AWSManagedRulesCommonRuleSet # this is the name of the managed rule -``` - -Managed rules require `overrideAction` set and `action` not set. - -For more information view the -[AWS Managed Rule Groups List](https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html). - -### Per Api Key rules - -In some cases, you might want to enable a rule only for a given API key only. You can specify `wafRules` under the `apiKeys` configuration. The rules will apply only to the api key under which the rule is set. - -```yml -apiKeys: - - name: MyApiKey - expiresAfter: 365d - wafRules: - - throttle # throttles this API key - - disableIntrospection # disables introspection for this API key -``` - -Adding a rule to an API key without any _statement_ will add a "match-all" rule for that key. -This is usefull for example to exclude api keys from high-level rules. In that case, you need to make sure to attribute a higher priority to that rule. - -Example: - -- Block all requests by default -- Add a rule to allow US requests -- Except for the `WorldWideApiKey` key, that should have worldwide access. - -```yml -wafConfig: - enabled: true - defaultAction: Block # Block all by default - rules: - # allow US requests - - action: Allow - name: UsOnly - priority: 5 - statement: - geoMatchStatement: - countryCodes: - - US -apiKeys: - - name: Key1 # no rule is set, the top-level rule applies (Us only) - - name: Key1 # no rule is set, the top-level rule applies (Us only) - - name: WorldWideApiKey - wafRules: - - name: WorldWideApiKeyRule - action: Allow - priority: 1 # Make sure the priority is higher (lower number) to evaluate it first -``` - -### About priority - -The priorities don't need to be consecutive, but they must all be different. - -Setting a priority to the rules is not required, but recommended. If you don't set priority, it will be automatically attributed (sequentially) according to the following rules: - -First the global rules (under `wafConfig.rules`), in the order that they are defined. Then, the api key rules, in order of api key definitions, then rule definition. -Auto-generated priorities start at 100. This gives you some room (0-99) to add other rules that should get a higher priority, if you need to. - -For more info about how rules are executed, pease refer to [the documentation](https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-processing.html) - -Example: - -```yml -wafConfig: - enabled: true - rules: - - name: Rule1 - # (no-set) Priority = 100 - - name: Rule2 - priority: 5 # Priority = 5 - - name: Rule3 - # (no-set) Priority = 101 -apiKeys: - - name: Key1 - wafRules: - - name: Rule4 - # (no-set) Priority = 102 - - name: Rule5 - # (no-set) Priority = 103 - - name: Key - wafRules: - - name: Rule6 - priority: 1 # Priority = 1 - - name: Rule7 - # (no-set) Priority = 104 -``` - -### Advanced usage - -You can also specify custom rules. For more info on how to define a rule, see the [Cfn documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-rule.html) - -Example: - -```yml -wafConfig: - enabled: true - defaultAction: Block - rules: - # Only allow US users - - action: Allow - name: UsOnly - statement: - geoMatchStatement: - countryCodes: - - US -``` - -### Stack Outputs & Exports - -`GraphQlApiId`and `GraphQlApiUrl` are exported to allow cross-stack resource reference using `Fn::ImportValue`. -Output Exports are named with a `${AWS::StackName}-` prefix to the logical IDs. For example, `${AWS::StackName}-GraphQlApiId`. - -> Note: CloudFormation stack outputs and logical IDs will be changed from the defaults to api name prefixed. This allows you to differentiate the APIs on your stack if you want to work with multiple APIs. - -# Cli Usage - -## `serverless deploy` - -This command will deploy all AppSync resources in the same CloudFormation template used by the other serverless resources. - -- Providing the `--conceal` option will conceal the API keys from the output when the authentication type of `API_KEY` is used. - -## `validate-schema` - -Validates your GraphQL Schema(s) without deploying. - -## `serverless graphql-playground` - -This command will start a local graphql-playground server which is connected to your deployed AppSync endpoint (in the cloud). The required options for the command are different depending on your AppSync authenticationType. - -For API_KEY, either the GraphQLApiKeyDefault output or the --apiKey option is required - -For AMAZON_COGNITO_USER_POOLS, the -u/--username and -p/--password arguments are required. The cognito user pool client id can be provided with the --clientId option or directly in the yaml file (`custom.appSync.userPoolConfig.playgroundClientId`) - -For OPENID_CONNECT, the --jwtToken option is required. - -The AWS_IAM authenticationType is not currently supported. - -# Offline support - -There are 2 ways to work with offline development for serverless appsync. - -## serverless-appsync-simulator - -[serverless-appsync-simulator](https://github.com/bboure/serverless-appsync-simulator) is a wrapper of aws's [amplify-cli](https://github.com/aws-amplify/amplify-cli) for serverless and this plugin. Both are actively maintained. - -## serverless-appsync-simulator (deprecated/unmaintained) - -[serverless-appsync-offline](https://github.com/aheissenberger/serverless-appsync-offline) is based on [AppSync Emulator](https://github.com/ConduitVC/aws-utils/tree/appsync/packages/appsync-emulator-serverless). Both these packages are currently unmaintained. - -# Split Stacks Plugin - -You can use [serverless-plugin-split-stacks](https://github.com/dougmoscrop/serverless-plugin-split-stacks) to migrate AppSync resources in nested stacks in order to work around the [~~200~~](~~) 500 resource limit. - -1. Install [serverless-plugin-split-stacks](https://github.com/dougmoscrop/serverless-plugin-split-stacks) +plugins: + - serverless-appsync-plugin -``` -yarn add --dev serverless-plugin-split-stacks - or -npm install --save-dev serverless-plugin-split-stacks -``` +provider: + name: aws -2. Follow the `serverless-plugin-split-stacks` installation instructions +appSync: + name: my-api + authentication: + type: API_KEY -3. Place `serverless-plugin-split-stacks` after `serverless-appsync-plugin` + resolvers: + Query.user: + dataSource: my-table -```yml -plugins: - - serverless-appsync-plugin - - serverless-plugin-split-stacks + dataSources: + my-table: + type: AMAZON_DYNAMODB + config: + tableName: ${sls:stage}-data ``` -4. Create `stacks-map.js` in the root folder - -```js -module.exports = { - 'AWS::AppSync::ApiKey': { destination: 'AppSync', allowSuffix: true }, - 'AWS::AppSync::DataSource': { destination: 'AppSync', allowSuffix: true }, - 'AWS::AppSync::FunctionConfiguration': { - destination: 'AppSync', - allowSuffix: true, - }, - 'AWS::AppSync::GraphQLApi': { destination: 'AppSync', allowSuffix: true }, - 'AWS::AppSync::GraphQLSchema': { destination: 'AppSync', allowSuffix: true }, - 'AWS::AppSync::Resolver': { destination: 'AppSync', allowSuffix: true }, -}; -``` +## Concepts -5. Enjoy :beers: +- [DataSources](docs/dataSources.md) # Contributing diff --git a/doc/dataSources.md b/doc/dataSources.md new file mode 100644 index 00000000..1a2f5d21 --- /dev/null +++ b/doc/dataSources.md @@ -0,0 +1,134 @@ +# DataSources + +All the DataSources in your AppSync API can be found in serverless.yml under the `appSync.dataSources` property. DataSources are defiend as key-value objects, the key being the name of the DataSource. + +## DynamoDB + +### quick start + +```yaml +appSync: + dataSources: + my-table: + type: AMAZON_DYNAMODB + description: 'My table' + config: + tableName: my-table +``` + +### config + +- `tableName`: the name of the DynamODB table +- `region`: the region of the table. Defaults to the stack's region +- `useCallerCredentials`: Set to `true` to use AWS Identity and Access Management with this data source +- `serviceRoleArn`: The service roel ARN for this DataSource. If not provided, a new one will be created. +- `iamRoleStatements`: Statements to use for the generated IAM Role. If not provided, default statements will be used. +- `versioned`: Set to `true` to use Conflict Detection and Resolution with this data source. +- `deltaSyncConfig`: + - `deltaSyncTableName`: The Delta Sync table name. + - `baseTableTTL`: The number of minutes that an Item is stored in the data source. + - `deltaSyncTableTTL`: The number of minutes that a Delta Sync log entry is stored in the Delta Sync table. + +## AWS Lambda + +### quick start + +```yaml +appSync: + dataSources: + myFunction: + type: 'AWS_LAMBDA' + config: + function: + timeout: 30 + handler: 'functions/myFunction.handler' +``` + +### config + +- `serviceRoleArn`: The service roel ARN for this DataSource. If not provided, a new one will be created. +- `iamRoleStatements`: Statements to use for the generated IAM Role. If not provided, default statements will be used. +- `function`: A Lambda function definition as you would define it under the `functions` section of you `serverless.yml file. +- `functionName`: The name of the function as defined under the `functions` section of the `serverless.yml` file +- `functionAlias`: A specific function alias to use +- `functionArn`: The function ARN to use for this DataSoruce. + +## OpenSearch (ElasticSearch) + +### Quick start + +```yaml +appSync: + dataSources: + search: + type: 'AMAZON_OPENSEARCH_SERVICE' + config: + endpoint: https://abcdefgh.us-east-1.es.amazonaws.com +``` + +### config + +- `endpoint`: The endpoint url to the OpenSearch domain +- `region`: The region of the OpenSearch domain. Default to the stack's region. +- `serviceRoleArn`: The service roel ARN for this DataSource. If not provided, a new one will be created. +- `iamRoleStatements`: Statements to use for the generated IAM Role. If not provided, default statements will be used. + +## HTTP + +### Quick start + +```yaml +appSync: + dataSources: + api: + type: 'HTTP' + config: + endpoint: https://api.example.com +``` + +### config + +- `endpoint`: The url of the HTTP endpoint. +- `serviceRoleArn`: The service roel ARN for this DataSource. If not provided, a new one will be created. +- `iamRoleStatements`: Statements to use for the generated IAM Role. If not provided, default statements will be used. +- `authorizationConfig`: + - `authorizationType`: The authorization type that the HTTP endpoint requires. + - `AWS_IAM`: The authorization type is Signature Version 4 (SigV4). + - `awsIamConfig`: + - `signingRegion`: The signing Region for AWS Identity and Access Management authorization. Defaults to the region of the stack. + - `signingServiceName`: The signing service name for AWS Identity and Access Management authorization. + +## Relational Database + +### Quick start + +```yaml +appSync: + dataSources: + api: + type: 'RELATIONAL_DATABASE' + config: + databaseName: myDatabase + dbClusterIdentifier: Ref: RDSCluster + awsSecretStoreArn: Ref: RDSClusterSecret + serviceRoleArn: !GetAtt RelationalDbServiceRole.Arn +``` + +### config + +- `databaseName`: The name of the database +- `region`: The region of the RDS HTTP ednpoint. Default to the region of the stack. +- `awsSecretStoreArn`: The ARN for database credentials stored in AWS Secrets Manager. +- `dbClusterIdentifier`: Amazon RDS cluster Amazon Resource Name (ARN). +- `schema`: Logical schema name. +- `serviceRoleArn`: The service roel ARN for this DataSource. If not provided, a new one will be created. +- `iamRoleStatements`: Statements to use for the generated IAM Role. If not provided, default statements will be used. + +## NONE + +```yaml +appSync: + dataSources: + api: + type: 'NONE' +``` From e8faf60ab6077e196fa61543f82b5eb1e24adddd Mon Sep 17 00:00:00 2001 From: bboure Date: Sun, 13 Feb 2022 11:42:04 +0100 Subject: [PATCH 02/16] Add resolver doc --- README.md | 3 ++ doc/caching.md | 3 ++ doc/resolvers.md | 99 ++++++++++++++++++++++++++++++++++++++++++++ doc/substitutions.md | 3 ++ 4 files changed, 108 insertions(+) create mode 100644 doc/caching.md create mode 100644 doc/resolvers.md create mode 100644 doc/substitutions.md diff --git a/README.md b/README.md index 2ca09948..c879d38b 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,9 @@ appSync: ## Concepts - [DataSources](docs/dataSources.md) +- [Resolvers](docs/resolvers.md) +- [VTL Template Substitutions](docs/substitutions.md) +- [Caching](docs/caching.md) # Contributing diff --git a/doc/caching.md b/doc/caching.md new file mode 100644 index 00000000..66954c56 --- /dev/null +++ b/doc/caching.md @@ -0,0 +1,3 @@ +# Caching + +> TODO diff --git a/doc/resolvers.md b/doc/resolvers.md new file mode 100644 index 00000000..708b76d7 --- /dev/null +++ b/doc/resolvers.md @@ -0,0 +1,99 @@ +# Resolvers + +All the Resolvers in your AppSync API can be found in serverless.yml under the `appSync.resolvers` property. + +Resolvers are defined using key-value pairs where the key can either be an arbitrary name for the resolver or the `type` and `field` in the schema it is attached to separated with a dot (`.`). + +## Quick start + +```yaml +appSync: + resolvers: + Query.user: + dataSource: my-table +``` + +## Configuration + +- `type`: The Type in the schema this resolver is attached to. Optional if specified in the configuration key. +- `field`: The Field in the schema this resolver is attached to. Optional if specified in the configuration key. +- `kind`: The kind of resolver. Can be `UNIT` or `PIPELINE` ([see below](#PIPELINE%20resolvers)). Defaults to `UNIT` +- `dataSource`: The name of the [dataSource](dataSoruces.md) this resolver uses. +- `maxBatchSize`: The maximum [batch size](https://aws.amazon.com/blogs/mobile/introducing-configurable-batching-size-for-aws-appsync-lambda-resolvers/) to use (only available for AWS Lambda datasoures) +- `request`: The request mapping template file name to use for this resolver, or `false` for [direct lambda](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html). Default to `{Type}.{Field}.request.vtl`. +- `response`: The request mapping template file name to use for this resolver, or `false` for [direct lambda](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html). Default to `{Type}.{Field}.response.vtl`. +- `substitutions`: See [VTL template substitutions](substitutions.md) +- `caching`: [See below](#Caching) +- `sync`: [See blow](#Sync) + +## PIPELINE resolvers + +When `kind` is `PIPELINE`, you must specify the pipeline functions to use + +```yaml +appSync: + resolvers: + Query.user: + dataSource: my-table + functions: + - function1 + - function2 +``` + +## Inline DataSources + +If a [datasource](dataSources.md) is only used in one single resolver, you can also define it inline in the resolver configuration. This is often the case for Lambda resolvers. + +You can even also define the Lambda function definition inline under the dataSource definition. This helps keep everything in one single place! + +```yaml +appSync: + resolvers: + Query.user: + dataSource: + type: 'AWS_LAMBDA' + config: + function: + timeout: 30 + handler: 'functions/getUser.handler' +``` + +## Caching + +```yaml +Query.user: + dataSource: my-table + caching: + ttl: 60 + keys: + - '$ctx.arguments.id' +``` + +You can either pass `true` which will use the default ttl (See the [global caching configuration](caching.md)) and no `keys`. + +You can also customize each resolver using the following config: + +- `ttl`: The TTL of the cache for this resolver in seconds +- `keys`: An array of keys to use for the cache. + +## Sync + +The [Delta Sync](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-delta-sync.html) configuration for this resolver. + +```yaml +Query.user: + dataSource: my-table + sync: + conflictDetection: 'VERSION' + conflictHandler: 'LAMBDA' + function: + timeout: 30 + handler: 'functions/userSync.handler' +``` + +- `conflictDetection`: Defaults to 'VERSION' +- `conflictHandler`: `OPTIMISTIC_CONCURRENCY`, `AUTOMERGE` or `LAMBDA` +- `function`: A Lambda function definition as you would define it under the `functions` section of you `serverless.yml file. +- `functionName`: When `conflictHandler` is `LAMBDA`, the name of the function as defined under the `functions` section of the `serverless.yml` file +- `functionAlias`: When `conflictHandler` is `LAMBDA`, a specific function alias to use +- `functionArn`: When `conflictHandler` is `LAMBDA`, the function ARN to use for this DataSoruce. diff --git a/doc/substitutions.md b/doc/substitutions.md new file mode 100644 index 00000000..9f383a58 --- /dev/null +++ b/doc/substitutions.md @@ -0,0 +1,3 @@ +# Substitutions + +> TODO From 134a95b1c7b3b89343eba602cda6d3642625d5f9 Mon Sep 17 00:00:00 2001 From: bboure Date: Sun, 13 Feb 2022 11:43:05 +0100 Subject: [PATCH 03/16] fix links --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c879d38b..a8540db6 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,10 @@ appSync: ## Concepts -- [DataSources](docs/dataSources.md) -- [Resolvers](docs/resolvers.md) -- [VTL Template Substitutions](docs/substitutions.md) -- [Caching](docs/caching.md) +- [DataSources](doc/dataSources.md) +- [Resolvers](doc/resolvers.md) +- [VTL Template Substitutions](doc/substitutions.md) +- [Caching](doc/caching.md) # Contributing From 285f6ce116579da0fe9eb03c2f9318d094271ffe Mon Sep 17 00:00:00 2001 From: bboure Date: Sun, 13 Feb 2022 12:09:45 +0100 Subject: [PATCH 04/16] Add API keys doc --- README.md | 1 + doc/API-keys.md | 36 ++++++++++++++++++++++++++++++++++++ doc/WAF.md | 3 +++ doc/authentication.md | 3 +++ doc/resolvers.md | 2 +- 5 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 doc/API-keys.md create mode 100644 doc/WAF.md create mode 100644 doc/authentication.md diff --git a/README.md b/README.md index a8540db6..85cdcbb2 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ appSync: - [DataSources](doc/dataSources.md) - [Resolvers](doc/resolvers.md) +- [API keys](doc/API-keys.md) - [VTL Template Substitutions](doc/substitutions.md) - [Caching](doc/caching.md) diff --git a/doc/API-keys.md b/doc/API-keys.md new file mode 100644 index 00000000..e2f1a5da --- /dev/null +++ b/doc/API-keys.md @@ -0,0 +1,36 @@ +# API Keys + +When you use `API_KEY` as an [authentication method](authentication.md), you can control how API keys are created under `appSync.apiKeys`. It takes an array of api key definitions or strings. + +## Quick start + +```yaml +appSync: + apiKeys: + - john + - name: jane + description: Jane's API key. + expiresAfter: 1M +``` + +## configuration + +It can either be string, which translates into the API key's name with default values for the other attributes, or use a custom configuration. + +- `name`: A unique name for this API key. Required. +- `description`: An optional description for this API key. +- `expiresAfter`: A time ater which this API key will expire. [See below](Expiry) for more details about expiry. Defaults to `365y`. +- `expiresAt`: A time at which this API key will expire. [See below](Expiry) for more details about expiry. +- `wafRules`: an array of [WAF rules](WAF.md) that will apply to this API key only. + +## Expiry + +You can control expiry of the API keys with the `expiresAfter` or `expiresAt` attribute. + +`expiresAfter` behaves as a sliding-window expiry date which extends after each deployment. It can be a number of hours until expiry or a more human-friendly string. e.g. `24h`, `30d`, `3M`, `1y`. + +`expiresAt` is an exact ISO datetime at which this API will expire. It will not be renewd unless you change this value. e.g. `2022-02-13T10:00:00`. + +`expiresAfter` takes precedence over `expiresAt`. If neither are passed, it defaults to a `expiresAfter` of `365d` + +**Note**: The minimum lifetime of an API key in AppSync is 1 day and maximum is 1 year (365 days). Expiry datetimes are always rounded down to the nearest hour. (e.g. `2022-02-13T10:45:00` becomes `2022-02-13T10:00:00`). diff --git a/doc/WAF.md b/doc/WAF.md new file mode 100644 index 00000000..3c1a6c09 --- /dev/null +++ b/doc/WAF.md @@ -0,0 +1,3 @@ +# Web Application Firewall (WAF) + +> TODO diff --git a/doc/authentication.md b/doc/authentication.md new file mode 100644 index 00000000..e2b39977 --- /dev/null +++ b/doc/authentication.md @@ -0,0 +1,3 @@ +# Authentication + +> TODO diff --git a/doc/resolvers.md b/doc/resolvers.md index 708b76d7..3d56998d 100644 --- a/doc/resolvers.md +++ b/doc/resolvers.md @@ -17,7 +17,7 @@ appSync: - `type`: The Type in the schema this resolver is attached to. Optional if specified in the configuration key. - `field`: The Field in the schema this resolver is attached to. Optional if specified in the configuration key. -- `kind`: The kind of resolver. Can be `UNIT` or `PIPELINE` ([see below](#PIPELINE%20resolvers)). Defaults to `UNIT` +- `kind`: The kind of resolver. Can be `UNIT` or `PIPELINE` ([see below](#PIPELINE-resolvers)). Defaults to `UNIT` - `dataSource`: The name of the [dataSource](dataSoruces.md) this resolver uses. - `maxBatchSize`: The maximum [batch size](https://aws.amazon.com/blogs/mobile/introducing-configurable-batching-size-for-aws-appsync-lambda-resolvers/) to use (only available for AWS Lambda datasoures) - `request`: The request mapping template file name to use for this resolver, or `false` for [direct lambda](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html). Default to `{Type}.{Field}.request.vtl`. From f6132b4f611c20943561200421e38204af69a19a Mon Sep 17 00:00:00 2001 From: bboure Date: Sun, 13 Feb 2022 12:42:12 +0100 Subject: [PATCH 05/16] Add Authentication doc --- README.md | 1 + doc/authentication.md | 99 ++++++++++++++++++++++++++++++++++++++++++- doc/dataSources.md | 4 +- doc/resolvers.md | 6 +-- 4 files changed, 104 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 85cdcbb2..54216a5c 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ appSync: - [DataSources](doc/dataSources.md) - [Resolvers](doc/resolvers.md) +- [Authentication](doc/authentication.md) - [API keys](doc/API-keys.md) - [VTL Template Substitutions](doc/substitutions.md) - [Caching](doc/caching.md) diff --git a/doc/authentication.md b/doc/authentication.md index e2b39977..6ec4ec55 100644 --- a/doc/authentication.md +++ b/doc/authentication.md @@ -1,3 +1,100 @@ # Authentication -> TODO +[Authentication](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html) definitions are found under the `appSync.authentication` (for the default authentication method) and `appSync.additionalAuthenticationProviders` (as an array) for additional ones + +## Quick start + +```yaml +appSync: + authentication: + type: 'API_KEY' + additionalAuthenticationProviders: + - type: 'AMAZON_COGNITO_USER_POOLS' + config: + userPoolId: '123456789' +``` + +## Configuration + +- `type`: The type of authentication. Can be `API_KEY`, `AWS_IAM`, `AMAZON_COGNITO_USER_POOLS`, `AWS_LAMBDA` or `OPENID_CONNECT` +- `config`: The configuration for the provided `type` (See below). + +### API Keys + +Enables the API Key based authentication. See the [API Keys section](API-keys.md) to see how to configure them. + +```yaml +appSync: + authentication: + type: 'API_KEY' +``` + +`config` is not required for this type. + +### IAM + +Allows IAM users ands roles to access the API. + +```yaml +appSync: + authentication: + type: 'AWS_IAM' +``` + +`config` is not required for this type. + +### Cognito + +Allows authentication using a Cognito. + +```yaml +appSync: + authentication: + type: 'AMAZON_COGNITO_USER_POOLS' + config: + userPoolId: '123456789' +``` + +- `userPoolId`: The user pool id to use. +- `awsRegion`: The region where the user pool is located. Default to the stack's region. +- `appIdClientRegex`: An optional regular expression for validating the incoming Amazon Cognito user pool app client ID. + +### OIDC + +Allows users to authenticate against the API using a third-party OIDC auth provider. + +```yaml +appSync: + authentication: + type: 'OPENID_CONNECT' + config: + issuer: 'https://auth.example.com' + clientId: '5fbc318d-5920-48a8-92ea-20d62d16cc60' +``` + +- `issuer`: The issuer of this OIDC config. +- `clientId`: Optional. The client identifier of the Relying party at the OpenID identity provider. This identifier is typically obtained when the Relying party is registered with the OpenID identity provider. You can specify a regular expression so that AWS AppSync can validate against multiple client identifiers at a time. +- `iatTTL`: Optional. The number of milliseconds that a token is valid after it's issued to a user. +- `authTTL`: Optional. The number of milliseconds that a token is valid after being authenticated. + +### Lambda + +Allows custom authentication thoguht Lambda. + +```yaml +appSync: + authentication: + type: 'AWS_LAMBDA' + config: + authorizerResultTtlInSeconds: 300 + function: + timeout: 30 + handler: 'functions/auth.handler' +``` + +- `identityValidationExpression`: Optional. A regular expression for validation of tokens before the Lambda function is called. +- `authorizerResultTtlInSeconds`: Optional. The number of seconds a response should be cached for. The default is 5 minutes (300 seconds). +- `function`: A Lambda function definition as you would define it under the `functions` section of your `serverless.yml` file. +- `functionName`: The name of the function as defined under the `functions` section of the `serverless.yml` file +- `functionAlias`: A specific function alias to use. +- `functionArn`: The function ARN to use. diff --git a/doc/dataSources.md b/doc/dataSources.md index 1a2f5d21..d819630d 100644 --- a/doc/dataSources.md +++ b/doc/dataSources.md @@ -48,10 +48,10 @@ appSync: - `serviceRoleArn`: The service roel ARN for this DataSource. If not provided, a new one will be created. - `iamRoleStatements`: Statements to use for the generated IAM Role. If not provided, default statements will be used. -- `function`: A Lambda function definition as you would define it under the `functions` section of you `serverless.yml file. +- `function`: A Lambda function definition as you would define it under the `functions` section of your `serverless.yml` file. - `functionName`: The name of the function as defined under the `functions` section of the `serverless.yml` file - `functionAlias`: A specific function alias to use -- `functionArn`: The function ARN to use for this DataSoruce. +- `functionArn`: The function ARN to use for this DataSource. ## OpenSearch (ElasticSearch) diff --git a/doc/resolvers.md b/doc/resolvers.md index 3d56998d..de695a78 100644 --- a/doc/resolvers.md +++ b/doc/resolvers.md @@ -93,7 +93,7 @@ Query.user: - `conflictDetection`: Defaults to 'VERSION' - `conflictHandler`: `OPTIMISTIC_CONCURRENCY`, `AUTOMERGE` or `LAMBDA` -- `function`: A Lambda function definition as you would define it under the `functions` section of you `serverless.yml file. +- `function`: A Lambda function definition as you would define it under the `functions` section of your `serverless.yml` file. - `functionName`: When `conflictHandler` is `LAMBDA`, the name of the function as defined under the `functions` section of the `serverless.yml` file -- `functionAlias`: When `conflictHandler` is `LAMBDA`, a specific function alias to use -- `functionArn`: When `conflictHandler` is `LAMBDA`, the function ARN to use for this DataSoruce. +- `functionAlias`: When `conflictHandler` is `LAMBDA`, a specific function alias to use. +- `functionArn`: When `conflictHandler` is `LAMBDA`, the function ARN to use. From 3269e5373c4c62daed6c2b47fd29c7c2a74051bc Mon Sep 17 00:00:00 2001 From: bboure Date: Sun, 13 Feb 2022 12:59:49 +0100 Subject: [PATCH 06/16] Add pipeline functions doc --- README.md | 1 + doc/authentication.md | 2 +- doc/dataSources.md | 4 ++-- doc/pipeline-functions.md | 42 +++++++++++++++++++++++++++++++++++++++ doc/resolvers.md | 11 ++++++---- 5 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 doc/pipeline-functions.md diff --git a/README.md b/README.md index 54216a5c..107b4c50 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ appSync: - [DataSources](doc/dataSources.md) - [Resolvers](doc/resolvers.md) +- [Pipeline Functions](doc/pipeline-functions.md) - [Authentication](doc/authentication.md) - [API keys](doc/API-keys.md) - [VTL Template Substitutions](doc/substitutions.md) diff --git a/doc/authentication.md b/doc/authentication.md index 6ec4ec55..5a691fd9 100644 --- a/doc/authentication.md +++ b/doc/authentication.md @@ -56,7 +56,7 @@ appSync: ``` - `userPoolId`: The user pool id to use. -- `awsRegion`: The region where the user pool is located. Default to the stack's region. +- `awsRegion`: The region where the user pool is located. Defaults to the stack's region. - `appIdClientRegex`: An optional regular expression for validating the incoming Amazon Cognito user pool app client ID. ### OIDC diff --git a/doc/dataSources.md b/doc/dataSources.md index d819630d..8e892d38 100644 --- a/doc/dataSources.md +++ b/doc/dataSources.md @@ -69,7 +69,7 @@ appSync: ### config - `endpoint`: The endpoint url to the OpenSearch domain -- `region`: The region of the OpenSearch domain. Default to the stack's region. +- `region`: The region of the OpenSearch domain. Defaults to the stack's region. - `serviceRoleArn`: The service roel ARN for this DataSource. If not provided, a new one will be created. - `iamRoleStatements`: Statements to use for the generated IAM Role. If not provided, default statements will be used. @@ -117,7 +117,7 @@ appSync: ### config - `databaseName`: The name of the database -- `region`: The region of the RDS HTTP ednpoint. Default to the region of the stack. +- `region`: The region of the RDS HTTP ednpoint. Defaults to the region of the stack. - `awsSecretStoreArn`: The ARN for database credentials stored in AWS Secrets Manager. - `dbClusterIdentifier`: Amazon RDS cluster Amazon Resource Name (ARN). - `schema`: Logical schema name. diff --git a/doc/pipeline-functions.md b/doc/pipeline-functions.md new file mode 100644 index 00000000..02d15fbe --- /dev/null +++ b/doc/pipeline-functions.md @@ -0,0 +1,42 @@ +# Pipeline functions + +When you use `PIPELINE` [resolvers](resolvers.md), you will also need to define the use pipeline functions. You can do so under the `appSync.pipelineFunctions` attribute. + +It's a key-value pair object whose key is the name of the function and the value is its configuration. + +The definition can also be a string in which case it's the [dataSource](dataSources.md) name to use. The other attributes use the default values. + +## Quick start + +```yaml +appSync: + pipelineFunctions: + myFunction: myDataSource + myOtherFunction: + dataSource: myOtherDataSource +``` + +## Configutation + +- `dataSource`: The name of the dataSource to use. +- `description`: An optional description for this pipeline function. +- `request`: The request mapping template file name to use for this resolver, or `false` for [direct lambda](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html). Defaults to `{functionName}.request.vtl`. +- `response`: The request mapping template file name to use for this resolver, or `false` for [direct lambda](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html). Defaults to `{functionName}.response.vtl`. +- `maxBatchSize`: The maximum [batch size](https://aws.amazon.com/blogs/mobile/introducing-configurable-batching-size-for-aws-appsync-lambda-resolvers/) to use (only available for AWS Lambda datasoures) +- `substitutions`: See [VTL template substitutions](substitutions.md) + +## Inline DataSources + +Just like with `UNIT` resolvers, you can [define the dataSource inline](resolvers.md#inline-datasources) in pipeline functions. + +```yaml +appSync: + pipelineFunctions: + myFunction: + dataSource: + type: 'AWS_LAMBDA' + config: + function: + timeout: 30 + handler: 'functions/myFunction.handler' +``` diff --git a/doc/resolvers.md b/doc/resolvers.md index de695a78..4601be01 100644 --- a/doc/resolvers.md +++ b/doc/resolvers.md @@ -2,7 +2,9 @@ All the Resolvers in your AppSync API can be found in serverless.yml under the `appSync.resolvers` property. -Resolvers are defined using key-value pairs where the key can either be an arbitrary name for the resolver or the `type` and `field` in the schema it is attached to separated with a dot (`.`). +Resolvers are defined using key-value pairs where the key can either be an arbitrary name for the resolver or the `type` and `field` in the schema it is attached to separated with a dot (`.`) abd the value is the configuration of the resolver. + +The definition can also be a string in which case it's the [dataSource](dataSources.md) name to use. The other attributes use the default values. ## Quick start @@ -11,6 +13,7 @@ appSync: resolvers: Query.user: dataSource: my-table + Query.users: my-table ``` ## Configuration @@ -20,15 +23,15 @@ appSync: - `kind`: The kind of resolver. Can be `UNIT` or `PIPELINE` ([see below](#PIPELINE-resolvers)). Defaults to `UNIT` - `dataSource`: The name of the [dataSource](dataSoruces.md) this resolver uses. - `maxBatchSize`: The maximum [batch size](https://aws.amazon.com/blogs/mobile/introducing-configurable-batching-size-for-aws-appsync-lambda-resolvers/) to use (only available for AWS Lambda datasoures) -- `request`: The request mapping template file name to use for this resolver, or `false` for [direct lambda](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html). Default to `{Type}.{Field}.request.vtl`. -- `response`: The request mapping template file name to use for this resolver, or `false` for [direct lambda](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html). Default to `{Type}.{Field}.response.vtl`. +- `request`: The request mapping template file name to use for this resolver, or `false` for [direct lambda](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html). Defaults to `{Type}.{Field}.request.vtl`. +- `response`: The request mapping template file name to use for this resolver, or `false` for [direct lambda](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html). Defaults to `{Type}.{Field}.response.vtl`. - `substitutions`: See [VTL template substitutions](substitutions.md) - `caching`: [See below](#Caching) - `sync`: [See blow](#Sync) ## PIPELINE resolvers -When `kind` is `PIPELINE`, you must specify the pipeline functions to use +When `kind` is `PIPELINE`, you can specify the [pipeline function](pipeline-functions.md) names to use: ```yaml appSync: From 7a62c8189a2dae5cc33100fa81dc3df7367efff1 Mon Sep 17 00:00:00 2001 From: bboure Date: Sun, 13 Feb 2022 15:39:58 +0100 Subject: [PATCH 07/16] Add general doc --- README.md | 3 + doc/custom-domain.md | 3 + doc/general-config.md | 192 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 doc/custom-domain.md create mode 100644 doc/general-config.md diff --git a/README.md b/README.md index 107b4c50..6a48ef3e 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,16 @@ appSync: ## Concepts +- [General config](doc/general-config.md) - [DataSources](doc/dataSources.md) - [Resolvers](doc/resolvers.md) - [Pipeline Functions](doc/pipeline-functions.md) - [Authentication](doc/authentication.md) - [API keys](doc/API-keys.md) +- [Custom Domain](doc/custom-domain.md) - [VTL Template Substitutions](doc/substitutions.md) - [Caching](doc/caching.md) +- [Web Application Firewall (WAF)](doc/WAF.md) # Contributing diff --git a/doc/custom-domain.md b/doc/custom-domain.md new file mode 100644 index 00000000..c497056a --- /dev/null +++ b/doc/custom-domain.md @@ -0,0 +1,3 @@ +# Custom Domains + +> TODO diff --git a/doc/general-config.md b/doc/general-config.md new file mode 100644 index 00000000..a7137ba6 --- /dev/null +++ b/doc/general-config.md @@ -0,0 +1,192 @@ +# General configuration + +## Quick start + +```yaml +service: my-app + +plugins: + - serverless-appsync-plugin + +provider: + name: aws + +appSync: + name: my-api + + authentication: + type: API_KEY + + apiKeys: + - name: myKey + expiresAfter: 1M + + dataSources: + my-table: + type: AMAZON_DYNAMODB + description: 'My table' + config: + tableName: my-table + + resolvers: + Query.user: + dataSource: my-table +``` + +## Configuration + +- `name`: The name of this AppSync API +- `schema`: The filename of the schema file. Defaults to `schema.graphql`. [Read more](#Schema) +- `authentication`: See [Authentication](authentication.md) +- `additionalAuthenticationProviders`: See [Authentication](authentication.md) +- `apiKeys`: See [API Keys](api-keys.md) +- `domain`: See [Custom domains](custom-domain.md) +- `dataSources`: See [DataSources](dataSources.md) +- `resolvers`: See [Resolvers](resolvers.md) +- `pipelineFunctions`: See [Pipeline functions](pipeline-functions.md) +- `substitutions`: See [Substitutions](substitutions.md) +- `caching`: See [Cacing](caching.md) +- `waf`: See [Web Application Firefall](WAF.md) +- `log`: See [Logs](#Logs) +- `defaultMappingTemplates`: + - `request`: Optional. A default request mapping template filename for all resolvers. + - `response`: Optional. A default response mapping template filename for all resolvers. +- `mappingTemplatesLocation`: + - `resolvers`: The location where to find resolver mapping templates, relative to the service path. Defaults to `mapping-templates`. + - `pipelineFunctions`: The location where to find pipeline functions mapping templates. Defaults to the same value as `mappingTemplatesLocation.resolvers`. +- `xrayEnabled`: Boolean. Enable or disable X-Ray tracing. +- `tags`: A key-value pair for tagging this AppSync API + +## Schema + +There are different ways to define your schema. By default the schema is found in the `schema.graphql` file. The path of the file is relative to the service directory (where your `serverless.yml` file is). + +```yaml +appSync: + name: my-api + schema: 'mySchema.graphql' +``` + +### Multiple files + +You can specify more than one file as (an array). This is useful if you want to organize your schema into several files. + +```yaml +appSync: + name: my-api + schema: + - 'schemas/user.graphql' + - 'schemas/posts.graphql' +``` + +You can also specify glob expressions to avoid specifying each individual file. + +```yaml +appSync: + name: my-api + schema: 'schemas/*.graphql' # include all graphql files in the `schemas` directory +``` + +### Schema stitching + +All the schema files will be merged together before it is sent to AppSync. If types are present (extended) in several files, you will need to use [Object extension](https://spec.graphql.org/October2021/#sec-Object-Extensions) + +```graphql +# base.graphql + +# You must create the types before you can extend them. +type Query +type Mutation +``` + +```graphql +# users.graphql + +extend type Query { + getUser(id: ID!): User! +} + +extend type Mutation { + createUser(user: UserInput!): User! +} + +type User { + id: ID! + name: String! +} +``` + +```graphql +# posts.graphql + +extend type Query { + getPost(id: ID!): Post! +} + +extend type Mutation { + createPost(post: PostInput!): Post! +} + +type Post { + id: ID! + title: String + author: User! +} +``` + +This will result into the folowing AppSync schema: + +```graphql +type Query { + getUser(id: ID!): User! + getPost(id: ID!): Post! +} + +type Mutation { + createUser(user: UserInput!): User! + createPost(post: PostInput!): Post! +} + +type User { + id: ID! + name: String! +} + +type Post { + id: ID! + title: String + author: User! +} +``` + +### Limitations and compatibility + +AppSync is currently using an older version of the [Graphql Specs](https://spec.graphql.org/). +This plugin intends to use modern schemas for future-proofing. Incompatibilities will wither be dropped or attempted to be fixed. + +**Descriptions** + +[Descriptions](https://spec.graphql.org/October2021/#sec-Descriptions) with three duble quotes (`"""`) are not supported by AppSync and will be removed. + +Old-style descriptions (using `#`) are supported by AppSync but will be removed by the [stitching procedure](#schema-stitching) which does not support them\*. Comments are also not supported on [enums](https://spec.graphql.org/October2021/#sec-Enums) by AppSync. + +\* If you want to retain `#` comments, the workwround is to skip schema stiching by putting your whole schema into one sigle file. + +**Multiple interfaces** + +Types can implement multiple [interfaces](https://spec.graphql.org/October2021/#sec-Interfaces) using an ampersand `&` in GraphQL, but AppSync uses the old coma (`,`) separator. `&` is the only separator suported by this plugin, but it will automatically be repalced by AppSync with a `,`. + +## Logs + +```yaml +appSync: + name: my-api + log: + level: ERROR + logRetentionInDays: 14 +``` + +- `level`: `ERROR`, `NONE`, or `ALL` +- `excludeVerboseContent`: Boolean. Exclude or not verbose content. +- `logRetentionInDays`: Number of days to retain the logs. +- `roleArn`: Optional. The role ARN to use for AppSync to write into CloudWatch. If not specified, a new role is created by default. From 0bfd0c7b8ff9528143b1f8fc3d669e9a013a104e Mon Sep 17 00:00:00 2001 From: bboure Date: Sun, 13 Feb 2022 16:03:10 +0100 Subject: [PATCH 08/16] Add substitution doc --- doc/substitutions.md | 62 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/doc/substitutions.md b/doc/substitutions.md index 9f383a58..e0271433 100644 --- a/doc/substitutions.md +++ b/doc/substitutions.md @@ -1,3 +1,63 @@ # Substitutions -> TODO +Substitutions is a feature that allows you to replace some variables in your VTL mapping templates with dynamic values. + +They are usually useful to replace parts of the template with resource names or ARNs coming from your IaC, such as a DynamoDB table names, for example; or the stage name, region name, etc. + +## Quick start + +```yaml +appSync: + name: my-api + substitutions: #global substitutions + postsTable: !Ref Posts + region: ${AWS::Region} + foo: bar + + resolvers: + Query.user: + dataSource: my-table + substitutions: #resolver substitutions + someVariable: someValue +``` + +```vtl +#set($postsdata = []) +#foreach($item in ${ctx.args.posts}) + $util.qr($postsdata.add($util.dynamodb.toMapValues($item))) +#end +{ + "version" : "2018-05-29", + "operation" : "BatchPutItem", + "tables" : { + "${postsTable}": $utils.toJson($postsdata) + } +} +``` + +## Usage + +Substutitions are defined as key-value pairs under the `appSync.substitutions`, `appSync.resolvers.[resolverName].substitutions` or `appSync.pipelineFunctions.[functionName].substitutions` attributes. + +Global substitutions are available globally to all mapping templates. Resovler and Pipeline funciton substitutions are only available where they are defined. Resolver and Pipeline function substitutions take precedence over global substitutions (values will be overwritten). + +Once defined, you can then use them within the mapping templates as if they were VTL variables. At deployment time, variables will be substituted with their corresponding value. + +```yaml +appSync: + name: my-api + substitutions: + postsTable: !Ref Posts +``` + +In the mapping template: + +```vtl +{ + "version" : "2018-05-29", + "operation" : "BatchPutItem", + "tables" : { + "${postsTable}": [...] + } +} +``` From a0d3a79fe44577134303191bd579d072d198d03c Mon Sep 17 00:00:00 2001 From: bboure Date: Sun, 13 Feb 2022 16:17:03 +0100 Subject: [PATCH 09/16] Add caching doc --- doc/caching.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/doc/caching.md b/doc/caching.md index 66954c56..25f00f27 100644 --- a/doc/caching.md +++ b/doc/caching.md @@ -1,3 +1,28 @@ # Caching -> TODO +AppSync supports [server-side data caching](https://docs.aws.amazon.com/appsync/latest/devguide/enabling-caching.html). You can find the caching configuration under the `appSync.caching` attribute. + +## Quick start + +```yaml +appSync: + name: my-api + caching: + behavior: 'PER_RESOLVER_CACHING' + type: 'SMALL' + ttl: 3600 + atRestEncryption: false + transitEncryption: false +``` + +## Configuration + +- `behavior`: `FULL_REQUEST_CACHING` or `PER_RESOLVER_CACHING` +- `type`: The type of the Redis instance. `SMALL`, `MEDIUM`, `LARGE`, `XLARGE`, `LARGE_2X`, `LARGE_4X`, `LARGE_8X`, `LARGE_12X`. Defaults to `SMALL` +- `ttl`: The default TTL of the cache in seconds. Defaults to `3600`. Maximum is `3600` +- `atRestEncryption`: Boolean. Whether to encrypt the data at rest. Defaults to `false` +- `transitEncryption`: Boolean. Whether to encrypt the data in transit. Detaults to `false` + +## Per resolver caching + +See [Resolver cacing](resolvers.md#caching) From 2b4161cdd757101e653801f049ea46903b1099bf Mon Sep 17 00:00:00 2001 From: bboure Date: Sun, 13 Feb 2022 16:35:53 +0100 Subject: [PATCH 10/16] Add variables doc --- README.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a48ef3e..3a23af2a 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ appSync: tableName: ${sls:stage}-data ``` -## Concepts +## Configuration - [General config](doc/general-config.md) - [DataSources](doc/dataSources.md) @@ -56,6 +56,35 @@ appSync: - [Caching](doc/caching.md) - [Web Application Firewall (WAF)](doc/WAF.md) +## Variables + +This plugin exports some handy serverless variables that you can use in your yml files to reference some values generated by CloudFormation: + +- `${appsync:id}`: The id of the AppSync API +- `${appsync:url}`: The ARN of the AppSync API +- `${appsync:arn}`: The ARN of the AppSync API +- `${appsync:apiKey.[NAME]}`: An API key + +Example: + +```yaml +provider: + environment: + APPSYNC_ID: ${appsync:id} + APPSYNC_ARN: ${appsync:arn} + APPSYNC_URL: ${appsync:url} + APPSYNC_API_KEY: ${appsync:apiKey.myKey} + +appSync: + name: my-api + + authentication: + type: API_KEY + + apiKeys: + - name: myKey +``` + # Contributing If you have any questions, issue, feature request, please feel free to [open an issue](/issues/new). From d7c293d1f29880e02b93e5b70cec4403f92331c3 Mon Sep 17 00:00:00 2001 From: bboure Date: Sun, 13 Feb 2022 17:11:54 +0100 Subject: [PATCH 11/16] Add Custom Domain doc --- doc/commands.md | 7 +++++ doc/custom-domain.md | 75 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 doc/commands.md diff --git a/doc/commands.md b/doc/commands.md new file mode 100644 index 00000000..41cb0da6 --- /dev/null +++ b/doc/commands.md @@ -0,0 +1,7 @@ +# Commands + +> TODO + +## Domain + +> TODO diff --git a/doc/custom-domain.md b/doc/custom-domain.md index c497056a..409d3a5c 100644 --- a/doc/custom-domain.md +++ b/doc/custom-domain.md @@ -1,3 +1,76 @@ # Custom Domains -> TODO +Appsync supports associating your API to a [custom domain](https://aws.amazon.com/blogs/mobile/introducing-custom-domain-names-for-aws-appsync-apis/). + +The configuration for custom domain can be found under the `appSync.domain` attribute. + +## Quick start + +```yaml +appSync: + name: my-api + domain: + name: api.example.rehab + certificateArn: arn:aws:acm:us-east-1:123456789:certificate/7e14a3b2-f7a5-4da5-8150-4a03ede7158c +``` + +## Configuration + +- `name`: Required. The fully qualified domain name to assiciate this API to. +- `certificateArn`: Required. A valid certificate ARN for the domain name. +- `useCloudFormation`: Boolean. Optional. Wheter to use CloudFormation or CLI commands to manage the domain. See [Using CloudFormation or CLI commands](#using-cloudformation-vs-the-cli-commands). Defaults to `true`. +- `retain`: Boolean. Optional. Whether to retain the domain and domain association when they are removed from CloudFormation. Defaults to `false`. See [Ejecting from CloudFormation](#ejecting-from-cloudformation) +- `route53`: See [Route53 configuration](#route53-configuration). Defaults to `true` + +## Certificate + +This plugin does not provide any way to generate or manage your domain certificate. This is usually a set-and-forget kind of operation. You still need to provide its ARN and it must be a valid certificate for the provided domain name. + +## Route 53 configuration + +When `true`, This plugin will try to create a Route53 CNAME entry in the Hosted Zone corresponding to the domain. This plugin will do its best to fint the best Hosted Zone that matches the domain name. + +When `false`, no CNAME record will be created. + +You can also specify which hosted zone you want to create the record into: + +- `hostedZoneName`: The specific hosted zone name where to create the CNAME record. +- `hostedZoneId`: The specific hosted zone id where to create the CNAME record. + +example: + +```yaml +appSync: + domain: api.example.com + route53: + hostedZoneId: ABCDEFGHIJ +``` + +## Using CloudFormation vs the CLI commands + +There are two ways to manage your custom domain: + +- using CloudFormation +- using the CLI [commands](commands.md#domain) + +If `useCloudFormation` is set to `true`, the domain and domain association will be automatically created and managed by CloudFormation. However, in some cases you might not want that. + +For example, if you wanted to use blue/green deployments, you might need to associate APIs from different stacks to the same domain. In that case, the only way to do it is to use the CLI. + +For more information about managing domains with the CLI, see the [Commands](commands.md#domain) + +## Ejecting from CloudFormation + +If you started to manage your domain through CloudFormation and want to eject from it, follow the following steps: + +1. Set `retain` to `true` + +To avoid breaking your API if it is already on production, you first need to tell CloudFormation to retain the domain and any association with an existing API. For that, you can set the `retain` attribute to `true`. **You Will then need to deploy one more time to make sure that CloudFormation takes the change into account.** + +2. Set `useCloudFormation` to `false` + +You can now set `useCloudFormation` to `false` and **deploy again**. The domain and domain association resources will be removed from the CloudFormation template, but the resources will be retained (see point 1.) + +3. Manage your domain using the CLI + +You can now manage your domain using the CLI [commands](commands.md#domain) From 3842e8546d95249152db3a4bda06f7df55d8ea48 Mon Sep 17 00:00:00 2001 From: bboure Date: Sun, 13 Feb 2022 17:54:38 +0100 Subject: [PATCH 12/16] Add command doc --- doc/caching.md | 4 ++ doc/commands.md | 139 ++++++++++++++++++++++++++++++++++++++++++++++-- src/index.ts | 16 +++++- 3 files changed, 154 insertions(+), 5 deletions(-) diff --git a/doc/caching.md b/doc/caching.md index 25f00f27..2a1d9912 100644 --- a/doc/caching.md +++ b/doc/caching.md @@ -26,3 +26,7 @@ appSync: ## Per resolver caching See [Resolver cacing](resolvers.md#caching) + +## Flushing the cache + +You can use the [flush-cache command](commands.md#flush-cache) to easily flush the cache. diff --git a/doc/commands.md b/doc/commands.md index 41cb0da6..bf6353e3 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -1,7 +1,140 @@ # Commands -> TODO +This plugin provides some useful commands to explore and manage your API. -## Domain +## `validate-schema` -> TODO +This commands allows you to validate your GraphQL schema. + +```bash +sls appsync validate-schema +``` + +## `get-introspection` + +Allows you to extract the introspection of the schema as a JSON or SDL. + +**Options** + +- `--format` or `-f`: the format in which to extract the schema. `JSON` or `SDL`. Defaults to `JSON` +- `--output` or `-o`: a file where to output the schema. If not specified, prints to stdout + +```bash +sls appsync get-introspection +``` + +## `flush-cache` + +If your API uses the server-side [Caching](caching.md), this command flushes the cache. + +```bash +sls appsync flush-cache +``` + +## `console` + +Opens a new browser tab to the AWS console page of this API. + +```bash +sls appsync console +``` + +## `cloudwatch` + +Opens a new browser tab to the CloudWatch logs page of this API. + +```bash +sls appsync cloudwatch +``` + +## `logs` + +Outputs the logs of the AppSync API to stdout. + +**Options** + +- `--startTime`: Starting time. You can use human-friendly relative times. e.g. `30m`, `1h`, etc. Default: `10m` (10 minutes ago) +- `--tail` or `-t`: Keep streaming new logs. +- `--interval` or `-i`: Tail polling interval in milliseconds. Default: `1000`. +- `--filter` or `-f`: A filter pattern to apply to the logs stream. + +```bash +sls appsync logs --filter '86771d0c-c0f3-4f54-b048-793a233e3ed9' +``` + +## `domain` + +Manage the domain for this AppSync API. + +## Create the domain + +Before associating a domain to an API, you must first create it. You can do so using the following command. + +**Options** + +- `--quiet` or `-q`: Don't return an error if the operation fails +- `--stage`: The stage to use + +```bash +sls appsync-domain create +``` + +## Delete the domain + +Deletes a domain from AppSync. + +**Options** + +- `--quiet` or `-q`: Don't return an error if the operation fails +- `--yes` or `-y`: Automatic yes to prompts +- `--stage`: The stage to use + +```bash +sls appsync-domain delete +``` + +If an API is associated to it, you will need to [disassociate](#disassociate-the-api-from-the-domain) it first. + +## Create a route53 record + +If you use Route53 for your hosted zone, you can also create the required CNAME record for your custom domain. + +- `--quiet` or `-q`: Don't return an error if the operation fails +- `--stage`: The stage to use + +```bash +sls appsync-domain create-record +``` + +## Delete the route53 record + +- `--quiet` or `-q`: Don't return an error if the operation fails +- `--yes` or `-y`: Automatic yes to prompts +- `--stage`: The stage to use + +```bash +sls appsync-domain delete-record +``` + +## Associate the API to the domain + +Associate the API in this stack to the domain. + +- `--quiet` or `-q`: Don't return an error if the operation fails +- `--stage`: The stage to use + +```bash +sls appsync-domain assoc --stage dev +``` + +You can associate an API to a domain that already has another API attached to it. The old API will be replaced by the new one. + +## Disassociate the API from the domain + +- `--quiet` or `-q`: Don't return an error if the operation fails +- `--yes` or `-y`: Automatic yes to prompts +- `--stage`: The stage to use + +```bash +sls appsync-domain disassoc --stage dev +``` diff --git a/src/index.ts b/src/index.ts index 400bd50f..b5d26c18 100644 --- a/src/index.ts +++ b/src/index.ts @@ -147,7 +147,7 @@ class ServerlessAppsyncPlugin { lifecycleEvents: ['run'], }, logs: { - usage: 'Output the logs of the AppSync API', + usage: 'Output the logs of the AppSync API to stdout', lifecycleEvents: ['run'], options: { startTime: { @@ -168,7 +168,7 @@ class ServerlessAppsyncPlugin { type: 'string', }, filter: { - usage: 'A filter pattern to apply', + usage: 'A filter pattern to apply to the logs', shortcut: 'f', required: false, type: 'string', @@ -200,6 +200,12 @@ class ServerlessAppsyncPlugin { required: false, type: 'boolean', }, + yes: { + usage: 'Automatic yes to prompts', + shortcut: 'y', + required: false, + type: 'boolean', + }, }, }, 'create-record': { @@ -224,6 +230,12 @@ class ServerlessAppsyncPlugin { required: false, type: 'boolean', }, + yes: { + usage: 'Automatic yes to prompts', + shortcut: 'y', + required: false, + type: 'boolean', + }, }, }, assoc: { From c4377f2ccbcc2288bfa7ffa49415755a33acb794 Mon Sep 17 00:00:00 2001 From: bboure Date: Sun, 13 Feb 2022 17:54:45 +0100 Subject: [PATCH 13/16] Complete domain doc --- doc/custom-domain.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/doc/custom-domain.md b/doc/custom-domain.md index 409d3a5c..c9168af4 100644 --- a/doc/custom-domain.md +++ b/doc/custom-domain.md @@ -74,3 +74,28 @@ You can now set `useCloudFormation` to `false` and **deploy again**. The domain 3. Manage your domain using the CLI You can now manage your domain using the CLI [commands](commands.md#domain) + +## Domains per stage + +You can use different domains by stage easily thanks to [Serverless Framework Stage Parameters](https://www.serverless.com/framework/docs/guides/parameters) + +```yaml +params: + prod: + domain: api.example.com + domainCert: arn:aws:acm:us-east-1:123456789:certificate/7e14a3b2-f7a5-4da5-8150-4a03ede7158c + + staging: + domain: qa.example.com + domainCert: arn:aws:acm:us-east-1:123456789:certificate/61d7d798-d656-4630-9ff9-d77a7d616dbe + + default: + domain: ${sls:stage}.example.com + domainCert: arn:aws:acm:us-east-1:379730309663:certificate/44211071-e102-4bf4-b7b0-06d0b78cd667 + +appSync: + name: my-api + domain: + name: ${param:domain} + certificateArn: ${param:domainCert} +``` From 01c48b74f06cdc477c08256b4aba2fb47cc49eab Mon Sep 17 00:00:00 2001 From: bboure Date: Sun, 13 Feb 2022 18:49:01 +0100 Subject: [PATCH 14/16] Add WAF doc --- doc/WAF.md | 190 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 189 insertions(+), 1 deletion(-) diff --git a/doc/WAF.md b/doc/WAF.md index 3c1a6c09..f4fd44ac 100644 --- a/doc/WAF.md +++ b/doc/WAF.md @@ -1,3 +1,191 @@ # Web Application Firewall (WAF) -> TODO +AppSync [supports WAF](https://aws.amazon.com/blogs/mobile/appsync-waf/). WAF is an Application Firewall that helps you protect your API against common web exploits. + +This plugin comes with some handy pre-defined rules that you can enable in just a few lines of code. + +You can configure WAF rules under the `appSync.waf` attribute. + +## Quick start + +```yaml +appSync: + name: my-api + waf: + enabled: true + defaultAction: 'Allow' + rules: + - throttle + - disableIntrospection +``` + +## Configuration + +- `enabled`: Boolean. Enable or disable WAF. Defaults to `true` when `appSync.waf` is defined. +- `name`: Optional. The name of this WAF instance. Defaults to the name of your API. +- `defaultAction`: Optional. The default action if a request does not match a rule. `Allow` or `Block`. Defaults to `Allow`. +- `description`: A description for this WAF instance. +- `visibilityConfig`: Optional. A [visibility config](https://docs.aws.amazon.com/waf/latest/APIReference/API_VisibilityConfig.html) for this WAF + - `name`: Metric name + - `cloudWatchMetricsEnabled`: A boolean indicating whether the associated resource sends metrics to Amazon CloudWatch + - `sampledRequestsEnabled`: A boolean indicating whether AWS WAF should store a sampling of the web requests that match the rule +- `rules`: An array of [rules](#rules). + +## Rules + +### Configuration + +Common configuration to all rules: + +- `name`: The name of the rule +- `action`: How this rule should handle the incoming request. `Allow` or `Deny`. Defaults to `Allow`. +- `priority`: The priority of this rule. See [Rules Priority](#rules-priority) +- `visibilityConfig`: The [visibility config](https://docs.aws.amazon.com/waf/latest/APIReference/API_VisibilityConfig.html) for this rule. + +### Throttling + +Throttling will disallow requests coming from the same ip address when a limit is reached within a 5-minutes period. It corresponds to a rules with a [RateBasedStatement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-ratebasedstatement.html). + +Examples: + +```yml +waf: + enabled: true + rules: + - throttle # limit to 100 requests per 5 minutes period + - throttle: 200 # limit to 200 requests per 5 minutes period + - throttle: + limit: 200 + priority: 10 + aggregateKeyType: FORWARDED_IP + forwardedIPConfig: + headerName: 'X-Forwarded-For' + fallbackBehavior: 'MATCH' +``` + +#### Configuration + +See the [CloudFormation documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-ratebasedstatement.html) + +- `aggregateKeyType`: `IP` or `FORWARDED_IP` +- `limit`: The limit of requests in a 5-minutes window for the same IP address. +- `forwardedIPConfig`: [forwardedIPConfig](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-forwardedipconfiguration.html) +- `scopeDownStatement`: [WebACL Statement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-statement.html) + +### Disable Introspection + +Sometimes, you want to disable introspection to disallow untrusted consumers to discover the structure of your API. + +```yml +waf: + enabled: true + rules: + - disableIntrospection # disables introspection for everyone + - disableIntrospection: # using custom configuration + name: Disable introspection + priority: 200 +``` + +### Custom rules + +You can also specify custom rules. For more info on how to define a rule, see the [Cfn documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-rule.html) + +Example: + +```yml +waf: + enabled: true + defaultAction: Block + rules: + # Only allow US users + - action: Allow + name: UsOnly + statement: + GeoMatchStatement: + CountryCodes: + - US +``` + +### Per API Key rules + +In some cases, you might want to enable a rule for a given API key only. You can specify `wafRules` under the `appSync.apiKeys` attribute. The rules will apply only to that API key. + +```yml +apiKeys: + - name: MyApiKey + expiresAfter: 365d + wafRules: + - throttle # throttles this API key + - disableIntrospection # disables introspection for this API key +``` + +Adding a rule to an API key without any _statement_ will add a _match-all_ rule for that key (all requests will match that rule). This is usefull for example to exclude API keys from global rules. In that case, you need to make sure to attribute a higher priority to that rule. + +Example: + +- Block all requests by default, except in the US. +- The `WorldWideApiKey` API key should be excluded from that rule. + +```yml +appSync: + waf: + enabled: true + defaultAction: Block # Block all by default + rules: + # allow US requests + - action: Allow + name: UsOnly + priority: 5 + statement: + geoMatchStatement: + countryCodes: + - US + apiKeys: + - name: Key1 # no rule is set, the global rule applies (US only) + - name: Key1 # no rule is set, the global rule applies (US only) + - name: WorldWideApiKey + wafRules: + - name: WorldWideApiKeyRule + action: Allow + priority: 1 # Since priority is higher than 5, all requests will be Allowed +``` + +### Rules priority + +The priorities don't need to be consecutive, but they must all be different. + +Setting a priority to the rules is not required, but recommended. If you don't set priority, it will be automatically attributed and sequentially incremented according to the following rules: + +First the global rules (under `appSync.waf.rules`), in the order that they are defined, then the API key rules, in order of the API keys and their rules. + +Auto-generated priorities start at 100. This gives you som room (0-99) to add other rules that should get a higher priority, if you need to. + +For more info about how rules are executed, pease refer to [the documentation](https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-processing.html) + +Example: + +```yml +appSync: + waf: + enabled: true + rules: + - name: Rule1 + # (no-set) Priority = 100 + - name: Rule2 + priority: 5 # Priority = 5 + - name: Rule3 + # (no-set) Priority = 101 + apiKeys: + - name: Key1 + wafRules: + - name: Rule4 + # (no-set) Priority = 102 + - name: Rule5 + # (no-set) Priority = 103 + - name: Key + wafRules: + - name: Rule6 + priority: 1 # Priority = 1 + - name: Rule7 + # (no-set) Priority = 104 +``` From 4d9cfe01091cbb16e2b51e4bddc099d0c9c19ba0 Mon Sep 17 00:00:00 2001 From: bboure Date: Sun, 13 Feb 2022 20:52:46 +0100 Subject: [PATCH 15/16] Add migration Guide --- README.md | 8 +- doc/dataSources.md | 12 ++- doc/resolvers.md | 10 ++ doc/upgrading-from-v1.md | 227 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 254 insertions(+), 3 deletions(-) create mode 100644 doc/upgrading-from-v1.md diff --git a/README.md b/README.md index 3a23af2a..30de2be5 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ appSync: tableName: ${sls:stage}-data ``` -## Configuration +# Configuration - [General config](doc/general-config.md) - [DataSources](doc/dataSources.md) @@ -56,7 +56,7 @@ appSync: - [Caching](doc/caching.md) - [Web Application Firewall (WAF)](doc/WAF.md) -## Variables +# Variables This plugin exports some handy serverless variables that you can use in your yml files to reference some values generated by CloudFormation: @@ -85,6 +85,10 @@ appSync: - name: myKey ``` +# Upgrading from v1 + +To upgrade form v1 of this plugin, [follow this guide](Upgrading from v1)[doc/upgrading-from-v1.md] + # Contributing If you have any questions, issue, feature request, please feel free to [open an issue](/issues/new). diff --git a/doc/dataSources.md b/doc/dataSources.md index 8e892d38..2c8f763f 100644 --- a/doc/dataSources.md +++ b/doc/dataSources.md @@ -4,7 +4,7 @@ All the DataSources in your AppSync API can be found in serverless.yml under the ## DynamoDB -### quick start +### Quick start ```yaml appSync: @@ -132,3 +132,13 @@ appSync: api: type: 'NONE' ``` + +# Organize your data sources + +You can define your data sources into several files for organizational reasons. You can pass each file into the `dataSources` attribute as an array. + +```yaml +dataSources: + - ${file(appsync/dataSources/users.yml)} + - ${file(appsync/dataSources/posts.yml)} +``` diff --git a/doc/resolvers.md b/doc/resolvers.md index 4601be01..f4da8368 100644 --- a/doc/resolvers.md +++ b/doc/resolvers.md @@ -100,3 +100,13 @@ Query.user: - `functionName`: When `conflictHandler` is `LAMBDA`, the name of the function as defined under the `functions` section of the `serverless.yml` file - `functionAlias`: When `conflictHandler` is `LAMBDA`, a specific function alias to use. - `functionArn`: When `conflictHandler` is `LAMBDA`, the function ARN to use. + +# Organize your resolvers + +You can define your data sources into several files for organizational reasons. You can pass each file into the `dataSources` attribute as an array. + +```yaml +resolvers: + - ${file(appsync/resolvers/users.yml)} + - ${file(appsync/resolvers/posts.yml)} +``` diff --git a/doc/upgrading-from-v1.md b/doc/upgrading-from-v1.md new file mode 100644 index 00000000..0bf1d092 --- /dev/null +++ b/doc/upgrading-from-v1.md @@ -0,0 +1,227 @@ +# Upgrading from v1 to v2 + +`v2` of this plugin tries to keep backward compatibility with `v1` from a CloudFormation perspective (i.e. Logical names are maintained). This pretends to make migration from `v1` possible. However, the API has changed quite a bit. + +This page will guide you through the process of migrating existing stacks to the newer version. + +## Breaking changes and dropped features + +There are a few breaking changes that you need to be aware of. + +### Support for Serverless Framework v3 only + +`v2` only supports the new [Serverless Frmework v3](https://www.serverless.com/blog/serverless-framework-v3-is-live). You will need to upgrade to [SF v3 first](https://www.serverless.com/framework/docs/guides/upgrading-v3). + +### Single API only + +Support for multiple APIs has been dropped in v2. There are several reasons for this: + +- **It was an edge case**: Most users would only have one API per Stack +- **It is probably bad practice**: Different APIs should be considered different micro-services and be deplopyed separately. +- **It is not supported by the Serverless Framework for API Gateway**: SF does not support multiple API Gateways in a single Stack. Why should this plugin do for AppSync? + +If you only have one API in your current stack, you should not worry about this change. You do need to make sure that you do not define your `appSync` API as an array (even with one element only), though. + +```yaml +custom: + appSync: + name: my-api # ✅ Compatible with v2 + + appSync: + - name: my-api # ❌ Incompatible with v2 +``` + +**Workaround** + +Place your APIs into defferent stacks. Unfortunately, this WILL require **the replacement of the APIs**. You can probably use [custom domains](custom-domain.md) to workaround that, if that's an option. + +### Graphiql "playground" + +The `graphql-playground` command which started a graphiql server pointing to the AppSync API has been removed. + +**Workaround** + +Use other solutions such as [Insomnia](https://insomnia.rest/), or [Postman](https://www.postman.com/) + +### Support for existing APIs + +`v1` offered a way to define some resoruce such as DataSources, Resolvers, etc. on an existing API (that was previously created using other mechanisms, for example manually). `v2` does no longer offer that possibility. It adds complexity, can behave unexpectidly and is probably a bad practice too. Prefer defining your whole API under the same stack. + +**Workaround** + +Define your API completely in the same stack. This might require **the replacement of the API**. You can use [custom domains](custom-domain.md) to workaround that, if that's an option. + +### Output variables + +`v1` exported some values to the stack Output by default. This is no longer the case. Instead, prefer using the new exported [variables](../README.md#variables). + +**Workaround** + +You still can export those values if you want but you'll have to doit explicitely yourself: + +```yaml +resources: + Outputs: + GraphQlApiUrl: + Value: ${appsync:url} +``` + +## Upgrade instructions + +⚠️ This guide tries to give instructions as clear as possible on how to upgrade from `v1` to `v2`. But, please note that there is **no guarantee** that everything will keep working as expected. Each case is unique and this guide might miss some subtilities that are applicable to you only. It is your responsibility to test your changes and check that they don't break your stack (e.g. in a dev/test environment). + +📚 I also strongly advice that you get familiar with [the new API](general-config.md) first as most changes will probably be obvious and straight forward. Check the documentation for every feature you are using to see if and how they have changed. + +💡 `v2` now validates the configuration. If anything is wrong, you should get a warning. I recommend that you enable the [service configuration validation](https://www.serverless.com/framework/docs/configuration-validation) in your `serverless.yml` and set it to `error`. Note that this does not guarantee to catch all the issues. + +```yaml +service: my-app + +configValidationMode: error +``` + +🙋‍♂️ If you find information that is inaccurate or incomplete in this guide, please [open a PR](https://github.com/sid88in/serverless-appsync-plugin/compare) to fix it 🙏. + +**Path of the appSync configuration** + +The first significant change is that you must now define your API under the `appSync` attribute directly at the root of your `serverless.yml` file, as opposed to placing it under `custom` in `v1`. + +Just move your configuration up one level. + +Example: + +```yaml +custom: + appSync: + name: my-api +``` + +becomes + +```yaml +appSync: + name: my-api +``` + +**Renamed attributes** + +Some attributes have been renamed for clarity. Here are the most important ones. + +- rename `mappingTemplates` to `resolvers` +- rename `functionConfigurations` to `pipelineFunctions` +- rename `logConfig` to `log` +- rename `wafConfig` to `waf` + +**DataSources** + +[DataSources](dataSources.md) are now defined as a key-value pair object. In `v1`, you passed them as an array. Replace the array with a key-value pair object where the key is what you used to have under `name`. + +Example + +```yaml +dataSources: + - type: AMAZON_DYNAMODB + name: myDatasource + config: + tableName: my-table +``` + +becomes: + +```yaml +dataSources: + myDatasource: + type: AMAZON_DYNAMODB + config: + tableName: my-table +``` + +**Resolvers** + +[Resolvers](resolvers.md) are now defined as a key-value pair object. In `v1` you passed them as an array. Replace the array with a key-value pair object. You can use any value as the key, or use the [`Type.field` shortcut](resolvers.md). + +Also rename `mappingTemplates` to `resolvers` + +Example: + +```yaml +mappingTemplates: + - type: Query + field: getUser + dataSource: myDaaSource +``` + +becomes + +```yaml +resolvers: + Query.getUser: + dataSource: myDaaSource +``` + +**Pipeline functions** + +[Pipeline functions](pipeline-functions.md) have moved from `functionConfigurations` to `pipelineFunctions`. Just like Resolvers and datasources, `pipelineFunction` expects a key-value pair object. Use the name of the function (`name`) as the key. + +Example: + +```yaml +functionConfigurations: + - name: myFunction + dataSource: myDataSource +``` + +becomes + +```yaml +pipelineFunctions: + myFunction: + dataSource: myDataSource +``` + +**Authentication** + +In `v1` you would define the principal [authentication](authentication.md) provider directly under the `appSync` attribute. In `v2` it has moved under `authentication`. + +- `authenticationType` becomes `authentication.type` +- All the specific `*Config` attributes moved to `authentication.config`. + +Example: + +```yaml +custom: + appSync: + authenticationType: AMAZON_COGNITO_USER_POOLS + userPoolConfig: + userPoolId: # user pool ID +``` + +becomes + +```yaml +appSync: + authentication: + type: AMAZON_COGNITO_USER_POOLS + config: + userPoolId: # user pool ID +``` + +Additional auth provider stay under `additionalAuthenticationProviders` but follow the same stucture as `authentication`. + +Example: + +```yaml +additionalAuthenticationProviders: + - authenticationType: AMAZON_COGNITO_USER_POOLS + userPoolConfig: + userPoolId: # user pool ID +``` + +becomes + +```yaml +additionalAuthenticationProviders: + - type: AMAZON_COGNITO_USER_POOLS + config: + userPoolId: # user pool ID +``` From 58533dad3cd15bf7a7f8ad150f94e65ef706d8ae Mon Sep 17 00:00:00 2001 From: bboure Date: Sun, 13 Feb 2022 20:53:51 +0100 Subject: [PATCH 16/16] fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 30de2be5..2cb7ee64 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ appSync: # Upgrading from v1 -To upgrade form v1 of this plugin, [follow this guide](Upgrading from v1)[doc/upgrading-from-v1.md] +To upgrade form v1 of this plugin, [follow this guide](doc/upgrading-from-v1.md) # Contributing