Skip to content

Commit 5dc3931

Browse files
tmokmssTikiTDO
authored andcommitted
feat(apigatewayv2): websocket api: grant manage connections (aws#16872)
closes aws#14828 By this PR, we can allow access to management API by the following code. ```ts const api = new WebSocketApi(stack, 'Api'); const defaultStage = new WebSocketStage(stack, 'Stage', { webSocketApi: api, stageName: 'dev', }); const principal = new User(stack, 'User'); api.grantManagementApiAccess(principal); // allow access to the management API for all the stage defaultStage.grantManagementApiAccess(principal); // allow access to the management API for a specific stage ``` We use WebSocket API Management API to send messages to a WebSocket API. [(doc)](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-how-to-call-websocket-api-connections.html) To use the API, we must set IAM statement as below [(doc)](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-control-access-iam.html): ```json { "Effect": "Allow", "Action": [ "execute-api:ManageConnections" ], "Resource": [ "arn:aws:execute-api:us-east-1:account-id:api-id/stage-name/POST/@connections/*" ] } ``` We need `/*` at the end of resource ARN because there will be arbitrary strings (`connectionId`). i.e. `{apiArn}/{stageName}/POST/@connections/{connectionId}` ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 6068711 commit 5dc3931

File tree

5 files changed

+155
-2
lines changed

5 files changed

+155
-2
lines changed

Diff for: packages/@aws-cdk/aws-apigatewayv2/README.md

+20
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Higher level constructs for Websocket APIs | ![Experimental](https://img.shields
4040
- [VPC Link](#vpc-link)
4141
- [Private Integration](#private-integration)
4242
- [WebSocket API](#websocket-api)
43+
- [Manage Connections Permission](#manage-connections-permission)
4344

4445
## Introduction
4546

@@ -403,3 +404,22 @@ webSocketApi.addRoute('sendmessage', {
403404
}),
404405
});
405406
```
407+
408+
### Manage Connections Permission
409+
410+
Grant permission to use API Gateway Management API of a WebSocket API by calling the `grantManageConnections` API.
411+
You can use Management API to send a callback message to a connected client, get connection information, or disconnect the client. Learn more at [Use @connections commands in your backend service](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-how-to-call-websocket-api-connections.html).
412+
413+
```ts
414+
const lambda = new lambda.Function(this, 'lambda', { /* ... */ });
415+
416+
const webSocketApi = new WebSocketApi(stack, 'mywsapi');
417+
const stage = new WebSocketStage(stack, 'mystage', {
418+
webSocketApi,
419+
stageName: 'dev',
420+
});
421+
// per stage permission
422+
stage.grantManageConnections(lambda);
423+
// for all the stages permission
424+
webSocketApi.grantManageConnections(lambda);
425+
```

Diff for: packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts

+21
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Grant, IGrantable } from '@aws-cdk/aws-iam';
2+
import { Stack } from '@aws-cdk/core';
13
import { Construct } from 'constructs';
24
import { CfnApi } from '../apigatewayv2.generated';
35
import { IApi } from '../common/api';
@@ -127,4 +129,23 @@ export class WebSocketApi extends ApiBase implements IWebSocketApi {
127129
...options,
128130
});
129131
}
132+
133+
/**
134+
* Grant access to the API Gateway management API for this WebSocket API to an IAM
135+
* principal (Role/Group/User).
136+
*
137+
* @param identity The principal
138+
*/
139+
public grantManageConnections(identity: IGrantable): Grant {
140+
const arn = Stack.of(this).formatArn({
141+
service: 'execute-api',
142+
resource: this.apiId,
143+
});
144+
145+
return Grant.addToPrincipal({
146+
grantee: identity,
147+
actions: ['execute-api:ManageConnections'],
148+
resourceArns: [`${arn}/*/POST/@connections/*`],
149+
});
150+
}
130151
}

Diff for: packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts

+20
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Grant, IGrantable } from '@aws-cdk/aws-iam';
12
import { Stack } from '@aws-cdk/core';
23
import { Construct } from 'constructs';
34
import { CfnStage } from '../apigatewayv2.generated';
@@ -114,4 +115,23 @@ export class WebSocketStage extends StageBase implements IWebSocketStage {
114115
const urlPath = this.stageName;
115116
return `https://${this.api.apiId}.execute-api.${s.region}.${s.urlSuffix}/${urlPath}`;
116117
}
118+
119+
/**
120+
* Grant access to the API Gateway management API for this WebSocket API Stage to an IAM
121+
* principal (Role/Group/User).
122+
*
123+
* @param identity The principal
124+
*/
125+
public grantManagementApiAccess(identity: IGrantable): Grant {
126+
const arn = Stack.of(this.api).formatArn({
127+
service: 'execute-api',
128+
resource: this.api.apiId,
129+
});
130+
131+
return Grant.addToPrincipal({
132+
grantee: identity,
133+
actions: ['execute-api:ManageConnections'],
134+
resourceArns: [`${arn}/${this.stageName}/POST/@connections/*`],
135+
});
136+
}
117137
}

Diff for: packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Template } from '@aws-cdk/assertions';
1+
import { Match, Template } from '@aws-cdk/assertions';
2+
import { User } from '@aws-cdk/aws-iam';
23
import { Stack } from '@aws-cdk/core';
34
import {
45
IWebSocketRouteIntegration, WebSocketApi, WebSocketIntegrationType,
@@ -80,6 +81,49 @@ describe('WebSocketApi', () => {
8081
RouteKey: '$default',
8182
});
8283
});
84+
85+
describe('grantManageConnections', () => {
86+
test('adds an IAM policy to the principal', () => {
87+
// GIVEN
88+
const stack = new Stack();
89+
const api = new WebSocketApi(stack, 'api');
90+
const principal = new User(stack, 'user');
91+
92+
// WHEN
93+
api.grantManageConnections(principal);
94+
95+
// THEN
96+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
97+
PolicyDocument: {
98+
Statement: Match.arrayWith([{
99+
Action: 'execute-api:ManageConnections',
100+
Effect: 'Allow',
101+
Resource: {
102+
'Fn::Join': ['', [
103+
'arn:',
104+
{
105+
Ref: 'AWS::Partition',
106+
},
107+
':execute-api:',
108+
{
109+
Ref: 'AWS::Region',
110+
},
111+
':',
112+
{
113+
Ref: 'AWS::AccountId',
114+
},
115+
':',
116+
{
117+
Ref: 'apiC8550315',
118+
},
119+
'/*/POST/@connections/*',
120+
]],
121+
},
122+
}]),
123+
},
124+
});
125+
});
126+
});
83127
});
84128

