-
Notifications
You must be signed in to change notification settings - Fork 4k
/
Copy pathlambda.ts
250 lines (215 loc) · 8.91 KB
/
lambda.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
import * as iam from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';
import { Construct, Duration, Lazy, Stack } from '@aws-cdk/core';
import { CfnAuthorizer } from '../apigateway.generated';
import { Authorizer, IAuthorizer } from '../authorizer';
import { RestApi } from '../restapi';
/**
* Base properties for all lambda authorizers
*/
export interface LambdaAuthorizerProps {
/**
* An optional human friendly name for the authorizer. Note that, this is not the primary identifier of the authorizer.
*
* @default this.node.uniqueId
*/
readonly authorizerName?: string;
/**
* The handler for the authorizer lambda function.
*
* The handler must follow a very specific protocol on the input it receives and the output it needs to produce.
* API Gateway has documented the handler's input specification
* {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-input.html | here} and output specification
* {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html | here}.
*/
readonly handler: lambda.IFunction;
/**
* How long APIGateway should cache the results. Max 1 hour.
* Disable caching by setting this to 0.
*
* @default Duration.minutes(5)
*/
readonly resultsCacheTtl?: Duration;
/**
* An optional IAM role for APIGateway to assume before calling the Lambda-based authorizer. The IAM role must be
* assumable by 'apigateway.amazonaws.com'.
*
* @default - A resource policy is added to the Lambda function allowing apigateway.amazonaws.com to invoke the function.
*/
readonly assumeRole?: iam.IRole;
}
abstract class LambdaAuthorizer extends Authorizer implements IAuthorizer {
/**
* The id of the authorizer.
* @attribute
*/
public abstract readonly authorizerId: string;
/**
* The ARN of the authorizer to be used in permission policies, such as IAM and resource-based grants.
*/
public abstract readonly authorizerArn: string;
/**
* The Lambda function handler that this authorizer uses.
*/
protected readonly handler: lambda.IFunction;
/**
* The IAM role that the API Gateway service assumes while invoking the Lambda function.
*/
protected readonly role?: iam.IRole;
protected restApiId?: string;
protected constructor(scope: Construct, id: string, props: LambdaAuthorizerProps) {
super(scope, id);
this.handler = props.handler;
this.role = props.assumeRole;
if (props.resultsCacheTtl && props.resultsCacheTtl?.toSeconds() > 3600) {
throw new Error('Lambda authorizer property \'resultsCacheTtl\' must not be greater than 3600 seconds (1 hour)');
}
}
/**
* Attaches this authorizer to a specific REST API.
* @internal
*/
public _attachToApi(restApi: RestApi) {
if (this.restApiId && this.restApiId !== restApi.restApiId) {
throw new Error('Cannot attach authorizer to two different rest APIs');
}
this.restApiId = restApi.restApiId;
}
/**
* Sets up the permissions necessary for the API Gateway service to invoke the Lambda function.
*/
protected setupPermissions() {
if (!this.role) {
this.handler.addPermission(`${this.node.uniqueId}:Permissions`, {
principal: new iam.ServicePrincipal('apigateway.amazonaws.com'),
sourceArn: this.authorizerArn,
});
} else if (this.role instanceof iam.Role) { // i.e. not imported
this.role.attachInlinePolicy(new iam.Policy(this, 'authorizerInvokePolicy', {
statements: [
new iam.PolicyStatement({
resources: [ this.handler.functionArn ],
actions: [ 'lambda:InvokeFunction' ],
}),
],
}));
}
}
/**
* Returns a token that resolves to the Rest Api Id at the time of synthesis.
* Throws an error, during token resolution, if no RestApi is attached to this authorizer.
*/
protected lazyRestApiId() {
return Lazy.stringValue({
produce: () => {
if (!this.restApiId) {
throw new Error(`Authorizer (${this.node.path}) must be attached to a RestApi`);
}
return this.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;
/**
* 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 `IdentitySource.header('Authorization')`
*/
readonly identitySource?: 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 {
public readonly authorizerId: string;
public readonly authorizerArn: string;
constructor(scope: Construct, id: string, props: TokenAuthorizerProps) {
super(scope, id, props);
const restApiId = this.lazyRestApiId();
const resource = new CfnAuthorizer(this, 'Resource', {
name: props.authorizerName ?? this.node.uniqueId,
restApiId,
type: 'TOKEN',
authorizerUri: `arn:aws:apigateway:${Stack.of(this).region}:lambda:path/2015-03-31/functions/${props.handler.functionArn}/invocations`,
authorizerCredentials: props.assumeRole?.roleArn,
authorizerResultTtlInSeconds: 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}`,
});
this.setupPermissions();
}
}
/**
* Properties for RequestAuthorizer
*/
export interface RequestAuthorizerProps extends LambdaAuthorizerProps {
/**
* An array of request header mapping expressions for identities. Supported parameter types are
* Header, Query String, Stage Variable, and Context. For instance, extracting an authorization
* token from a header would use the identity source `IdentitySource.header('Authorizer')`.
*
* Note: API Gateway uses the specified identity sources as the request authorizer caching key. When caching is
* enabled, API Gateway calls the authorizer's Lambda function only after successfully verifying that all the
* specified identity sources are present at runtime. If a specified identify source is missing, null, or empty,
* API Gateway returns a 401 Unauthorized response without calling the authorizer Lambda function.
*
* @see https://docs.aws.amazon.com/apigateway/api-reference/link-relation/authorizer-create/#identitySource
*/
readonly identitySources: string[];
}
/**
* 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 {
public readonly authorizerId: string;
public readonly authorizerArn: string;
constructor(scope: Construct, id: string, props: RequestAuthorizerProps) {
super(scope, id, props);
if ((props.resultsCacheTtl === undefined || props.resultsCacheTtl.toSeconds() !== 0) && props.identitySources.length === 0) {
throw new Error('At least one Identity Source is required for a REQUEST-based Lambda authorizer if caching is enabled.');
}
const restApiId = this.lazyRestApiId();
const resource = new CfnAuthorizer(this, 'Resource', {
name: props.authorizerName ?? this.node.uniqueId,
restApiId,
type: 'REQUEST',
authorizerUri: `arn:aws:apigateway:${Stack.of(this).region}:lambda:path/2015-03-31/functions/${props.handler.functionArn}/invocations`,
authorizerCredentials: props.assumeRole?.roleArn,
authorizerResultTtlInSeconds: props.resultsCacheTtl?.toSeconds(),
identitySource: props.identitySources.map(is => is.toString()).join(','),
});
this.authorizerId = resource.ref;
this.authorizerArn = Stack.of(this).formatArn({
service: 'execute-api',
resource: restApiId,
resourceName: `authorizers/${this.authorizerId}`,
});
this.setupPermissions();
}
}