serverless-bedrock-agentcore-plugin 0.2.0 → 0.4.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+
12
+ - Resource-based policy support for AgentCore Runtime
13
+ - Enable cross-account access to agent runtimes
14
+ - Standard IAM policy document format
15
+ - Support for multiple principals and statements
16
+ - New example: `cross-account-access` demonstrating resource policy usage
17
+ - Schema validation for `resourcePolicy` configuration
18
+ - Test coverage for resource policy builder functions
19
+
10
20
  ## [0.2.0] - 2025-12-19
11
21
 
12
22
  ### Added
package/README.md CHANGED
@@ -45,15 +45,15 @@ agents:
45
45
  myAgent:
46
46
  type: runtime
47
47
  description: My AI agent
48
- handler:
49
- type: docker
50
- image:
51
- dockerfile: ./Dockerfile
52
- context: .
53
- protocol: AWS_MCP
54
- networkMode: PUBLIC
55
- authorizationConfig:
56
- authorizationType: NONE
48
+ artifact:
49
+ docker:
50
+ path: .
51
+ file: Dockerfile
52
+ repository: my-agent
53
+ protocol: HTTP
54
+ network:
55
+ networkMode: PUBLIC
56
+ # Omit 'authorizer' for no authentication
57
57
  ```
58
58
 
59
59
  ## Resource Types
@@ -67,35 +67,75 @@ agents:
67
67
  myAgent:
68
68
  type: runtime
69
69
  description: My AI agent
70
- handler:
71
- type: docker
72
- image:
73
- dockerfile: ./Dockerfile
74
- context: .
75
- protocol: AWS_MCP # AWS_MCP, HTTP, or A2A
76
- networkMode: PUBLIC # PUBLIC or VPC
77
- authorizationConfig:
78
- authorizationType: NONE # NONE or AWS_IAM
70
+ artifact:
71
+ docker:
72
+ path: .
73
+ file: Dockerfile
74
+ repository: my-agent
75
+ protocol: HTTP # HTTP, MCP, or A2A
76
+ network:
77
+ networkMode: PUBLIC # PUBLIC or VPC
78
+ # Optional: JWT authorization (omit for no auth)
79
+ authorizer:
80
+ customJwtAuthorizer:
81
+ discoveryUrl: https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxx/.well-known/openid-configuration
82
+ allowedAudience:
83
+ - my-client-id
79
84
  # Optional: Pass specific headers to the runtime
80
85
  requestHeaders:
81
86
  allowlist:
82
87
  - X-User-Id
83
88
  - X-Session-Id
84
89
  - Authorization
90
+ # Optional: Resource-based policy for cross-account access
91
+ resourcePolicy:
92
+ Version: '2012-10-17'
93
+ Statement:
94
+ - Sid: AllowCrossAccountInvoke
95
+ Effect: Allow
96
+ Principal:
97
+ AWS: arn:aws:iam::123456789012:role/MyRole
98
+ Action:
99
+ - bedrock-agentcore:InvokeAgentRuntime
100
+ Resource: '*'
85
101
  ```
86
102
 