85129
class DummyIntegration implements IWebSocketRouteIntegration {

Diff for: packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts

+49-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Template } from '@aws-cdk/assertions';
1+
import { Match, Template } from '@aws-cdk/assertions';
2+
import { User } from '@aws-cdk/aws-iam';
23
import { Stack } from '@aws-cdk/core';
34
import { WebSocketApi, WebSocketStage } from '../../lib';
45

@@ -59,4 +60,51 @@ describe('WebSocketStage', () => {
5960
expect(defaultStage.callbackUrl.endsWith('/dev')).toBe(true);
6061
expect(defaultStage.callbackUrl.startsWith('https://')).toBe(true);
6162
});
63+
64+
describe('grantManageConnections', () => {
65+
test('adds an IAM policy to the principal', () => {
66+
// GIVEN
67+
const stack = new Stack();
68+
const api = new WebSocketApi(stack, 'Api');
69+
const defaultStage = new WebSocketStage(stack, 'Stage', {
70+
webSocketApi: api,
71+
stageName: 'dev',
72+
});
73+
const principal = new User(stack, 'User');
74+
75+
// WHEN
76+
defaultStage.grantManagementApiAccess(principal);
77+
78+
// THEN
79+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
80+
PolicyDocument: {
81+
Statement: Match.arrayWith([{
82+
Action: 'execute-api:ManageConnections',
83+
Effect: 'Allow',
84+
Resource: {
85+
'Fn::Join': ['', [
86+
'arn:',
87+
{
88+
Ref: 'AWS::Partition',
89+
},
90+
':execute-api:',
91+
{
92+
Ref: 'AWS::Region',
93+
},
94+
':',
95+
{
96+
Ref: 'AWS::AccountId',
97+
},
98+
':',
99+
{
100+
Ref: 'ApiF70053CD',
101+
},
102+
`/${defaultStage.stageName}/POST/@connections/*`,
103+
]],
104+
},
105+
}]),
106+
},
107+
});
108+
});
109+
});
62110
});

0 commit comments

Comments
 (0)