-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
262 lines (234 loc) · 8.01 KB
/
index.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
251
252
253
254
255
256
257
258
259
260
261
262
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as random from "@pulumi/random";
// Generate a unique UUID for the item
const itemID = new random.RandomUuid("itemID");
// Create an IAM role for the Lambda function
const lambdaRole = new aws.iam.Role("lambdaRole", {
assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({ Service: "lambda.amazonaws.com" }),
});
// Attach the basic execution policy to the role
const lambdaRolePolicyAttachment = new aws.iam.RolePolicyAttachment("lambdaRolePolicyAttachment", {
role: lambdaRole,
policyArn: aws.iam.ManagedPolicies.AWSLambdaBasicExecutionRole,
});
// Create a custom policy for logging, DynamoDB access, and Lambda invocation, and attach it to the role
// These are loose privileges!!! - the lambda role should be broken down by each lambda and Resource should be ARNs only not *
const lambdaPolicy = new aws.iam.RolePolicy("lambdaPolicy", {
role: lambdaRole.id,
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [
{
Action: "logs:*",
Resource: "*",
Effect: "Allow",
},
{
Action: [
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:Scan",
"dynamodb:Query"
],
Resource: "*", // You might want to restrict this to the specific table ARN
Effect: "Allow",
},
{
Action: [
"apigateway:GET",
"apigateway:POST",
"apigateway:DELETE"
],
Resource: "*", // You might want to restrict this to the specific API Gateway ARN
Effect: "Allow",
},
{
Action: [
"lambda:InvokeFunction"
],
Resource: "*", // You might want to restrict this to specific Lambda ARNs
Effect: "Allow",
}
],
}),
});
// Create the first Lambda function
const getCartLambda = new aws.lambda.Function("getCartLambda", {
runtime: "nodejs18.x",
role: lambdaRole.arn,
handler: "index.handler",
code: new pulumi.asset.AssetArchive({
".": new pulumi.asset.FileArchive("./lambda"),
}),
environment: {
variables: {
"ENV": "production",
},
},
});
// Create an API Gateway
const api = new aws.apigateway.RestApi("cartApi", {
description: "API Gateway for Cart Service",
});
// Create the /getcart resource
const getCartResource = new aws.apigateway.Resource("getCartResource", {
restApi: api.id,
parentId: api.rootResourceId,
pathPart: "getcart",
});
// Create the GET method
const getMethod = new aws.apigateway.Method("getMethod", {
restApi: api.id,
resourceId: getCartResource.id,
httpMethod: "GET",
authorization: "NONE",
apiKeyRequired: true,
});
// Lambda integration for GET method
const lambdaIntegration = new aws.apigateway.Integration("getLambdaIntegration", {
restApi: api.id,
resourceId: getCartResource.id,
httpMethod: getMethod.httpMethod,
integrationHttpMethod: "POST",
type: "AWS_PROXY",
uri: getCartLambda.invokeArn,
});
// Create an API Key
const apiKey = new aws.apigateway.ApiKey("cartApiKey", {
description: "API key for cart service",
enabled: true,
});
// Create a unique API Stage
const stage = new aws.apigateway.Stage("cartStage", {
restApi: api.id,
stageName: pulumi.interpolate`prod-${pulumi.getStack()}`, // Ensure the stage name is unique
deployment: new aws.apigateway.Deployment("cartDeployment", {
restApi: api.id,
stageName: pulumi.interpolate`prod1-${pulumi.getStack()}`, // Ensure the stage name is unique
triggers: {
redeployment: pulumi.all([getMethod.id, lambdaIntegration.id]).apply(([methodId, integrationId]) => `${methodId}-${integrationId}`),
},
}).id,
});
// Create a Usage Plan
const usagePlan = new aws.apigateway.UsagePlan("cartUsagePlan", {
description: "Usage plan for Cart API",
apiStages: [{
apiId: api.id,
stage: stage.stageName,
}],
throttleSettings: {
rateLimit: 100,
burstLimit: 200,
},
quotaSettings: {
limit: 10000,
period: "MONTH",
},
});
// Associate API Key with the Usage Plan
new aws.apigateway.UsagePlanKey("cartUsagePlanKey", {
keyId: apiKey.id,
keyType: "API_KEY",
usagePlanId: usagePlan.id,
});
// Grant permissions to API Gateway to invoke the Lambda function
new aws.lambda.Permission("apiLambdaPermission", {
action: "lambda:InvokeFunction",
function: getCartLambda.arn,
principal: "apigateway.amazonaws.com",
sourceArn: pulumi.interpolate`${api.executionArn}/*/*`,
});
// Create a DynamoDB table with indexed attributes
const table = new aws.dynamodb.Table("apiKeysTable", {
attributes: [
{ name: "itemID", type: "S" },
{ name: "APIGWKeyID", type: "S" },
{ name: "usagePlanID", type: "S" }
],
hashKey: "itemID",
globalSecondaryIndexes: [
{
name: "usagePlanIDIndex",
hashKey: "usagePlanID",
projectionType: "ALL",
},
{
name: "APIGWKeyIDIndex",
hashKey: "APIGWKeyID",
projectionType: "ALL",
},
],
billingMode: "PAY_PER_REQUEST",
});
// Ensure the table is created before adding items
const tableCreation = table.id.apply(id => id);
// Get the current timestamp in ISO 8601 format
const updateTime = new Date().toISOString();
// Put the API key and usage plan ID into the DynamoDB table
const putItem = new aws.dynamodb.TableItem("apiKeyItem", {
tableName: table.name,
hashKey: itemID.result,
item: pulumi.interpolate`{
"itemID": {"S": "${itemID.result}"},
"APIGWKeyID": {"S": "${apiKey.id}"},
"APIKeyValue": {"S": "${apiKey.value}"},
"usagePlanID": {"S": "${usagePlan.id}"},
"updateTime": {"S": "${updateTime}"}
}`,
});
export const tableName = table.name;
// Create the second Lambda function for API key and DynamoDB operations
const rotationLambda = new aws.lambda.Function("rotationLambda", {
runtime: "nodejs18.x",
role: lambdaRole.arn,
handler: "index.handler",
code: new pulumi.asset.AssetArchive({
".": new pulumi.asset.FileArchive("./rotationLambda"),
}),
environment: {
variables: {
"ENV": "production",
"TABLE_NAME": table.name,
},
},
});
// Create the second Lambda function for API key and DynamoDB operations
const schedulingLambda = new aws.lambda.Function("schedulingLambda", {
runtime: "nodejs18.x",
role: lambdaRole.arn,
handler: "index.handler",
code: new pulumi.asset.AssetArchive({
".": new pulumi.asset.FileArchive("./schedulingLambda"),
}),
environment: {
variables: {
"ENV": "production",
"TABLE_NAME": table.name,
"ROTATION_LAMBDA_ARN": rotationLambda.arn,
},
},
});
// Create an EventBridge rule to trigger the schedulingLambda every 30 days
const eventRule = new aws.cloudwatch.EventRule("schedulingRule", {
scheduleExpression: "rate(30 days)",
});
// Add the schedulingLambda as the target for the EventBridge rule
const eventTarget = new aws.cloudwatch.EventTarget("schedulingTarget", {
rule: eventRule.name,
arn: schedulingLambda.arn,
});
// Grant permissions for EventBridge to invoke the schedulingLambda
new aws.lambda.Permission("eventBridgePermission", {
action: "lambda:InvokeFunction",
function: schedulingLambda.arn,
principal: "events.amazonaws.com",
sourceArn: eventRule.arn,
});
// Export stuff we may want to see
export const rotationLambdaARN = rotationLambda.arn;
export const apiUrl = pulumi.interpolate`${api.executionArn}/${stage.stageName}`;
export const apiKeyValue = apiKey.value;