87
- | Property | Required | Description |
88
- | --------------------------------------- | -------- | ----------------------------------- |
89
- | `type` | Yes | `runtime` |
90
- | `handler.type` | Yes | `docker` |
91
- | `handler.image.dockerfile` | Yes | Path to Dockerfile |
92
- | `handler.image.context` | Yes | Docker build context |
93
- | `protocol` | No | `AWS_MCP`, `HTTP`, or `A2A` |
94
- | `networkMode` | No | `PUBLIC` or `VPC` |
95
- | `authorizationConfig.authorizationType` | No | `NONE` or `AWS_IAM` |
96
- | `requestHeaders.allowlist` | No | Headers to pass to runtime (max 20) |
97
- | `description` | No | Runtime description |
98
- | `roleArn` | No | Custom IAM role ARN |
103
+ | Property | Required | Description |
104
+ | ------------------------------------------------ | --------- | ---------------------------------------- |
105
+ | `type` | Yes | `runtime` |
106
+ | `artifact.docker.path` | Yes\* | Docker build context path |
107
+ | `artifact.docker.file` | No | Dockerfile name (default: Dockerfile) |
108
+ | `artifact.docker.repository` | No | ECR repository name |
109
+ | `artifact.containerImage` | Yes\* | Pre-built container image URI |
110
+ | `protocol` | No | `HTTP`, `MCP`, or `A2A` |
111
+ | `network.networkMode` | No | `PUBLIC` or `VPC` |
112
+ | `authorizer.customJwtAuthorizer` | No | JWT authorizer config (omit for no auth) |
113
+ | `authorizer.customJwtAuthorizer.discoveryUrl` | Yes\*\* | OIDC discovery URL |
114
+ | `authorizer.customJwtAuthorizer.allowedAudience` | No | Array of allowed audience values |
115
+ | `authorizer.customJwtAuthorizer.allowedClients` | No | Array of allowed client IDs |
116
+ | `requestHeaders.allowlist` | No | Headers to pass to runtime (max 20) |
117
+ | `resourcePolicy` | No | Resource-based policy (IAM policy doc) |
118
+ | `resourcePolicy.Statement` | Yes\*\*\* | Array of policy statements |
119
+ | `description` | No | Runtime description |
120
+ | `roleArn` | No | Custom IAM role ARN |
121
+
122
+ \*Either `artifact.docker` or `artifact.containerImage` is required
123
+
124
+ \*\*Required when using `customJwtAuthorizer`
125
+
126
+ \*\*\*Required when using `resourcePolicy`
127
+
128
+ #### Resource-Based Policies
129
+
130
+ Resource-based policies allow you to grant cross-account or cross-principal access to invoke your AgentCore runtime. This is useful when you need to allow another AWS account or service to invoke your agent.
131
+
132
+ Example use cases:
133
+
134
+ - **Cross-account access**: Allow an ECS task in another account to invoke your agent
135
+ - **Service-to-service**: Grant specific IAM roles permission to invoke the runtime
136
+ - **Multi-tenant architectures**: Control access across different AWS accounts
137
+
138
+ The `resourcePolicy` follows standard IAM policy document format with `Version` (defaults to `2012-10-17`) and `Statement` array.
99
139
 
100
140
  ### Memory
101
141
 
@@ -111,25 +151,18 @@ agents:
111
151
  # Semantic search strategy
112
152
  - SemanticMemoryStrategy:
113
153
  Name: ConversationSearch
114
- Type: SEMANTIC
115
154
  Namespaces:
116
155
  - /conversations/{sessionId}
117
- SemanticMemoryConfiguration:
118
- ModelId: amazon.titan-embed-text-v2:0
119
- SimilarityThreshold: 0.75
120
156
 
121
157
  # Summarization strategy
122
158
  - SummaryMemoryStrategy:
123
159
  Name: SessionSummary
124
- Type: SUMMARIZATION
125
- SummaryConfiguration:
126
- SummaryModelId: anthropic.claude-3-haiku-20240307-v1:0
127
- MaxMessages: 100
160
+ Namespaces:
161
+ - /sessions/{sessionId}
128
162
 
129
163
  # User preference strategy
130
164
  - UserPreferenceMemoryStrategy:
131
165
  Name: UserPrefs
132
- Type: USER_PREFERENCE
133
166
  Namespaces:
134
167
  - /users/{userId}/preferences
135
168
  ```
@@ -150,12 +183,8 @@ agents:
150
183
  ```yaml
151
184
  - SemanticMemoryStrategy:
152
185
  Name: Search
153
- Type: SEMANTIC
154
186
  Namespaces:
155
187
  - /sessions/{sessionId}
