Skip to content

Commit fd8e0e3

Browse files
authored
feat(apigatewayv2): http api - IAM authorizer support (#17519)
Fixes #15123 See also: [@nija-at's comments on `grantInvoke`](#14853 (comment)), #10534 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent b7be71c commit fd8e0e3

File tree

10 files changed

+673
-16
lines changed

10 files changed

+673
-16
lines changed

packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md

+25
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
- [JWT Authorizers](#jwt-authorizers)
2626
- [User Pool Authorizer](#user-pool-authorizer)
2727
- [Lambda Authorizers](#lambda-authorizers)
28+
- [IAM Authorizers](#iam-authorizers)
2829
- [WebSocket APIs](#websocket-apis)
2930
- [Lambda Authorizer](#lambda-authorizer)
3031

@@ -199,6 +200,30 @@ api.addRoutes({
199200
});
200201
```
201202

203+
### IAM Authorizers
204+
205+
API Gateway supports IAM via the included `HttpIamAuthorizer` and grant syntax:
206+
207+
```ts
208+
import { HttpIamAuthorizer } from '@aws-cdk/aws-apigatewayv2-authorizers';
209+
import { HttpUrlIntegration } from '@aws-cdk/aws-apigatewayv2-integrations';
210+
211+
declare const principal: iam.AnyPrincipal;
212+
213+
const authorizer = new HttpIamAuthorizer();
214+
215+
const httpApi = new apigwv2.HttpApi(this, 'HttpApi', {
216+
defaultAuthorizer: authorizer,
217+
});
218+
219+
const routes = httpApi.addRoutes({
220+
integration: new HttpUrlIntegration('BooksIntegration', 'https://get-books-proxy.myproxy.internal'),
221+
path: '/books/{book}',
222+
});
223+
224+
routes[0].grantInvoke(principal);
225+
```
226+
202227
## WebSocket APIs
203228

204229
You can set an authorizer to your WebSocket API's `$connect` route to control access to your API.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {
2+
HttpAuthorizerType,
3+
HttpRouteAuthorizerBindOptions,
4+
HttpRouteAuthorizerConfig,
5+
IHttpRouteAuthorizer,
6+
} from '@aws-cdk/aws-apigatewayv2';
7+
8+
/**
9+
* Authorize HTTP API Routes with IAM
10+
*/
11+
export class HttpIamAuthorizer implements IHttpRouteAuthorizer {
12+
public bind(_options: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig {
13+
return {
14+
authorizationType: HttpAuthorizerType.IAM,
15+
};
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './user-pool';
22
export * from './jwt';
3-
export * from './lambda';
3+
export * from './lambda';
4+
export * from './iam';

packages/@aws-cdk/aws-apigatewayv2-authorizers/rosetta/default.ts-fixture

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { Construct } from 'constructs';
33
import { Duration, Stack } from '@aws-cdk/core';
44
import * as apigwv2 from '@aws-cdk/aws-apigatewayv2';
5+
import * as iam from '@aws-cdk/aws-iam';
56
import * as lambda from '@aws-cdk/aws-lambda';
67

78
class Fixture extends Stack {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
{
2+
"Resources": {
3+
"User00B015A1": {
4+
"Type": "AWS::IAM::User"
5+
},
6+
"UserDefaultPolicy1F97781E": {
7+
"Type": "AWS::IAM::Policy",
8+
"Properties": {
9+
"PolicyDocument": {
10+
"Statement": [
11+
{
12+
"Action": "execute-api:Invoke",
13+
"Effect": "Allow",
14+
"Resource": {
15+
"Fn::Join": [
16+
"",
17+
[
18+
"arn:aws:execute-api:",
19+
{
20+
"Ref": "AWS::Region"
21+
},
22+
":",
23+
{
24+
"Ref": "AWS::AccountId"
25+
},
26+
":",
27+
{
28+
"Ref": "HttpApiF5A9A8A7"
29+
},
30+
"/*/*/foo"
31+
]
32+
]
33+
}
34+
},
35+
{
36+
"Action": "execute-api:Invoke",
37+
"Effect": "Allow",
38+
"Resource": {
39+
"Fn::Join": [
40+
"",
41+
[
42+
"arn:aws:execute-api:",
43+
{
44+
"Ref": "AWS::Region"
45+
},
46+
":",
47+
{
48+
"Ref": "AWS::AccountId"
49+
},
50+
":",
51+
{
52+
"Ref": "HttpApiF5A9A8A7"
53+
},
54+
"/*/*/books/*"
55+
]
56+
]
57+
}
58+
}
59+
],
60+
"Version": "2012-10-17"
61+
},
62+
"PolicyName": "UserDefaultPolicy1F97781E",
63+
"Users": [
64+
{
65+
"Ref": "User00B015A1"
66+
}
67+
]
68+
}
69+
},
70+
"UserAccess": {
71+
"Type": "AWS::IAM::AccessKey",
72+
"Properties": {
73+
"UserName": {
74+
"Ref": "User00B015A1"
75+
}
76+
}
77+
},
78+
"HttpApiF5A9A8A7": {
79+
"Type": "AWS::ApiGatewayV2::Api",
80+
"Properties": {
81+
"Name": "HttpApi",
82+
"ProtocolType": "HTTP"
83+
}
84+
},
85+
"HttpApiDefaultStage3EEB07D6": {
86+
"Type": "AWS::ApiGatewayV2::Stage",
87+
"Properties": {
88+
"ApiId": {
89+
"Ref": "HttpApiF5A9A8A7"
90+
},
91+
"StageName": "$default",
92+
"AutoDeploy": true
93+
}
94+
},
95+
"HttpApiANYfooexamplecom903F7A9F": {
96+
"Type": "AWS::ApiGatewayV2::Integration",
97+
"Properties": {
98+
"ApiId": {
99+
"Ref": "HttpApiF5A9A8A7"
100+
},
101+
"IntegrationType": "HTTP_PROXY",
102+
"IntegrationMethod": "GET",
103+
"IntegrationUri": "https://www.example.com/",
104+
"PayloadFormatVersion": "1.0"
105+
}
106+
},
107+
"HttpApiANYfooD178456F": {
108+
"Type": "AWS::ApiGatewayV2::Route",
109+
"Properties": {
110+
"ApiId": {
111+
"Ref": "HttpApiF5A9A8A7"
112+
},
113+
"RouteKey": "ANY /foo",
114+
"AuthorizationType": "AWS_IAM",
115+
"Target": {
116+
"Fn::Join": [
117+
"",
118+
[
119+
"integrations/",
120+
{
121+
"Ref": "HttpApiANYfooexamplecom903F7A9F"
122+
}
123+
]
124+
]
125+
}
126+
}
127+
},
128+
"HttpApiANYbooksbookexamplecom5C333C98": {
129+
"Type": "AWS::ApiGatewayV2::Integration",
130+
"Properties": {
131+
"ApiId": {
132+
"Ref": "HttpApiF5A9A8A7"
133+
},
134+
"IntegrationType": "HTTP_PROXY",
135+
"IntegrationMethod": "GET",
136+
"IntegrationUri": "https://www.example.com/",
137+
"PayloadFormatVersion": "1.0"
138+
}
139+
},
140+
"HttpApiANYbooksbook2F78361C": {
141+
"Type": "AWS::ApiGatewayV2::Route",
142+
"Properties": {
143+
"ApiId": {
144+
"Ref": "HttpApiF5A9A8A7"
145+
},
146+
"RouteKey": "ANY /books/{book}",
147+
"AuthorizationType": "AWS_IAM",
148+
"Target": {
149+
"Fn::Join": [
150+
"",
151+
[
152+
"integrations/",
153+
{
154+
"Ref": "HttpApiANYbooksbookexamplecom5C333C98"
155+
}
156+
]
157+
]
158+
}
159+
}
160+
}
161+
},
162+
"Outputs": {
163+
"API": {
164+
"Value": {
165+
"Fn::Join": [
166+
"",
167+
[
168+
"https://",
169+
{
170+
"Ref": "HttpApiF5A9A8A7"
171+
},
172+
".execute-api.",
173+
{
174+
"Ref": "AWS::Region"
175+
},
176+
".",
177+
{
178+
"Ref": "AWS::URLSuffix"
179+
},
180+
"/"
181+
]
182+
]
183+
}
184+
},
185+
"TESTACCESSKEYID": {
186+
"Value": {
187+
"Ref": "UserAccess"
188+
}
189+
},
190+
"TESTSECRETACCESSKEY": {
191+
"Value": {
192+
"Fn::GetAtt": [
193+
"UserAccess",
194+
"SecretAccessKey"
195+
]
196+
}
197+
},
198+
"TESTREGION": {
199+
"Value": {
200+
"Ref": "AWS::Region"
201+
}
202+
}
203+
}
204+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import * as apigatewayv2 from '@aws-cdk/aws-apigatewayv2';
2+
import * as iam from '@aws-cdk/aws-iam';
3+
import * as cdk from '@aws-cdk/core';
4+
import { HttpIamAuthorizer } from '../../lib';
5+
6+
class ExampleComIntegration extends apigatewayv2.HttpRouteIntegration {
7+
public bind(): apigatewayv2.HttpRouteIntegrationConfig {
8+
return {
9+
type: apigatewayv2.HttpIntegrationType.HTTP_PROXY,
10+
payloadFormatVersion: apigatewayv2.PayloadFormatVersion.VERSION_1_0,
11+
method: apigatewayv2.HttpMethod.GET,
12+
uri: 'https://www.example.com/',
13+
};
14+
}
15+
}
16+
17+
const app = new cdk.App();
18+
const stack = new cdk.Stack(app, 'IntegApiGatewayV2Iam');
19+
const user = new iam.User(stack, 'User');
20+
const userAccessKey = new iam.CfnAccessKey(stack, 'UserAccess', {
21+
userName: user.userName,
22+
});
23+
24+
const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', {
25+
defaultAuthorizer: new HttpIamAuthorizer(),
26+
});
27+
28+
const [fooRoute] = httpApi.addRoutes({
29+
integration: new ExampleComIntegration('examplecom'),
30+
path: '/foo',
31+
});
32+
33+
fooRoute.grantInvoke(user);
34+
35+
const [booksRoute] = httpApi.addRoutes({
36+
integration: new ExampleComIntegration('examplecom'),
37+
path: '/books/{book}',
38+
});
39+
40+
booksRoute.grantInvoke(user);
41+
42+
new cdk.CfnOutput(stack, 'API', {
43+
value: httpApi.url!,
44+
});
45+
46+
new cdk.CfnOutput(stack, 'TESTACCESSKEYID', {
47+
value: userAccessKey.ref,
48+
});
49+
50+
new cdk.CfnOutput(stack, 'TESTSECRETACCESSKEY', {
51+
value: userAccessKey.attrSecretAccessKey,
52+
});
53+
54+
new cdk.CfnOutput(stack, 'TESTREGION', {
55+
value: stack.region,
56+
});
57+
58+
/*
59+
* Stack verification steps:
60+
* * Get cURL version 7.75.0 or later so you can use the --aws-sigv4 option
61+
* * Curl <url>/foo without sigv4 and expect a 403
62+
* * Curl <url>/books/something without sigv4 and expect a 403
63+
* * Curl <url>/foo with sigv4 from the authorized user and expect 200
64+
* * Curl <url>/books/something with sigv4 from the authorized user and expect 200
65+
*
66+
* Reference:
67+
* * Using cURL 7.75.0 or later via the official docker image: docker run --rm curlimages/curl -s -o/dev/null -w"%{http_code}" <url>
68+
* * Args to enable sigv4 with authorized credentials: --user "$TESTACCESSKEYID:$TESTSECRETACCESSKEY" --aws-sigv4 "aws:amz:$TESTREGION:execute-api"
69+
*/

packages/@aws-cdk/aws-apigatewayv2/lib/http/authorizer.ts

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { IHttpRoute } from './route';
1010
* Supported Authorizer types
1111
*/
1212
export enum HttpAuthorizerType {
13+
/** IAM Authorizer */
14+
IAM = 'AWS_IAM',
15+
1316
/** JSON Web Tokens */
1417
JWT = 'JWT',
1518

@@ -221,6 +224,7 @@ export interface HttpRouteAuthorizerConfig {
221224
* The type of authorization
222225
*
223226
* Possible values are:
227+
* - AWS_IAM - IAM Authorizer
224228
* - JWT - JSON Web Token Authorizer
225229
* - CUSTOM - Lambda Authorizer
226230
* - NONE - No Authorization

packages/@aws-cdk/aws-apigatewayv2/lib/http/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ export * from './route';
33
export * from './integration';
44
export * from './stage';
55
export * from './vpc-link';
6-
export * from './authorizer';
6+
export * from './authorizer';

0 commit comments

Comments
 (0)