thinkwork-cli 0.9.0 → 0.9.1
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/LICENSE +202 -0
- package/README.md +2 -2
- package/dist/cli.js +1187 -315
- package/dist/terraform/examples/greenfield/main.tf +325 -19
- package/dist/terraform/examples/greenfield/terraform.tfvars.example +14 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base +61 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/README.md +54 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +197 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/scripts/build_and_push_sandbox_base.sh +70 -0
- package/dist/terraform/modules/app/agentcore-flue/README.md +58 -0
- package/dist/terraform/modules/app/agentcore-flue/main.tf +322 -0
- package/dist/terraform/modules/app/agentcore-flue/outputs.tf +23 -0
- package/dist/terraform/modules/app/agentcore-flue/variables.tf +91 -0
- package/dist/terraform/modules/app/agentcore-memory/scripts/create_or_find_memory.sh +0 -0
- package/dist/terraform/modules/app/agentcore-runtime/main.tf +165 -0
- package/dist/terraform/modules/app/appsync-subscriptions/main.tf +4 -0
- package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +5 -0
- package/dist/terraform/modules/app/computer-runtime/README.md +15 -0
- package/dist/terraform/modules/app/computer-runtime/main.tf +406 -0
- package/dist/terraform/modules/app/computer-runtime/outputs.tf +75 -0
- package/dist/terraform/modules/app/computer-runtime/variables.tf +66 -0
- package/dist/terraform/modules/app/hindsight-memory/main.tf +6 -0
- package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +128 -0
- package/dist/terraform/modules/app/lambda-api/handlers.tf +1454 -43
- package/dist/terraform/modules/app/lambda-api/main.tf +221 -12
- package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +118 -0
- package/dist/terraform/modules/app/lambda-api/oauth-secrets.tf +49 -0
- package/dist/terraform/modules/app/lambda-api/outputs.tf +38 -0
- package/dist/terraform/modules/app/lambda-api/slack-app-secrets.tf +43 -0
- package/dist/terraform/modules/app/lambda-api/stripe-secrets.tf +53 -0
- package/dist/terraform/modules/app/lambda-api/variables.tf +349 -2
- package/dist/terraform/modules/app/lambda-api/workspace-events.tf +125 -0
- package/dist/terraform/modules/app/routines-stepfunctions/main.tf +453 -0
- package/dist/terraform/modules/app/sandbox-log-scrubber/README.md +66 -0
- package/dist/terraform/modules/app/sandbox-log-scrubber/main.tf +200 -0
- package/dist/terraform/modules/app/static-site/main.tf +146 -5
- package/dist/terraform/modules/app/www-dns/main.tf +118 -15
- package/dist/terraform/modules/app/www-dns/outputs.tf +10 -0
- package/dist/terraform/modules/app/www-dns/variables.tf +42 -0
- package/dist/terraform/modules/data/aurora-postgres/main.tf +164 -3
- package/dist/terraform/modules/data/aurora-postgres/outputs.tf +34 -0
- package/dist/terraform/modules/data/aurora-postgres/variables.tf +16 -0
- package/dist/terraform/modules/data/compliance-audit-bucket/README.md +145 -0
- package/dist/terraform/modules/data/compliance-audit-bucket/main.tf +573 -0
- package/dist/terraform/modules/data/compliance-audit-bucket/outputs.tf +43 -0
- package/dist/terraform/modules/data/compliance-audit-bucket/variables.tf +93 -0
- package/dist/terraform/modules/data/compliance-exports-bucket/main.tf +269 -0
- package/dist/terraform/modules/data/compliance-exports-bucket/outputs.tf +23 -0
- package/dist/terraform/modules/data/compliance-exports-bucket/variables.tf +50 -0
- package/dist/terraform/modules/data/s3-backups-bucket/main.tf +123 -0
- package/dist/terraform/modules/data/s3-buckets/main.tf +13 -0
- package/dist/terraform/modules/foundation/cognito/variables.tf +2 -2
- package/dist/terraform/modules/thinkwork/main.tf +439 -21
- package/dist/terraform/modules/thinkwork/outputs.tf +121 -0
- package/dist/terraform/modules/thinkwork/variables.tf +153 -2
- package/dist/terraform/schema.graphql +17 -0
- package/package.json +15 -14
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
################################################################################
|
|
2
|
+
# Routines Step Functions — App Module (stage-level)
|
|
3
|
+
#
|
|
4
|
+
# Stage-scoped substrate for the Step Functions Routines runtime:
|
|
5
|
+
# * Execution role (one per stage, ABAC-tenant-tagged, used by ALL routines).
|
|
6
|
+
# * CloudWatch log group for state machine execution histories.
|
|
7
|
+
# * S3 bucket for python() recipe output offload (mandatory due to the
|
|
8
|
+
# 256KB ASL state-payload + 25K-event execution-history caps).
|
|
9
|
+
#
|
|
10
|
+
# **State machines themselves are NOT created here.** Each Routine
|
|
11
|
+
# provisions its own state machine via the createRoutine GraphQL
|
|
12
|
+
# resolver (Phase B U7) using the role + log group exported by this
|
|
13
|
+
# module. This mirrors the agentcore-code-interpreter pattern: stage
|
|
14
|
+
# substrate in Terraform, per-resource fan-out at runtime.
|
|
15
|
+
#
|
|
16
|
+
# **Tenant isolation via ABAC.** The execution role's inline policies use
|
|
17
|
+
# tag-condition matching (aws:PrincipalTag/tenantId vs.
|
|
18
|
+
# aws:ResourceTag/tenantId). Each state machine is created with the
|
|
19
|
+
# tenantId/agentId/routineId tags so cross-tenant invocations fail at
|
|
20
|
+
# the IAM layer.
|
|
21
|
+
################################################################################
|
|
22
|
+
|
|
23
|
+
terraform {
|
|
24
|
+
required_providers {
|
|
25
|
+
aws = {
|
|
26
|
+
source = "hashicorp/aws"
|
|
27
|
+
version = ">= 5.0"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
variable "stage" {
|
|
33
|
+
description = "Deployment stage (dev, prod, etc.)."
|
|
34
|
+
type = string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
variable "region" {
|
|
38
|
+
description = "AWS region."
|
|
39
|
+
type = string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
variable "account_id" {
|
|
43
|
+
description = "AWS account ID (used to construct IAM resource ARNs)."
|
|
44
|
+
type = string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
variable "log_retention_days" {
|
|
48
|
+
description = "CloudWatch log retention for routine state machine executions."
|
|
49
|
+
type = number
|
|
50
|
+
default = 30
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
variable "execution_callback_lambda_arn" {
|
|
54
|
+
description = "ARN of the routine-execution-callback Lambda. EventBridge sends SFN execution-state-change events here so routine_executions lifecycle status mirrors the SFN-side reality. When empty, the EventBridge rule is not provisioned (Phase B U9 hasn't deployed yet)."
|
|
55
|
+
type = string
|
|
56
|
+
default = ""
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
locals {
|
|
60
|
+
log_group_name = "/aws/vendedlogs/states/thinkwork-${var.stage}-routines"
|
|
61
|
+
output_bucket = "thinkwork-${var.stage}-routine-output"
|
|
62
|
+
role_name = "thinkwork-${var.stage}-routines-execution-role"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
################################################################################
|
|
66
|
+
# CloudWatch Log Group
|
|
67
|
+
#
|
|
68
|
+
# Uses the /aws/vendedlogs/states/ prefix to dodge the resource-policy size
|
|
69
|
+
# cap when the tenant accumulates hundreds of state machines.
|
|
70
|
+
################################################################################
|
|
71
|
+
|
|
72
|
+
resource "aws_cloudwatch_log_group" "routines" {
|
|
73
|
+
name = local.log_group_name
|
|
74
|
+
retention_in_days = var.log_retention_days
|
|
75
|
+
|
|
76
|
+
tags = {
|
|
77
|
+
Name = local.log_group_name
|
|
78
|
+
Stage = var.stage
|
|
79
|
+
Purpose = "step-functions-routines"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
################################################################################
|
|
84
|
+
# S3 bucket — python() recipe output offload
|
|
85
|
+
#
|
|
86
|
+
# Path layout: s3://<bucket>/<tenantId>/<executionArn>/<nodeId>/{stdout,stderr}.log
|
|
87
|
+
# The python() Task wrapper writes here; the run-detail surface signs URLs
|
|
88
|
+
# server-side with tenant-scoped IAM and short expirations.
|
|
89
|
+
################################################################################
|
|
90
|
+
|
|
91
|
+
resource "aws_s3_bucket" "routine_output" {
|
|
92
|
+
bucket = local.output_bucket
|
|
93
|
+
|
|
94
|
+
tags = {
|
|
95
|
+
Name = local.output_bucket
|
|
96
|
+
Stage = var.stage
|
|
97
|
+
Purpose = "routine-python-output"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
resource "aws_s3_bucket_public_access_block" "routine_output" {
|
|
102
|
+
bucket = aws_s3_bucket.routine_output.id
|
|
103
|
+
|
|
104
|
+
block_public_acls = true
|
|
105
|
+
block_public_policy = true
|
|
106
|
+
ignore_public_acls = true
|
|
107
|
+
restrict_public_buckets = true
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
resource "aws_s3_bucket_server_side_encryption_configuration" "routine_output" {
|
|
111
|
+
bucket = aws_s3_bucket.routine_output.id
|
|
112
|
+
|
|
113
|
+
rule {
|
|
114
|
+
apply_server_side_encryption_by_default {
|
|
115
|
+
sse_algorithm = "AES256"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
resource "aws_s3_bucket_versioning" "routine_output" {
|
|
121
|
+
bucket = aws_s3_bucket.routine_output.id
|
|
122
|
+
|
|
123
|
+
versioning_configuration {
|
|
124
|
+
status = "Disabled"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
################################################################################
|
|
129
|
+
# Step Functions Execution Role — single role for all routines
|
|
130
|
+
#
|
|
131
|
+
# Trust: states.amazonaws.com.
|
|
132
|
+
# ABAC: inline policies condition on tag matching where applicable.
|
|
133
|
+
################################################################################
|
|
134
|
+
|
|
135
|
+
resource "aws_iam_role" "execution" {
|
|
136
|
+
name = local.role_name
|
|
137
|
+
|
|
138
|
+
assume_role_policy = jsonencode({
|
|
139
|
+
Version = "2012-10-17"
|
|
140
|
+
Statement = [{
|
|
141
|
+
Effect = "Allow"
|
|
142
|
+
Principal = { Service = "states.amazonaws.com" }
|
|
143
|
+
Action = "sts:AssumeRole"
|
|
144
|
+
Condition = {
|
|
145
|
+
StringEquals = {
|
|
146
|
+
"aws:SourceAccount" = var.account_id
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}]
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
tags = {
|
|
153
|
+
Name = local.role_name
|
|
154
|
+
Stage = var.stage
|
|
155
|
+
Purpose = "step-functions-routines-execution"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# CloudWatch logs (X-Ray + CloudWatch Logs Delivery for Step Functions).
|
|
160
|
+
# Step Functions requires these specific actions to populate execution
|
|
161
|
+
# history into the vendedlogs prefix.
|
|
162
|
+
resource "aws_iam_role_policy" "execution_logs" {
|
|
163
|
+
name = "logs-and-xray"
|
|
164
|
+
role = aws_iam_role.execution.id
|
|
165
|
+
|
|
166
|
+
policy = jsonencode({
|
|
167
|
+
Version = "2012-10-17"
|
|
168
|
+
Statement = [
|
|
169
|
+
{
|
|
170
|
+
Effect = "Allow"
|
|
171
|
+
Action = [
|
|
172
|
+
"logs:CreateLogDelivery",
|
|
173
|
+
"logs:GetLogDelivery",
|
|
174
|
+
"logs:UpdateLogDelivery",
|
|
175
|
+
"logs:DeleteLogDelivery",
|
|
176
|
+
"logs:ListLogDeliveries",
|
|
177
|
+
"logs:PutResourcePolicy",
|
|
178
|
+
"logs:DescribeResourcePolicies",
|
|
179
|
+
"logs:DescribeLogGroups",
|
|
180
|
+
]
|
|
181
|
+
Resource = "*"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
Effect = "Allow"
|
|
185
|
+
Action = [
|
|
186
|
+
"logs:CreateLogStream",
|
|
187
|
+
"logs:PutLogEvents",
|
|
188
|
+
]
|
|
189
|
+
Resource = "${aws_cloudwatch_log_group.routines.arn}:*"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
Effect = "Allow"
|
|
193
|
+
Action = [
|
|
194
|
+
"xray:PutTraceSegments",
|
|
195
|
+
"xray:PutTelemetryRecords",
|
|
196
|
+
"xray:GetSamplingRules",
|
|
197
|
+
"xray:GetSamplingTargets",
|
|
198
|
+
]
|
|
199
|
+
Resource = "*"
|
|
200
|
+
},
|
|
201
|
+
]
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# Lambda invocation — scoped to the routine task wrapper Lambdas. The
|
|
206
|
+
# wrappers themselves enforce per-tenant authorization (validating the
|
|
207
|
+
# routine_id + execution_arn against the DB) so wildcard here is
|
|
208
|
+
# acceptable; tightening to specific function ARNs causes a chicken-and-
|
|
209
|
+
# egg with the lambda-api module which depends on outputs from this one.
|
|
210
|
+
resource "aws_iam_role_policy" "execution_lambda_invoke" {
|
|
211
|
+
name = "lambda-invoke-task-wrappers"
|
|
212
|
+
role = aws_iam_role.execution.id
|
|
213
|
+
|
|
214
|
+
policy = jsonencode({
|
|
215
|
+
Version = "2012-10-17"
|
|
216
|
+
Statement = [{
|
|
217
|
+
Effect = "Allow"
|
|
218
|
+
Action = ["lambda:InvokeFunction"]
|
|
219
|
+
Resource = [
|
|
220
|
+
"arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-routine-task-*",
|
|
221
|
+
"arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-routine-resume",
|
|
222
|
+
"arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-routine-approval-callback",
|
|
223
|
+
"arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-email-send",
|
|
224
|
+
"arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-admin-ops-mcp",
|
|
225
|
+
"arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-slack-send",
|
|
226
|
+
]
|
|
227
|
+
}]
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# Bedrock AgentCore — code interpreter (python() recipe) and agent runtime
|
|
232
|
+
# (agent_invoke recipe via aws-sdk:bedrockagentcore:invokeAgentRuntime
|
|
233
|
+
# direct integration).
|
|
234
|
+
resource "aws_iam_role_policy" "execution_bedrock_agentcore" {
|
|
235
|
+
name = "bedrock-agentcore"
|
|
236
|
+
role = aws_iam_role.execution.id
|
|
237
|
+
|
|
238
|
+
policy = jsonencode({
|
|
239
|
+
Version = "2012-10-17"
|
|
240
|
+
Statement = [
|
|
241
|
+
{
|
|
242
|
+
Effect = "Allow"
|
|
243
|
+
Action = [
|
|
244
|
+
"bedrock-agentcore:StartCodeInterpreterSession",
|
|
245
|
+
"bedrock-agentcore:InvokeCodeInterpreter",
|
|
246
|
+
"bedrock-agentcore:StopCodeInterpreterSession",
|
|
247
|
+
]
|
|
248
|
+
Resource = "*"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
Effect = "Allow"
|
|
252
|
+
Action = ["bedrock-agentcore:InvokeAgentRuntime"]
|
|
253
|
+
Resource = "*"
|
|
254
|
+
},
|
|
255
|
+
]
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
# Secrets Manager — for routines that reach for tenant-scoped secrets
|
|
260
|
+
# (e.g. API tokens) via the python() recipe. Path-scoped per tenant.
|
|
261
|
+
resource "aws_iam_role_policy" "execution_secrets" {
|
|
262
|
+
name = "secrets-manager"
|
|
263
|
+
role = aws_iam_role.execution.id
|
|
264
|
+
|
|
265
|
+
policy = jsonencode({
|
|
266
|
+
Version = "2012-10-17"
|
|
267
|
+
Statement = [{
|
|
268
|
+
Effect = "Allow"
|
|
269
|
+
Action = [
|
|
270
|
+
"secretsmanager:GetSecretValue",
|
|
271
|
+
"secretsmanager:DescribeSecret",
|
|
272
|
+
]
|
|
273
|
+
Resource = "arn:aws:secretsmanager:${var.region}:${var.account_id}:secret:thinkwork/${var.stage}/routines/*"
|
|
274
|
+
}]
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
# Step Functions self-invocation — for the routine_invoke recipe (one
|
|
279
|
+
# routine calling another via startExecution.sync:2). Plus task-token
|
|
280
|
+
# completion (SendTaskSuccess / SendTaskFailure) needed by the inbox-
|
|
281
|
+
# approval bridge that runs out-of-band from this state machine.
|
|
282
|
+
#
|
|
283
|
+
# **ABAC tenant isolation is application-layer in Phase A.** A previous
|
|
284
|
+
# revision had an ABAC tag-condition on StartExecution
|
|
285
|
+
# (aws:ResourceTag/tenantId vs aws:PrincipalTag/tenantId). The shared
|
|
286
|
+
# execution role has no tenantId principal tag, so the condition resolved
|
|
287
|
+
# to '' on every invocation and denied all sub-routine calls. True ABAC
|
|
288
|
+
# for a shared role requires session tags via sts:AssumeRole + TagSession
|
|
289
|
+
# (Phase B follow-up — see review residuals). For now tenant isolation
|
|
290
|
+
# lives at the GraphQL resolver layer (routine_id auth check + tenant_id
|
|
291
|
+
# FK), matching the rest of the platform's tenant-scoped resources.
|
|
292
|
+
resource "aws_iam_role_policy" "execution_states" {
|
|
293
|
+
name = "states-self-invoke"
|
|
294
|
+
role = aws_iam_role.execution.id
|
|
295
|
+
|
|
296
|
+
policy = jsonencode({
|
|
297
|
+
Version = "2012-10-17"
|
|
298
|
+
Statement = [
|
|
299
|
+
{
|
|
300
|
+
# StartExecution operates on the stateMachine ARN.
|
|
301
|
+
Effect = "Allow"
|
|
302
|
+
Action = [
|
|
303
|
+
"states:StartExecution",
|
|
304
|
+
]
|
|
305
|
+
Resource = "arn:aws:states:${var.region}:${var.account_id}:stateMachine:thinkwork-${var.stage}-routine-*"
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
# DescribeExecution and StopExecution operate on the execution
|
|
309
|
+
# ARN, not the stateMachine ARN. startExecution.sync:2 polls
|
|
310
|
+
# DescribeExecution under the hood; without this statement the
|
|
311
|
+
# routine_invoke recipe would fail at runtime with an implicit
|
|
312
|
+
# IAM deny.
|
|
313
|
+
Effect = "Allow"
|
|
314
|
+
Action = [
|
|
315
|
+
"states:DescribeExecution",
|
|
316
|
+
"states:StopExecution",
|
|
317
|
+
]
|
|
318
|
+
Resource = "arn:aws:states:${var.region}:${var.account_id}:execution:thinkwork-${var.stage}-routine-*:*"
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
Effect = "Allow"
|
|
322
|
+
Action = [
|
|
323
|
+
"states:SendTaskSuccess",
|
|
324
|
+
"states:SendTaskFailure",
|
|
325
|
+
"states:SendTaskHeartbeat",
|
|
326
|
+
]
|
|
327
|
+
Resource = "*"
|
|
328
|
+
},
|
|
329
|
+
]
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# S3 — write to the python() output bucket, scoped by tenant prefix.
|
|
334
|
+
resource "aws_iam_role_policy" "execution_s3_output" {
|
|
335
|
+
name = "s3-routine-output"
|
|
336
|
+
role = aws_iam_role.execution.id
|
|
337
|
+
|
|
338
|
+
policy = jsonencode({
|
|
339
|
+
Version = "2012-10-17"
|
|
340
|
+
Statement = [
|
|
341
|
+
{
|
|
342
|
+
Effect = "Allow"
|
|
343
|
+
Action = [
|
|
344
|
+
"s3:PutObject",
|
|
345
|
+
"s3:GetObject",
|
|
346
|
+
]
|
|
347
|
+
Resource = "${aws_s3_bucket.routine_output.arn}/*"
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
Effect = "Allow"
|
|
351
|
+
Action = ["s3:ListBucket"]
|
|
352
|
+
Resource = aws_s3_bucket.routine_output.arn
|
|
353
|
+
},
|
|
354
|
+
]
|
|
355
|
+
})
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
################################################################################
|
|
359
|
+
# Outputs
|
|
360
|
+
################################################################################
|
|
361
|
+
|
|
362
|
+
output "execution_role_arn" {
|
|
363
|
+
description = "ARN of the Step Functions execution role (assumed by all routine state machines)."
|
|
364
|
+
value = aws_iam_role.execution.arn
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
output "execution_role_name" {
|
|
368
|
+
description = "Name of the Step Functions execution role."
|
|
369
|
+
value = aws_iam_role.execution.name
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
output "log_group_arn" {
|
|
373
|
+
description = "CloudWatch log group ARN for state machine execution histories."
|
|
374
|
+
value = aws_cloudwatch_log_group.routines.arn
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
output "log_group_name" {
|
|
378
|
+
description = "CloudWatch log group name."
|
|
379
|
+
value = aws_cloudwatch_log_group.routines.name
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
output "output_bucket_name" {
|
|
383
|
+
description = "S3 bucket for python() recipe stdout/stderr offload."
|
|
384
|
+
value = aws_s3_bucket.routine_output.bucket
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
output "output_bucket_arn" {
|
|
388
|
+
description = "S3 bucket ARN for python() recipe stdout/stderr offload."
|
|
389
|
+
value = aws_s3_bucket.routine_output.arn
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
output "stage" {
|
|
393
|
+
description = "Echo of the stage variable (convenience for downstream modules)."
|
|
394
|
+
value = var.stage
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
################################################################################
|
|
398
|
+
# EventBridge → routine-execution-callback (Phase B U9)
|
|
399
|
+
#
|
|
400
|
+
# AWS EventBridge fires `Step Functions Execution Status Change` events on
|
|
401
|
+
# every status transition (RUNNING/SUCCEEDED/FAILED/TIMED_OUT/ABORTED). The
|
|
402
|
+
# routine-execution-callback Lambda speaks both API Gateway POST shape and
|
|
403
|
+
# this EventBridge shape; the Lambda detects the EventBridge event by
|
|
404
|
+
# `source === "aws.states"` and adapts.
|
|
405
|
+
#
|
|
406
|
+
# The rule is gated on var.execution_callback_lambda_arn being set so the
|
|
407
|
+
# substrate module remains provisionable before the lambda-api module
|
|
408
|
+
# defines the function. The thinkwork composite module wires the ARN
|
|
409
|
+
# back in once both modules apply.
|
|
410
|
+
################################################################################
|
|
411
|
+
|
|
412
|
+
resource "aws_cloudwatch_event_rule" "sfn_state_change" {
|
|
413
|
+
count = var.execution_callback_lambda_arn != "" ? 1 : 0
|
|
414
|
+
name = "thinkwork-${var.stage}-routines-sfn-state-change"
|
|
415
|
+
description = "Forward SFN execution-state-change events to routine-execution-callback so routine_executions tracks lifecycle status."
|
|
416
|
+
|
|
417
|
+
event_pattern = jsonencode({
|
|
418
|
+
source = ["aws.states"]
|
|
419
|
+
"detail-type" = ["Step Functions Execution Status Change"]
|
|
420
|
+
detail = {
|
|
421
|
+
# Constrain to state machines provisioned by createRoutine — the
|
|
422
|
+
# alias-pointing state machine ARNs all live under the
|
|
423
|
+
# `thinkwork-${stage}-routine-` prefix per the publish flow's naming
|
|
424
|
+
# contract (Phase B U7).
|
|
425
|
+
stateMachineArn = [
|
|
426
|
+
{
|
|
427
|
+
prefix = "arn:aws:states:${var.region}:${var.account_id}:stateMachine:thinkwork-${var.stage}-routine-"
|
|
428
|
+
},
|
|
429
|
+
]
|
|
430
|
+
}
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
tags = {
|
|
434
|
+
Name = "thinkwork-${var.stage}-routines-sfn-state-change"
|
|
435
|
+
Stage = var.stage
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
resource "aws_cloudwatch_event_target" "sfn_state_change" {
|
|
440
|
+
count = var.execution_callback_lambda_arn != "" ? 1 : 0
|
|
441
|
+
rule = aws_cloudwatch_event_rule.sfn_state_change[0].name
|
|
442
|
+
target_id = "routine-execution-callback"
|
|
443
|
+
arn = var.execution_callback_lambda_arn
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
resource "aws_lambda_permission" "sfn_state_change" {
|
|
447
|
+
count = var.execution_callback_lambda_arn != "" ? 1 : 0
|
|
448
|
+
statement_id = "AllowEventBridgeInvokeRoutineExecutionCallback"
|
|
449
|
+
action = "lambda:InvokeFunction"
|
|
450
|
+
function_name = var.execution_callback_lambda_arn
|
|
451
|
+
principal = "events.amazonaws.com"
|
|
452
|
+
source_arn = aws_cloudwatch_event_rule.sfn_state_change[0].arn
|
|
453
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# `sandbox-log-scrubber` — R13 CloudWatch backstop
|
|
2
|
+
|
|
3
|
+
**Secondary** scrubber for the AgentCore Code Interpreter sandbox. Pattern-
|
|
4
|
+
redacts known-shape OAuth tokens in AgentCore `APPLICATION_LOGS` before they
|
|
5
|
+
land in the long-term CloudWatch tier. See plan Unit 12 +
|
|
6
|
+
`docs/brainstorms/2026-04-22-agentcore-code-sandbox-requirements.md` R13.
|
|
7
|
+
|
|
8
|
+
## Relationship to the primary scrubber
|
|
9
|
+
|
|
10
|
+
The primary layer is the base-image `sitecustomize.py` stdio wrapper shipped
|
|
11
|
+
by [`terraform/modules/app/agentcore-code-interpreter`](../agentcore-code-interpreter/)
|
|
12
|
+
(plan Unit 4). That layer redacts by **value** — session-scoped token
|
|
13
|
+
strings registered by the preamble — and can therefore catch any token the
|
|
14
|
+
agent's own preamble has handled.
|
|
15
|
+
|
|
16
|
+
This backstop redacts by **pattern**: it has no access to session values,
|
|
17
|
+
only to the bytes that reach CloudWatch. It exists to mitigate *stdio-bypass
|
|
18
|
+
classes* named in R13's residual list:
|
|
19
|
+
|
|
20
|
+
- `subprocess.run(['env'])`, `subprocess.run(['cat', '/proc/self/environ'])`
|
|
21
|
+
- `os.write(fd, ...)` at the file-descriptor level
|
|
22
|
+
- C-extension writes to fd 1 directly
|
|
23
|
+
- `multiprocessing` workers with fresh Python interpreters where the
|
|
24
|
+
session token set hasn't been populated
|
|
25
|
+
- Adversarial split-writes that fragment a token across more bytes than the
|
|
26
|
+
rolling-buffer window
|
|
27
|
+
|
|
28
|
+
When those bytes carry a token whose *shape* is recognizable, this backstop
|
|
29
|
+
catches it. When they don't (bespoke token formats, high-entropy opaque
|
|
30
|
+
strings with no prefix), only the v2 in-process credential proxy can help.
|
|
31
|
+
|
|
32
|
+
## Pattern set
|
|
33
|
+
|
|
34
|
+
Defined in `packages/lambda/sandbox-log-scrubber.ts`; add to it before
|
|
35
|
+
relying on new token shapes in production:
|
|
36
|
+
|
|
37
|
+
- `Authorization:\s*Bearer\s+<opaque-run>`
|
|
38
|
+
- JWT three-dotted base64url (16 chars min per segment)
|
|
39
|
+
- `gh[oprsu]_<A-Za-z0-9>{20,}` — GitHub PATs, OAuth, server-to-server
|
|
40
|
+
- `xox[abep]-<A-Za-z0-9-]{10,}` — Slack bot / app / user / export
|
|
41
|
+
- `ya29.<A-Za-z0-9_->{20,}` — Google short-lived OAuth
|
|
42
|
+
|
|
43
|
+
## What it creates
|
|
44
|
+
|
|
45
|
+
- `/thinkwork/{stage}/sandbox/scrubbed` CloudWatch log group (90-day retention)
|
|
46
|
+
- IAM execution role `thinkwork-{stage}-sandbox-log-scrubber`
|
|
47
|
+
- Lambda function `thinkwork-{stage}-sandbox-log-scrubber` (node20, 256MB, 30s)
|
|
48
|
+
- Subscription filter on the source log group — delivers every event (no filter pattern)
|
|
49
|
+
- Invoke permission from CloudWatch Logs to the Lambda
|
|
50
|
+
|
|
51
|
+
## Failure tolerance
|
|
52
|
+
|
|
53
|
+
If the scrubber Lambda fails — OOM, bug, cold-start timeout — source events
|
|
54
|
+
remain in the original CloudWatch log group. S3 export of the scrubbed group
|
|
55
|
+
is delayed, but no data is lost. The source group retains for whatever its
|
|
56
|
+
own retention policy specifies (typically 90 days).
|
|
57
|
+
|
|
58
|
+
## Build + wire
|
|
59
|
+
|
|
60
|
+
1. `bash scripts/build-lambdas.sh sandbox-log-scrubber` — produces
|
|
61
|
+
`dist/lambdas/sandbox-log-scrubber.zip`.
|
|
62
|
+
2. In the caller module (e.g., `terraform/examples/greenfield`), pass
|
|
63
|
+
`lambda_zip_path` and `lambda_zip_hash` (typically `filebase64sha256()`
|
|
64
|
+
of the zip).
|
|
65
|
+
3. Pass `source_log_group_name` pointing at the AgentCore runtime group
|
|
66
|
+
emitting APPLICATION_LOGS for the stage.
|