156
- SemanticMemoryConfiguration:
157
- ModelId: amazon.titan-embed-text-v2:0
158
- SimilarityThreshold: 0.75
159
188
  ```
160
189
 
161
190
  **SummaryMemoryStrategy** - Summarize long conversations:
@@ -163,10 +192,8 @@ agents:
163
192
  ```yaml
164
193
  - SummaryMemoryStrategy:
165
194
  Name: Summary
166
- Type: SUMMARIZATION
167
- SummaryConfiguration:
168
- SummaryModelId: anthropic.claude-3-haiku-20240307-v1:0
169
- MaxMessages: 100
195
+ Namespaces:
196
+ - /sessions/{sessionId}
170
197
  ```
171
198
 
172
199
  **UserPreferenceMemoryStrategy** - Track user preferences:
@@ -174,7 +201,6 @@ agents:
174
201
  ```yaml
175
202
  - UserPreferenceMemoryStrategy:
176
203
  Name: Preferences
177
- Type: USER_PREFERENCE
178
204
  Namespaces:
179
205
  - /users/{userId}
180
206
  ```
@@ -184,8 +210,7 @@ agents:
184
210
  ```yaml
185
211
  - CustomMemoryStrategy:
186
212
  Name: Custom
187
- Type: CUSTOM
188
- CustomConfiguration:
213
+ Configuration:
189
214
  key: value
190
215
  ```
191
216
 
@@ -265,33 +290,65 @@ Integrate external APIs as agent tools.
265
290
 
266
291
  ```yaml
267
292
  agents:
268
- apiGateway:
293
+ # Gateway without authentication
294
+ publicGateway:
269
295
  type: gateway
270
- description: External API gateway
271
- authorizationType: NONE
296
+ description: Public API gateway
297
+ authorizerType: NONE
272
298
  targets:
273
- weatherApi:
274
- type: lambdaArn
275
- lambdaArn:
299
+ - name: WeatherAPI
300
+ type: lambda
301
+ description: Get weather data
302
+ functionArn:
276
303
  Fn::GetAtt:
277
304
  - WeatherFunction
278
305
  - Arn
279
- name: WeatherAPI
280
- description: Get weather data
306
+
307
+ # Gateway with JWT authentication
308
+ secureGateway:
309
+ type: gateway
310
+ description: Secure API gateway with JWT auth
311
+ authorizerType: CUSTOM_JWT
312
+ authorizerConfiguration:
313
+ customJwtAuthorizer:
314
+ discoveryUrl: https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxx/.well-known/openid-configuration
315
+ allowedAudience:
316
+ - my-client-id
317
+ allowedClients:
318
+ - my-app-client
319
+ targets:
320
+ - name: SecureAPI
321
+ type: lambda
322
+ functionArn:
323
+ Fn::GetAtt:
324
+ - SecureFunction
325
+ - Arn
281
326
  ```
282
327
 
283
- | Property | Required | Description |
284
- | ------------------- | -------- | ---------------------------------- |
285
- | `type` | Yes | `gateway` |
286
- | `authorizationType` | No | `NONE` or `AWS_IAM` |
287
- | `targets` | No | Gateway targets (Lambda functions) |
288
- | `description` | No | Gateway description |
289
- | `roleArn` | No | Custom IAM role ARN |
328
+ | Property | Required | Description |
329
+ | ------------------------------------------------------------- | -------- | ------------------------------------------------------------------ |
330
+ | `type` | Yes | `gateway` |
331
+ | `authorizerType` | No | `NONE`, `AWS_IAM`, or `CUSTOM_JWT` (default: `AWS_IAM`) |
332
+ | `authorizerConfiguration.customJwtAuthorizer` | No\* | JWT authorizer config (required when `authorizerType: CUSTOM_JWT`) |
333
+ | `authorizerConfiguration.customJwtAuthorizer.discoveryUrl` | Yes\*\* | OIDC discovery URL |
334
+ | `authorizerConfiguration.customJwtAuthorizer.allowedAudience` | No | Array of allowed audience values |
335
+ | `authorizerConfiguration.customJwtAuthorizer.allowedClients` | No | Array of allowed client IDs |
336
+ | `protocolType` | No | `MCP` (default: `MCP`) |
337
+ | `targets` | No | Gateway targets (Lambda functions) |
338
+ | `description` | No | Gateway description |
339
+ | `roleArn` | No | Custom IAM role ARN |
340
+
341
+ \*Required when `authorizerType` is `CUSTOM_JWT`
342
+
343
+ \*\*Required when using `customJwtAuthorizer`
290
344
 
291
345
  ## Commands
292
346
 
293
347
  ```bash
