Skip to content

Commit

Permalink
feat(apigatewayv2): WebSocket API - IAM authorizer support (#21393)
Browse files Browse the repository at this point in the history
This adds support for `AWS_IAM` as Authorizer for Websocket $connect route.

The CDK supports adding IAM Authorizer as `authorizationType` for `HttpApi`, but does not support it for `WebSocketApi` L2 construct

IAM Authorization is covered in the docs [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-control-access-iam.html). 

It works the same way as REST or HTTP API's where you can make an endpoint (connect route for websocket) publicly inaccessible, and setup an IAM user, and allow access using signed URL's

The above doc links back to [this](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html) section which explains the `AWS_IAM`

This is also available from the AWS console for Websocket connect Route:

<img width="1234" alt="Screenshot 2022-07-30 at 17 47 51" src="https://user-images.githubusercontent.com/3215958/181933570-99dc6019-8464-444f-bbc0-d1e26358b5ab.png">

---

* [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md)

### Adding new Unconventional Dependencies:

* [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies)

### New Features

* [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)?
	* [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)?

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
d0z0 authored Aug 4, 2022
1 parent 7ccc644 commit a1a6e6c
Show file tree
Hide file tree
Showing 13 changed files with 951 additions and 6 deletions.
40 changes: 40 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
- [IAM Authorizers](#iam-authorizers)
- [WebSocket APIs](#websocket-apis)
- [Lambda Authorizer](#lambda-authorizer)
- [IAM Authorizers](#iam-authorizer)

## Introduction

Expand Down Expand Up @@ -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],
}),
],
}));

```
Original file line number Diff line number Diff line change
@@ -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,
};
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './lambda';
export * from './iam';
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version":"20.0.0"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": "20.0.0",
"testCases": {
"ApiGatewayV2WebSocketIamTest/DefaultTest": {
"stacks": [
"IntegApiGatewayV2Iam"
],
"assertionStack": "ApiGatewayV2WebSocketIamTestDefaultTestDeployAssert2B412D7B"
}
}
}
Loading

0 comments on commit a1a6e6c

Please sign in to comment.