Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(apigatewayv2): http api - default authorizer options #13172

Merged
merged 31 commits into from Mar 17, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
253a9b0
feat(apigatewayv2): http api - default authorizer options
iRoachie Feb 21, 2021
105684c
docs: add example of default settings
iRoachie Feb 21, 2021
a953e06
Update packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts
iRoachie Feb 25, 2021
51132a3
change route behaviour to override default scopes
iRoachie Feb 25, 2021
2404a77
Add NoneAuthorizer
iRoachie Feb 25, 2021
4dceadf
Add docs for NoneAuthorizer
iRoachie Feb 25, 2021
3ec0ca9
Merge branch 'master' into ft/default-authorizer
iRoachie Feb 25, 2021
bae9a27
Add optional default documentation
iRoachie Feb 25, 2021
764552c
Update packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts
iRoachie Feb 26, 2021
19b1383
Update packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts
iRoachie Feb 26, 2021
8577089
prefix internal variable with default keyword
iRoachie Feb 26, 2021
8e071ae
Update packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts
iRoachie Feb 26, 2021
f4dd89b
Update packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts
iRoachie Feb 26, 2021
a97a90e
relocate NoneAuthorizer
iRoachie Feb 26, 2021
df0bae2
Update packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts
iRoachie Feb 26, 2021
32aa124
don't set empty authorizationScopes
iRoachie Feb 26, 2021
9eb1403
update integ test
iRoachie Feb 26, 2021
0345169
update integ tests
iRoachie Feb 26, 2021
ab4a090
Merge branch 'master' into ft/default-authorizer
iRoachie Feb 27, 2021
84bf186
update integ test
iRoachie Feb 27, 2021
c221da5
Merge branch 'master' into ft/default-authorizer
iRoachie Mar 6, 2021
07e1569
Update packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts
iRoachie Mar 10, 2021
f5ac48a
docs: update authorizationr readme
iRoachie Mar 10, 2021
1660110
Update packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md
iRoachie Mar 10, 2021
f56c52a
Update packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md
iRoachie Mar 10, 2021
59f3d52
Update packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md
iRoachie Mar 10, 2021
62e7e24
Merge branch 'master' into ft/default-authorizer
mergify[bot] Mar 10, 2021
6a3a44b
Merge branch 'master' into ft/default-authorizer
iRoachie Mar 14, 2021
36593ee
update integ
iRoachie Mar 15, 2021
1805a21
Merge branch 'master' into ft/default-authorizer
mergify[bot] Mar 17, 2021
c96dfe5
Merge branch 'master' into ft/default-authorizer
mergify[bot] Mar 17, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 31 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md
Expand Up @@ -28,6 +28,37 @@ classified into Lambda Authorizers, JWT authorizers and standard AWS IAM roles a
available at [Controlling and managing access to an HTTP
API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-access-control.html).

Authorizers, and scopes can either be applied to the Gateway (applied to all routes) or specifically for each route.

The example below applies the authorizer to all routes.

```ts
const authorizer = new HttpJwtAuthorizer({
iRoachie marked this conversation as resolved.
Show resolved Hide resolved
jwtAudience: ['3131231'],
jwtIssuer: 'https://test.us.auth0.com',
});

const api = new HttpApi(stack, 'HttpApi', {
defaultAuthorizer: authorizer,
defaultAuthorizationScopes: ['read:books']
});

api.addRoutes({
integration: new HttpProxyIntegration({
url: 'https://add-books-proxy.myproxy.internal',
}),
path: '/books', // This route will inherit the authorizer and scopes from the gateway
});

api.addRoutes({
integration: new HttpProxyIntegration({
url: 'https://get-books-proxy.myproxy.internal',
}),
path: '/books',
authorizer: new NoneAuthorizer(), // This route will remove the default authorizer from the gateway
});
```

## JWT Authorizers

JWT authorizers allow the use of JSON Web Tokens (JWTs) as part of [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) and [OAuth 2.0](https://oauth.net/2/) frameworks to allow and restrict clients from accessing HTTP APIs.
Expand Down
65 changes: 54 additions & 11 deletions packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts
Expand Up @@ -4,7 +4,7 @@ import { Duration, IResource, Resource, Stack } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnApi, CfnApiProps } from '../apigatewayv2.generated';
import { DefaultDomainMappingOptions } from '../http/stage';
import { IHttpRouteAuthorizer } from './authorizer';
import { HttpAuthorizerType, HttpRouteAuthorizerBindOptions, HttpRouteAuthorizerConfig, IHttpRouteAuthorizer } from './authorizer';
import { IHttpRouteIntegration, HttpIntegration, HttpRouteIntegrationConfig } from './integration';
import { BatchHttpRouteOptions, HttpMethod, HttpRoute, HttpRouteKey } from './route';
import { HttpStage, HttpStageOptions } from './stage';
Expand Down Expand Up @@ -145,6 +145,20 @@ export interface HttpApiProps {
* @default false execute-api endpoint enabled.
*/
readonly disableExecuteApiEndpoint?: boolean;

/**
* Default Authorizer to applied to all routes in the gateway
*
* @default - No authorizer
*/
readonly defaultAuthorizer?: IHttpRouteAuthorizer;

/**
* Default OIDC scopes attached to all routes in the gateway
*
* @default - no additional authorization scopes
iRoachie marked this conversation as resolved.
Show resolved Hide resolved
*/
readonly defaultAuthorizationScopes?: string[];
}