294
348
  sls agentcore info # Show defined resources
349
+ sls agentcore build # Build Docker images
350
+ sls agentcore invoke # Invoke a deployed agent
351
+ sls agentcore logs # Fetch logs for a runtime
295
352
  sls package # Generate CloudFormation
296
353
  sls deploy # Deploy to AWS
297
354
  sls remove # Remove deployed resources
@@ -305,7 +362,7 @@ The `examples/` directory contains complete working examples:
305
362
 
306
363
  A comprehensive example showing all resource types working together:
307
364
 
308
- - Runtime with Docker handler
365
+ - Runtime with Docker artifact
309
366
  - Memory with multiple strategies
310
367
  - Browser for web interactions
311
368
  - CodeInterpreter for code execution
@@ -456,8 +513,8 @@ The memory strategy format has changed to a typed union structure. The legacy fo
456
513
  strategies:
457
514
  - type: semantic
458
515
  name: Search
459
- configuration:
460
- modelId: amazon.titan-embed-text-v2:0
516
+ namespaces:
517
+ - /sessions/{sessionId}
461
518
  ```
462
519
 
463
520
  **New format:**
@@ -466,9 +523,8 @@ strategies:
466
523
  strategies:
467
524
  - SemanticMemoryStrategy:
468
525
  Name: Search
469
- Type: SEMANTIC
470
- SemanticMemoryConfiguration:
471
- ModelId: amazon.titan-embed-text-v2:0
526
+ Namespaces:
527
+ - /sessions/{sessionId}
472
528
  ```
473
529
 
474
530
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serverless-bedrock-agentcore-plugin",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Serverless Framework plugin for AWS Bedrock AgentCore - deploy Runtime, Memory, and Gateway resources",
5
5
  "main": "src/index.js",
