Skip to content

Commit

Permalink
feat(apigateway): add lambda request authorizer construct
Browse files Browse the repository at this point in the history
This creates a common LambdaAuthorizer base class so that the
token and request authorizers can share common functionality.
  • Loading branch information
Adam Plumer committed Jan 5, 2020
1 parent e9ede13 commit cc6cef8
Show file tree
Hide file tree
Showing 8 changed files with 962 additions and 51 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ cdk.context.json
.cdk.staging/
cdk.out/

.nycrc
144 changes: 96 additions & 48 deletions packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ import { CfnAuthorizer } from '../apigateway.generated';
import { Authorizer, IAuthorizer } from '../authorizer';
import { RestApi } from '../restapi';

/**
* Properties for TokenAuthorizer
*/
export interface TokenAuthorizerProps {

export interface LambdaAuthorizerProps {
/**
* An optional human friendly name for the authorizer. Note that, this is not the primary identifier of the authorizer.
*
Expand All @@ -31,7 +27,7 @@ export interface TokenAuthorizerProps {
* The request header mapping expression for the bearer token. This is typically passed as part of the header, in which case
* this should be `method.request.header.Authorizer` where Authorizer is the header containing the bearer token.
* @see https://docs.aws.amazon.com/apigateway/api-reference/link-relation/authorizer-create/#identitySource
* @default 'method.request.header.Authorization'
* @default 'method.request.header.Authorization' for TOKEN and 'Header' for REQUEST
*/
readonly identitySource?: string;

Expand All @@ -43,14 +39,6 @@ export interface TokenAuthorizerProps {
*/
readonly resultsCacheTtl?: Duration;

/**
* An optional regex to be matched against the authorization token. When matched the authorizer lambda is invoked,
* otherwise a 401 Unauthorized is returned to the client.
*
* @default - no regex filter will be applied.
*/
readonly validationRegex?: string;

/**
* An optional IAM role for APIGateway to assume before calling the Lambda-based authorizer. The IAM role must be
* assumable by 'apigateway.amazonaws.com'.
Expand All @@ -60,56 +48,28 @@ export interface TokenAuthorizerProps {
readonly assumeRole?: iam.IRole;
}

/**
* Token based lambda authorizer that recognizes the caller's identity as a bearer token,
* such as a JSON Web Token (JWT) or an OAuth token.
* Based on the token, authorization is performed by a lambda function.
*
* @resource AWS::ApiGateway::Authorizer
*/
export class TokenAuthorizer extends Authorizer implements IAuthorizer {
export abstract class LambdaAuthorizer extends Authorizer implements IAuthorizer {

/**
* The id of the authorizer.
* @attribute
*/
public readonly authorizerId: string;
public authorizerId: string;

/**
* The ARN of the authorizer to be used in permission policies, such as IAM and resource-based grants.
*/
public readonly authorizerArn: string;
public authorizerArn: string;

private restApiId?: string;
protected restApiId?: string;

constructor(scope: Construct, id: string, props: TokenAuthorizerProps) {
protected constructor(scope: Construct, id: string, props: LambdaAuthorizerProps) {
super(scope, id);

if (props.resultsCacheTtl && props.resultsCacheTtl.toSeconds() > 3600) {
throw new Error(`Lambda authorizer property 'resultsCacheTtl' must not be greater than 3600 seconds (1 hour)`);
}

const restApiId = Lazy.stringValue({ produce: () => this.restApiId });

const resource = new CfnAuthorizer(this, 'Resource', {
name: props.authorizerName,
restApiId,
type: 'TOKEN',
authorizerUri: `arn:aws:apigateway:${Stack.of(this).region}:lambda:path/2015-03-31/functions/${props.handler.functionArn}/invocations`,
authorizerCredentials: props.assumeRole ? props.assumeRole.roleArn : undefined,
authorizerResultTtlInSeconds: props.resultsCacheTtl && props.resultsCacheTtl.toSeconds(),
identitySource: props.identitySource || 'method.request.header.Authorization',
identityValidationExpression: props.validationRegex,
});

this.authorizerId = resource.ref;

this.authorizerArn = Stack.of(this).formatArn({
service: 'execute-api',
resource: restApiId,
resourceName: `authorizers/${this.authorizerId}`
});

if (!props.assumeRole) {
props.handler.addPermission(`${this.node.uniqueId}:Permissions`, {
principal: new iam.ServicePrincipal('apigateway.amazonaws.com'),
Expand Down Expand Up @@ -138,4 +98,92 @@ export class TokenAuthorizer extends Authorizer implements IAuthorizer {

this.restApiId = restApi.restApiId;
}
}
}

/**
* Properties for TokenAuthorizer
*/
export interface TokenAuthorizerProps extends LambdaAuthorizerProps {
/**
* An optional regex to be matched against the authorization token. When matched the authorizer lambda is invoked,
* otherwise a 401 Unauthorized is returned to the client.
*
* @default - no regex filter will be applied.
*/
readonly validationRegex?: string;
}

/**
* Token based lambda authorizer that recognizes the caller's identity as a bearer token,
* such as a JSON Web Token (JWT) or an OAuth token.
* Based on the token, authorization is performed by a lambda function.
*
* @resource AWS::ApiGateway::Authorizer
*/
export class TokenAuthorizer extends LambdaAuthorizer {

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

const restApiId = Lazy.stringValue({ produce: () => this.restApiId });

const resource = new CfnAuthorizer(this, 'Resource', {
name: props.authorizerName,
restApiId,
type: 'TOKEN',
authorizerUri: `arn:aws:apigateway:${Stack.of(this).region}:lambda:path/2015-03-31/functions/${props.handler.functionArn}/invocations`,
authorizerCredentials: props.assumeRole ? props.assumeRole.roleArn : undefined,
authorizerResultTtlInSeconds: props.resultsCacheTtl && props.resultsCacheTtl.toSeconds(),
identitySource: props.identitySource || 'method.request.header.Authorization',
identityValidationExpression: props.validationRegex,
});

this.authorizerId = resource.ref;

this.authorizerArn = Stack.of(this).formatArn({
service: 'execute-api',
resource: restApiId,
resourceName: `authorizers/${this.authorizerId}`
});
}
}

/**
* Properties for RequestAuthorizerProps
*/
export interface RequestAuthorizerProps extends LambdaAuthorizerProps {
}

/**
* Request based lambda authorizer that recognizes the caller's identity via request parameters,
* such as headers, paths, query strings, stage variables, or context variables.
* Based on the request, authorization is performed by a lambda function.
*
* @resource AWS::ApiGateway::Authorizer
*/
export class RequestAuthorizer extends LambdaAuthorizer {

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

const restApiId = Lazy.stringValue({ produce: () => this.restApiId });

const resource = new CfnAuthorizer(this, 'Resource', {
name: props.authorizerName,
restApiId,
type: 'REQUEST',
authorizerUri: `arn:aws:apigateway:${Stack.of(this).region}:lambda:path/2015-03-31/functions/${props.handler.functionArn}/invocations`,
authorizerCredentials: props.assumeRole ? props.assumeRole.roleArn : undefined,
authorizerResultTtlInSeconds: props.resultsCacheTtl && props.resultsCacheTtl.toSeconds(),
identitySource: props.identitySource || 'Header',
});

this.authorizerId = resource.ref;

this.authorizerArn = Stack.of(this).formatArn({
service: 'execute-api',
resource: restApiId,
resourceName: `authorizers/${this.authorizerId}`
});
}
}
Loading

0 comments on commit cc6cef8

Please sign in to comment.