/**
Expand Down Expand Up @@ -205,15 +219,20 @@ export interface AddRoutesOptions extends BatchHttpRouteOptions {

/**
* Authorizer to be associated to these routes.
* @default - No authorizer
*
* Use @see NoneAuthorizer to remove the default authorizer for the api
iRoachie marked this conversation as resolved.
Show resolved Hide resolved
*
* @default - uses the default authorizer if one is specified on the HttpApi
*/
readonly authorizer?: IHttpRouteAuthorizer;

/**
* The list of OIDC scopes to include in the authorization.
*
* These scopes will be merged with the scopes from the attached authorizer
* @default - no additional authorization scopes
* These scopes will override the default authorization scopes on the gateway.
* Set to [] to remove default scopes
*
* @default - no additional authorization scopes once no default ones set on the gateway.
iRoachie marked this conversation as resolved.
Show resolved Hide resolved
*/
readonly authorizationScopes?: string[];
}
Expand Down Expand Up @@ -353,6 +372,9 @@ export class HttpApi extends HttpApiBase {

private readonly _apiEndpoint: string;

private readonly authorizer?: IHttpRouteAuthorizer;
private readonly authorizationScopes?: string[];
iRoachie marked this conversation as resolved.
Show resolved Hide resolved

constructor(scope: Construct, id: string, props?: HttpApiProps) {
super(scope, id);

Expand Down Expand Up @@ -394,6 +416,8 @@ export class HttpApi extends HttpApiBase {
const resource = new CfnApi(this, 'Resource', apiProps);
this.httpApiId = resource.ref;
this._apiEndpoint = resource.attrApiEndpoint;
this.authorizer = props?.defaultAuthorizer;
this.authorizationScopes = props?.defaultAuthorizationScopes;

if (props?.defaultIntegration) {
new HttpRoute(this, 'DefaultRoute', {
Expand Down Expand Up @@ -457,12 +481,31 @@ export class HttpApi extends HttpApiBase {
*/
public addRoutes(options: AddRoutesOptions): HttpRoute[] {
const methods = options.methods ?? [HttpMethod.ANY];
return methods.map((method) => new HttpRoute(this, `${method}${options.path}`, {
httpApi: this,
routeKey: HttpRouteKey.with(options.path, method),
integration: options.integration,
authorizer: options.authorizer,
authorizationScopes: options.authorizationScopes,
}));
return methods.map((method) => {
let authorizationScopes = this.authorizationScopes;

if (Array.isArray(options.authorizationScopes)) {
iRoachie marked this conversation as resolved.
Show resolved Hide resolved
authorizationScopes = options.authorizationScopes;
}

return new HttpRoute(this, `${method}${options.path}`, {
httpApi: this,
routeKey: HttpRouteKey.with(options.path, method),
integration: options.integration,
authorizer: options.authorizer ?? this.authorizer,
authorizationScopes,
iRoachie marked this conversation as resolved.
Show resolved Hide resolved
});
});
}
}

/**
* Used to remove the default authorizer for a route
iRoachie marked this conversation as resolved.
Show resolved Hide resolved
*/
export class NoneAuthorizer implements IHttpRouteAuthorizer {
iRoachie marked this conversation as resolved.
Show resolved Hide resolved
public bind(_: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig {
return {
authorizationType: HttpAuthorizerType.NONE,
};
}
}
7 changes: 6 additions & 1 deletion packages/@aws-cdk/aws-apigatewayv2/lib/http/authorizer.ts
Expand Up @@ -15,6 +15,9 @@ export enum HttpAuthorizerType {

/** Lambda Authorizer */
LAMBDA = 'REQUEST',

/** No authorizer */
NONE = 'NONE'
}

/**
Expand Down Expand Up @@ -145,8 +148,10 @@ export interface HttpRouteAuthorizerBindOptions {
export interface HttpRouteAuthorizerConfig {
/**
* The authorizer id
*
* @default - No authorizer id (useful for AWS_IAM route authorizer)
*/
readonly authorizerId: string;
readonly authorizerId?: string;
/**
* The type of authorization
*/
Expand Down
6 changes: 4 additions & 2 deletions packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts
Expand Up @@ -3,7 +3,7 @@ import { Construct } from 'constructs';
import { CfnRoute, CfnRouteProps } from '../apigatewayv2.generated';
import { IRoute } from '../common';
import { IHttpApi } from './api';
import { IHttpRouteAuthorizer } from './authorizer';
import { HttpAuthorizerType, IHttpRouteAuthorizer } from './authorizer';
import { IHttpRouteIntegration } from './integration';

/**
Expand Down Expand Up @@ -156,12 +156,14 @@ export class HttpRoute extends Resource implements IHttpRoute {
]));
}

const authorizationType = authBindResult?.authorizationType === HttpAuthorizerType.NONE ? undefined : authBindResult?.authorizationType;
nija-at marked this conversation as resolved.
Show resolved Hide resolved

const routeProps: CfnRouteProps = {
apiId: props.httpApi.httpApiId,
routeKey: props.routeKey.key,
target: `integrations/${integration.integrationId}`,
authorizerId: authBindResult?.authorizerId,
authorizationType: authBindResult?.authorizationType,
authorizationType,
authorizationScopes,
};

Expand Down
117 changes: 116 additions & 1 deletion packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts
Expand Up @@ -5,7 +5,7 @@ import * as ec2 from '@aws-cdk/aws-ec2';
import { Duration, Stack } from '@aws-cdk/core';
import {
HttpApi, HttpAuthorizer, HttpAuthorizerType, HttpIntegrationType, HttpMethod, HttpRouteAuthorizerBindOptions, HttpRouteAuthorizerConfig,
HttpRouteIntegrationBindOptions, HttpRouteIntegrationConfig, IHttpRouteAuthorizer, IHttpRouteIntegration, PayloadFormatVersion,
HttpRouteIntegrationBindOptions, HttpRouteIntegrationConfig, IHttpRouteAuthorizer, IHttpRouteIntegration, NoneAuthorizer, PayloadFormatVersion,
} from '../../lib';

describe('HttpApi', () => {
Expand Down Expand Up @@ -371,6 +371,121 @@ describe('HttpApi', () => {

expect(() => api.apiEndpoint).toThrow(/apiEndpoint is not configured/);
});


describe('default authorization settings', () => {
test('can add default authorizer', () => {
nija-at marked this conversation as resolved.
Show resolved Hide resolved
const stack = new Stack();

const authorizer = new DummyAuthorizer();

const httpApi = new HttpApi(stack, 'api', {
defaultAuthorizer: authorizer,
defaultAuthorizationScopes: ['read:pets'],
});

httpApi.addRoutes({
path: '/pets',
methods: [HttpMethod.GET],
integration: new DummyRouteIntegration(),
});

expect(stack).toHaveResource('AWS::ApiGatewayV2::Route', {
AuthorizerId: 'auth-1234',
AuthorizationType: 'JWT',
AuthorizationScopes: ['read:pets'],
});
});

test('can add default authorizer, but remove it for a route', () => {
const stack = new Stack();
const authorizer = new DummyAuthorizer();

const httpApi = new HttpApi(stack, 'api', {
defaultAuthorizer: authorizer,
defaultAuthorizationScopes: ['read:pets'],
});

httpApi.addRoutes({
path: '/pets',
methods: [HttpMethod.GET],
integration: new DummyRouteIntegration(),
});

httpApi.addRoutes({
path: '/chickens',
methods: [HttpMethod.GET],
integration: new DummyRouteIntegration(),
authorizer: new NoneAuthorizer(),
});

expect(stack).toHaveResource('AWS::ApiGatewayV2::Route', {
RouteKey: 'GET /pets',
AuthorizerId: 'auth-1234',
});

expect(stack).toHaveResource('AWS::ApiGatewayV2::Route', {
RouteKey: 'GET /chickens',
AuthorizerId: ABSENT,
});
});

test('can remove default scopes for a route', () => {
const stack = new Stack();

const authorizer = new DummyAuthorizer();

const httpApi = new HttpApi(stack, 'api', {
defaultAuthorizer: authorizer,
defaultAuthorizationScopes: ['read:books'],
});

httpApi.addRoutes({
path: '/pets',
methods: [HttpMethod.GET, HttpMethod.PATCH],
integration: new DummyRouteIntegration(),
authorizationScopes: [],
});

expect(stack).toHaveResource('AWS::ApiGatewayV2::Route', {
AuthorizationScopes: [],
});
});

test('can override scopes for a route', () => {
const stack = new Stack();

const authorizer = new DummyAuthorizer();

const httpApi = new HttpApi(stack, 'api', {
defaultAuthorizer: authorizer,
defaultAuthorizationScopes: ['read:pets'],
});

httpApi.addRoutes({
path: '/pets',
methods: [HttpMethod.GET, HttpMethod.PATCH],
integration: new DummyRouteIntegration(),
});

httpApi.addRoutes({
path: '/chickens',
methods: [HttpMethod.GET, HttpMethod.PATCH],
integration: new DummyRouteIntegration(),
authorizationScopes: ['read:chickens'],
});

expect(stack).toHaveResource('AWS::ApiGatewayV2::Route', {
RouteKey: 'GET /pets',
AuthorizationScopes: ['read:pets'],
});

expect(stack).toHaveResource('AWS::ApiGatewayV2::Route', {
RouteKey: 'GET /chickens',
AuthorizationScopes: ['read:chickens'],
});
});
});
});

class DummyRouteIntegration implements IHttpRouteIntegration {
Expand Down