6
6
  "engines": {
@@ -4,19 +4,28 @@ const { getLogicalId } = require('../utils/naming');
4
4
 
5
5
  /**
6
6
  * Build authorizer configuration for the gateway
7
+ * Only used when authorizerType is CUSTOM_JWT
7
8
  *
8
9
  * @param {Object} authConfig - The authorizer configuration from serverless.yml
9
10
  * @returns {Object|null} CloudFormation authorizer configuration or null
10
11
  */
11
12
  function buildGatewayAuthorizerConfiguration(authConfig) {
12
- if (!authConfig) {
13
+ if (!authConfig || !authConfig.customJwtAuthorizer) {
13
14
  return null;
14
15
  }
15
16
 
17
+ const jwtConfig = authConfig.customJwtAuthorizer;
18
+
19
+ if (!jwtConfig.discoveryUrl) {
20
+ throw new Error('Gateway CustomJWTAuthorizer requires discoveryUrl');
21
+ }
22
+
16
23
  return {
17
- ...(authConfig.allowedAudiences && { AllowedAudiences: authConfig.allowedAudiences }),
18
- ...(authConfig.allowedClients && { AllowedClients: authConfig.allowedClients }),
19
- ...(authConfig.allowedIssuers && { AllowedIssuers: authConfig.allowedIssuers }),
24
+ CustomJWTAuthorizer: {
25
+ DiscoveryUrl: jwtConfig.discoveryUrl,
26
+ ...(jwtConfig.allowedAudience && { AllowedAudience: jwtConfig.allowedAudience }),
27
+ ...(jwtConfig.allowedClients && { AllowedClients: jwtConfig.allowedClients }),
28
+ },
20
29
  };
21
30
  }
22
31
 
@@ -109,6 +109,7 @@ function buildMemoryStrategies(strategies) {
109
109
  if (isLegacyFormat(strategy)) {
110
110
  // Emit deprecation warning once per deployment
111
111
  if (!deprecationWarningShown) {
112
+ // eslint-disable-next-line no-console -- Intentional user-facing deprecation warning
112
113
  console.warn(
113
114
  '\x1b[33m%s\x1b[0m', // Yellow color
114
115
  'DEPRECATION WARNING: Memory strategy format has changed to typed union structure. ' +
@@ -182,6 +182,26 @@ function buildRequestHeaderConfiguration(requestHeaders) {
182
182
  };
183
183
  }
184
184
 
185
+ /**
186
+ * Build resource-based policy for the runtime
187
+ * Allows cross-account or cross-principal access to invoke the agent
188
+ *
189
+ * @param {Object} resourcePolicy - The resource policy configuration from serverless.yml
190
+ * @returns {Object|null} CloudFormation ResourcePolicy or null
191
+ */
192
+ function buildResourcePolicy(resourcePolicy) {
193
+ if (!resourcePolicy || !resourcePolicy.Statement || resourcePolicy.Statement.length === 0) {
194
+ return null;
195
+ }
196
+
197
+ // CloudFormation expects the policy as a JSON object
198
+ // The policy should be in standard IAM policy document format
199
+ return {
200
+ Version: resourcePolicy.Version || '2012-10-17',
201
+ Statement: resourcePolicy.Statement,
202
+ };
203
+ }
204
+
185
205
  /**
186
206
  * Compile a Runtime resource to CloudFormation
187
207
  *
@@ -203,6 +223,7 @@ function compileRuntime(name, config, context, tags) {
203
223
  const protocolConfig = buildProtocolConfiguration(config.protocol);
204
224
  const envVars = buildEnvironmentVariables(config.environment);
205
225
  const requestHeaderConfig = buildRequestHeaderConfiguration(config.requestHeaders);
226
+ const resourcePolicy = buildResourcePolicy(config.resourcePolicy);
206
227
 
207
228
  return {
208
229
  Type: 'AWS::BedrockAgentCore::Runtime',
@@ -217,6 +238,7 @@ function compileRuntime(name, config, context, tags) {
217
238
  ...(protocolConfig && { ProtocolConfiguration: protocolConfig }),
218
239
  ...(envVars && { EnvironmentVariables: envVars }),
219
240
  ...(requestHeaderConfig && { RequestHeaderConfiguration: requestHeaderConfig }),
241
+ ...(resourcePolicy && { ResourcePolicy: resourcePolicy }),
220
242
  ...(Object.keys(tags).length > 0 && { Tags: tags }),
221
243
  },
222
244
  };
@@ -231,4 +253,5 @@ module.exports = {
231
253
  buildProtocolConfiguration,
232
254
  buildEnvironmentVariables,
233
255
  buildRequestHeaderConfiguration,
256
+ buildResourcePolicy,
234
257
  };
package/src/index.js CHANGED
@@ -309,7 +309,7 @@ class ServerlessBedrockAgentCore {
309
309
  * Validate gateway configuration
310
310
  */
311
311
  validateGateway(name, config) {
312
- const validAuthTypes = ['AWS_IAM', 'CUSTOM_JWT'];
312
+ const validAuthTypes = ['NONE', 'AWS_IAM', 'CUSTOM_JWT'];
313
313
  if (config.authorizerType && !validAuthTypes.includes(config.authorizerType)) {
314
314
  throw new this.serverless.classes.Error(
315
315
  `Gateway '${name}' has invalid authorizerType '${config.authorizerType}'. Valid types: ${validAuthTypes.join(', ')}`
@@ -101,6 +101,57 @@ function defineAgentsSchema(serverless) {
101
101
  maxConcurrency: { type: 'number' },
102
102
  },
103
103
  },
104
+ resourcePolicy: {
105
+ type: 'object',
106
+ properties: {
107
+ Version: { type: 'string' },
108
+ Statement: {
109
+ type: 'array',
110
+ items: {
111
+ type: 'object',
112
+ properties: {
113
+ Sid: { type: 'string' },
114
+ Effect: {
115
+ type: 'string',
116
+ enum: ['Allow', 'Deny'],
117
+ },
118
+ Principal: { type: 'object' },
119
+ Action: {
120
+ oneOf: [
121
+ { type: 'string' },
122
+ {
123
+ type: 'array',
124
+ items: { type: 'string' },
125
+ },
126
+ ],
127
+ },
128
+ Resource: {
129
+ oneOf: [
130
+ { type: 'string' },
131
+ {
132
+ type: 'array',
133
+ items: { type: 'string' },
134
+ },
135
+ ],
136
+ },
137
+ Condition: { type: 'object' },
138
+ },
139
+ required: ['Effect', 'Principal', 'Action'],
140
+ },
141
+ },
142
+ },
143
+ required: ['Statement'],
144
+ },
145
+ requestHeaders: {
146
+ type: 'object',
147
+ properties: {
148
+ allowlist: {
149
+ type: 'array',
150
+ items: { type: 'string' },
151
+ maxItems: 20,
152
+ },
153
+ },
154
+ },
104
155
  endpoints: {
105
156
  type: 'array',
106
157
  items: {
@@ -126,28 +177,17 @@ function defineAgentsSchema(serverless) {
126
177
  type: 'array',
127
178
  items: {
128
179
  type: 'object',
129
- properties: {
130
- type: {
131
- type: 'string',
132
- enum: ['semantic', 'userPreference', 'summary', 'custom'],
133
- },
134
- name: { type: 'string' },
135
- namespaces: {
136
- type: 'array',
137
- items: { type: 'string' },
138
- },
139
- configuration: {
140
- type: 'object',
141
- },
142
- },
143
- required: ['type', 'name'],
180
+ // Supports both legacy format and new typed union format
181
+ // Legacy: { type: 'semantic', name: 'Search', ... }
182
+ // New: { SemanticMemoryStrategy: { Name: 'Search', ... } }
183
+ additionalProperties: true,
144
184
  },
145
185
  },
146
186
 
147
187
  // Gateway-specific properties
148
188
  authorizerType: {
149
189
  type: 'string',
150
- enum: ['AWS_IAM', 'CUSTOM_JWT'],
190
+ enum: ['NONE', 'AWS_IAM', 'CUSTOM_JWT'],
151
191
  },
152
192
  protocolType: {
153
193
  type: 'string',
@@ -156,17 +196,20 @@ function defineAgentsSchema(serverless) {
156
196
  authorizerConfiguration: {
157
197
  type: 'object',
158
198
  properties: {
159
- allowedAudiences: {
160
- type: 'array',
161
- items: { type: 'string' },
162
- },
163
- allowedClients: {
164
- type: 'array',
165
- items: { type: 'string' },
166
- },
167
- allowedIssuers: {
168
- type: 'array',
169
- items: { type: 'string' },
199
+ customJwtAuthorizer: {
200
+ type: 'object',
201
+ properties: {
202
+ discoveryUrl: { type: 'string' },
203
+ allowedAudience: {
204
+ type: 'array',
205
+ items: { type: 'string' },
206
+ },
207
+ allowedClients: {
208
+ type: 'array',
209
+ items: { type: 'string' },
210
+ },
211
+ },
212
+ required: ['discoveryUrl'],
170
213
  },
171
214
  },
172
215
  },