From 28916e746a00b704f8b7b74ce97a0664c43157dc Mon Sep 17 00:00:00 2001 From: tmokmss Date: Sat, 9 Oct 2021 13:26:11 +0900 Subject: [PATCH 01/10] feat: websocket authorizer --- .../aws-apigatewayv2-authorizers/lib/index.ts | 1 + .../lib/websocket/index.ts | 1 + .../lib/websocket/lambda.ts | 92 +++++++++ .../test/websocket/lambda.test.ts | 48 +++++ packages/@aws-cdk/aws-apigatewayv2/README.md | 31 +-- .../aws-apigatewayv2/lib/http/authorizer.ts | 2 +- .../lib/websocket/authorizer.ts | 176 ++++++++++++++++++ .../aws-apigatewayv2/lib/websocket/index.ts | 1 + .../aws-apigatewayv2/lib/websocket/route.ts | 24 ++- .../test/websocket/authorizer.test.ts | 26 +++ 10 files changed, 388 insertions(+), 14 deletions(-) create mode 100644 packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/index.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/lambda.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/lambda.test.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/websocket/authorizer.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/websocket/authorizer.test.ts diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/index.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/index.ts index c202386ae710e..fd16aff655ff2 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/index.ts @@ -1 +1,2 @@ export * from './http'; +export * from './websocket'; diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/index.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/index.ts new file mode 100644 index 0000000000000..04a64da0c7540 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/index.ts @@ -0,0 +1 @@ +export * from './lambda'; diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/lambda.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/lambda.ts new file mode 100644 index 0000000000000..fa3fada5a856f --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/lambda.ts @@ -0,0 +1,92 @@ +import { + WebSocketAuthorizer, + WebSocketAuthorizerType, + WebSocketRouteAuthorizerBindOptions, + WebSocketRouteAuthorizerConfig, + IWebSocketRouteAuthorizer, + IWebSocketApi, +} from '@aws-cdk/aws-apigatewayv2'; +import { ServicePrincipal } from '@aws-cdk/aws-iam'; +import { IFunction } from '@aws-cdk/aws-lambda'; +import { Stack, Names } from '@aws-cdk/core'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + +/** + * Properties to initialize WebSocketTokenAuthorizer. + */ +export interface WebSocketLambdaAuthorizerProps { + + /** + * The name of the authorizer + */ + readonly authorizerName: string; + + /** + * The identity source for which authorization is requested. + * + * @default ['$request.header.Authorization'] + */ + readonly identitySource?: string[]; + + /** + * The lambda function used for authorization + */ + readonly handler: IFunction; +} + +/** + * Authorize WebSocket Api routes via a lambda function + */ +export class WebSocketLambdaAuthorizer implements IWebSocketRouteAuthorizer { + private authorizer?: WebSocketAuthorizer; + private webSocketApi?: IWebSocketApi; + + constructor(private readonly props: WebSocketLambdaAuthorizerProps) { + } + + public bind(options: WebSocketRouteAuthorizerBindOptions): WebSocketRouteAuthorizerConfig { + if (this.webSocketApi && (this.webSocketApi.apiId !== options.route.webSocketApi.apiId)) { + throw new Error('Cannot attach the same authorizer to multiple Apis'); + } + + if (!this.authorizer) { + const id = this.props.authorizerName; + + this.webSocketApi = options.route.webSocketApi; + this.authorizer = new WebSocketAuthorizer(options.scope, id, { + webSocketApi: options.route.webSocketApi, + identitySource: this.props.identitySource ?? [ + '$request.header.Authorization', + ], + type: WebSocketAuthorizerType.LAMBDA, + authorizerName: this.props.authorizerName, + authorizerUri: lambdaAuthorizerArn(this.props.handler), + }); + + this.props.handler.addPermission(`${Names.nodeUniqueId(this.authorizer.node)}-Permission`, { + scope: options.scope as CoreConstruct, + principal: new ServicePrincipal('apigateway.amazonaws.com'), + sourceArn: Stack.of(options.route).formatArn({ + service: 'execute-api', + resource: options.route.webSocketApi.apiId, + resourceName: `authorizers/${this.authorizer.authorizerId}`, + }), + }); + } + + return { + authorizerId: this.authorizer.authorizerId, + authorizationType: 'CUSTOM', + }; + } +} + +/** + * constructs the authorizerURIArn. + */ +function lambdaAuthorizerArn(handler: IFunction) { + return `arn:${Stack.of(handler).partition}:apigateway:${Stack.of(handler).region}:lambda:path/2015-03-31/functions/${handler.functionArn}/invocations`; +} diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/lambda.test.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/lambda.test.ts new file mode 100644 index 0000000000000..48fdfd4e521fd --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/lambda.test.ts @@ -0,0 +1,48 @@ +import { Template } from '@aws-cdk/assertions'; +import { WebSocketApi } from '@aws-cdk/aws-apigatewayv2'; +import { LambdaWebSocketIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; +import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; +import { Stack } from '@aws-cdk/core'; +import { WebSocketLambdaAuthorizer } from '../../lib'; + +describe('WebSocketLambdaAuthorizer', () => { + test('default', () => { + // GIVEN + const stack = new Stack(); + + const handler = new Function(stack, 'auth-function', { + runtime: Runtime.NODEJS_12_X, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', + }); + const integration = new LambdaWebSocketIntegration({ + handler, + }); + + const authorizer = new WebSocketLambdaAuthorizer({ + authorizerName: 'default-authorizer', + handler, + }); + + // WHEN + new WebSocketApi(stack, 'WebSocketApi', { + connectRouteOptions: { + integration, + authorizer, + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Authorizer', { + Name: 'default-authorizer', + AuthorizerType: 'REQUEST', + IdentitySource: [ + '$request.header.Authorization', + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Route', { + AuthorizationType: 'CUSTOM', + }); + }); +}); diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index cf0a2ab0552c7..0d013350d3c1e 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -28,17 +28,20 @@ Higher level constructs for Websocket APIs | ![Experimental](https://img.shields ## Table of Contents -- [Introduction](#introduction) -- [HTTP API](#http-api) - - [Defining HTTP APIs](#defining-http-apis) - - [Cross Origin Resource Sharing (CORS)](#cross-origin-resource-sharing-cors) - - [Publishing HTTP APIs](#publishing-http-apis) - - [Custom Domain](#custom-domain) - - [Managing access](#managing-access) - - [Metrics](#metrics) - - [VPC Link](#vpc-link) - - [Private Integration](#private-integration) -- [WebSocket API](#websocket-api) +- [AWS::APIGatewayv2 Construct Library](#awsapigatewayv2-construct-library) + - [Table of Contents](#table-of-contents) + - [Introduction](#introduction) + - [HTTP API](#http-api) + - [Defining HTTP APIs](#defining-http-apis) + - [Cross Origin Resource Sharing (CORS)](#cross-origin-resource-sharing-cors) + - [Publishing HTTP APIs](#publishing-http-apis) + - [Custom Domain](#custom-domain) + - [Managing access](#managing-access) + - [Metrics](#metrics) + - [VPC Link](#vpc-link) + - [Private Integration](#private-integration) + - [WebSocket API](#websocket-api) + - [Managing access](#managing-access-1) ## Introduction @@ -336,3 +339,9 @@ webSocketApi.addRoute('sendmessage', { }), }); ``` + +### Managing access + +API Gateway supports multiple mechanisms for [controlling and managing access to a WebSocket API](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-control-access.html) through authorizers. + +These authorizers can be found in the [APIGatewayV2-Authorizers](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-apigatewayv2-authorizers-readme.html) constructs library. diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/authorizer.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/authorizer.ts index 08936ecf36d8f..d4a7cf21b4ac4 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/authorizer.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/authorizer.ts @@ -166,7 +166,7 @@ export class HttpAuthorizer extends Resource implements IHttpAuthorizer { } /** - * This check is required because Cloudformation will fail stack creation is this property + * This check is required because Cloudformation will fail stack creation if this property * is set for the JWT authorizer. AuthorizerPayloadFormatVersion can only be set for REQUEST authorizer */ if (props.type === HttpAuthorizerType.LAMBDA && typeof authorizerPayloadFormatVersion === 'undefined') { diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/authorizer.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/authorizer.ts new file mode 100644 index 0000000000000..5abb420c80bad --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/authorizer.ts @@ -0,0 +1,176 @@ +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnAuthorizer } from '../apigatewayv2.generated'; + +import { IAuthorizer } from '../common'; +import { IWebSocketApi } from './api'; +import { IWebSocketRoute } from './route'; + +/** + * Supported Authorizer types + */ +export enum WebSocketAuthorizerType { + /** Lambda Authorizer */ + LAMBDA = 'REQUEST', +} + +/** + * Properties to initialize an instance of `WebSocketAuthorizer`. + */ +export interface WebSocketAuthorizerProps { + /** + * Name of the authorizer + * @default - id of the WebSocketAuthorizer construct. + */ + readonly authorizerName?: string + + /** + * WebSocket Api to attach the authorizer to + */ + readonly webSocketApi: IWebSocketApi + + /** + * The type of authorizer + */ + readonly type: WebSocketAuthorizerType; + + /** + * The identity source for which authorization is requested. + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-authorizer.html#cfn-apigatewayv2-authorizer-identitysource + */ + readonly identitySource: string[]; + + /** + * The authorizer's Uniform Resource Identifier (URI). + * + * For REQUEST authorizers, this must be a well-formed Lambda function URI. + * + * @default - required for Request authorizer types + */ + readonly authorizerUri?: string; +} + +/** + * An authorizer for WebSocket APIs + */ +export interface IWebSocketAuthorizer extends IAuthorizer { +} + +/** + * Reference to an WebSocket authorizer + */ +export interface WebSocketAuthorizerAttributes { + /** + * Id of the Authorizer + */ + readonly authorizerId: string + + /** + * Type of authorizer + * + * Possible values are: + * - CUSTOM - Lambda Authorizer + * - NONE - No Authorization + */ + readonly authorizerType: string +} + +/** + * An authorizer for WebSocket Apis + * @resource AWS::ApiGatewayV2::Authorizer + */ +export class WebSocketAuthorizer extends Resource implements IWebSocketAuthorizer { + /** + * Import an existing WebSocket Authorizer into this CDK app. + */ + public static fromWebSocketAuthorizerAttributes(scope: Construct, id: string, attrs: WebSocketAuthorizerAttributes): IWebSocketRouteAuthorizer { + class Import extends Resource implements IWebSocketRouteAuthorizer { + public readonly authorizerId = attrs.authorizerId; + public readonly authorizerType = attrs.authorizerType; + + public bind(): WebSocketRouteAuthorizerConfig { + return { + authorizerId: attrs.authorizerId, + authorizationType: attrs.authorizerType, + }; + } + } + return new Import(scope, id); + } + + public readonly authorizerId: string; + + constructor(scope: Construct, id: string, props: WebSocketAuthorizerProps) { + super(scope, id); + + if (props.type === WebSocketAuthorizerType.LAMBDA && !props.authorizerUri) { + throw new Error('authorizerUri is mandatory for Lambda authorizers'); + } + + const resource = new CfnAuthorizer(this, 'Resource', { + name: props.authorizerName ?? id, + apiId: props.webSocketApi.apiId, + authorizerType: props.type, + identitySource: props.identitySource, + authorizerUri: props.authorizerUri, + }); + + this.authorizerId = resource.ref; + } +} + +/** + * Input to the bind() operation, that binds an authorizer to a route. + */ +export interface WebSocketRouteAuthorizerBindOptions { + /** + * The route to which the authorizer is being bound. + */ + readonly route: IWebSocketRoute; + /** + * The scope for any constructs created as part of the bind. + */ + readonly scope: Construct; +} + +/** + * Results of binding an authorizer to an WebSocket route. + */ +export interface WebSocketRouteAuthorizerConfig { + /** + * The authorizer id + * + * @default - No authorizer id (useful for AWS_IAM route authorizer) + */ + readonly authorizerId?: string; + + /** + * The type of authorization + * + * Possible values are: + * - CUSTOM - Lambda Authorizer + * - NONE - No Authorization + */ + readonly authorizationType: string; +} + +/** + * An authorizer that can attach to an WebSocket Route. + */ +export interface IWebSocketRouteAuthorizer { + /** + * Bind this authorizer to a specified WebSocket route. + */ + bind(options: WebSocketRouteAuthorizerBindOptions): WebSocketRouteAuthorizerConfig; +} + +/** + * Explicitly configure no authorizers on specific WebSocket API routes. + */ +export class WebSocketNoneAuthorizer implements IWebSocketRouteAuthorizer { + public bind(_: WebSocketRouteAuthorizerBindOptions): WebSocketRouteAuthorizerConfig { + return { + authorizationType: 'NONE', + }; + } +} diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/index.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/index.ts index b0ce6a8a91419..4fe65943cbb8b 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/index.ts @@ -2,3 +2,4 @@ export * from './api'; export * from './route'; export * from './stage'; export * from './integration'; +export * from './authorizer'; diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts index 0588889a603bc..9ce86ef72e209 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts @@ -1,8 +1,10 @@ import { Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; +import { WebSocketNoneAuthorizer } from '.'; import { CfnRoute } from '../apigatewayv2.generated'; import { IRoute } from '../common'; import { IWebSocketApi } from './api'; +import { IWebSocketRouteAuthorizer } from './authorizer'; import { IWebSocketRouteIntegration } from './integration'; /** @@ -29,15 +31,21 @@ export interface WebSocketRouteOptions { * The integration to be configured on this route. */ readonly integration: IWebSocketRouteIntegration; -} + /** + * The authorize to this route. You can only set authorizer to a $connect route. + * + * @default - No Authorizer + */ + readonly authorizer?: IWebSocketRouteAuthorizer; +} /** * Properties to initialize a new Route */ export interface WebSocketRouteProps extends WebSocketRouteOptions { /** - * the API the route is associated with + * The API the route is associated with. */ readonly webSocketApi: IWebSocketApi; @@ -64,6 +72,10 @@ export class WebSocketRoute extends Resource implements IWebSocketRoute { constructor(scope: Construct, id: string, props: WebSocketRouteProps) { super(scope, id); + if (props.routeKey != '$connect' && props.authorizer) { + throw new Error('You cannot set any authorizer to other than $connect route.'); + } + this.webSocketApi = props.webSocketApi; this.routeKey = props.routeKey; @@ -74,10 +86,18 @@ export class WebSocketRoute extends Resource implements IWebSocketRoute { const integration = props.webSocketApi._addIntegration(this, config); + const authorizer = props.authorizer ?? new WebSocketNoneAuthorizer(); + const authBindResult = authorizer.bind({ + route: this, + scope: this.webSocketApi instanceof Construct ? this.webSocketApi : this, // scope under the API if it's not imported + }); + const route = new CfnRoute(this, 'Resource', { apiId: props.webSocketApi.apiId, routeKey: props.routeKey, target: `integrations/${integration.integrationId}`, + authorizerId: authBindResult?.authorizerId, + authorizationType: authBindResult?.authorizationType ?? 'NONE', // must be explicitly NONE (not undefined) for stack updates to work correctly }); this.routeId = route.ref; } diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/authorizer.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/authorizer.test.ts new file mode 100644 index 0000000000000..105b7f168e74e --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/authorizer.test.ts @@ -0,0 +1,26 @@ +import { Template } from '@aws-cdk/assertions'; +import { Stack } from '@aws-cdk/core'; +import { + WebSocketApi, WebSocketAuthorizer, WebSocketAuthorizerType, +} from '../../lib'; + +describe('WebSocketAuthorizer', () => { + describe('lambda', () => { + it('default', () => { + const stack = new Stack(); + const webSocketApi = new WebSocketApi(stack, 'WebSocketApi'); + + new WebSocketAuthorizer(stack, 'WebSocketAuthorizer', { + webSocketApi, + identitySource: ['identitysource.1', 'identitysource.2'], + type: WebSocketAuthorizerType.LAMBDA, + authorizerUri: 'arn:cool-lambda-arn', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Authorizer', { + AuthorizerType: 'REQUEST', + AuthorizerUri: 'arn:cool-lambda-arn', + }); + }); + }); +}); From d93218971fd8a05e6675114ea04663b285188728 Mon Sep 17 00:00:00 2001 From: Masashi Tomooka Date: Sat, 9 Oct 2021 13:30:47 +0900 Subject: [PATCH 02/10] Update README.md --- packages/@aws-cdk/aws-apigatewayv2/README.md | 26 +++++++++----------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 0d013350d3c1e..76c7dbc597a4c 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -28,20 +28,18 @@ Higher level constructs for Websocket APIs | ![Experimental](https://img.shields ## Table of Contents -- [AWS::APIGatewayv2 Construct Library](#awsapigatewayv2-construct-library) - - [Table of Contents](#table-of-contents) - - [Introduction](#introduction) - - [HTTP API](#http-api) - - [Defining HTTP APIs](#defining-http-apis) - - [Cross Origin Resource Sharing (CORS)](#cross-origin-resource-sharing-cors) - - [Publishing HTTP APIs](#publishing-http-apis) - - [Custom Domain](#custom-domain) - - [Managing access](#managing-access) - - [Metrics](#metrics) - - [VPC Link](#vpc-link) - - [Private Integration](#private-integration) - - [WebSocket API](#websocket-api) - - [Managing access](#managing-access-1) +- [Introduction](#introduction) +- [HTTP API](#http-api) + - [Defining HTTP APIs](#defining-http-apis) + - [Cross Origin Resource Sharing (CORS)](#cross-origin-resource-sharing-cors) + - [Publishing HTTP APIs](#publishing-http-apis) + - [Custom Domain](#custom-domain) + - [Managing access](#managing-access) + - [Metrics](#metrics) + - [VPC Link](#vpc-link) + - [Private Integration](#private-integration) +- [WebSocket API](#websocket-api) + - [Managing access](#managing-access-1) ## Introduction From 6d37469f9daa89ce4e2c8632096e9f90124efd7e Mon Sep 17 00:00:00 2001 From: Masashi Tomooka Date: Sat, 9 Oct 2021 14:03:21 +0900 Subject: [PATCH 03/10] Update README.md --- packages/@aws-cdk/aws-apigatewayv2/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 76c7dbc597a4c..ec984da7a6499 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -34,12 +34,12 @@ Higher level constructs for Websocket APIs | ![Experimental](https://img.shields - [Cross Origin Resource Sharing (CORS)](#cross-origin-resource-sharing-cors) - [Publishing HTTP APIs](#publishing-http-apis) - [Custom Domain](#custom-domain) - - [Managing access](#managing-access) + - [Managing access to HTTP APIs](#managing-access-to-http-apis) - [Metrics](#metrics) - [VPC Link](#vpc-link) - [Private Integration](#private-integration) - [WebSocket API](#websocket-api) - - [Managing access](#managing-access-1) + - [Managing access to WebSocket APIs](#managing-access-to-websocket-apis) ## Introduction @@ -231,7 +231,7 @@ You can retrieve the full domain URL with mapping key using the `domainUrl` prop const demoDomainUrl = apiDemo.defaultStage.domainUrl; // returns "https://example.com/demo" ``` -### Managing access +### Managing access to HTTP APIs API Gateway supports multiple mechanisms for [controlling and managing access to your HTTP API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-access-control.html) through authorizers. @@ -338,7 +338,7 @@ webSocketApi.addRoute('sendmessage', { }); ``` -### Managing access +### Managing access to WebSocket APIs API Gateway supports multiple mechanisms for [controlling and managing access to a WebSocket API](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-control-access.html) through authorizers. From 80692d52ebefb2efe845180674a11da700912ff1 Mon Sep 17 00:00:00 2001 From: tmokmss Date: Sat, 9 Oct 2021 14:30:28 +0900 Subject: [PATCH 04/10] fix --- .../test/websocket/integ.lambda.expected.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/websocket/integ.lambda.expected.json b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/websocket/integ.lambda.expected.json index 54a0f51203342..22a15ea38e899 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/websocket/integ.lambda.expected.json +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/websocket/integ.lambda.expected.json @@ -284,6 +284,7 @@ "Ref": "mywsapi32E6CE11" }, "RouteKey": "$connect", + "AuthorizationType": "NONE", "Target": { "Fn::Join": [ "", @@ -373,6 +374,7 @@ "Ref": "mywsapi32E6CE11" }, "RouteKey": "$disconnect", + "AuthorizationType": "NONE", "Target": { "Fn::Join": [ "", @@ -462,6 +464,7 @@ "Ref": "mywsapi32E6CE11" }, "RouteKey": "$default", + "AuthorizationType": "NONE", "Target": { "Fn::Join": [ "", @@ -551,6 +554,7 @@ "Ref": "mywsapi32E6CE11" }, "RouteKey": "sendmessage", + "AuthorizationType": "NONE", "Target": { "Fn::Join": [ "", From 4ca683b43e8bd50db2b2d8cf19b1d44c326a176a Mon Sep 17 00:00:00 2001 From: tmokmss Date: Sat, 9 Oct 2021 15:04:51 +0900 Subject: [PATCH 05/10] Update route.ts --- .../@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts index 9ce86ef72e209..7d9f710632f30 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts @@ -1,10 +1,9 @@ import { Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { WebSocketNoneAuthorizer } from '.'; import { CfnRoute } from '../apigatewayv2.generated'; import { IRoute } from '../common'; import { IWebSocketApi } from './api'; -import { IWebSocketRouteAuthorizer } from './authorizer'; +import { IWebSocketRouteAuthorizer, WebSocketNoneAuthorizer } from './authorizer'; import { IWebSocketRouteIntegration } from './integration'; /** @@ -73,7 +72,7 @@ export class WebSocketRoute extends Resource implements IWebSocketRoute { super(scope, id); if (props.routeKey != '$connect' && props.authorizer) { - throw new Error('You cannot set any authorizer to other than $connect route.'); + throw new Error('You can only set a WebSocket authorizer to a $connect route.'); } this.webSocketApi = props.webSocketApi; @@ -86,7 +85,7 @@ export class WebSocketRoute extends Resource implements IWebSocketRoute { const integration = props.webSocketApi._addIntegration(this, config); - const authorizer = props.authorizer ?? new WebSocketNoneAuthorizer(); + const authorizer = props.authorizer ?? new WebSocketNoneAuthorizer(); // must be explicitly NONE (not undefined) for stack updates to work correctly const authBindResult = authorizer.bind({ route: this, scope: this.webSocketApi instanceof Construct ? this.webSocketApi : this, // scope under the API if it's not imported @@ -96,8 +95,8 @@ export class WebSocketRoute extends Resource implements IWebSocketRoute { apiId: props.webSocketApi.apiId, routeKey: props.routeKey, target: `integrations/${integration.integrationId}`, - authorizerId: authBindResult?.authorizerId, - authorizationType: authBindResult?.authorizationType ?? 'NONE', // must be explicitly NONE (not undefined) for stack updates to work correctly + authorizerId: authBindResult.authorizerId, + authorizationType: authBindResult.authorizationType, }); this.routeId = route.ref; } From 9d00b2b7e63e554fba7a5dfb7d639972d6cc515a Mon Sep 17 00:00:00 2001 From: tmokmss Date: Tue, 14 Dec 2021 22:46:34 +0900 Subject: [PATCH 06/10] fix merge error --- packages/@aws-cdk/aws-apigatewayv2/README.md | 6 +++--- packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index a52a6409635f2..b531f68b001ef 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -34,7 +34,7 @@ Higher level constructs for Websocket APIs | ![Experimental](https://img.shields - [Cross Origin Resource Sharing (CORS)](#cross-origin-resource-sharing-cors) - [Publishing HTTP APIs](#publishing-http-apis) - [Custom Domain](#custom-domain) -- [Mutual TLS (mTLS)](#mutual-tls-mtls) + - [Mutual TLS](#mutual-tls-mtls) - [Managing access to HTTP APIs](#managing-access-to-http-apis) - [Metrics](#metrics) - [VPC Link](#vpc-link) @@ -255,7 +255,7 @@ declare const apiDemo: apigwv2.HttpApi; const demoDomainUrl = apiDemo.defaultStage?.domainUrl; // returns "https://example.com/demo" ``` -## Mutual TLS (mTLS) +### Mutual TLS (mTLS) Mutual TLS can be configured to limit access to your API based by using client certificates instead of (or as an extension of) using authorization headers. @@ -276,9 +276,9 @@ new DomainName(stack, 'DomainName', { }) ``` -### Managing access to HTTP APIs Instructions for configuring your trust store can be found [here](https://aws.amazon.com/blogs/compute/introducing-mutual-tls-authentication-for-amazon-api-gateway/) +### Managing access to HTTP APIs API Gateway supports multiple mechanisms for [controlling and managing access to your HTTP API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-access-control.html) through authorizers. diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts index f07dc077c8383..0aaa93587015c 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts @@ -83,8 +83,6 @@ export class WebSocketRoute extends Resource implements IWebSocketRoute { scope: this, }); - const integration = props.webSocketApi._addIntegration(this, config); - const authorizer = props.authorizer ?? new WebSocketNoneAuthorizer(); // must be explicitly NONE (not undefined) for stack updates to work correctly const authBindResult = authorizer.bind({ route: this, From ccd799ee1d1b0ecab3de7d7c623705babd3cab3b Mon Sep 17 00:00:00 2001 From: tmokmss Date: Tue, 14 Dec 2021 23:29:29 +0900 Subject: [PATCH 07/10] update API to be consistent with HttpApi --- .../lib/websocket/lambda.ts | 23 ++++++++----------- .../test/websocket/lambda.test.ts | 12 ++++------ packages/@aws-cdk/aws-apigatewayv2/README.md | 1 + 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/lambda.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/lambda.ts index fa3fada5a856f..2e60cbdd7b547 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/lambda.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/lambda.ts @@ -21,8 +21,9 @@ export interface WebSocketLambdaAuthorizerProps { /** * The name of the authorizer + * @default - same value as `id` passed in the constructor. */ - readonly authorizerName: string; + readonly authorizerName?: string; /** * The identity source for which authorization is requested. @@ -30,11 +31,6 @@ export interface WebSocketLambdaAuthorizerProps { * @default ['$request.header.Authorization'] */ readonly identitySource?: string[]; - - /** - * The lambda function used for authorization - */ - readonly handler: IFunction; } /** @@ -44,7 +40,10 @@ export class WebSocketLambdaAuthorizer implements IWebSocketRouteAuthorizer { private authorizer?: WebSocketAuthorizer; private webSocketApi?: IWebSocketApi; - constructor(private readonly props: WebSocketLambdaAuthorizerProps) { + constructor( + private readonly id: string, + private readonly handler: IFunction, + private readonly props: WebSocketLambdaAuthorizerProps = {}) { } public bind(options: WebSocketRouteAuthorizerBindOptions): WebSocketRouteAuthorizerConfig { @@ -53,20 +52,18 @@ export class WebSocketLambdaAuthorizer implements IWebSocketRouteAuthorizer { } if (!this.authorizer) { - const id = this.props.authorizerName; - this.webSocketApi = options.route.webSocketApi; - this.authorizer = new WebSocketAuthorizer(options.scope, id, { + this.authorizer = new WebSocketAuthorizer(options.scope, this.id, { webSocketApi: options.route.webSocketApi, identitySource: this.props.identitySource ?? [ '$request.header.Authorization', ], type: WebSocketAuthorizerType.LAMBDA, - authorizerName: this.props.authorizerName, - authorizerUri: lambdaAuthorizerArn(this.props.handler), + authorizerName: this.props.authorizerName ?? this.id, + authorizerUri: lambdaAuthorizerArn(this.handler), }); - this.props.handler.addPermission(`${Names.nodeUniqueId(this.authorizer.node)}-Permission`, { + this.handler.addPermission(`${Names.nodeUniqueId(this.authorizer.node)}-Permission`, { scope: options.scope as CoreConstruct, principal: new ServicePrincipal('apigateway.amazonaws.com'), sourceArn: Stack.of(options.route).formatArn({ diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/lambda.test.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/lambda.test.ts index 48fdfd4e521fd..c171247801911 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/lambda.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/lambda.test.ts @@ -1,6 +1,6 @@ import { Template } from '@aws-cdk/assertions'; import { WebSocketApi } from '@aws-cdk/aws-apigatewayv2'; -import { LambdaWebSocketIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; +import { WebSocketLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; import { Stack } from '@aws-cdk/core'; import { WebSocketLambdaAuthorizer } from '../../lib'; @@ -15,14 +15,12 @@ describe('WebSocketLambdaAuthorizer', () => { code: Code.fromInline('exports.handler = () => {return true}'), handler: 'index.handler', }); - const integration = new LambdaWebSocketIntegration({ + const integration = new WebSocketLambdaIntegration( + 'Integration', handler, - }); + ); - const authorizer = new WebSocketLambdaAuthorizer({ - authorizerName: 'default-authorizer', - handler, - }); + const authorizer = new WebSocketLambdaAuthorizer('default-authorizer', handler); // WHEN new WebSocketApi(stack, 'WebSocketApi', { diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index b531f68b001ef..a09d015dc87a2 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -279,6 +279,7 @@ new DomainName(stack, 'DomainName', { Instructions for configuring your trust store can be found [here](https://aws.amazon.com/blogs/compute/introducing-mutual-tls-authentication-for-amazon-api-gateway/) ### Managing access to HTTP APIs + API Gateway supports multiple mechanisms for [controlling and managing access to your HTTP API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-access-control.html) through authorizers. From 5e807f4b56b2f5395e2980cf9687efef3cab4edc Mon Sep 17 00:00:00 2001 From: tmokmss Date: Tue, 14 Dec 2021 23:44:14 +0900 Subject: [PATCH 08/10] Update README.md --- .../aws-apigatewayv2-authorizers/README.md | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md b/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md index d1aebb7477b82..34367ab0a15ed 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md @@ -22,9 +22,11 @@ - [HTTP APIs](#http-apis) - [Default Authorization](#default-authorization) - [Route Authorization](#route-authorization) -- [JWT Authorizers](#jwt-authorizers) - - [User Pool Authorizer](#user-pool-authorizer) -- [Lambda Authorizers](#lambda-authorizers) + - [JWT Authorizers](#jwt-authorizers) + - [User Pool Authorizer](#user-pool-authorizer) + - [Lambda Authorizers](#lambda-authorizers) +- [WebSocket APIs](#websocket-apis) + - [Lambda Authorizer](#lambda-authorizer) ## Introduction @@ -37,7 +39,7 @@ API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-acces Access control for Http Apis is managed by restricting which routes can be invoked via. -Authorizers, and scopes can either be applied to the api, or specifically for each route. +Authorizers and scopes can either be applied to the api, or specifically for each route. ### Default Authorization @@ -110,7 +112,7 @@ api.addRoutes({ }); ``` -## JWT Authorizers +### JWT Authorizers JWT authorizers allow the use of JSON Web Tokens (JWTs) as part of [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) and [OAuth 2.0](https://oauth.net/2/) frameworks to allow and restrict clients from accessing HTTP APIs. @@ -144,7 +146,7 @@ api.addRoutes({ }); ``` -### User Pool Authorizer +#### User Pool Authorizer User Pool Authorizer is a type of JWT Authorizer that uses a Cognito user pool and app client to control who can access your Api. After a successful authorization from the app client, the generated access token will be used as the JWT. @@ -170,7 +172,7 @@ api.addRoutes({ }); ``` -## Lambda Authorizers +### Lambda Authorizers Lambda authorizers use a Lambda function to control access to your HTTP API. When a client calls your API, API Gateway invokes your Lambda function and uses the response to determine whether the client can access your API. @@ -196,3 +198,36 @@ api.addRoutes({ authorizer, }); ``` + +## WebSocket APIs + +You can set an authorizer to your WebSocket API's `$connect` route to control access to your API. + +### Lambda Authorizer + +Lambda authorizers use a Lambda function to control access to your WebSocket API. When a client connects to your API, API Gateway invokes your Lambda function and uses the response to determine whether the client can access your API. + +```ts +import { WebSocketLambdaAuthorizer } from '@aws-cdk/aws-apigatewayv2-authorizers'; +import { WebSocketLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + +// This function handles your auth logic +declare const authHandler: lambda.Function; + +// This function handles your WebSocket requests +declare const handler: lambda.Function; + +const authorizer = new WebSocketLambdaAuthorizer('Authorizer', authHandler); + +const integration = new WebSocketLambdaIntegration( + 'Integration', + handler, +); + +new WebSocketApi(stack, 'WebSocketApi', { + connectRouteOptions: { + integration, + authorizer, + }, +}); +``` From 1540da85cb4e6d57fd32a632ac54f4bf250357fb Mon Sep 17 00:00:00 2001 From: tmokmss Date: Wed, 15 Dec 2021 00:32:02 +0900 Subject: [PATCH 09/10] Update README.md --- packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md b/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md index 34367ab0a15ed..caec6537934fd 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md @@ -224,7 +224,7 @@ const integration = new WebSocketLambdaIntegration( handler, ); -new WebSocketApi(stack, 'WebSocketApi', { +new apigwv2.WebSocketApi(stack, 'WebSocketApi', { connectRouteOptions: { integration, authorizer, From 2451099c55e18ecc20f3343b7eb8c605bfa0abe4 Mon Sep 17 00:00:00 2001 From: tmokmss Date: Wed, 15 Dec 2021 00:34:47 +0900 Subject: [PATCH 10/10] Update README.md --- packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md b/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md index caec6537934fd..e2d9f13198711 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md @@ -224,7 +224,7 @@ const integration = new WebSocketLambdaIntegration( handler, ); -new apigwv2.WebSocketApi(stack, 'WebSocketApi', { +new apigwv2.WebSocketApi(this, 'WebSocketApi', { connectRouteOptions: { integration, authorizer,