serverless-bedrock-agentcore-plugin 0.1.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.
@@ -0,0 +1,591 @@
1
+ 'use strict';
2
+
3
+ const { getResourceName } = require('../utils/naming');
4
+
5
+ /**
6
+ * Generate IAM role for AgentCore Runtime
7
+ *
8
+ * @param {string} name - The agent name
9
+ * @param {Object} config - The runtime configuration
10
+ * @param {Object} context - The compilation context
11
+ * @returns {Object} CloudFormation IAM Role resource
12
+ */
13
+ function generateRuntimeRole(name, config, context) {
14
+ const { serviceName, stage } = context;
15
+ const roleName = getResourceName(serviceName, `${name}-runtime-role`, stage);
16
+
17
+ const assumeRolePolicy = {
18
+ Version: '2012-10-17',
19
+ Statement: [
20
+ {
21
+ Effect: 'Allow',
22
+ Principal: {
23
+ Service: 'bedrock-agentcore.amazonaws.com',
24
+ },
25
+ Action: 'sts:AssumeRole',
26
+ Condition: {
27
+ StringEquals: {
28
+ 'aws:SourceAccount': { Ref: 'AWS::AccountId' },
29
+ },
30
+ },
31
+ },
32
+ ],
33
+ };
34
+
35
+ // Base permissions for runtime
36
+ const policies = [
37
+ {
38
+ PolicyName: `${roleName}-base-policy`,
39
+ PolicyDocument: {
40
+ Version: '2012-10-17',
41
+ Statement: [
42
+ // CloudWatch Logs permissions
43
+ {
44
+ Effect: 'Allow',
45
+ Action: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'],
46
+ Resource: {
47
+ 'Fn::Sub':
48
+ 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/bedrock-agentcore/*',
49
+ },
50
+ },
51
+ // ECR permissions if using container image
52
+ ...(config.artifact?.containerImage
53
+ ? [
54
+ {
55
+ Effect: 'Allow',
56
+ Action: ['ecr:GetAuthorizationToken'],
57
+ Resource: '*',
58
+ },
59
+ {
60
+ Effect: 'Allow',
61
+ Action: [
62
+ 'ecr:BatchCheckLayerAvailability',
63
+ 'ecr:GetDownloadUrlForLayer',
64
+ 'ecr:BatchGetImage',
65
+ ],
66
+ Resource: {
67
+ 'Fn::Sub':
68
+ 'arn:${AWS::Partition}:ecr:${AWS::Region}:${AWS::AccountId}:repository/*',
69
+ },
70
+ },
71
+ ]
72
+ : []),
73
+ // S3 permissions if using S3 artifact
74
+ ...(config.artifact?.s3
75
+ ? [
76
+ {
77
+ Effect: 'Allow',
78
+ Action: ['s3:GetObject', 's3:GetObjectVersion'],
79
+ Resource: {
80
+ 'Fn::Sub': `arn:\${AWS::Partition}:s3:::${config.artifact.s3.bucket}/${config.artifact.s3.key}`,
81
+ },
82
+ },
83
+ ]
84
+ : []),
85
+ // Bedrock model invocation permissions (all regions for cross-region inference)
86
+ {
87
+ Effect: 'Allow',
88
+ Action: ['bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream'],
89
+ Resource: {
90
+ 'Fn::Sub': 'arn:${AWS::Partition}:bedrock:*::foundation-model/*',
91
+ },
92
+ },
93
+ // Bedrock cross-region inference profile permissions
94
+ {
95
+ Effect: 'Allow',
96
+ Action: [
97
+ 'bedrock:InvokeModel',
98
+ 'bedrock:InvokeModelWithResponseStream',
99
+ 'bedrock:GetInferenceProfile',
100
+ ],
101
+ Resource: [
102
+ 'arn:aws:bedrock:*:*:inference-profile/us.*',
103
+ 'arn:aws:bedrock:*:*:inference-profile/eu.*',
104
+ 'arn:aws:bedrock:*:*:inference-profile/global.*',
105
+ ],
106
+ },
107
+ ],
108
+ },
109
+ },
110
+ ];
111
+
112
+ // Add VPC permissions if VPC mode
113
+ if (config.network?.networkMode === 'VPC') {
114
+ policies[0].PolicyDocument.Statement.push({
115
+ Effect: 'Allow',
116
+ Action: [
117
+ 'ec2:CreateNetworkInterface',
118
+ 'ec2:DescribeNetworkInterfaces',
119
+ 'ec2:DeleteNetworkInterface',
120
+ 'ec2:AssignPrivateIpAddresses',
121
+ 'ec2:UnassignPrivateIpAddresses',
122
+ ],
123
+ Resource: '*',
124
+ });
125
+ }
126
+
127
+ return {
128
+ Type: 'AWS::IAM::Role',
129
+ Properties: {
130
+ RoleName: roleName,
131
+ AssumeRolePolicyDocument: assumeRolePolicy,
132
+ Policies: policies,
133
+ Tags: [
134
+ { Key: 'serverless:service', Value: serviceName },
135
+ { Key: 'serverless:stage', Value: stage },
136
+ { Key: 'agentcore:resource', Value: name },
137
+ { Key: 'agentcore:type', Value: 'runtime-role' },
138
+ ],
139
+ },
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Generate IAM role for AgentCore Memory
145
+ *
146
+ * @param {string} name - The agent name
147
+ * @param {Object} config - The memory configuration
148
+ * @param {Object} context - The compilation context
149
+ * @returns {Object} CloudFormation IAM Role resource
150
+ */
151
+ function generateMemoryRole(name, config, context) {
152
+ const { serviceName, stage } = context;
153
+ const roleName = getResourceName(serviceName, `${name}-memory-role`, stage);
154
+
155
+ const assumeRolePolicy = {
156
+ Version: '2012-10-17',
157
+ Statement: [
158
+ {
159
+ Effect: 'Allow',
160
+ Principal: {
161
+ Service: 'bedrock-agentcore.amazonaws.com',
162
+ },
163
+ Action: 'sts:AssumeRole',
164
+ Condition: {
165
+ StringEquals: {
166
+ 'aws:SourceAccount': { Ref: 'AWS::AccountId' },
167
+ },
168
+ },
169
+ },
170
+ ],
171
+ };
172
+
173
+ const policies = [
174
+ {
175
+ PolicyName: `${roleName}-base-policy`,
176
+ PolicyDocument: {
177
+ Version: '2012-10-17',
178
+ Statement: [
179
+ // CloudWatch Logs permissions
180
+ {
181
+ Effect: 'Allow',
182
+ Action: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'],
183
+ Resource: {
184
+ 'Fn::Sub':
185
+ 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/bedrock-agentcore/*',
186
+ },
187
+ },
188
+ // Bedrock model invocation for memory strategies (all regions for cross-region inference)
189
+ {
190
+ Effect: 'Allow',
191
+ Action: ['bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream'],
192
+ Resource: {
193
+ 'Fn::Sub': 'arn:${AWS::Partition}:bedrock:*::foundation-model/*',
194
+ },
195
+ },
196
+ // Bedrock cross-region inference profile permissions
197
+ {
198
+ Effect: 'Allow',
199
+ Action: [
200
+ 'bedrock:InvokeModel',
201
+ 'bedrock:InvokeModelWithResponseStream',
202
+ 'bedrock:GetInferenceProfile',
203
+ ],
204
+ Resource: [
205
+ 'arn:aws:bedrock:*:*:inference-profile/us.*',
206
+ 'arn:aws:bedrock:*:*:inference-profile/eu.*',
207
+ 'arn:aws:bedrock:*:*:inference-profile/global.*',
208
+ ],
209
+ },
210
+ // KMS permissions if encryption key is specified
211
+ ...(config.encryptionKeyArn
212
+ ? [
213
+ {
214
+ Effect: 'Allow',
215
+ Action: ['kms:Decrypt', 'kms:Encrypt', 'kms:GenerateDataKey', 'kms:DescribeKey'],
216
+ Resource: config.encryptionKeyArn,
217
+ },
218
+ ]
219
+ : []),
220
+ ],
221
+ },
222
+ },
223
+ ];
224
+
225
+ return {
226
+ Type: 'AWS::IAM::Role',
227
+ Properties: {
228
+ RoleName: roleName,
229
+ AssumeRolePolicyDocument: assumeRolePolicy,
230
+ Policies: policies,
231
+ Tags: [
232
+ { Key: 'serverless:service', Value: serviceName },
233
+ { Key: 'serverless:stage', Value: stage },
234
+ { Key: 'agentcore:resource', Value: name },
235
+ { Key: 'agentcore:type', Value: 'memory-role' },
236
+ ],
237
+ },
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Generate IAM role for AgentCore Gateway
243
+ *
244
+ * @param {string} name - The agent name
245
+ * @param {Object} config - The gateway configuration
246
+ * @param {Object} context - The compilation context
247
+ * @returns {Object} CloudFormation IAM Role resource
248
+ */
249
+ function generateGatewayRole(name, config, context) {
250
+ const { serviceName, stage } = context;
251
+ const roleName = getResourceName(serviceName, `${name}-gateway-role`, stage);
252
+
253
+ const assumeRolePolicy = {
254
+ Version: '2012-10-17',
255
+ Statement: [
256
+ {
257
+ Effect: 'Allow',
258
+ Principal: {
259
+ Service: 'bedrock-agentcore.amazonaws.com',
260
+ },
261
+ Action: 'sts:AssumeRole',
262
+ Condition: {
263
+ StringEquals: {
264
+ 'aws:SourceAccount': { Ref: 'AWS::AccountId' },
265
+ },
266
+ },
267
+ },
268
+ ],
269
+ };
270
+
271
+ // Build Lambda invocation permissions if there are Lambda targets
272
+ const lambdaTargets = (config.targets || []).filter(
273
+ (t) => t.type === 'lambda' || (!t.type && (t.functionArn || t.functionName))
274
+ );
275
+
276
+ const policies = [
277
+ {
278
+ PolicyName: `${roleName}-base-policy`,
279
+ PolicyDocument: {
280
+ Version: '2012-10-17',
281
+ Statement: [
282
+ // CloudWatch Logs permissions
283
+ {
284
+ Effect: 'Allow',
285
+ Action: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'],
286
+ Resource: {
287
+ 'Fn::Sub':
288
+ 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/bedrock-agentcore/*',
289
+ },
290
+ },
291
+ // Lambda invocation permissions for Lambda targets
292
+ ...(lambdaTargets.length > 0
293
+ ? [
294
+ {
295
+ Effect: 'Allow',
296
+ Action: ['lambda:InvokeFunction'],
297
+ Resource: lambdaTargets.map((t) => {
298
+ if (t.functionArn) {
299
+ return t.functionArn;
300
+ }
301
+ // Reference the function from the stack
302
+ const functionLogicalId =
303
+ t.functionName
304
+ .split(/[-_]/)
305
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
306
+ .join('') + 'LambdaFunction';
307
+
308
+ return { 'Fn::GetAtt': [functionLogicalId, 'Arn'] };
309
+ }),
310
+ },
311
+ ]
312
+ : []),
313
+ // S3 permissions for OpenAPI/Smithy specs
314
+ ...getS3PermissionsForTargets(config.targets || []),
315
+ // Secrets Manager permissions for OAuth/API Key credentials
316
+ ...getSecretsManagerPermissions(config.targets || []),
317
+ // KMS permissions if encryption key is specified
318
+ ...(config.kmsKeyArn
319
+ ? [
320
+ {
321
+ Effect: 'Allow',
322
+ Action: ['kms:Decrypt', 'kms:Encrypt', 'kms:GenerateDataKey', 'kms:DescribeKey'],
323
+ Resource: config.kmsKeyArn,
324
+ },
325
+ ]
326
+ : []),
327
+ ],
328
+ },
329
+ },
330
+ ];
331
+
332
+ return {
333
+ Type: 'AWS::IAM::Role',
334
+ Properties: {
335
+ RoleName: roleName,
336
+ AssumeRolePolicyDocument: assumeRolePolicy,
337
+ Policies: policies,
338
+ Tags: [
339
+ { Key: 'serverless:service', Value: serviceName },
340
+ { Key: 'serverless:stage', Value: stage },
341
+ { Key: 'agentcore:resource', Value: name },
342
+ { Key: 'agentcore:type', Value: 'gateway-role' },
343
+ ],
344
+ },
345
+ };
346
+ }
347
+
348
+ /**
349
+ * Get S3 permissions for targets with S3 configurations
350
+ *
351
+ * @param {Array} targets - Array of target configurations
352
+ * @returns {Array} Array of IAM policy statements
353
+ */
354
+ function getS3PermissionsForTargets(targets) {
355
+ const s3Targets = targets.filter((t) => t.s3);
356
+
357
+ if (s3Targets.length === 0) {
358
+ return [];
359
+ }
360
+
361
+ return [
362
+ {
363
+ Effect: 'Allow',
364
+ Action: ['s3:GetObject', 's3:GetObjectVersion'],
365
+ Resource: s3Targets.map((t) => ({
366
+ 'Fn::Sub': `arn:\${AWS::Partition}:s3:::${t.s3.bucket}/${t.s3.key}`,
367
+ })),
368
+ },
369
+ ];
370
+ }
371
+
372
+ /**
373
+ * Get Secrets Manager permissions for OAuth/API Key credentials
374
+ *
375
+ * @param {Array} targets - Array of target configurations
376
+ * @returns {Array} Array of IAM policy statements
377
+ */
378
+ function getSecretsManagerPermissions(targets) {
379
+ const secretArns = targets
380
+ .filter((t) => t.credentialProvider)
381
+ .map((t) => {
382
+ const cp = t.credentialProvider;
383
+ if (cp.oauthConfig?.secretArn) {
384
+ return cp.oauthConfig.secretArn;
385
+ }
386
+ if (cp.apiKeyConfig?.secretArn) {
387
+ return cp.apiKeyConfig.secretArn;
388
+ }
389
+
390
+ return null;
391
+ })
392
+ .filter(Boolean);
393
+
394
+ if (secretArns.length === 0) {
395
+ return [];
396
+ }
397
+
398
+ return [
399
+ {
400
+ Effect: 'Allow',
401
+ Action: ['secretsmanager:GetSecretValue'],
402
+ Resource: secretArns,
403
+ },
404
+ ];
405
+ }
406
+
407
+ /**
408
+ * Generate IAM role for AgentCore BrowserCustom
409
+ *
410
+ * @param {string} name - The browser name
411
+ * @param {Object} config - The browser configuration
412
+ * @param {Object} context - The compilation context
413
+ * @returns {Object} CloudFormation IAM Role resource
414
+ */
415
+ function generateBrowserRole(name, config, context) {
416
+ const { serviceName, stage } = context;
417
+ const roleName = getResourceName(serviceName, `${name}-browser-role`, stage);
418
+
419
+ const assumeRolePolicy = {
420
+ Version: '2012-10-17',
421
+ Statement: [
422
+ {
423
+ Effect: 'Allow',
424
+ Principal: {
425
+ Service: 'bedrock-agentcore.amazonaws.com',
426
+ },
427
+ Action: 'sts:AssumeRole',
428
+ Condition: {
429
+ StringEquals: {
430
+ 'aws:SourceAccount': { Ref: 'AWS::AccountId' },
431
+ },
432
+ },
433
+ },
434
+ ],
435
+ };
436
+
437
+ const policies = [
438
+ {
439
+ PolicyName: `${roleName}-policy`,
440
+ PolicyDocument: {
441
+ Version: '2012-10-17',
442
+ Statement: [
443
+ // CloudWatch Logs permissions
444
+ {
445
+ Effect: 'Allow',
446
+ Action: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'],
447
+ Resource: {
448
+ 'Fn::Sub':
449
+ 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/bedrock-agentcore/*',
450
+ },
451
+ },
452
+ // S3 permissions for recordings (if enabled)
453
+ ...(config.recording?.s3Location
454
+ ? [
455
+ {
456
+ Effect: 'Allow',
457
+ Action: ['s3:PutObject', 's3:GetObject'],
458
+ Resource: {
459
+ 'Fn::Sub': `arn:\${AWS::Partition}:s3:::${config.recording.s3Location.bucket}/${config.recording.s3Location.prefix || ''}*`,
460
+ },
461
+ },
462
+ ]
463
+ : []),
464
+ // VPC permissions (if VPC mode)
465
+ ...(config.network?.networkMode === 'VPC'
466
+ ? [
467
+ {
468
+ Effect: 'Allow',
469
+ Action: [
470
+ 'ec2:CreateNetworkInterface',
471
+ 'ec2:DescribeNetworkInterfaces',
472
+ 'ec2:DeleteNetworkInterface',
473
+ 'ec2:AssignPrivateIpAddresses',
474
+ 'ec2:UnassignPrivateIpAddresses',
475
+ ],
476
+ Resource: '*',
477
+ },
478
+ ]
479
+ : []),
480
+ ],
481
+ },
482
+ },
483
+ ];
484
+
485
+ return {
486
+ Type: 'AWS::IAM::Role',
487
+ Properties: {
488
+ RoleName: roleName,
489
+ AssumeRolePolicyDocument: assumeRolePolicy,
490
+ Policies: policies,
491
+ Tags: [
492
+ { Key: 'serverless:service', Value: serviceName },
493
+ { Key: 'serverless:stage', Value: stage },
494
+ { Key: 'agentcore:resource', Value: name },
495
+ { Key: 'agentcore:type', Value: 'browser-role' },
496
+ ],
497
+ },
498
+ };
499
+ }
500
+
501
+ /**
502
+ * Generate IAM role for AgentCore CodeInterpreterCustom
503
+ *
504
+ * @param {string} name - The code interpreter name
505
+ * @param {Object} config - The code interpreter configuration
506
+ * @param {Object} context - The compilation context
507
+ * @returns {Object} CloudFormation IAM Role resource
508
+ */
509
+ function generateCodeInterpreterRole(name, config, context) {
510
+ const { serviceName, stage } = context;
511
+ const roleName = getResourceName(serviceName, `${name}-ci-role`, stage);
512
+
513
+ const assumeRolePolicy = {
514
+ Version: '2012-10-17',
515
+ Statement: [
516
+ {
517
+ Effect: 'Allow',
518
+ Principal: {
519
+ Service: 'bedrock-agentcore.amazonaws.com',
520
+ },
521
+ Action: 'sts:AssumeRole',
522
+ Condition: {
523
+ StringEquals: {
524
+ 'aws:SourceAccount': { Ref: 'AWS::AccountId' },
525
+ },
526
+ },
527
+ },
528
+ ],
529
+ };
530
+
531
+ const policies = [
532
+ {
533
+ PolicyName: `${roleName}-policy`,
534
+ PolicyDocument: {
535
+ Version: '2012-10-17',
536
+ Statement: [
537
+ // CloudWatch Logs permissions
538
+ {
539
+ Effect: 'Allow',
540
+ Action: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'],
541
+ Resource: {
542
+ 'Fn::Sub':
543
+ 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/bedrock-agentcore/*',
544
+ },
545
+ },
546
+ // VPC permissions (if VPC mode)
547
+ ...(config.network?.networkMode === 'VPC'
548
+ ? [
549
+ {
550
+ Effect: 'Allow',
551
+ Action: [
552
+ 'ec2:CreateNetworkInterface',
553
+ 'ec2:DescribeNetworkInterfaces',
554
+ 'ec2:DeleteNetworkInterface',
555
+ 'ec2:AssignPrivateIpAddresses',
556
+ 'ec2:UnassignPrivateIpAddresses',
557
+ ],
558
+ Resource: '*',
559
+ },
560
+ ]
561
+ : []),
562
+ ],
563
+ },
564
+ },
565
+ ];
566
+
567
+ return {
568
+ Type: 'AWS::IAM::Role',
569
+ Properties: {
570
+ RoleName: roleName,
571
+ AssumeRolePolicyDocument: assumeRolePolicy,
572
+ Policies: policies,
573
+ Tags: [
574
+ { Key: 'serverless:service', Value: serviceName },
575
+ { Key: 'serverless:stage', Value: stage },
576
+ { Key: 'agentcore:resource', Value: name },
577
+ { Key: 'agentcore:type', Value: 'codeinterpreter-role' },
578
+ ],
579
+ },
580
+ };
581
+ }
582
+
583
+ module.exports = {
584
+ generateRuntimeRole,
585
+ generateMemoryRole,
586
+ generateGatewayRole,
587
+ generateBrowserRole,
588
+ generateCodeInterpreterRole,
589
+ getS3PermissionsForTargets,
590
+ getSecretsManagerPermissions,
591
+ };