thinkwork-cli 0.13.1 → 0.13.2
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/dist/{api-client-4DZ266J5.js → api-client-JYZXYQJR.js} +3 -1
- package/dist/bowser-6Z6JFAIR.js +2822 -0
- package/dist/chunk-DV3MRBR5.js +43 -0
- package/dist/chunk-DVPBI5HM.js +1728 -0
- package/dist/chunk-EI5ZX7CN.js +522 -0
- package/dist/chunk-EUJ7X6YD.js +126 -0
- package/dist/chunk-GG5WKBOX.js +436 -0
- package/dist/chunk-I64EZSU5.js +358 -0
- package/dist/chunk-ILZKG5WV.js +6151 -0
- package/dist/chunk-KMIJ5KGT.js +128 -0
- package/dist/chunk-OCPHU34C.js +996 -0
- package/dist/chunk-QOERT44A.js +3286 -0
- package/dist/chunk-XYM7PE2A.js +2264 -0
- package/dist/{chunk-Y2WHJECR.js → chunk-Z2F6RJFR.js} +1 -39
- package/dist/cli.js +43080 -13150
- package/dist/dist-es-CRV46D5B.js +315 -0
- package/dist/dist-es-DCY4IC3Y.js +2000 -0
- package/dist/dist-es-ENBUC3JP.js +14726 -0
- package/dist/dist-es-G54LBMRN.js +48 -0
- package/dist/dist-es-G7PTG75M.js +176 -0
- package/dist/dist-es-GKDBE7E6.js +88 -0
- package/dist/dist-es-HY3535OI.js +70 -0
- package/dist/dist-es-KL7Y5NB2.js +377 -0
- package/dist/dist-es-LDE437AM.js +170 -0
- package/dist/dist-es-P26FWFLT.js +485 -0
- package/dist/dist-es-Q57SZXZ7.js +70 -0
- package/dist/dist-es-R7FGL2IB.js +88 -0
- package/dist/dist-es-USORBXZ5.js +318 -0
- package/dist/dist-es-ZKC2QKD2.js +377 -0
- package/dist/drizzle/0076_scheduled_jobs_marco_backfill.sql +2 -1
- package/dist/drizzle/0079_seed_tenant_customize_catalog.sql +1 -0
- package/dist/drizzle/0090_brain_schema_extraction.sql +10 -0
- package/dist/drizzle/0132_drop_computer_tables.sql +3 -0
- package/dist/drizzle/0160_compliance_event_types_plugins.sql +2 -0
- package/dist/drizzle/0214_think137_drop_judge_evidence_roi.sql +1 -0
- package/dist/drizzle/0216_drop_agent_loop_versions_goal_worker_policy.sql +1 -0
- package/dist/drizzle/0227_analyst_reader_role.sql +6 -0
- package/dist/drizzle/0230_analyst_rls.sql +9 -0
- package/dist/drizzle/0250_kg_schema_extraction.sql +27 -0
- package/dist/drizzle/0251_brain_stragglers.sql +21 -0
- package/dist/drizzle/0253_drop_brain_substrate.sql +2 -0
- package/dist/drizzle/0257_agents_runtime_allow_harness.sql +24 -0
- package/dist/drizzle/0261_managed_harness_multiplayer.sql +313 -0
- package/dist/drizzle/0261_native_auth_control_plane.sql +328 -0
- package/dist/drizzle/0262_auth_subscription_tickets.sql +59 -0
- package/dist/drizzle/0262_parallel_harness_threads.sql +9 -0
- package/dist/drizzle/0263_agentcore_runtime_identifier.sql +69 -0
- package/dist/drizzle/0263_drop_workos_auth_runtime.sql +236 -0
- package/dist/drizzle/0264_auth_enrollment_attempt_limits.sql +15 -0
- package/dist/drizzle/0264_harness_tool_execution_ledger.sql +113 -0
- package/dist/drizzle/0265_eval_runs_requester_identity.sql +28 -0
- package/dist/drizzle/0265_require_fully_valid_auth_publication.sql +27 -0
- package/dist/drizzle/0266_auth_identity_recovery_grants.sql +9 -0
- package/dist/drizzle/0266_eval_profiles_runtime_type.sql +12 -0
- package/dist/drizzle/0267_ontology_candidate_rejections.sql +53 -0
- package/dist/drizzle/0268_identity_crosswalk_routing.sql +182 -0
- package/dist/drizzle/0269_ontology_system_map.sql +31 -0
- package/dist/event-streams-7JNLJEEL.js +894 -0
- package/dist/event-streams-KDF5RWR6.js +1515 -0
- package/dist/loadSso-DJVB7BXE.js +576 -0
- package/dist/loadSso-JAMNA3WY.js +583 -0
- package/dist/plugins/catalog/package.json +1 -2
- package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +0 -1
- package/dist/plugins/catalog/src/__tests__/contracts.test.ts +0 -69
- package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +0 -2
- package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -5
- package/dist/plugins/catalog/src/contracts.ts +2 -179
- package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -6
- package/dist/signin-UGDWTGS4.js +821 -0
- package/dist/sso-oidc-Q4VXANXY.js +837 -0
- package/dist/sts-CWLSE3GH.js +1241 -0
- package/dist/sts-DNZLNVOV.js +1245 -0
- package/dist/terraform/examples/greenfield/main.tf +223 -66
- package/dist/terraform/examples/greenfield/terraform.tfvars.example +15 -1
- package/dist/terraform/modules/app/agentcore-gateway/main.tf +305 -0
- package/dist/terraform/modules/app/agentcore-gateway/outputs.tf +44 -0
- package/dist/terraform/modules/app/agentcore-gateway/scripts/delete_gateway.sh +93 -0
- package/dist/terraform/modules/app/agentcore-gateway/scripts/read_gateway.sh +33 -0
- package/dist/terraform/modules/app/agentcore-gateway/scripts/reconcile_gateway.sh +543 -0
- package/dist/terraform/modules/app/agentcore-gateway/variables.tf +70 -0
- package/dist/terraform/modules/app/agentcore-harness/main.tf +237 -8
- package/dist/terraform/modules/app/agentcore-harness/outputs.tf +63 -0
- package/dist/terraform/modules/app/agentcore-harness/scripts/delete_harness.sh +5 -0
- package/dist/terraform/modules/app/agentcore-harness/scripts/harness-lifecycle.mjs +430 -0
- package/dist/terraform/modules/app/agentcore-harness/scripts/harness-readback.mjs +180 -0
- package/dist/terraform/modules/app/agentcore-harness/scripts/harness-tool-contract.mjs +193 -0
- package/dist/terraform/modules/app/agentcore-harness/scripts/prune_harness.sh +5 -0
- package/dist/terraform/modules/app/agentcore-harness/scripts/read_harness.sh +5 -0
- package/dist/terraform/modules/app/agentcore-harness/scripts/reconcile_harness.sh +5 -0
- package/dist/terraform/modules/app/agentcore-harness/variables.tf +72 -1
- package/dist/terraform/modules/app/agentcore-identity/main.tf +173 -0
- package/dist/terraform/modules/app/agentcore-identity/outputs.tf +51 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/bootstrap_twenty_oauth_client.sh +122 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/delete_identity.sh +25 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/delete_twenty_identity.sh +16 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/read_identity.sh +40 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_identity.sh +73 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_identity.sh +74 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_provider.mjs +61 -0
- package/dist/terraform/modules/app/agentcore-identity/variables.tf +54 -0
- package/dist/terraform/modules/app/appsync-subscriptions/main.tf +127 -21
- package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +0 -6
- package/dist/terraform/modules/app/appsync-subscriptions/variables.tf +5 -0
- package/dist/terraform/modules/app/deployment-control-plane/runner.py +966 -27
- package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +581 -22
- package/dist/terraform/modules/app/lambda-api/agentcore-harness-alarms.tf +88 -0
- package/dist/terraform/modules/app/lambda-api/agentcore-identity-alarms.tf +91 -0
- package/dist/terraform/modules/app/lambda-api/handlers.tf +298 -74
- package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +99 -54
- package/dist/terraform/modules/app/lambda-api/main.tf +24 -0
- package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +181 -4
- package/dist/terraform/modules/app/lambda-api/outputs.tf +35 -0
- package/dist/terraform/modules/app/lambda-api/runtime-config.tf +3 -21
- package/dist/terraform/modules/app/lambda-api/variables.tf +109 -16
- package/dist/terraform/modules/foundation/cognito/main.tf +294 -72
- package/dist/terraform/modules/foundation/cognito/outputs.tf +42 -0
- package/dist/terraform/modules/foundation/cognito/variables.tf +114 -4
- package/dist/terraform/modules/thinkwork/main.tf +269 -46
- package/dist/terraform/modules/thinkwork/outputs.tf +136 -8
- package/dist/terraform/modules/thinkwork/variables.tf +124 -17
- package/dist/terraform/schema.graphql +69 -36
- package/dist/workspace-defaults/files/USER.md +1 -0
- package/package.json +3 -2
- package/dist/drizzle/0261_thread_turns_origin_turn_index.sql +0 -11
- package/dist/plugins/workos-auth/package.json +0 -22
- package/dist/plugins/workos-auth/src/index.ts +0 -30
- package/dist/plugins/workos-auth/src/manifest.ts +0 -40
- package/dist/plugins/workos-auth/src/provider-contract.ts +0 -39
- package/dist/plugins/workos-auth/test/manifest.test.ts +0 -94
- package/dist/plugins/workos-auth/tsconfig.json +0 -8
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const GOVERNED_TOOL_TYPES = new Map([
|
|
4
|
+
["thinkwork_gateway", "agentcore_gateway"],
|
|
5
|
+
["browser", "agentcore_browser"],
|
|
6
|
+
["emit_document", "inline_function"],
|
|
7
|
+
["goal_complete", "inline_function"],
|
|
8
|
+
["submit_skill_draft", "inline_function"],
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
export function canonicalJson(value) {
|
|
12
|
+
if (Array.isArray(value)) {
|
|
13
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
14
|
+
}
|
|
15
|
+
if (value && typeof value === "object") {
|
|
16
|
+
return `{${Object.keys(value)
|
|
17
|
+
.sort()
|
|
18
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
|
19
|
+
.join(",")}}`;
|
|
20
|
+
}
|
|
21
|
+
return JSON.stringify(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function fingerprintGovernedHarnessTools(value) {
|
|
25
|
+
return createHash("sha256").update(canonicalJson(value)).digest("hex");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function selectHarnessEndpointsForRetention(
|
|
29
|
+
endpoints,
|
|
30
|
+
{ activeEndpointName, endpointPrefix, legacyEndpointName },
|
|
31
|
+
) {
|
|
32
|
+
const managed = endpoints.filter(
|
|
33
|
+
(endpoint) =>
|
|
34
|
+
endpoint.endpointName === legacyEndpointName ||
|
|
35
|
+
endpoint.endpointName?.startsWith(endpointPrefix),
|
|
36
|
+
);
|
|
37
|
+
const rollbackEndpointName = managed
|
|
38
|
+
.filter((endpoint) => endpoint.endpointName !== activeEndpointName)
|
|
39
|
+
.sort((left, right) =>
|
|
40
|
+
String(right.liveVersion ?? right.targetVersion ?? "").localeCompare(
|
|
41
|
+
String(left.liveVersion ?? left.targetVersion ?? ""),
|
|
42
|
+
undefined,
|
|
43
|
+
{ numeric: true },
|
|
44
|
+
),
|
|
45
|
+
)[0]?.endpointName;
|
|
46
|
+
const retainedEndpointNames = new Set(
|
|
47
|
+
[activeEndpointName, rollbackEndpointName].filter(
|
|
48
|
+
(value) => typeof value === "string",
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
return {
|
|
52
|
+
rollbackEndpointName,
|
|
53
|
+
retainedEndpointNames: [...retainedEndpointNames],
|
|
54
|
+
deletedEndpointNames: managed
|
|
55
|
+
.map((endpoint) => endpoint.endpointName)
|
|
56
|
+
.filter(
|
|
57
|
+
(endpointName) =>
|
|
58
|
+
endpointName && !retainedEndpointNames.has(endpointName),
|
|
59
|
+
),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* One canonical producer contract for both Harness reconciliation and
|
|
65
|
+
* immutable-version readback. Keeping the complete inline schemas here makes
|
|
66
|
+
* schema drift a deployment failure instead of re-attesting it into SSM.
|
|
67
|
+
*/
|
|
68
|
+
export function buildGovernedHarnessTools({ gatewayArn, providerArn }) {
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
type: "agentcore_gateway",
|
|
72
|
+
name: "thinkwork_gateway",
|
|
73
|
+
config: {
|
|
74
|
+
agentCoreGateway: {
|
|
75
|
+
gatewayArn,
|
|
76
|
+
outboundAuth: {
|
|
77
|
+
oauth: {
|
|
78
|
+
providerArn,
|
|
79
|
+
scopes: ["gateway:invoke"],
|
|
80
|
+
customParameters: {
|
|
81
|
+
subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
|
|
82
|
+
},
|
|
83
|
+
grantType: "TOKEN_EXCHANGE",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
type: "agentcore_browser",
|
|
91
|
+
name: "browser",
|
|
92
|
+
config: { agentCoreBrowser: {} },
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: "inline_function",
|
|
96
|
+
name: "emit_document",
|
|
97
|
+
config: {
|
|
98
|
+
inlineFunction: {
|
|
99
|
+
description:
|
|
100
|
+
"Emit a durable ThinkWork HTML plate from markdown. Use genre report, plan, brief, or ideation. The platform compiles, validates, persists, and attaches it to the thread. On rejection, fix every diagnostic and call again with document_id when supplied.",
|
|
101
|
+
inputSchema: {
|
|
102
|
+
type: "object",
|
|
103
|
+
properties: {
|
|
104
|
+
genre: {
|
|
105
|
+
type: "string",
|
|
106
|
+
enum: ["report", "plan", "brief", "ideation"],
|
|
107
|
+
},
|
|
108
|
+
title: { type: "string" },
|
|
109
|
+
abstract: { type: "string" },
|
|
110
|
+
digest_markdown: {
|
|
111
|
+
type: "string",
|
|
112
|
+
description: "Complete markdown document body.",
|
|
113
|
+
},
|
|
114
|
+
status: { type: "string", enum: ["draft", "final"] },
|
|
115
|
+
document_id: { type: "string" },
|
|
116
|
+
space_id: { type: "string" },
|
|
117
|
+
},
|
|
118
|
+
required: ["genre", "title", "abstract", "digest_markdown"],
|
|
119
|
+
additionalProperties: false,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
type: "inline_function",
|
|
126
|
+
name: "goal_complete",
|
|
127
|
+
config: {
|
|
128
|
+
inlineFunction: {
|
|
129
|
+
description:
|
|
130
|
+
"Mark the current ThinkWork-managed Goal mode objective complete. Call exactly once only after the objective is fully satisfied.",
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: "object",
|
|
133
|
+
properties: {
|
|
134
|
+
summary: {
|
|
135
|
+
type: "string",
|
|
136
|
+
description: "Concise user-facing completion summary.",
|
|
137
|
+
},
|
|
138
|
+
completion_notes: {
|
|
139
|
+
type: "string",
|
|
140
|
+
description: "Optional bounded completion details.",
|
|
141
|
+
},
|
|
142
|
+
verification_notes: {
|
|
143
|
+
type: "array",
|
|
144
|
+
items: { type: "string" },
|
|
145
|
+
maxItems: 5,
|
|
146
|
+
description: "Concrete checks proving completion.",
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
required: ["summary"],
|
|
150
|
+
additionalProperties: false,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
type: "inline_function",
|
|
157
|
+
name: "submit_skill_draft",
|
|
158
|
+
config: {
|
|
159
|
+
inlineFunction: {
|
|
160
|
+
description:
|
|
161
|
+
"Submit a complete Agent Skills draft to ThinkWork's governed review and trust queue. Available only during a trusted /skill-creator turn; this does not publish the skill.",
|
|
162
|
+
inputSchema: {
|
|
163
|
+
type: "object",
|
|
164
|
+
properties: {
|
|
165
|
+
skill_markdown: {
|
|
166
|
+
type: "string",
|
|
167
|
+
description:
|
|
168
|
+
"Complete SKILL.md including valid name and description frontmatter.",
|
|
169
|
+
},
|
|
170
|
+
supporting_files: {
|
|
171
|
+
type: "array",
|
|
172
|
+
maxItems: 20,
|
|
173
|
+
items: {
|
|
174
|
+
type: "object",
|
|
175
|
+
properties: {
|
|
176
|
+
path: { type: "string" },
|
|
177
|
+
content: { type: "string" },
|
|
178
|
+
},
|
|
179
|
+
required: ["path", "content"],
|
|
180
|
+
additionalProperties: false,
|
|
181
|
+
},
|
|
182
|
+
description:
|
|
183
|
+
"Optional bounded text references, scripts, or assets required by the skill.",
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
required: ["skill_markdown"],
|
|
187
|
+
additionalProperties: false,
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
variable "enabled" {
|
|
2
|
-
description = "Create the AgentCore Harness
|
|
2
|
+
description = "Create the AgentCore Harness execution IAM surface. The role is independent of whether a managed tenant/profile runtime is enabled."
|
|
3
3
|
type = bool
|
|
4
4
|
default = true
|
|
5
5
|
}
|
|
@@ -23,3 +23,74 @@ variable "bucket_name" {
|
|
|
23
23
|
description = "Stage workspace S3 bucket name — Harness microVMs read tenant skill sources under tenants/*"
|
|
24
24
|
type = string
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
variable "managed_runtime_enabled" {
|
|
28
|
+
description = "Reconcile the managed tenant/profile AgentCore Harness runtime. The base IAM role may remain enabled while this is false."
|
|
29
|
+
type = bool
|
|
30
|
+
default = false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
variable "tenant_slug" {
|
|
34
|
+
description = "Tenant slug whose skill prefix and managed Harness profile are admitted."
|
|
35
|
+
type = string
|
|
36
|
+
default = ""
|
|
37
|
+
|
|
38
|
+
validation {
|
|
39
|
+
condition = var.tenant_slug == "" || can(regex("^[a-z0-9][a-z0-9-]{0,47}$", var.tenant_slug))
|
|
40
|
+
error_message = "tenant_slug must be a lowercase slug up to 48 characters."
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
variable "trust_profile" {
|
|
45
|
+
description = "Stable execution/trust profile. Ordinary agent/user/tool differences do not create more Harnesses."
|
|
46
|
+
type = string
|
|
47
|
+
default = "default"
|
|
48
|
+
|
|
49
|
+
validation {
|
|
50
|
+
condition = can(regex("^[a-z][a-z0-9-]{0,31}$", var.trust_profile))
|
|
51
|
+
error_message = "trust_profile must be a lowercase slug up to 32 characters."
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
variable "discovery_url" {
|
|
56
|
+
description = "OIDC discovery URL used by the Harness CUSTOM_JWT authorizer."
|
|
57
|
+
type = string
|
|
58
|
+
default = ""
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
variable "harness_audience" {
|
|
62
|
+
description = "Exact audience accepted by the Harness CUSTOM_JWT authorizer."
|
|
63
|
+
type = string
|
|
64
|
+
default = ""
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
variable "gateway_arn" {
|
|
68
|
+
description = "Exact selected AgentCore Gateway ARN exposed as the native Harness tool."
|
|
69
|
+
type = string
|
|
70
|
+
default = ""
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
variable "oauth_credential_provider_arn" {
|
|
74
|
+
description = "Identity provider ARN used for the Harness on-behalf-of Gateway token exchange."
|
|
75
|
+
type = string
|
|
76
|
+
default = ""
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
variable "oauth_credential_secret_arn" {
|
|
80
|
+
description = "AgentCore-managed Secrets Manager ARN backing the selected OAuth provider."
|
|
81
|
+
type = string
|
|
82
|
+
default = ""
|
|
83
|
+
sensitive = true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
variable "oauth_return_url" {
|
|
87
|
+
description = "Application callback that verifies the active user and completes AgentCore Identity OAuth session binding."
|
|
88
|
+
type = string
|
|
89
|
+
default = ""
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
variable "model_id" {
|
|
93
|
+
description = "Safe baseline Bedrock model for the managed Harness profile. Per-turn trusted projection may narrow/replace it."
|
|
94
|
+
type = string
|
|
95
|
+
default = "us.anthropic.claude-haiku-4-5-20251001-v1:0"
|
|
96
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
################################################################################
|
|
2
|
+
|
|
3
|
+
terraform {
|
|
4
|
+
required_providers {
|
|
5
|
+
aws = {
|
|
6
|
+
source = "hashicorp/aws"
|
|
7
|
+
version = ">= 5.0"
|
|
8
|
+
}
|
|
9
|
+
external = {
|
|
10
|
+
source = "hashicorp/external"
|
|
11
|
+
version = ">= 2.3.0"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
# THINK-316 U1 — AgentCore Identity proof substrate
|
|
16
|
+
#
|
|
17
|
+
# The AWS provider does not yet model Workload Identity or OAuth2 credential
|
|
18
|
+
# providers. A terraform_data lifecycle wrapper reconciles them with the
|
|
19
|
+
# current AWS CLI and owns destroy cleanup. The client secret is passed only in
|
|
20
|
+
# the local-exec environment and never printed by the scripts.
|
|
21
|
+
################################################################################
|
|
22
|
+
|
|
23
|
+
locals {
|
|
24
|
+
workload_identity_name = "thinkwork-${var.stage}-multiplayer-proof"
|
|
25
|
+
credential_provider_name = "thinkwork-${var.stage}-proof-oauth"
|
|
26
|
+
twenty_credential_provider_name = "thinkwork-${var.stage}-twenty-crm"
|
|
27
|
+
oauth_issuer = trimsuffix(var.oauth_issuer, "/")
|
|
28
|
+
oauth_return_url = "${local.oauth_issuer}/complete"
|
|
29
|
+
allowed_oauth_return_urls = distinct(concat([local.oauth_return_url], var.user_federation_return_urls))
|
|
30
|
+
configuration_hash = nonsensitive(sha256(jsonencode({
|
|
31
|
+
issuer = local.oauth_issuer
|
|
32
|
+
client_id = var.oauth_client_id
|
|
33
|
+
secret_digest = sha256(var.oauth_client_secret)
|
|
34
|
+
return_url = local.oauth_return_url
|
|
35
|
+
})))
|
|
36
|
+
twenty_configuration_hash = nonsensitive(sha256(jsonencode({
|
|
37
|
+
allowed_oauth_return_urls = local.allowed_oauth_return_urls
|
|
38
|
+
twenty_credential_provider_name = local.twenty_credential_provider_name
|
|
39
|
+
twenty_oauth_issuer = var.twenty_oauth_issuer
|
|
40
|
+
twenty_oauth_resource = var.twenty_oauth_resource
|
|
41
|
+
twenty_client_secret_arn = try(aws_secretsmanager_secret.twenty_oauth_client[0].arn, "")
|
|
42
|
+
})))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# The DCR client secret is written by the reconciler, not Terraform, so its
|
|
46
|
+
# value never enters Terraform state. AgentCore Identity reads the JSON key
|
|
47
|
+
# directly and remains the sole custodian of each user's downstream grant.
|
|
48
|
+
resource "aws_secretsmanager_secret" "twenty_oauth_client" {
|
|
49
|
+
count = var.enabled ? 1 : 0
|
|
50
|
+
|
|
51
|
+
name = "thinkwork/${var.stage}/agentcore-identity/twenty-crm-oauth-client"
|
|
52
|
+
description = "Confidential Twenty OAuth client used only by AgentCore Identity Token Vault"
|
|
53
|
+
recovery_window_in_days = 7
|
|
54
|
+
|
|
55
|
+
tags = {
|
|
56
|
+
"managed-by" = "terraform"
|
|
57
|
+
"thinkwork:stage" = var.stage
|
|
58
|
+
"thinkwork:purpose" = "agentcore-identity-user-federation"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
resource "aws_secretsmanager_secret_policy" "twenty_oauth_client" {
|
|
63
|
+
count = var.enabled ? 1 : 0
|
|
64
|
+
secret_arn = aws_secretsmanager_secret.twenty_oauth_client[0].arn
|
|
65
|
+
policy = jsonencode({
|
|
66
|
+
Version = "2012-10-17"
|
|
67
|
+
Statement = [{
|
|
68
|
+
Sid = "AgentCoreIdentityRead"
|
|
69
|
+
Effect = "Allow"
|
|
70
|
+
Principal = { Service = "bedrock-agentcore.amazonaws.com" }
|
|
71
|
+
Action = "secretsmanager:GetSecretValue"
|
|
72
|
+
Resource = aws_secretsmanager_secret.twenty_oauth_client[0].arn
|
|
73
|
+
Condition = {
|
|
74
|
+
StringEquals = { "aws:SourceAccount" = var.account_id }
|
|
75
|
+
}
|
|
76
|
+
}]
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
resource "terraform_data" "identity_lifecycle" {
|
|
81
|
+
count = var.enabled ? 1 : 0
|
|
82
|
+
|
|
83
|
+
input = {
|
|
84
|
+
region = var.region
|
|
85
|
+
workload_identity_name = local.workload_identity_name
|
|
86
|
+
credential_provider_name = local.credential_provider_name
|
|
87
|
+
}
|
|
88
|
+
triggers_replace = [local.configuration_hash]
|
|
89
|
+
|
|
90
|
+
provisioner "local-exec" {
|
|
91
|
+
command = "bash ${path.module}/scripts/reconcile_identity.sh"
|
|
92
|
+
environment = {
|
|
93
|
+
AWS_REGION = var.region
|
|
94
|
+
WORKLOAD_IDENTITY_NAME = local.workload_identity_name
|
|
95
|
+
CREDENTIAL_PROVIDER_NAME = local.credential_provider_name
|
|
96
|
+
OAUTH_ISSUER = local.oauth_issuer
|
|
97
|
+
OAUTH_CLIENT_ID = var.oauth_client_id
|
|
98
|
+
OAUTH_CLIENT_SECRET = var.oauth_client_secret
|
|
99
|
+
OAUTH_RETURN_URL = local.oauth_return_url
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
provisioner "local-exec" {
|
|
104
|
+
when = destroy
|
|
105
|
+
command = "bash ${path.module}/scripts/delete_identity.sh"
|
|
106
|
+
environment = {
|
|
107
|
+
AWS_REGION = self.input.region
|
|
108
|
+
WORKLOAD_IDENTITY_NAME = self.input.workload_identity_name
|
|
109
|
+
CREDENTIAL_PROVIDER_NAME = self.input.credential_provider_name
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# Additive user-federation lifecycle. It may update the workload's callback
|
|
115
|
+
# allowlist in place, but it never owns or deletes the workload/Gateway provider.
|
|
116
|
+
# This keeps the proven exact-user admission path live while Twenty is added,
|
|
117
|
+
# rotated, or removed independently.
|
|
118
|
+
resource "terraform_data" "twenty_identity_lifecycle" {
|
|
119
|
+
count = var.enabled ? 1 : 0
|
|
120
|
+
|
|
121
|
+
input = {
|
|
122
|
+
region = var.region
|
|
123
|
+
workload_identity_name = local.workload_identity_name
|
|
124
|
+
twenty_credential_provider_name = local.twenty_credential_provider_name
|
|
125
|
+
}
|
|
126
|
+
triggers_replace = [local.twenty_configuration_hash]
|
|
127
|
+
|
|
128
|
+
provisioner "local-exec" {
|
|
129
|
+
command = "bash ${path.module}/scripts/reconcile_twenty_identity.sh"
|
|
130
|
+
environment = {
|
|
131
|
+
AWS_REGION = var.region
|
|
132
|
+
WORKLOAD_IDENTITY_NAME = local.workload_identity_name
|
|
133
|
+
OAUTH_RETURN_URLS_JSON = jsonencode(local.allowed_oauth_return_urls)
|
|
134
|
+
TWENTY_CREDENTIAL_PROVIDER_NAME = local.twenty_credential_provider_name
|
|
135
|
+
TWENTY_CLIENT_SECRET_ARN = aws_secretsmanager_secret.twenty_oauth_client[0].arn
|
|
136
|
+
TWENTY_OAUTH_ISSUER = trimsuffix(var.twenty_oauth_issuer, "/")
|
|
137
|
+
TWENTY_OAUTH_RESOURCE = var.twenty_oauth_resource
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
provisioner "local-exec" {
|
|
142
|
+
when = destroy
|
|
143
|
+
command = "bash ${path.module}/scripts/delete_twenty_identity.sh"
|
|
144
|
+
environment = {
|
|
145
|
+
AWS_REGION = self.input.region
|
|
146
|
+
TWENTY_CREDENTIAL_PROVIDER_NAME = self.input.twenty_credential_provider_name
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
depends_on = [
|
|
151
|
+
terraform_data.identity_lifecycle,
|
|
152
|
+
aws_secretsmanager_secret_policy.twenty_oauth_client,
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# Read back the service-generated ARNs only after apply-time reconciliation.
|
|
157
|
+
# Gateway IAM can therefore scope vault and secret access exactly rather than
|
|
158
|
+
# granting a token-vault or Secrets Manager wildcard.
|
|
159
|
+
data "external" "identity_state" {
|
|
160
|
+
count = var.enabled ? 1 : 0
|
|
161
|
+
depends_on = [
|
|
162
|
+
terraform_data.identity_lifecycle,
|
|
163
|
+
terraform_data.twenty_identity_lifecycle,
|
|
164
|
+
]
|
|
165
|
+
program = ["bash", "${path.module}/scripts/read_identity.sh"]
|
|
166
|
+
|
|
167
|
+
query = {
|
|
168
|
+
region = var.region
|
|
169
|
+
workload_identity_name = local.workload_identity_name
|
|
170
|
+
credential_provider_name = local.credential_provider_name
|
|
171
|
+
twenty_credential_provider_name = local.twenty_credential_provider_name
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
output "workload_identity_name" {
|
|
2
|
+
description = "Name used for GetWorkloadAccessTokenForJWT. Empty when disabled."
|
|
3
|
+
value = var.enabled ? local.workload_identity_name : ""
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
output "credential_provider_name" {
|
|
7
|
+
description = "AgentCore Identity OAuth2 credential-provider name. Empty when disabled."
|
|
8
|
+
value = var.enabled ? local.credential_provider_name : ""
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
output "oauth_return_url" {
|
|
12
|
+
description = "Allowed user-federation return URL. Empty when disabled."
|
|
13
|
+
value = var.enabled ? local.oauth_return_url : ""
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
output "twenty_credential_provider_name" {
|
|
17
|
+
description = "AgentCore Identity provider name for per-user Twenty grants."
|
|
18
|
+
value = var.enabled ? local.twenty_credential_provider_name : ""
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
output "twenty_credential_provider_arn" {
|
|
22
|
+
description = "AgentCore Identity provider ARN for per-user Twenty grants."
|
|
23
|
+
value = var.enabled ? data.external.identity_state[0].result.twenty_credential_provider_arn : ""
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
output "twenty_oauth_callback_url" {
|
|
27
|
+
description = "Service-generated callback URL registered with Twenty OAuth."
|
|
28
|
+
value = var.enabled ? data.external.identity_state[0].result.twenty_oauth_callback_url : ""
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
output "twenty_client_secret_arn" {
|
|
32
|
+
description = "External Secrets Manager client record consumed by AgentCore Identity."
|
|
33
|
+
value = var.enabled ? aws_secretsmanager_secret.twenty_oauth_client[0].arn : ""
|
|
34
|
+
sensitive = true
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
output "workload_identity_arn" {
|
|
38
|
+
description = "Service-generated workload identity ARN. Empty when disabled."
|
|
39
|
+
value = var.enabled ? data.external.identity_state[0].result.workload_identity_arn : ""
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
output "credential_provider_arn" {
|
|
43
|
+
description = "Service-generated OAuth2 credential-provider ARN. Empty when disabled."
|
|
44
|
+
value = var.enabled ? data.external.identity_state[0].result.credential_provider_arn : ""
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
output "credential_secret_arn" {
|
|
48
|
+
description = "AgentCore-managed Secrets Manager ARN backing the provider. Empty when disabled."
|
|
49
|
+
value = var.enabled ? data.external.identity_state[0].result.credential_secret_arn : ""
|
|
50
|
+
sensitive = true
|
|
51
|
+
}
|
package/dist/terraform/modules/app/agentcore-identity/scripts/bootstrap_twenty_oauth_client.sh
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
: "${AWS_REGION:?AWS_REGION is required}"
|
|
5
|
+
: "${TWENTY_OAUTH_ISSUER:?TWENTY_OAUTH_ISSUER is required}"
|
|
6
|
+
: "${TWENTY_CLIENT_SECRET_ARN:?TWENTY_CLIENT_SECRET_ARN is required}"
|
|
7
|
+
: "${TWENTY_ADMIN_TOKEN_SECRET_ARN:?TWENTY_ADMIN_TOKEN_SECRET_ARN is required}"
|
|
8
|
+
|
|
9
|
+
callback_url="${TWENTY_CALLBACK_URL:-https://bedrock-agentcore.${AWS_REGION}.amazonaws.com/identities/oauth2/callback}"
|
|
10
|
+
|
|
11
|
+
if existing_secret="$(aws secretsmanager get-secret-value \
|
|
12
|
+
--region "$AWS_REGION" \
|
|
13
|
+
--secret-id "$TWENTY_CLIENT_SECRET_ARN" \
|
|
14
|
+
--query SecretString \
|
|
15
|
+
--output text 2>/dev/null)" &&
|
|
16
|
+
jq -e '.client_id | type == "string" and length > 0' <<<"$existing_secret" >/dev/null &&
|
|
17
|
+
jq -e '.client_secret | type == "string" and length > 0' <<<"$existing_secret" >/dev/null; then
|
|
18
|
+
existing_callback="$(jq -r '.redirect_uris[0] // empty' <<<"$existing_secret")"
|
|
19
|
+
if [[ "$existing_callback" == "$callback_url" ]]; then
|
|
20
|
+
printf 'Twenty confidential OAuth client already bootstrapped: secret=%s\n' \
|
|
21
|
+
"$TWENTY_CLIENT_SECRET_ARN"
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# Twenty's RFC 7591 endpoint intentionally downgrades all dynamic clients to
|
|
27
|
+
# public clients. Its authenticated metadata API is the supported path for a
|
|
28
|
+
# confidential application registration: Twenty generates and bcrypt-hashes
|
|
29
|
+
# the secret server-side and returns the plaintext exactly once.
|
|
30
|
+
admin_record="$(aws secretsmanager get-secret-value \
|
|
31
|
+
--region "$AWS_REGION" \
|
|
32
|
+
--secret-id "$TWENTY_ADMIN_TOKEN_SECRET_ARN" \
|
|
33
|
+
--query SecretString \
|
|
34
|
+
--output text)"
|
|
35
|
+
admin_token="$(jq -r '.access_token // empty' <<<"$admin_record")"
|
|
36
|
+
if [[ -z "$admin_token" ]]; then
|
|
37
|
+
printf '%s\n' 'Twenty administrator token secret has no access_token' >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
if [[ -n "${existing_secret:-}" ]] &&
|
|
42
|
+
jq -e '.registration_id | type == "string" and length > 0' \
|
|
43
|
+
<<<"$existing_secret" >/dev/null; then
|
|
44
|
+
registration_id="$(jq -r '.registration_id' <<<"$existing_secret")"
|
|
45
|
+
update_payload="$(jq -n \
|
|
46
|
+
--arg id "$registration_id" \
|
|
47
|
+
--arg callback "$callback_url" \
|
|
48
|
+
'{
|
|
49
|
+
query: "mutation UpdateAgentCoreRegistration($input: UpdateApplicationRegistrationInput!) { updateApplicationRegistration(input: $input) { id oAuthRedirectUris oAuthScopes } }",
|
|
50
|
+
variables: {
|
|
51
|
+
input: {
|
|
52
|
+
id: $id,
|
|
53
|
+
update: {oAuthRedirectUris: [$callback]}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}')"
|
|
57
|
+
update_response="$(curl --silent --show-error --fail-with-body \
|
|
58
|
+
-H "authorization: Bearer $admin_token" \
|
|
59
|
+
-H 'content-type: application/json' \
|
|
60
|
+
--data-binary "$update_payload" \
|
|
61
|
+
"${TWENTY_OAUTH_ISSUER%/}/metadata")"
|
|
62
|
+
if jq -e '.errors | type == "array" and length > 0' <<<"$update_response" >/dev/null; then
|
|
63
|
+
jq '{errors: [.errors[] | {message, extensions: {code: .extensions.code}}]}' \
|
|
64
|
+
<<<"$update_response" >&2
|
|
65
|
+
printf '%s\n' 'Twenty OAuth callback update failed' >&2
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
updated_secret="$(jq --arg callback "$callback_url" \
|
|
69
|
+
'.redirect_uris = [$callback]' <<<"$existing_secret")"
|
|
70
|
+
printf '%s' "$updated_secret" | aws secretsmanager put-secret-value \
|
|
71
|
+
--region "$AWS_REGION" \
|
|
72
|
+
--secret-id "$TWENTY_CLIENT_SECRET_ARN" \
|
|
73
|
+
--secret-string file:///dev/stdin >/dev/null
|
|
74
|
+
printf 'Twenty confidential OAuth client callback updated: secret=%s callback=%s\n' \
|
|
75
|
+
"$TWENTY_CLIENT_SECRET_ARN" "$callback_url"
|
|
76
|
+
exit 0
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
graphql_payload="$(jq -n \
|
|
80
|
+
--arg callback "$callback_url" \
|
|
81
|
+
'{
|
|
82
|
+
query: "mutation CreateAgentCoreRegistration($input: CreateApplicationRegistrationInput!) { createApplicationRegistration(input: $input) { applicationRegistration { id universalIdentifier oAuthClientId oAuthRedirectUris oAuthScopes } clientSecret } }",
|
|
83
|
+
variables: {
|
|
84
|
+
input: {
|
|
85
|
+
name: "ThinkWork AgentCore Identity Twenty CRM",
|
|
86
|
+
oAuthRedirectUris: [$callback],
|
|
87
|
+
oAuthScopes: ["api", "profile"]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}')"
|
|
91
|
+
graphql_response="$(curl --silent --show-error --fail-with-body \
|
|
92
|
+
-H "authorization: Bearer $admin_token" \
|
|
93
|
+
-H 'content-type: application/json' \
|
|
94
|
+
--data-binary "$graphql_payload" \
|
|
95
|
+
"${TWENTY_OAUTH_ISSUER%/}/metadata")"
|
|
96
|
+
|
|
97
|
+
if jq -e '.errors | type == "array" and length > 0' <<<"$graphql_response" >/dev/null; then
|
|
98
|
+
jq '{errors: [.errors[] | {message, extensions: {code: .extensions.code}}]}' \
|
|
99
|
+
<<<"$graphql_response" >&2
|
|
100
|
+
printf '%s\n' 'Twenty confidential OAuth client bootstrap failed' >&2
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
client_record="$(jq -e '{
|
|
105
|
+
client_id: .data.createApplicationRegistration.applicationRegistration.oAuthClientId,
|
|
106
|
+
client_secret: .data.createApplicationRegistration.clientSecret,
|
|
107
|
+
registration_id: .data.createApplicationRegistration.applicationRegistration.id,
|
|
108
|
+
universal_identifier: .data.createApplicationRegistration.applicationRegistration.universalIdentifier,
|
|
109
|
+
redirect_uris: .data.createApplicationRegistration.applicationRegistration.oAuthRedirectUris,
|
|
110
|
+
scopes: .data.createApplicationRegistration.applicationRegistration.oAuthScopes
|
|
111
|
+
} | select(
|
|
112
|
+
(.client_id | type == "string" and length > 0) and
|
|
113
|
+
(.client_secret | type == "string" and length > 0)
|
|
114
|
+
)' <<<"$graphql_response")"
|
|
115
|
+
|
|
116
|
+
printf '%s' "$client_record" | aws secretsmanager put-secret-value \
|
|
117
|
+
--region "$AWS_REGION" \
|
|
118
|
+
--secret-id "$TWENTY_CLIENT_SECRET_ARN" \
|
|
119
|
+
--secret-string file:///dev/stdin >/dev/null
|
|
120
|
+
|
|
121
|
+
printf 'Twenty confidential OAuth client bootstrapped: secret=%s callback=%s\n' \
|
|
122
|
+
"$TWENTY_CLIENT_SECRET_ARN" "$callback_url"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
: "${AWS_REGION:?AWS_REGION is required}"
|
|
5
|
+
: "${WORKLOAD_IDENTITY_NAME:?WORKLOAD_IDENTITY_NAME is required}"
|
|
6
|
+
: "${CREDENTIAL_PROVIDER_NAME:?CREDENTIAL_PROVIDER_NAME is required}"
|
|
7
|
+
|
|
8
|
+
if aws bedrock-agentcore-control get-oauth2-credential-provider \
|
|
9
|
+
--region "$AWS_REGION" \
|
|
10
|
+
--name "$CREDENTIAL_PROVIDER_NAME" >/dev/null 2>&1; then
|
|
11
|
+
aws bedrock-agentcore-control delete-oauth2-credential-provider \
|
|
12
|
+
--region "$AWS_REGION" \
|
|
13
|
+
--name "$CREDENTIAL_PROVIDER_NAME" >/dev/null
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if aws bedrock-agentcore-control get-workload-identity \
|
|
17
|
+
--region "$AWS_REGION" \
|
|
18
|
+
--name "$WORKLOAD_IDENTITY_NAME" >/dev/null 2>&1; then
|
|
19
|
+
aws bedrock-agentcore-control delete-workload-identity \
|
|
20
|
+
--region "$AWS_REGION" \
|
|
21
|
+
--name "$WORKLOAD_IDENTITY_NAME" >/dev/null
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
printf 'AgentCore Identity proof resources deleted: workload=%s provider=%s\n' \
|
|
25
|
+
"$WORKLOAD_IDENTITY_NAME" "$CREDENTIAL_PROVIDER_NAME"
|