@aws/nx-plugin 1.0.0-rc.68 → 1.0.0-rc.69
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/README.md +1 -0
- package/generators.json +7 -0
- package/migrations.json +1 -1
- package/package.json +1 -1
- package/src/agentcore-harness/__snapshots__/generator.spec.ts.snap +238 -0
- package/src/agentcore-harness/files/project/README.md.template +83 -0
- package/src/agentcore-harness/files/project/scripts/chat.ts.template +99 -0
- package/src/agentcore-harness/files/project/src/PROMPT.md.template +16 -0
- package/src/agentcore-harness/generator.d.ts +62 -0
- package/src/agentcore-harness/generator.js +158 -0
- package/src/agentcore-harness/generator.js.map +1 -0
- package/src/agentcore-harness/schema.d.js +8 -0
- package/src/agentcore-harness/schema.d.js.map +1 -0
- package/src/agentcore-harness/schema.d.ts +17 -0
- package/src/agentcore-harness/schema.json +53 -0
- package/src/preset/__snapshots__/generator.spec.ts.snap +2 -0
- package/src/utils/agent-core-constructs/agent-core-constructs.d.ts +25 -0
- package/src/utils/agent-core-constructs/agent-core-constructs.js +59 -0
- package/src/utils/agent-core-constructs/agent-core-constructs.js.map +1 -1
- package/src/utils/agent-core-constructs/files/cdk/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.ts.template +366 -0
- package/src/utils/agent-core-constructs/files/terraform/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.tf.template +282 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as url from 'node:url';
|
|
4
|
+
import { CfnOutput, Lazy, Names, Stack } from 'aws-cdk-lib';
|
|
5
|
+
import * as agentcore from 'aws-cdk-lib/aws-bedrockagentcore';
|
|
6
|
+
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
|
7
|
+
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
8
|
+
import { Construct, IConstruct } from 'constructs';
|
|
9
|
+
import { suppressRules } from '../../../core/checkov<% if (esm) { %>.js<% } %>';
|
|
10
|
+
import { RuntimeConfig } from '../../../core/runtime-config<% if (esm) { %>.js<% } %>';
|
|
11
|
+
import { findWorkspaceRoot } from '../../../core/workspace<% if (esm) { %>.js<% } %>';
|
|
12
|
+
|
|
13
|
+
// Checkov rules flagging IAM statements whose resource cannot be scoped.
|
|
14
|
+
const WILDCARD_IAM_RULES = ['CKV_AWS_107', 'CKV_AWS_111', 'CKV_AWS_356'];
|
|
15
|
+
|
|
16
|
+
// Read at synth time, anchored on the workspace root so it still resolves once
|
|
17
|
+
// this file compiles to dist/.
|
|
18
|
+
const systemPrompt = fs.readFileSync(
|
|
19
|
+
path.join(
|
|
20
|
+
findWorkspaceRoot(url.fileURLToPath(new URL(<% if (esm) { %>import.meta.url<% } else { %>require('url').pathToFileURL(__filename).href<% } %>))),
|
|
21
|
+
'<%- promptPathFromCdk %>',
|
|
22
|
+
),
|
|
23
|
+
'utf-8',
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Properties for the <%- nameClassName %> Harness. Any native Harness field
|
|
28
|
+
* supplied here takes precedence over the generated defaults.
|
|
29
|
+
*/
|
|
30
|
+
export interface <%- nameClassName %>Props
|
|
31
|
+
extends Partial<
|
|
32
|
+
Omit<agentcore.CfnHarnessProps, 'executionRoleArn' | 'allowedTools'>
|
|
33
|
+
> {
|
|
34
|
+
/** Existing execution role to use instead of the generated role. */
|
|
35
|
+
executionRole?: iam.IRole;
|
|
36
|
+
/** Model resources the generated execution role may invoke. */
|
|
37
|
+
modelResourceArns?: string[];
|
|
38
|
+
/** Tools the Harness may use. Deploys with none unless supplied here. */
|
|
39
|
+
allowedTools?: agentcore.CfnHarnessProps['allowedTools'];
|
|
40
|
+
/**
|
|
41
|
+
* Run the Harness in a VPC so it can reach private resources. Selects private
|
|
42
|
+
* subnets with egress unless `vpcSubnets` narrows it, and creates a security
|
|
43
|
+
* group unless `securityGroups` supplies them.
|
|
44
|
+
*/
|
|
45
|
+
vpc?: ec2.IVpc;
|
|
46
|
+
/** Subnets to place the Harness in. Requires `vpc`. */
|
|
47
|
+
vpcSubnets?: ec2.SubnetSelection;
|
|
48
|
+
/** Security groups for the Harness. Requires `vpc`. */
|
|
49
|
+
securityGroups?: ec2.ISecurityGroup[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A managed Amazon Bedrock AgentCore Harness, using IAM inbound auth. */
|
|
53
|
+
export class <%- nameClassName %>
|
|
54
|
+
extends Construct
|
|
55
|
+
implements iam.IGrantable, ec2.IConnectable
|
|
56
|
+
{
|
|
57
|
+
public readonly harness: agentcore.CfnHarness;
|
|
58
|
+
public readonly executionRole: iam.IRole;
|
|
59
|
+
private readonly _connections?: ec2.Connections;
|
|
60
|
+
|
|
61
|
+
constructor(scope: Construct, id: string, props?: <%- nameClassName %>Props) {
|
|
62
|
+
super(scope, id);
|
|
63
|
+
|
|
64
|
+
const stack = Stack.of(this);
|
|
65
|
+
const {
|
|
66
|
+
executionRole,
|
|
67
|
+
modelResourceArns = [
|
|
68
|
+
`arn:${stack.partition}:bedrock:*::foundation-model/*`,
|
|
69
|
+
`arn:${stack.partition}:bedrock:${stack.region}:${stack.account}:*`,
|
|
70
|
+
],
|
|
71
|
+
vpc,
|
|
72
|
+
vpcSubnets,
|
|
73
|
+
securityGroups,
|
|
74
|
+
...harnessProps
|
|
75
|
+
} = props ?? {};
|
|
76
|
+
|
|
77
|
+
if (!vpc && (vpcSubnets || securityGroups)) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
'vpcSubnets and securityGroups require vpc to be supplied.',
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (vpc) {
|
|
84
|
+
this._connections = new ec2.Connections({
|
|
85
|
+
securityGroups: securityGroups ?? [
|
|
86
|
+
new ec2.SecurityGroup(this, 'SecurityGroup', {
|
|
87
|
+
vpc,
|
|
88
|
+
description: `Harness ${id}`,
|
|
89
|
+
}),
|
|
90
|
+
],
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Harness names allow ASCII letters, digits and underscores, must start
|
|
95
|
+
// with a letter, and are at most 40 characters.
|
|
96
|
+
const uniqueName = Names.uniqueResourceName(this, {
|
|
97
|
+
maxLength: 39,
|
|
98
|
+
separator: '_',
|
|
99
|
+
allowedSpecialCharacters: '_',
|
|
100
|
+
});
|
|
101
|
+
const harnessName =
|
|
102
|
+
harnessProps.harnessName ??
|
|
103
|
+
(/^[A-Za-z]/.test(uniqueName) ? uniqueName : `H${uniqueName}`);
|
|
104
|
+
|
|
105
|
+
this.executionRole =
|
|
106
|
+
executionRole ??
|
|
107
|
+
new iam.Role(this, 'ExecutionRole', {
|
|
108
|
+
assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com', {
|
|
109
|
+
conditions: {
|
|
110
|
+
StringEquals: { 'aws:SourceAccount': stack.account },
|
|
111
|
+
ArnLike: {
|
|
112
|
+
'aws:SourceArn': `arn:${stack.partition}:bedrock-agentcore:${stack.region}:${stack.account}:*`,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (!executionRole) {
|
|
119
|
+
// Baseline permissions from the AgentCore Harness security guidance:
|
|
120
|
+
// https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness-security.html
|
|
121
|
+
[
|
|
122
|
+
new iam.PolicyStatement({
|
|
123
|
+
sid: 'BedrockModelInvocation',
|
|
124
|
+
actions: [
|
|
125
|
+
'bedrock:InvokeModel',
|
|
126
|
+
'bedrock:InvokeModelWithResponseStream',
|
|
127
|
+
],
|
|
128
|
+
resources: modelResourceArns,
|
|
129
|
+
}),
|
|
130
|
+
new iam.PolicyStatement({
|
|
131
|
+
sid: 'EcrPublicTokenAccess',
|
|
132
|
+
actions: ['ecr-public:GetAuthorizationToken'],
|
|
133
|
+
resources: ['*'],
|
|
134
|
+
}),
|
|
135
|
+
new iam.PolicyStatement({
|
|
136
|
+
sid: 'StsForEcrPublicPull',
|
|
137
|
+
actions: ['sts:GetServiceBearerToken'],
|
|
138
|
+
resources: ['*'],
|
|
139
|
+
}),
|
|
140
|
+
new iam.PolicyStatement({
|
|
141
|
+
sid: 'XRayTracingAccess',
|
|
142
|
+
actions: [
|
|
143
|
+
'xray:PutTraceSegments',
|
|
144
|
+
'xray:PutTelemetryRecords',
|
|
145
|
+
'xray:GetSamplingRules',
|
|
146
|
+
'xray:GetSamplingTargets',
|
|
147
|
+
],
|
|
148
|
+
resources: ['*'],
|
|
149
|
+
}),
|
|
150
|
+
new iam.PolicyStatement({
|
|
151
|
+
sid: 'CloudWatchLogsGroup',
|
|
152
|
+
actions: ['logs:CreateLogGroup', 'logs:DescribeLogStreams'],
|
|
153
|
+
resources: [
|
|
154
|
+
`arn:${stack.partition}:logs:${stack.region}:${stack.account}:log-group:/aws/bedrock-agentcore/runtimes/*`,
|
|
155
|
+
],
|
|
156
|
+
}),
|
|
157
|
+
new iam.PolicyStatement({
|
|
158
|
+
sid: 'CloudWatchLogsDescribeGroups',
|
|
159
|
+
actions: ['logs:DescribeLogGroups'],
|
|
160
|
+
resources: [
|
|
161
|
+
`arn:${stack.partition}:logs:${stack.region}:${stack.account}:log-group:*`,
|
|
162
|
+
],
|
|
163
|
+
}),
|
|
164
|
+
new iam.PolicyStatement({
|
|
165
|
+
sid: 'CloudWatchLogsStream',
|
|
166
|
+
actions: ['logs:CreateLogStream', 'logs:PutLogEvents'],
|
|
167
|
+
resources: [
|
|
168
|
+
`arn:${stack.partition}:logs:${stack.region}:${stack.account}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*`,
|
|
169
|
+
],
|
|
170
|
+
}),
|
|
171
|
+
new iam.PolicyStatement({
|
|
172
|
+
sid: 'CloudWatchMetricsPublish',
|
|
173
|
+
actions: ['cloudwatch:PutMetricData'],
|
|
174
|
+
resources: ['*'],
|
|
175
|
+
conditions: {
|
|
176
|
+
StringEquals: { 'cloudwatch:namespace': 'bedrock-agentcore' },
|
|
177
|
+
},
|
|
178
|
+
}),
|
|
179
|
+
new iam.PolicyStatement({
|
|
180
|
+
sid: 'AgentCoreWorkloadIdentity',
|
|
181
|
+
actions: [
|
|
182
|
+
'bedrock-agentcore:GetWorkloadAccessToken',
|
|
183
|
+
'bedrock-agentcore:GetWorkloadAccessTokenForJWT',
|
|
184
|
+
],
|
|
185
|
+
resources: [
|
|
186
|
+
`arn:${stack.partition}:bedrock-agentcore:${stack.region}:${stack.account}:workload-identity-directory/default`,
|
|
187
|
+
`arn:${stack.partition}:bedrock-agentcore:${stack.region}:${stack.account}:workload-identity-directory/default/workload-identity/harness_${harnessName}-*`,
|
|
188
|
+
],
|
|
189
|
+
}),
|
|
190
|
+
new iam.PolicyStatement({
|
|
191
|
+
sid: 'AgentCoreBrowserDefault',
|
|
192
|
+
actions: [
|
|
193
|
+
'bedrock-agentcore:StartBrowserSession',
|
|
194
|
+
'bedrock-agentcore:StopBrowserSession',
|
|
195
|
+
'bedrock-agentcore:GetBrowserSession',
|
|
196
|
+
'bedrock-agentcore:ListBrowserSessions',
|
|
197
|
+
'bedrock-agentcore:UpdateBrowserStream',
|
|
198
|
+
'bedrock-agentcore:ConnectBrowserAutomationStream',
|
|
199
|
+
'bedrock-agentcore:ConnectBrowserLiveViewStream',
|
|
200
|
+
],
|
|
201
|
+
resources: [
|
|
202
|
+
`arn:${stack.partition}:bedrock-agentcore:${stack.region}:aws:browser/*`,
|
|
203
|
+
],
|
|
204
|
+
}),
|
|
205
|
+
new iam.PolicyStatement({
|
|
206
|
+
sid: 'AgentCoreCodeInterpreterDefault',
|
|
207
|
+
actions: [
|
|
208
|
+
'bedrock-agentcore:StartCodeInterpreterSession',
|
|
209
|
+
'bedrock-agentcore:StopCodeInterpreterSession',
|
|
210
|
+
'bedrock-agentcore:GetCodeInterpreterSession',
|
|
211
|
+
'bedrock-agentcore:ListCodeInterpreterSessions',
|
|
212
|
+
'bedrock-agentcore:InvokeCodeInterpreter',
|
|
213
|
+
],
|
|
214
|
+
resources: [
|
|
215
|
+
`arn:${stack.partition}:bedrock-agentcore:${stack.region}:aws:code-interpreter/*`,
|
|
216
|
+
],
|
|
217
|
+
}),
|
|
218
|
+
].forEach((statement) =>
|
|
219
|
+
this.executionRole.addToPrincipalPolicy(statement),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// These statements take no resource-level permission.
|
|
223
|
+
const isPolicy = (c: IConstruct) => c instanceof iam.Policy;
|
|
224
|
+
suppressRules(
|
|
225
|
+
this.executionRole,
|
|
226
|
+
WILDCARD_IAM_RULES,
|
|
227
|
+
'EcrPublicTokenAccess: ecr-public:GetAuthorizationToken has no resource-level permission.',
|
|
228
|
+
isPolicy,
|
|
229
|
+
);
|
|
230
|
+
suppressRules(
|
|
231
|
+
this.executionRole,
|
|
232
|
+
WILDCARD_IAM_RULES,
|
|
233
|
+
'StsForEcrPublicPull: sts:GetServiceBearerToken has no resource-level permission.',
|
|
234
|
+
isPolicy,
|
|
235
|
+
);
|
|
236
|
+
suppressRules(
|
|
237
|
+
this.executionRole,
|
|
238
|
+
WILDCARD_IAM_RULES,
|
|
239
|
+
'XRayTracingAccess: X-Ray segment and sampling APIs have no resource-level permission.',
|
|
240
|
+
isPolicy,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Rendered lazily so security groups added through `connections` after
|
|
245
|
+
// construction are still picked up.
|
|
246
|
+
const environment: agentcore.CfnHarnessProps['environment'] | undefined = vpc
|
|
247
|
+
? Lazy.any({
|
|
248
|
+
produce: () => ({
|
|
249
|
+
...(harnessProps.environment as
|
|
250
|
+
| agentcore.CfnHarness.HarnessEnvironmentProviderProperty
|
|
251
|
+
| undefined),
|
|
252
|
+
agentCoreRuntimeEnvironment: {
|
|
253
|
+
...(
|
|
254
|
+
harnessProps.environment as
|
|
255
|
+
| agentcore.CfnHarness.HarnessEnvironmentProviderProperty
|
|
256
|
+
| undefined
|
|
257
|
+
)?.agentCoreRuntimeEnvironment,
|
|
258
|
+
networkConfiguration: {
|
|
259
|
+
networkMode: 'VPC',
|
|
260
|
+
networkModeConfig: {
|
|
261
|
+
subnets: vpc.selectSubnets(
|
|
262
|
+
vpcSubnets ?? {
|
|
263
|
+
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
|
|
264
|
+
},
|
|
265
|
+
).subnetIds,
|
|
266
|
+
securityGroups: this._connections!.securityGroups.map(
|
|
267
|
+
(group) => group.securityGroupId,
|
|
268
|
+
),
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
}),
|
|
273
|
+
})
|
|
274
|
+
: harnessProps.environment;
|
|
275
|
+
|
|
276
|
+
this.harness = new agentcore.CfnHarness(this, 'Harness', {
|
|
277
|
+
model: {
|
|
278
|
+
bedrockModelConfig: {
|
|
279
|
+
modelId: 'global.anthropic.claude-sonnet-4-6',
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
systemPrompt: [{ text: systemPrompt }],
|
|
283
|
+
...harnessProps,
|
|
284
|
+
// Set after harnessProps so the deployed name always matches the
|
|
285
|
+
// execution role's workload-identity resource pattern.
|
|
286
|
+
harnessName,
|
|
287
|
+
executionRoleArn: this.executionRole.roleArn,
|
|
288
|
+
// Carries the VPC network configuration when `vpc` is supplied, and is
|
|
289
|
+
// the caller's own `environment` otherwise.
|
|
290
|
+
environment,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// The Harness provisions managed memory unless `memory` is supplied, and
|
|
294
|
+
// the service names it, so the grant scopes to the resulting ARN.
|
|
295
|
+
if (!executionRole && harnessProps.memory === undefined) {
|
|
296
|
+
this.executionRole.addToPrincipalPolicy(
|
|
297
|
+
new iam.PolicyStatement({
|
|
298
|
+
sid: 'AgentCoreManagedMemory',
|
|
299
|
+
actions: [
|
|
300
|
+
'bedrock-agentcore:CreateEvent',
|
|
301
|
+
'bedrock-agentcore:DeleteEvent',
|
|
302
|
+
'bedrock-agentcore:GetEvent',
|
|
303
|
+
'bedrock-agentcore:ListEvents',
|
|
304
|
+
'bedrock-agentcore:RetrieveMemoryRecords',
|
|
305
|
+
],
|
|
306
|
+
resources: [
|
|
307
|
+
this.harness.attrMemoryManagedMemoryConfigurationArn,
|
|
308
|
+
],
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const rc = RuntimeConfig.ensure(this);
|
|
314
|
+
rc.set('agentcore', 'harnesses', {
|
|
315
|
+
...rc.get('agentcore').harnesses,
|
|
316
|
+
<%- nameClassName %>: this.harness.attrArn,
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Materialises the AppConfig resources so the entry above is deployed and
|
|
320
|
+
// the chat script's RUNTIME_CONFIG_APP_ID lookup resolves.
|
|
321
|
+
void rc.appConfigApplicationId;
|
|
322
|
+
|
|
323
|
+
new CfnOutput(this, 'HarnessArn', { value: this.harness.attrArn });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
public get grantPrincipal(): iam.IPrincipal {
|
|
327
|
+
return this.executionRole.grantPrincipal;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Network connections for this Harness, for granting access to resources such
|
|
332
|
+
* as a database. Only available when the Harness runs in a VPC.
|
|
333
|
+
*/
|
|
334
|
+
public get connections(): ec2.Connections {
|
|
335
|
+
if (!this._connections) {
|
|
336
|
+
throw new Error(
|
|
337
|
+
'Connections are only available when the Harness runs in a VPC. Supply the vpc prop.',
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
return this._connections;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** The deployed Harness ARN. */
|
|
344
|
+
public get harnessArn(): string {
|
|
345
|
+
return this.harness.attrArn;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Add permissions required by tools, skills, memory, or custom models. */
|
|
349
|
+
public addToRolePolicy(
|
|
350
|
+
statement: iam.PolicyStatement,
|
|
351
|
+
): iam.AddToPrincipalPolicyResult {
|
|
352
|
+
return this.executionRole.addToPrincipalPolicy(statement);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Grant an IAM principal permission to invoke this Harness. */
|
|
356
|
+
public grantInvokeAccess(grantee: iam.IGrantable): iam.Grant {
|
|
357
|
+
return iam.Grant.addToPrincipal({
|
|
358
|
+
grantee,
|
|
359
|
+
actions: [
|
|
360
|
+
'bedrock-agentcore:InvokeHarness',
|
|
361
|
+
'bedrock-agentcore:InvokeAgentRuntime',
|
|
362
|
+
],
|
|
363
|
+
resourceArns: [this.harness.attrArn],
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
terraform {
|
|
2
|
+
required_version = ">= 1.0"
|
|
3
|
+
|
|
4
|
+
required_providers {
|
|
5
|
+
aws = {
|
|
6
|
+
source = "hashicorp/aws"
|
|
7
|
+
version = "<%- awsProviderVersion %>"
|
|
8
|
+
}
|
|
9
|
+
random = {
|
|
10
|
+
source = "hashicorp/random"
|
|
11
|
+
version = "<%- randomProviderVersion %>"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
variable "model_id" {
|
|
17
|
+
description = "Amazon Bedrock model or inference profile used by default."
|
|
18
|
+
type = string
|
|
19
|
+
default = "global.anthropic.claude-sonnet-4-6"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
variable "model_resource_arns" {
|
|
23
|
+
description = "Bedrock model and inference-profile ARNs the execution role may invoke. Defaults to every foundation model plus this account's Bedrock resources in the deployment region; replace with narrower ARNs to restrict baseline model access."
|
|
24
|
+
type = set(string)
|
|
25
|
+
default = null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
variable "additional_execution_role_policy_statements" {
|
|
29
|
+
description = "Additional least-privilege IAM policy statements required by configured optional capabilities (e.g. customer-owned Gateways, memory, custom browsers or code interpreters, skills, secrets). Sid and Condition are optional; null fields are omitted from the rendered policy."
|
|
30
|
+
type = list(object({
|
|
31
|
+
Sid = optional(string)
|
|
32
|
+
Effect = string
|
|
33
|
+
Action = list(string)
|
|
34
|
+
Resource = list(string)
|
|
35
|
+
Condition = optional(any)
|
|
36
|
+
}))
|
|
37
|
+
default = []
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
data "aws_caller_identity" "current" {}
|
|
41
|
+
data "aws_partition" "current" {}
|
|
42
|
+
data "aws_region" "current" {}
|
|
43
|
+
|
|
44
|
+
resource "random_id" "unique_suffix" {
|
|
45
|
+
byte_length = 4
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
locals {
|
|
49
|
+
account_id = data.aws_caller_identity.current.account_id
|
|
50
|
+
partition = data.aws_partition.current.partition
|
|
51
|
+
region = data.aws_region.current.region
|
|
52
|
+
|
|
53
|
+
# Capped at 31 characters so the suffix keeps the deployed Harness name
|
|
54
|
+
# within its 40-character limit.
|
|
55
|
+
harness_name_prefix = "<%- harnessNamePrefix %>"
|
|
56
|
+
harness_name = "${local.harness_name_prefix}_${random_id.unique_suffix.hex}"
|
|
57
|
+
|
|
58
|
+
# Set var.model_resource_arns to narrow the default model access.
|
|
59
|
+
model_resource_arns = var.model_resource_arns != null ? var.model_resource_arns : [
|
|
60
|
+
"arn:${local.partition}:bedrock:*::foundation-model/*",
|
|
61
|
+
"arn:${local.partition}:bedrock:${local.region}:${local.account_id}:*",
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
resource "aws_iam_role" "execution_role" {
|
|
66
|
+
name = "${local.harness_name_prefix}-HarnessRole-${random_id.unique_suffix.hex}"
|
|
67
|
+
|
|
68
|
+
assume_role_policy = jsonencode({
|
|
69
|
+
Version = "2012-10-17"
|
|
70
|
+
Statement = [{
|
|
71
|
+
Effect = "Allow"
|
|
72
|
+
Principal = {
|
|
73
|
+
Service = "bedrock-agentcore.amazonaws.com"
|
|
74
|
+
}
|
|
75
|
+
Action = "sts:AssumeRole"
|
|
76
|
+
Condition = {
|
|
77
|
+
StringEquals = {
|
|
78
|
+
"aws:SourceAccount" = local.account_id
|
|
79
|
+
}
|
|
80
|
+
ArnLike = {
|
|
81
|
+
"aws:SourceArn" = "arn:${local.partition}:bedrock-agentcore:${local.region}:${local.account_id}:*"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}]
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Baseline permissions per the AgentCore Harness security guidance; extend for
|
|
89
|
+
# customer-owned resources via var.additional_execution_role_policy_statements.
|
|
90
|
+
# https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness-security.html
|
|
91
|
+
resource "aws_iam_role_policy" "execution_role" {
|
|
92
|
+
name = "${local.harness_name_prefix}-HarnessPolicy"
|
|
93
|
+
role = aws_iam_role.execution_role.id
|
|
94
|
+
|
|
95
|
+
policy = jsonencode({
|
|
96
|
+
Version = "2012-10-17"
|
|
97
|
+
Statement = concat([
|
|
98
|
+
{
|
|
99
|
+
Sid = "BedrockModelInvocation"
|
|
100
|
+
Effect = "Allow"
|
|
101
|
+
Action = [
|
|
102
|
+
"bedrock:InvokeModel",
|
|
103
|
+
"bedrock:InvokeModelWithResponseStream",
|
|
104
|
+
]
|
|
105
|
+
Resource = local.model_resource_arns
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
#checkov:skip=CKV_AWS_355:EcrPublicTokenAccess requires a wildcard resource; ecr-public:GetAuthorizationToken has no resource-level permission
|
|
109
|
+
Sid = "EcrPublicTokenAccess"
|
|
110
|
+
Effect = "Allow"
|
|
111
|
+
Action = ["ecr-public:GetAuthorizationToken"]
|
|
112
|
+
Resource = ["*"]
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
#checkov:skip=CKV_AWS_355:StsForEcrPublicPull requires a wildcard resource; sts:GetServiceBearerToken has no resource-level permission
|
|
116
|
+
Sid = "StsForEcrPublicPull"
|
|
117
|
+
Effect = "Allow"
|
|
118
|
+
Action = ["sts:GetServiceBearerToken"]
|
|
119
|
+
Resource = ["*"]
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
#checkov:skip=CKV_AWS_355:XRayTracingAccess requires a wildcard resource; the X-Ray segment and sampling APIs have no resource-level permission
|
|
123
|
+
#checkov:skip=CKV_AWS_290:XRayTracingAccess requires a wildcard resource; the X-Ray segment and sampling APIs have no resource-level permission
|
|
124
|
+
Sid = "XRayTracingAccess"
|
|
125
|
+
Effect = "Allow"
|
|
126
|
+
Action = [
|
|
127
|
+
"xray:PutTraceSegments",
|
|
128
|
+
"xray:PutTelemetryRecords",
|
|
129
|
+
"xray:GetSamplingRules",
|
|
130
|
+
"xray:GetSamplingTargets",
|
|
131
|
+
]
|
|
132
|
+
Resource = ["*"]
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
Sid = "CloudWatchLogsGroup"
|
|
136
|
+
Effect = "Allow"
|
|
137
|
+
Action = [
|
|
138
|
+
"logs:CreateLogGroup",
|
|
139
|
+
"logs:DescribeLogStreams",
|
|
140
|
+
]
|
|
141
|
+
Resource = [
|
|
142
|
+
"arn:${local.partition}:logs:${local.region}:${local.account_id}:log-group:/aws/bedrock-agentcore/runtimes/*",
|
|
143
|
+
]
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
Sid = "CloudWatchLogsDescribeGroups"
|
|
147
|
+
Effect = "Allow"
|
|
148
|
+
Action = ["logs:DescribeLogGroups"]
|
|
149
|
+
Resource = ["arn:${local.partition}:logs:${local.region}:${local.account_id}:log-group:*"]
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
Sid = "CloudWatchLogsStream"
|
|
153
|
+
Effect = "Allow"
|
|
154
|
+
Action = [
|
|
155
|
+
"logs:CreateLogStream",
|
|
156
|
+
"logs:PutLogEvents",
|
|
157
|
+
]
|
|
158
|
+
Resource = [
|
|
159
|
+
"arn:${local.partition}:logs:${local.region}:${local.account_id}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*",
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
#checkov:skip=CKV_AWS_355:CloudWatchMetricsPublish requires a wildcard resource; cloudwatch:PutMetricData is scoped by the namespace condition instead
|
|
164
|
+
#checkov:skip=CKV_AWS_290:CloudWatchMetricsPublish requires a wildcard resource; cloudwatch:PutMetricData is scoped by the namespace condition instead
|
|
165
|
+
Sid = "CloudWatchMetricsPublish"
|
|
166
|
+
Effect = "Allow"
|
|
167
|
+
Action = ["cloudwatch:PutMetricData"]
|
|
168
|
+
Resource = ["*"]
|
|
169
|
+
Condition = {
|
|
170
|
+
StringEquals = {
|
|
171
|
+
"cloudwatch:namespace" = "bedrock-agentcore"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
Sid = "AgentCoreWorkloadIdentity"
|
|
177
|
+
Effect = "Allow"
|
|
178
|
+
Action = [
|
|
179
|
+
"bedrock-agentcore:GetWorkloadAccessToken",
|
|
180
|
+
"bedrock-agentcore:GetWorkloadAccessTokenForJWT",
|
|
181
|
+
]
|
|
182
|
+
Resource = [
|
|
183
|
+
"arn:${local.partition}:bedrock-agentcore:${local.region}:${local.account_id}:workload-identity-directory/default",
|
|
184
|
+
"arn:${local.partition}:bedrock-agentcore:${local.region}:${local.account_id}:workload-identity-directory/default/workload-identity/harness_${local.harness_name}-*",
|
|
185
|
+
]
|
|
186
|
+
},
|
|
187
|
+
# The service names the managed memory from the harness name plus a
|
|
188
|
+
# generated suffix, so this scopes to that prefix.
|
|
189
|
+
{
|
|
190
|
+
Sid = "AgentCoreManagedMemory"
|
|
191
|
+
Effect = "Allow"
|
|
192
|
+
Action = [
|
|
193
|
+
"bedrock-agentcore:CreateEvent",
|
|
194
|
+
"bedrock-agentcore:DeleteEvent",
|
|
195
|
+
"bedrock-agentcore:GetEvent",
|
|
196
|
+
"bedrock-agentcore:ListEvents",
|
|
197
|
+
"bedrock-agentcore:RetrieveMemoryRecords",
|
|
198
|
+
]
|
|
199
|
+
Resource = [
|
|
200
|
+
"arn:${local.partition}:bedrock-agentcore:${local.region}:${local.account_id}:memory/${local.harness_name}-*",
|
|
201
|
+
]
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
Sid = "AgentCoreBrowserDefault"
|
|
205
|
+
Effect = "Allow"
|
|
206
|
+
Action = [
|
|
207
|
+
"bedrock-agentcore:StartBrowserSession",
|
|
208
|
+
"bedrock-agentcore:StopBrowserSession",
|
|
209
|
+
"bedrock-agentcore:GetBrowserSession",
|
|
210
|
+
"bedrock-agentcore:ListBrowserSessions",
|
|
211
|
+
"bedrock-agentcore:UpdateBrowserStream",
|
|
212
|
+
"bedrock-agentcore:ConnectBrowserAutomationStream",
|
|
213
|
+
"bedrock-agentcore:ConnectBrowserLiveViewStream",
|
|
214
|
+
]
|
|
215
|
+
Resource = [
|
|
216
|
+
"arn:${local.partition}:bedrock-agentcore:${local.region}:aws:browser/*",
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
Sid = "AgentCoreCodeInterpreterDefault"
|
|
221
|
+
Effect = "Allow"
|
|
222
|
+
Action = [
|
|
223
|
+
"bedrock-agentcore:StartCodeInterpreterSession",
|
|
224
|
+
"bedrock-agentcore:StopCodeInterpreterSession",
|
|
225
|
+
"bedrock-agentcore:GetCodeInterpreterSession",
|
|
226
|
+
"bedrock-agentcore:ListCodeInterpreterSessions",
|
|
227
|
+
"bedrock-agentcore:InvokeCodeInterpreter",
|
|
228
|
+
]
|
|
229
|
+
Resource = [
|
|
230
|
+
"arn:${local.partition}:bedrock-agentcore:${local.region}:aws:code-interpreter/*",
|
|
231
|
+
]
|
|
232
|
+
},
|
|
233
|
+
], [
|
|
234
|
+
# Null Sid/Condition fields are dropped from the rendered policy.
|
|
235
|
+
for statement in var.additional_execution_role_policy_statements :
|
|
236
|
+
{ for key, value in statement : key => value if value != null }
|
|
237
|
+
])
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
# Omitting authorizer_configuration uses IAM inbound authorization; add a
|
|
242
|
+
# custom_jwt_authorizer block to change that.
|
|
243
|
+
resource "aws_bedrockagentcore_harness" "this" {
|
|
244
|
+
harness_name = local.harness_name
|
|
245
|
+
execution_role_arn = aws_iam_role.execution_role.arn
|
|
246
|
+
|
|
247
|
+
model {
|
|
248
|
+
bedrock_model_config {
|
|
249
|
+
model_id = var.model_id
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# Read at plan time; the walk up from path.module reaches the workspace root.
|
|
254
|
+
system_prompt {
|
|
255
|
+
text = file("${path.module}/../../../../../../../<%- promptPathFromTerraform %>")
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
depends_on = [aws_iam_role_policy.execution_role]
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
module "add_harness_arn_to_runtime_config" {
|
|
262
|
+
source = "../../../core/runtime-config/entry"
|
|
263
|
+
|
|
264
|
+
namespace = "agentcore"
|
|
265
|
+
key = "harnesses"
|
|
266
|
+
value = { "<%- nameClassName %>" = aws_bedrockagentcore_harness.this.arn }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
output "harness_id" {
|
|
270
|
+
description = "ID of the Amazon Bedrock AgentCore Harness"
|
|
271
|
+
value = aws_bedrockagentcore_harness.this.harness_id
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
output "harness_arn" {
|
|
275
|
+
description = "ARN of the Amazon Bedrock AgentCore Harness"
|
|
276
|
+
value = aws_bedrockagentcore_harness.this.arn
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
output "execution_role_arn" {
|
|
280
|
+
description = "ARN of the IAM role assumed by the Harness"
|
|
281
|
+
value = aws_iam_role.execution_role.arn
|
|
282
|
+
}
|