@kumologica/sdk 3.2.0-beta8 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,449 +0,0 @@
1
- const AWS = require('aws-sdk');
2
- const jp = require('jsonpath');
3
- const KLRekognition = require('./kl-rekognition-api');
4
-
5
- class AWSCFTemplate {
6
- constructor() {
7
- this.rekognition = new KLRekognition();
8
- }
9
-
10
- /*
11
- * Creates cloud formation template file for current lambda:
12
- * It is composed of:
13
- * - lambda definition
14
- * - lambda's role
15
- * - lambda's policy
16
- * - if event subriptions confirmed then event source mappings for:
17
- * - dynamodb stream
18
- * - sns
19
- * - kinesis
20
- * - if flow nodes are provided then
21
- * - role added for each downstream aws node
22
- */
23
- createCfTemplate(params, settings, nodes, region) {
24
- let ts = new Date();
25
-
26
- let template = {
27
- AWSTemplateFormatVersion: '2010-09-09',
28
- Resources: {},
29
- Outputs: {
30
- LambdaArn: {
31
- Description: 'The Arn of the cloud action flow lambda.',
32
- Value: { 'Fn::GetAtt': ['Lambda', 'Arn'] }
33
- }
34
- }
35
- };
36
-
37
- let lambda = {
38
- Type: "AWS::Lambda::Function",
39
- Properties: {
40
- FunctionName: settings.functionName,
41
- Handler: "lambda.handler",
42
- Role: { "Fn::GetAtt": [ "LambdaRole", "Arn" ] },
43
- Code: {
44
- S3Bucket: settings.deploymentBucketName,
45
- S3Key: settings.zipFileName
46
- },
47
- Runtime: "nodejs12.x",
48
- Timeout: 300,
49
- Environment: {
50
- Variables: {}
51
- },
52
- Tags: [
53
- {
54
- Key : "Kumologica-ts",
55
- Value : `${ts.toISOString()}`
56
- }
57
- ]
58
- }
59
- };
60
- template.Resources['Lambda'] = lambda;
61
-
62
- let lambdaRole = {
63
- Type: 'AWS::IAM::Role',
64
- Properties: {
65
- RoleName: settings.roleName,
66
- AssumeRolePolicyDocument: {
67
- Version: '2012-10-17',
68
- Statement: [
69
- {
70
- Effect: 'Allow',
71
- Principal: {
72
- Service: ['lambda.amazonaws.com']
73
- },
74
- Action: ['sts:AssumeRole']
75
- }
76
- ]
77
- },
78
- Path: '/',
79
- Policies: [
80
- {
81
- PolicyName: 'AWSLambdaBasicExecutionPolicy',
82
- PolicyDocument: {
83
- Version: '2012-10-17',
84
- Statement: [
85
- {
86
- Effect: 'Allow',
87
- Action: [
88
- 'logs:CreateLogGroup',
89
- 'logs:CreateLogStream',
90
- 'logs:PutLogEvents'
91
- ],
92
- Resource: '*'
93
- }
94
- ]
95
- }
96
- }
97
- ]
98
- }
99
- };
100
-
101
- const xRayPolicy = {
102
- PolicyName: 'AWSLambdaXRayPolicy',
103
- PolicyDocument: {
104
- Version: '2012-10-17',
105
- Statement: [
106
- {
107
- Effect: 'Allow',
108
- Action: [
109
- 'xray:PutTraceSegments',
110
- 'xray:PutTelemetryRecords'
111
- ],
112
- Resource: '*'
113
- }
114
- ]
115
- }
116
- };
117
-
118
- if (params.description) {
119
- lambda.Properties.Description = params.description;
120
- }
121
- if (params.memory) {
122
- lambda.Properties.MemorySize = params.memory;
123
- }
124
- if (params.timeout) {
125
- lambda.Properties.Timeout = params.timeout;
126
- }
127
-
128
- if (params.reservedConcurrency) {
129
- lambda.Properties.ReservedConcurrentExecutions = params.reservedConcurrency;
130
- }
131
-
132
- if (params.xRay) {
133
- lambdaRole.Properties.Policies.push(xRayPolicy);
134
- lambda.Properties.TracingConfig = {'Mode': 'Active'};
135
- }
136
-
137
- if (params.role) {
138
- lambda.Properties.Role = params.role;
139
- }
140
- if (params.environment) {
141
- params.environment.forEach(function(env, index) {
142
- lambda.Properties.Environment.Variables[env.key] = env.value;
143
- });
144
- }
145
-
146
- if (params.tags) {
147
- params.tags.forEach(function(tag, index) {
148
- lambda.Properties.Tags.push({'Key': tag.key, 'Value': tag.value});
149
- });
150
- }
151
-
152
- // adding inbound services permissions, only for dynamodb stream, sqs and kinesis
153
- if (params.events) {
154
- for (var i = 0; i < params.events.length; i++) {
155
- if (['dynamodb', 'sqs', 'kinesis'].includes(params.events[i].source)) {
156
- var mappingName = `${params.events[i].source}Mapping${i}`;
157
-
158
- template.Resources[mappingName] = this.eventSourceMapping(params.events[i], settings);
159
-
160
- if (params.events[i].source === 'dynamodb') {
161
- lambdaRole.Properties.Policies.push(
162
- this.dynamoDbStreamPolicy(params.events[i])
163
- );
164
- }
165
- if (params.events[i].source === 'sqs') {
166
- lambdaRole.Properties.Policies.push(
167
- this.sqsPolicy(params.events[i])
168
- );
169
- }
170
- if (params.events[i].source === 'kinesis') {
171
- lambdaRole.Properties.Policies.push(
172
- this.kinesisPolicy(params.events[i])
173
- );
174
- }
175
- }
176
- }
177
- }
178
-
179
- // adding all outbound services operations to iam role
180
- if (nodes) {
181
- for (var i=0; i<nodes.length; i++) {
182
- // exluding lambda for time being
183
- if (this.validNodeType(nodes[i])) {
184
- // if (nodes[i] && nodes[i].type && ['Rekognition', 'S3', 'SQS', 'Cloudwatch', 'Dynamo DB', 'SNS', 'SES', 'SSM'].includes(nodes[i].type)) {
185
-
186
- if (nodes[i].type === 'Dynamo DB') {
187
- //arn:aws:dynamodb:ap-southeast-2:174842903734:table/contact_us
188
- let tableArn = this.handleValue(lambda, nodes[i].tableArn);
189
- let tableParts = tableArn.split(":");
190
- let tableName = '*';
191
- if (tableParts && tableParts[5]) {
192
- tableName = tableParts[5].replace('table/', '');
193
- }
194
-
195
- this.handlePolicy(lambdaRole, `KLDDB${tableName}`, `dynamodb:${nodes[i].operation}`, tableArn);
196
-
197
- } else if (nodes[i].type === 'Lambda') {
198
- //arn:aws:lambda:ap-southeast-2:174450237637:function:kumologica-deployments-flow-lambda
199
- let lambdaArn = this.handleValue(lambda, nodes[i].LambdaArn);
200
- let lambdaParts = lambdaArn.split(":");
201
- let lambdaName = '*';
202
-
203
- if (lambdaParts && lambdaParts[6]) {
204
- lambdaName = lambdaParts[6];
205
- }
206
-
207
- this.handlePolicy(lambdaRole, `KLLBD${lambdaName}`, `lambda:InvokeFunction`, lambdaArn);
208
-
209
- } else if (nodes[i].type === 'SQS') {
210
- let queueArn = this.handleValue(lambda, nodes[i].QueueArn);
211
- let queueName = queueArn.split(":")[5];
212
- this.handlePolicy(lambdaRole, `KLSQS${queueName}`, `sqs:${nodes[i].operation}`, queueArn);
213
-
214
- } else if (nodes[i].type === 'SNS') {
215
- const topic = this.handleValue(lambda, nodes[i].publishTopic);
216
- this.handlePolicy(lambdaRole, `KLSNS${topic}`, `sns:${nodes[i].operation}`, topic);
217
-
218
- } else if (nodes[i].type === 'SES') {
219
- this.handlePolicy(lambdaRole, `KLSES`, `ses:${nodes[i].operation}`, '*');
220
-
221
- } else if (nodes[i].type === 'SSM') {
222
- let key;
223
- if (nodes[i].operation === "GetParametersByPath") {
224
- key = this.handleValue(lambda, nodes[i].Path);
225
- } else {
226
- key = this.handleValue(lambda, nodes[i].Key);
227
- }
228
-
229
- if (key.startsWith('/')) {
230
- key = key.substring(1);
231
- }
232
- const ssmArn = {'Fn::Sub': 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/' + key};
233
- this.handlePolicy(lambdaRole, `KLSSM${new Date().valueOf()}`, `ssm:${nodes[i].operation}`, ssmArn);
234
-
235
- if (nodes[i].operation === "GetParameter" && nodes[i].Key.startsWith("/aws/reference/secretsmanager/")) {
236
- const smArn = {'Fn::Sub': 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:*'};
237
- this.handlePolicy(lambdaRole, `KLSM${new Date().valueOf()}`, "secretsmanager:GetSecretValue", smArn);
238
- }
239
- } else if (nodes[i].type === 'S3') {
240
- let bucketName = this.handleValue(lambda, nodes[i].Bucket);
241
- const bucketArn = `arn:aws:s3:::${bucketName}/*`;
242
- this.handlePolicy(lambdaRole, `KLS3${bucketName}`, `s3:${nodes[i].operation}`, bucketArn);
243
-
244
- } else if (nodes[i].type === 'Cloudwatch') {
245
- let source = this.handleValue(lambda, nodes[i].Source);
246
- this.handlePolicy(lambdaRole, `KLCWE${source}`, `event:${nodes[i].operation}`, source);
247
-
248
- } else if (nodes[i].type === 'Rekognition') {
249
- const resource = this.rekognition.mapResource(nodes[i].operation, region, '*', nodes[i]);
250
- this.handlePolicy(lambdaRole, `KLR${nodes[i].operation}`, `rekognition:${nodes[i].operation}`, resource);
251
-
252
- if (['DetectModerationLabels',
253
- 'DetectText',
254
- 'DetectLabels',
255
- 'DetectFaces',
256
- 'IndexFaces',
257
- 'RecognizeCelebrities',
258
- 'SearchFacesByImage',
259
- 'StartStreamProcessor',
260
- 'StopStreamProcessor'].includes(nodes[i].operation)) {
261
-
262
- const bucketName = nodes[i].Image;
263
- const bucketArn = `arn:aws:s3:::${bucketName}/*`;
264
- this.handlePolicy(lambdaRole, `KLS3${bucketName}`, `s3:Get*`, bucketArn);
265
- this.handlePolicy(lambdaRole, `KLS3${bucketName}`, `s3:List*`, bucketArn);
266
- }
267
- }
268
- }
269
- }
270
- }
271
-
272
- if (!params.role) {
273
- template.Resources['LambdaRole'] = lambdaRole;
274
- }
275
-
276
- return template;
277
- }
278
-
279
- validNodeType(node) {
280
- return node
281
- && node.type
282
- && ['Rekognition', 'S3', 'SQS', 'Cloudwatch', 'Dynamo DB', 'SNS', 'SES', 'SSM', 'Lambda'].includes(node.type);
283
- }
284
-
285
- //
286
- // Function allows the storage of aws resource identifier (arn or other)
287
- // in either:
288
- // - string property - value returned as is (key input)
289
- // - env property - key input is a key to reference environment variable.
290
- // if env variables got key missing then error is returned
291
- // if var or msg used then error is returned, in these cases its impossible
292
- // to determine the value
293
- handleValue(lambda, key) {
294
- let value;
295
-
296
- if (!key) {
297
- return key;
298
- }
299
- let keyValue = key.replace(/\s+/g, '');
300
-
301
- if (keyValue.startsWith("env.")) {
302
- keyValue = keyValue.replace("env.", "");
303
- value = lambda.Properties.Environment.Variables[keyValue];
304
- if (!value) {
305
- throw new Error(`Missing Environment variable: ${keyValue}`);
306
- }
307
- } else if (key.startsWith("msg.") || key.startsWith("vars.")) {
308
- throw new Error('Only literal string and Environment variable references env. are supported sources of values. To use other types you must specify explicit role arn in function properties.');
309
- } else {
310
- value = key;
311
- }
312
- return this.sanitizeValue(value);
313
- }
314
-
315
- // Removes strings in quotes, they will cause cloudformation script to be invalid
316
- sanitizeValue(value) {
317
- let v = value.trim();
318
- if (v.charAt(0) === '"' && v.charAt(v.length -1) === '"') {
319
- return v.substr(1,v.length -2);
320
- }
321
- return value;
322
- }
323
-
324
- //
325
- // Function handles policy changes for specific resource:
326
- // - creates new policy for resource if not found
327
- // - updates existing resource policy (if found) with new permissions
328
- //
329
- handlePolicy(lambdaRole, policyName, operation, id) {
330
- policyName = policyName.replace('/', '-').replace('*', 'all');
331
- let policy = jp.query(lambdaRole.Properties, `Policies[?(@.PolicyName=='${policyName}')]`);
332
-
333
- if (policy.length == 1) {
334
- policy[0].PolicyDocument.Statement[0].Action.push(operation);
335
- } else {
336
- lambdaRole.Properties.Policies.push(
337
- this.outputPolicy(policyName, operation, id));
338
- }
339
- }
340
-
341
- dynamoDbStreamPolicy(event) {
342
- if (!event.stream) {
343
- throw new Error(`Missing Trigger Parameter: DynamoDB Stream's ARN`);
344
- }
345
-
346
- return {
347
- PolicyName: 'KumologicaLambdaDynamoDbStreamPolicy',
348
- PolicyDocument: {
349
- Version: '2012-10-17',
350
- Statement: [
351
- {
352
- Action: [
353
- 'dynamodb:DescribeStream',
354
- 'dynamodb:GetRecords',
355
- 'dynamodb:GetShardIterator'
356
- ],
357
- Resource: event.stream,
358
- Effect: 'Allow'
359
- }
360
- ]
361
- }
362
- };
363
- }
364
-
365
- outputPolicy(name, action, resource) {
366
- return {
367
- PolicyName: name,
368
- PolicyDocument: {
369
- Version: '2012-10-17',
370
- Statement: [
371
- {
372
- Action: [action],
373
- Resource: resource,
374
- Effect: 'Allow'
375
- }
376
- ]
377
- }
378
- };
379
- }
380
-
381
- sqsPolicy(event) {
382
- if (!event.stream) {
383
- throw new Error(`Missing Trigger Parameter: SQS url`);
384
- }
385
- return {
386
- PolicyName: 'KLSQSPolicy'+process.hrtime(),
387
- PolicyDocument: {
388
- Version: '2012-10-17',
389
- Statement: [
390
- {
391
- Action: [
392
- 'sqs:ReceiveMessage',
393
- 'sqs:DeleteMessage',
394
- 'sqs:GetQueueAttributes'
395
- ],
396
- Resource: event.stream,
397
- Effect: 'Allow'
398
- }
399
- ]
400
- }
401
- };
402
- }
403
-
404
- kinesisPolicy(event) {
405
- return {
406
- PolicyName: 'KumologicaLambdaKinesisPolicy',
407
- PolicyDocument: {
408
- Version: '2012-10-17',
409
- Statement: [
410
- {
411
- Action: [
412
- 'kinesis:DescribeStream',
413
- 'kinesis:DescribeStreamSummary',
414
- 'kinesis:GetRecords',
415
- 'kinesis:GetShardIterator',
416
- 'kinesis:ListShards',
417
- 'kinesis:ListStreams',
418
- 'kinesis:SubscribeToShard'
419
- ],
420
- Resource: event.stream,
421
- Effect: 'Allow'
422
- }
423
- ]
424
- }
425
- };
426
- }
427
-
428
- eventSourceMapping(event, settings) {
429
-
430
- let mapping = {
431
- Type: 'AWS::Lambda::EventSourceMapping',
432
- DependsOn: 'Lambda',
433
- Properties: {
434
- EventSourceArn: event.stream,
435
- FunctionName: { 'Fn::GetAtt': ['Lambda', 'Arn'] },
436
- BatchSize: event.batchSize,
437
- MaximumBatchingWindowInSeconds: event.batchWindow,
438
- Enabled: true
439
- }
440
- };
441
-
442
- if (event.startingPosition) {
443
- mapping.Properties.StartingPosition = event.startingPosition;
444
- }
445
- return mapping;
446
- }
447
- }
448
-
449
- module.exports = AWSCFTemplate;