diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md b/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md index 16a7b3c591b27..2320be5505ba4 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md @@ -28,6 +28,7 @@ - [IAM Authorizers](#iam-authorizers) - [WebSocket APIs](#websocket-apis) - [Lambda Authorizer](#lambda-authorizer) + - [IAM Authorizers](#iam-authorizer) ## Introduction @@ -256,3 +257,42 @@ new apigwv2.WebSocketApi(this, 'WebSocketApi', { }, }); ``` + +### IAM Authorizer + +IAM authorizers can be used to allow identity-based access to your WebSocket API. + +```ts +import { WebSocketIamAuthorizer } from '@aws-cdk/aws-apigatewayv2-authorizers'; +import { WebSocketLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + +// This function handles your connect route +declare const connectHandler: lambda.Function; + +const webSocketApi = new apigwv2.WebSocketApi(this, 'WebSocketApi'); + +webSocketApi.addRoute('$connect', { + integration: new WebSocketLambdaIntegration('Integration', connectHandler), + authorizer: new WebSocketIamAuthorizer() +}); + +// Create an IAM user (identity) +const user = new iam.User(this, 'User'); + +const webSocketArn = Stack.of(this).formatArn({ + service: 'execute-api', + resource: webSocketApi.apiId, +}); + +// Grant access to the IAM user +user.attachInlinePolicy(new iam.Policy(this, 'AllowInvoke', { + statements: [ + new iam.PolicyStatement({ + actions: ['execute-api:Invoke'], + effect: iam.Effect.ALLOW, + resources: [webSocketArn], + }), + ], +})); + +``` diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/iam.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/iam.ts new file mode 100644 index 0000000000000..1adac6766e3b7 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/iam.ts @@ -0,0 +1,19 @@ +import { + WebSocketAuthorizerType, + WebSocketRouteAuthorizerBindOptions, + WebSocketRouteAuthorizerConfig, + IWebSocketRouteAuthorizer, +} from '@aws-cdk/aws-apigatewayv2'; + +/** + * Authorize WebSocket API Routes with IAM + */ +export class WebSocketIamAuthorizer implements IWebSocketRouteAuthorizer { + public bind( + _options: WebSocketRouteAuthorizerBindOptions, + ): WebSocketRouteAuthorizerConfig { + return { + authorizationType: WebSocketAuthorizerType.IAM, + }; + } +} diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/index.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/index.ts index 04a64da0c7540..645c9d0758583 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/index.ts @@ -1 +1,2 @@ export * from './lambda'; +export * from './iam'; diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json index 9327e2d103707..54aea78d30e1f 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json @@ -86,6 +86,7 @@ "@aws-cdk/integ-runner": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/aws-lambda": "^8.10.101", + "@aws-cdk/integ-tests": "0.0.0", "@types/jest": "^27.5.2" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/ApiGatewayV2WebSocketIamTestDefaultTestDeployAssert2B412D7B.template.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/ApiGatewayV2WebSocketIamTestDefaultTestDeployAssert2B412D7B.template.json new file mode 100644 index 0000000000000..9e26dfeeb6e64 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/ApiGatewayV2WebSocketIamTestDefaultTestDeployAssert2B412D7B.template.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/IntegApiGatewayV2Iam.template.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/IntegApiGatewayV2Iam.template.json new file mode 100644 index 0000000000000..8083d38ced5f5 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/IntegApiGatewayV2Iam.template.json @@ -0,0 +1,226 @@ +{ + "Resources": { + "User00B015A1": { + "Type": "AWS::IAM::User" + }, + "UserAccessEC42ADF7": { + "Type": "AWS::IAM::AccessKey", + "Properties": { + "UserName": { + "Ref": "User00B015A1" + } + } + }, + "authfunctionServiceRoleFCB72198": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "authfunction96361832": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = () => {return true}" + }, + "Role": { + "Fn::GetAtt": [ + "authfunctionServiceRoleFCB72198", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "authfunctionServiceRoleFCB72198" + ] + }, + "WebSocketApi34BCF99B": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "WebSocketApi", + "ProtocolType": "WEBSOCKET", + "RouteSelectionExpression": "$request.body.action" + } + }, + "WebSocketApiconnectRouteWebSocketLambdaIntegrationPermission76CD86C6": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "authfunction96361832", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "WebSocketApi34BCF99B" + }, + "/*/*$connect" + ] + ] + } + } + }, + "WebSocketApiconnectRouteWebSocketLambdaIntegration3D2B13DD": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "WebSocketApi34BCF99B" + }, + "IntegrationType": "AWS_PROXY", + "IntegrationUri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "authfunction96361832", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + }, + "WebSocketApiconnectRoute846149DD": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "WebSocketApi34BCF99B" + }, + "RouteKey": "$connect", + "AuthorizationType": "AWS_IAM", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "WebSocketApiconnectRouteWebSocketLambdaIntegration3D2B13DD" + } + ] + ] + } + } + }, + "AllowInvoke767865EA": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "WebSocketApi34BCF99B" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AllowInvoke767865EA", + "Users": [ + { + "Ref": "User00B015A1" + } + ] + } + } + }, + "Outputs": { + "TESTACCESSKEYID": { + "Value": { + "Ref": "UserAccessEC42ADF7" + } + }, + "TESTSECRETACCESSKEY": { + "Value": { + "Fn::GetAtt": [ + "UserAccessEC42ADF7", + "SecretAccessKey" + ] + } + }, + "TESTREGION": { + "Value": { + "Ref": "AWS::Region" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..588d7b269d34f --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/integ.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/integ.json new file mode 100644 index 0000000000000..c1ef6336fee4f --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/integ.json @@ -0,0 +1,11 @@ +{ + "version": "20.0.0", + "testCases": { + "ApiGatewayV2WebSocketIamTest/DefaultTest": { + "stacks": [ + "IntegApiGatewayV2Iam" + ], + "assertionStack": "ApiGatewayV2WebSocketIamTestDefaultTestDeployAssert2B412D7B" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..0731b24a3b278 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/manifest.json @@ -0,0 +1,103 @@ +{ + "version": "20.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "IntegApiGatewayV2Iam": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "IntegApiGatewayV2Iam.template.json", + "validateOnSynth": false + }, + "metadata": { + "/IntegApiGatewayV2Iam/User/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "User00B015A1" + } + ], + "/IntegApiGatewayV2Iam/UserAccess/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "UserAccessEC42ADF7" + } + ], + "/IntegApiGatewayV2Iam/auth-function/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "authfunctionServiceRoleFCB72198" + } + ], + "/IntegApiGatewayV2Iam/auth-function/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "authfunction96361832" + } + ], + "/IntegApiGatewayV2Iam/WebSocketApi/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "WebSocketApi34BCF99B" + } + ], + "/IntegApiGatewayV2Iam/WebSocketApi/$connect-Route/WebSocketLambdaIntegration-Permission": [ + { + "type": "aws:cdk:logicalId", + "data": "WebSocketApiconnectRouteWebSocketLambdaIntegrationPermission76CD86C6" + } + ], + "/IntegApiGatewayV2Iam/WebSocketApi/$connect-Route/WebSocketLambdaIntegration/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "WebSocketApiconnectRouteWebSocketLambdaIntegration3D2B13DD" + } + ], + "/IntegApiGatewayV2Iam/WebSocketApi/$connect-Route/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "WebSocketApiconnectRoute846149DD" + } + ], + "/IntegApiGatewayV2Iam/AllowInvoke/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AllowInvoke767865EA" + } + ], + "/IntegApiGatewayV2Iam/TESTACCESSKEYID": [ + { + "type": "aws:cdk:logicalId", + "data": "TESTACCESSKEYID" + } + ], + "/IntegApiGatewayV2Iam/TESTSECRETACCESSKEY": [ + { + "type": "aws:cdk:logicalId", + "data": "TESTSECRETACCESSKEY" + } + ], + "/IntegApiGatewayV2Iam/TESTREGION": [ + { + "type": "aws:cdk:logicalId", + "data": "TESTREGION" + } + ] + }, + "displayName": "IntegApiGatewayV2Iam" + }, + "ApiGatewayV2WebSocketIamTestDefaultTestDeployAssert2B412D7B": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "ApiGatewayV2WebSocketIamTestDefaultTestDeployAssert2B412D7B.template.json", + "validateOnSynth": false + }, + "displayName": "ApiGatewayV2WebSocketIamTest/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/tree.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/tree.json new file mode 100644 index 0000000000000..f0b23496709c0 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/tree.json @@ -0,0 +1,443 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.58" + } + }, + "IntegApiGatewayV2Iam": { + "id": "IntegApiGatewayV2Iam", + "path": "IntegApiGatewayV2Iam", + "children": { + "User": { + "id": "User", + "path": "IntegApiGatewayV2Iam/User", + "children": { + "Resource": { + "id": "Resource", + "path": "IntegApiGatewayV2Iam/User/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::User", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnUser", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.User", + "version": "0.0.0" + } + }, + "UserAccess": { + "id": "UserAccess", + "path": "IntegApiGatewayV2Iam/UserAccess", + "children": { + "Resource": { + "id": "Resource", + "path": "IntegApiGatewayV2Iam/UserAccess/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::AccessKey", + "aws:cdk:cloudformation:props": { + "userName": { + "Ref": "User00B015A1" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnAccessKey", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.AccessKey", + "version": "0.0.0" + } + }, + "auth-function": { + "id": "auth-function", + "path": "IntegApiGatewayV2Iam/auth-function", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "IntegApiGatewayV2Iam/auth-function/ServiceRole", + "children": { + "Resource": { + "id": "Resource", + "path": "IntegApiGatewayV2Iam/auth-function/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "IntegApiGatewayV2Iam/auth-function/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "zipFile": "exports.handler = () => {return true}" + }, + "role": { + "Fn::GetAtt": [ + "authfunctionServiceRoleFCB72198", + "Arn" + ] + }, + "handler": "index.handler", + "runtime": "nodejs14.x" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.CfnFunction", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.Function", + "version": "0.0.0" + } + }, + "WebSocketApi": { + "id": "WebSocketApi", + "path": "IntegApiGatewayV2Iam/WebSocketApi", + "children": { + "Resource": { + "id": "Resource", + "path": "IntegApiGatewayV2Iam/WebSocketApi/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::Api", + "aws:cdk:cloudformation:props": { + "name": "WebSocketApi", + "protocolType": "WEBSOCKET", + "routeSelectionExpression": "$request.body.action" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigatewayv2.CfnApi", + "version": "0.0.0" + } + }, + "$connect-Route": { + "id": "$connect-Route", + "path": "IntegApiGatewayV2Iam/WebSocketApi/$connect-Route", + "children": { + "WebSocketLambdaIntegration-Permission": { + "id": "WebSocketLambdaIntegration-Permission", + "path": "IntegApiGatewayV2Iam/WebSocketApi/$connect-Route/WebSocketLambdaIntegration-Permission", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Permission", + "aws:cdk:cloudformation:props": { + "action": "lambda:InvokeFunction", + "functionName": { + "Fn::GetAtt": [ + "authfunction96361832", + "Arn" + ] + }, + "principal": "apigateway.amazonaws.com", + "sourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "WebSocketApi34BCF99B" + }, + "/*/*$connect" + ] + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.CfnPermission", + "version": "0.0.0" + } + }, + "WebSocketLambdaIntegration": { + "id": "WebSocketLambdaIntegration", + "path": "IntegApiGatewayV2Iam/WebSocketApi/$connect-Route/WebSocketLambdaIntegration", + "children": { + "Resource": { + "id": "Resource", + "path": "IntegApiGatewayV2Iam/WebSocketApi/$connect-Route/WebSocketLambdaIntegration/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::Integration", + "aws:cdk:cloudformation:props": { + "apiId": { + "Ref": "WebSocketApi34BCF99B" + }, + "integrationType": "AWS_PROXY", + "integrationUri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "authfunction96361832", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigatewayv2.CfnIntegration", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketIntegration", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "IntegApiGatewayV2Iam/WebSocketApi/$connect-Route/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::Route", + "aws:cdk:cloudformation:props": { + "apiId": { + "Ref": "WebSocketApi34BCF99B" + }, + "routeKey": "$connect", + "authorizationType": "AWS_IAM", + "target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "WebSocketApiconnectRouteWebSocketLambdaIntegration3D2B13DD" + } + ] + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigatewayv2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketApi", + "version": "0.0.0" + } + }, + "AllowInvoke": { + "id": "AllowInvoke", + "path": "IntegApiGatewayV2Iam/AllowInvoke", + "children": { + "Resource": { + "id": "Resource", + "path": "IntegApiGatewayV2Iam/AllowInvoke/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "WebSocketApi34BCF99B" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "AllowInvoke767865EA", + "users": [ + { + "Ref": "User00B015A1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + }, + "TESTACCESSKEYID": { + "id": "TESTACCESSKEYID", + "path": "IntegApiGatewayV2Iam/TESTACCESSKEYID", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "TESTSECRETACCESSKEY": { + "id": "TESTSECRETACCESSKEY", + "path": "IntegApiGatewayV2Iam/TESTSECRETACCESSKEY", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "TESTREGION": { + "id": "TESTREGION", + "path": "IntegApiGatewayV2Iam/TESTREGION", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "ApiGatewayV2WebSocketIamTest": { + "id": "ApiGatewayV2WebSocketIamTest", + "path": "ApiGatewayV2WebSocketIamTest", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "ApiGatewayV2WebSocketIamTest/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "ApiGatewayV2WebSocketIamTest/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.58" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "ApiGatewayV2WebSocketIamTest/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.test.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.test.ts new file mode 100644 index 0000000000000..f0f34a3c11214 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.test.ts @@ -0,0 +1,36 @@ +import { Template } from '@aws-cdk/assertions'; +import { WebSocketApi } from '@aws-cdk/aws-apigatewayv2'; +import { WebSocketLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; +import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; +import { Stack } from '@aws-cdk/core'; +import { WebSocketIamAuthorizer } from '../../lib'; + +describe('WebSocketLambdaAuthorizer', () => { + test('default', () => { + const stack = new Stack(); + + const handler = new Function(stack, 'auth-function', { + runtime: Runtime.NODEJS_14_X, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', + }); + const integration = new WebSocketLambdaIntegration('Integration', handler); + + const authorizer = new WebSocketIamAuthorizer(); + + new WebSocketApi(stack, 'WebSocketApi', { + connectRouteOptions: { + integration, + authorizer, + }, + }); + + Template.fromStack(stack).hasResourceProperties( + 'AWS::ApiGatewayV2::Route', + { + RouteKey: '$connect', + AuthorizationType: 'AWS_IAM', + }, + ); + }); +}); diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/integ.iam.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/integ.iam.ts new file mode 100644 index 0000000000000..281268ab6bee8 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/integ.iam.ts @@ -0,0 +1,61 @@ +import * as apigatewayv2 from '@aws-cdk/aws-apigatewayv2'; +import { WebSocketLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; +import * as iam from '@aws-cdk/aws-iam'; +import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import { Stack } from '@aws-cdk/core'; +import * as integ from '@aws-cdk/integ-tests'; +import { WebSocketIamAuthorizer } from '../../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'IntegApiGatewayV2Iam'); +const user = new iam.User(stack, 'User'); +const userAccessKey = new iam.AccessKey(stack, 'UserAccess', { + user, +}); + +const handler = new Function(stack, 'auth-function', { + runtime: Runtime.NODEJS_14_X, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', +}); + +const webSocketApi = new apigatewayv2.WebSocketApi(stack, 'WebSocketApi', { + connectRouteOptions: { + integration: new WebSocketLambdaIntegration('WebSocketLambdaIntegration', handler), + authorizer: new WebSocketIamAuthorizer(), + }, +}); + +const arn = Stack.of(stack).formatArn({ + service: 'execute-api', + resource: webSocketApi.apiId, +}); + +user.attachInlinePolicy(new iam.Policy(stack, 'AllowInvoke', { + statements: [ + new iam.PolicyStatement({ + actions: ['execute-api:Invoke'], + effect: iam.Effect.ALLOW, + resources: [arn], + }), + ], +})); + +new integ.IntegTest(app, 'ApiGatewayV2WebSocketIamTest', { + testCases: [stack], +}); + +new cdk.CfnOutput(stack, 'TESTACCESSKEYID', { + value: userAccessKey.accessKeyId, +}); + +new cdk.CfnOutput(stack, 'TESTSECRETACCESSKEY', { + value: userAccessKey.secretAccessKey.unsafeUnwrap(), +}); + +new cdk.CfnOutput(stack, 'TESTREGION', { + value: stack.region, +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/authorizer.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/authorizer.ts index 5abb420c80bad..28d002d3a1044 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/authorizer.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/authorizer.ts @@ -12,6 +12,9 @@ import { IWebSocketRoute } from './route'; export enum WebSocketAuthorizerType { /** Lambda Authorizer */ LAMBDA = 'REQUEST', + + /** IAM Authorizer */ + IAM = 'AWS_IAM', } /** @@ -22,12 +25,12 @@ export interface WebSocketAuthorizerProps { * Name of the authorizer * @default - id of the WebSocketAuthorizer construct. */ - readonly authorizerName?: string + readonly authorizerName?: string; /** * WebSocket Api to attach the authorizer to */ - readonly webSocketApi: IWebSocketApi + readonly webSocketApi: IWebSocketApi; /** * The type of authorizer @@ -53,8 +56,7 @@ export interface WebSocketAuthorizerProps { /** * An authorizer for WebSocket APIs */ -export interface IWebSocketAuthorizer extends IAuthorizer { -} +export interface IWebSocketAuthorizer extends IAuthorizer {} /** * Reference to an WebSocket authorizer @@ -63,7 +65,7 @@ export interface WebSocketAuthorizerAttributes { /** * Id of the Authorizer */ - readonly authorizerId: string + readonly authorizerId: string; /** * Type of authorizer @@ -72,7 +74,7 @@ export interface WebSocketAuthorizerAttributes { * - CUSTOM - Lambda Authorizer * - NONE - No Authorization */ - readonly authorizerType: string + readonly authorizerType: string; } /**