Skip to content

Commit

Permalink
feat(apigatewayv2): http api - IAM authorizer support (aws#17519)
Browse files Browse the repository at this point in the history
Fixes aws#15123

See also: [@nija-at's comments on `grantInvoke`](aws#14853 (comment)), aws#10534

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
misterjoshua authored and TikiTDO committed Feb 21, 2022
1 parent 420ce56 commit 0e4c58e
Show file tree
Hide file tree
Showing 10 changed files with 673 additions and 16 deletions.
25 changes: 25 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- [JWT Authorizers](#jwt-authorizers)
- [User Pool Authorizer](#user-pool-authorizer)
- [Lambda Authorizers](#lambda-authorizers)
- [IAM Authorizers](#iam-authorizers)
- [WebSocket APIs](#websocket-apis)
- [Lambda Authorizer](#lambda-authorizer)

Expand Down Expand Up @@ -199,6 +200,30 @@ api.addRoutes({
});
```

### IAM Authorizers

API Gateway supports IAM via the included `HttpIamAuthorizer` and grant syntax:

```ts
import { HttpIamAuthorizer } from '@aws-cdk/aws-apigatewayv2-authorizers';
import { HttpUrlIntegration } from '@aws-cdk/aws-apigatewayv2-integrations';

declare const principal: iam.AnyPrincipal;

const authorizer = new HttpIamAuthorizer();

const httpApi = new apigwv2.HttpApi(this, 'HttpApi', {
defaultAuthorizer: authorizer,
});

const routes = httpApi.addRoutes({
integration: new HttpUrlIntegration('BooksIntegration', 'https://get-books-proxy.myproxy.internal'),
path: '/books/{book}',
});

routes[0].grantInvoke(principal);
```

## WebSocket APIs

You can set an authorizer to your WebSocket API's `$connect` route to control access to your API.
Expand Down
17 changes: 17 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/iam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {
HttpAuthorizerType,
HttpRouteAuthorizerBindOptions,
HttpRouteAuthorizerConfig,
IHttpRouteAuthorizer,
} from '@aws-cdk/aws-apigatewayv2';

/**
* Authorize HTTP API Routes with IAM
*/
export class HttpIamAuthorizer implements IHttpRouteAuthorizer {
public bind(_options: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig {
return {
authorizationType: HttpAuthorizerType.IAM,
};
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './user-pool';
export * from './jwt';
export * from './lambda';
export * from './lambda';
export * from './iam';
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { Construct } from 'constructs';
import { Duration, Stack } from '@aws-cdk/core';
import * as apigwv2 from '@aws-cdk/aws-apigatewayv2';
import * as iam from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';

class Fixture extends Stack {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
{
"Resources": {
"User00B015A1": {
"Type": "AWS::IAM::User"
},
"UserDefaultPolicy1F97781E": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow",
"Resource": {
"Fn::Join": [
"",
[
"arn:aws:execute-api:",
{
"Ref": "AWS::Region"
},
":",
{
"Ref": "AWS::AccountId"
},
":",
{
"Ref": "HttpApiF5A9A8A7"
},
"/*/*/foo"
]
]
}
},
{
"Action": "execute-api:Invoke",
"Effect": "Allow",
"Resource": {
"Fn::Join": [
"",
[
"arn:aws:execute-api:",
{
"Ref": "AWS::Region"
},
":",
{
"Ref": "AWS::AccountId"
},
":",
{
"Ref": "HttpApiF5A9A8A7"
},
"/*/*/books/*"
]
]
}
}
],
"Version": "2012-10-17"
},
"PolicyName": "UserDefaultPolicy1F97781E",
"Users": [
{
"Ref": "User00B015A1"
}
]
}
},
"UserAccess": {
"Type": "AWS::IAM::AccessKey",
"Properties": {
"UserName": {
"Ref": "User00B015A1"
}
}
},
"HttpApiF5A9A8A7": {
"Type": "AWS::ApiGatewayV2::Api",
"Properties": {
"Name": "HttpApi",
"ProtocolType": "HTTP"
}
},
"HttpApiDefaultStage3EEB07D6": {
"Type": "AWS::ApiGatewayV2::Stage",
"Properties": {
"ApiId": {
"Ref": "HttpApiF5A9A8A7"
},
"StageName": "$default",
"AutoDeploy": true
}
},
"HttpApiANYfooexamplecom903F7A9F": {
"Type": "AWS::ApiGatewayV2::Integration",
"Properties": {
"ApiId": {
"Ref": "HttpApiF5A9A8A7"
},
"IntegrationType": "HTTP_PROXY",
"IntegrationMethod": "GET",
"IntegrationUri": "https://www.example.com/",
"PayloadFormatVersion": "1.0"
}
},
"HttpApiANYfooD178456F": {
"Type": "AWS::ApiGatewayV2::Route",
"Properties": {
"ApiId": {
"Ref": "HttpApiF5A9A8A7"
},
"RouteKey": "ANY /foo",
"AuthorizationType": "AWS_IAM",
"Target": {
"Fn::Join": [
"",
[
"integrations/",
{
"Ref": "HttpApiANYfooexamplecom903F7A9F"
}
]
]
}
}
},
"HttpApiANYbooksbookexamplecom5C333C98": {
"Type": "AWS::ApiGatewayV2::Integration",
"Properties": {
"ApiId": {
"Ref": "HttpApiF5A9A8A7"
},
"IntegrationType": "HTTP_PROXY",
"IntegrationMethod": "GET",
"IntegrationUri": "https://www.example.com/",
"PayloadFormatVersion": "1.0"
}
},
"HttpApiANYbooksbook2F78361C": {
"Type": "AWS::ApiGatewayV2::Route",
"Properties": {
"ApiId": {
"Ref": "HttpApiF5A9A8A7"
},
"RouteKey": "ANY /books/{book}",
"AuthorizationType": "AWS_IAM",
"Target": {
"Fn::Join": [
"",
[
"integrations/",
{
"Ref": "HttpApiANYbooksbookexamplecom5C333C98"
}
]
]
}
}
}
},
"Outputs": {
"API": {
"Value": {
"Fn::Join": [
"",
[
"https://",
{
"Ref": "HttpApiF5A9A8A7"
},
".execute-api.",
{
"Ref": "AWS::Region"
},
".",
{
"Ref": "AWS::URLSuffix"
},
"/"
]
]
}
},
"TESTACCESSKEYID": {
"Value": {
"Ref": "UserAccess"
}
},
"TESTSECRETACCESSKEY": {
"Value": {
"Fn::GetAtt": [
"UserAccess",
"SecretAccessKey"
]
}
},
"TESTREGION": {
"Value": {
"Ref": "AWS::Region"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import * as apigatewayv2 from '@aws-cdk/aws-apigatewayv2';
import * as iam from '@aws-cdk/aws-iam';
import * as cdk from '@aws-cdk/core';
import { HttpIamAuthorizer } from '../../lib';

class ExampleComIntegration extends apigatewayv2.HttpRouteIntegration {
public bind(): apigatewayv2.HttpRouteIntegrationConfig {
return {
type: apigatewayv2.HttpIntegrationType.HTTP_PROXY,
payloadFormatVersion: apigatewayv2.PayloadFormatVersion.VERSION_1_0,
method: apigatewayv2.HttpMethod.GET,
uri: 'https://www.example.com/',
};
}
}

const app = new cdk.App();
const stack = new cdk.Stack(app, 'IntegApiGatewayV2Iam');
const user = new iam.User(stack, 'User');
const userAccessKey = new iam.CfnAccessKey(stack, 'UserAccess', {
userName: user.userName,
});

const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', {
defaultAuthorizer: new HttpIamAuthorizer(),
});

const [fooRoute] = httpApi.addRoutes({
integration: new ExampleComIntegration('examplecom'),
path: '/foo',
});

fooRoute.grantInvoke(user);

const [booksRoute] = httpApi.addRoutes({
integration: new ExampleComIntegration('examplecom'),
path: '/books/{book}',
});

booksRoute.grantInvoke(user);

new cdk.CfnOutput(stack, 'API', {
value: httpApi.url!,
});

new cdk.CfnOutput(stack, 'TESTACCESSKEYID', {
value: userAccessKey.ref,
});

new cdk.CfnOutput(stack, 'TESTSECRETACCESSKEY', {
value: userAccessKey.attrSecretAccessKey,
});

new cdk.CfnOutput(stack, 'TESTREGION', {
value: stack.region,
});

/*
* Stack verification steps:
* * Get cURL version 7.75.0 or later so you can use the --aws-sigv4 option
* * Curl <url>/foo without sigv4 and expect a 403
* * Curl <url>/books/something without sigv4 and expect a 403
* * Curl <url>/foo with sigv4 from the authorized user and expect 200
* * Curl <url>/books/something with sigv4 from the authorized user and expect 200
*
* Reference:
* * 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>
* * Args to enable sigv4 with authorized credentials: --user "$TESTACCESSKEYID:$TESTSECRETACCESSKEY" --aws-sigv4 "aws:amz:$TESTREGION:execute-api"
*/
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2/lib/http/authorizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { IHttpRoute } from './route';
* Supported Authorizer types
*/
export enum HttpAuthorizerType {
/** IAM Authorizer */
IAM = 'AWS_IAM',

/** JSON Web Tokens */
JWT = 'JWT',

Expand Down Expand Up @@ -221,6 +224,7 @@ export interface HttpRouteAuthorizerConfig {
* The type of authorization
*
* Possible values are:
* - AWS_IAM - IAM Authorizer
* - JWT - JSON Web Token Authorizer
* - CUSTOM - Lambda Authorizer
* - NONE - No Authorization
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-apigatewayv2/lib/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ export * from './route';
export * from './integration';
export * from './stage';
export * from './vpc-link';
export * from './authorizer';
export * from './authorizer';
Loading

0 comments on commit 0e4c58e

Please sign in to comment.