@raindancers/raindancers-crew 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.jsii +10552 -0
- package/.kiro/specs/private-brain-construct-changes/design.md +147 -0
- package/.kiro/specs/private-brain-construct-changes/requirements.md +123 -0
- package/.kiro/specs/private-brain-construct-changes/tasks.md +119 -0
- package/API.md +1821 -0
- package/LICENSE +202 -0
- package/README.md +253 -0
- package/lib/crew-backup-bucket.d.ts +62 -0
- package/lib/crew-backup-bucket.js +108 -0
- package/lib/fargate-crew-base.d.ts +68 -0
- package/lib/fargate-crew-base.js +102 -0
- package/lib/fargate-crew.d.ts +89 -0
- package/lib/fargate-crew.js +200 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +22 -0
- package/lib/remote-crew-instance-props.d.ts +287 -0
- package/lib/remote-crew-instance-props.js +15 -0
- package/lib/remote-crew-instance.d.ts +35 -0
- package/lib/remote-crew-instance.js +309 -0
- package/package.json +138 -0
- package/src/assets/backup.sh +57 -0
- package/src/assets/bootstrap.sh +339 -0
- package/src/assets/restore-from-s3.sh +51 -0
- package/src/crew-backup-bucket.ts +107 -0
- package/src/fargate-crew-base.ts +109 -0
- package/src/fargate-crew.ts +248 -0
- package/src/index.ts +5 -0
- package/src/remote-crew-instance-props.ts +316 -0
- package/src/remote-crew-instance.ts +395 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { Aws, Tags } from 'aws-cdk-lib';
|
|
2
|
+
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
3
|
+
import * as logs from 'aws-cdk-lib/aws-logs';
|
|
4
|
+
import { Construct } from 'constructs';
|
|
5
|
+
import { ICrewBackupBucket } from './remote-crew-instance-props';
|
|
6
|
+
|
|
7
|
+
// Mirrors _CREW_RE in the upstream cloud/fargate/identity.py exactly. A
|
|
8
|
+
// trailing hyphen would make the derived role end in "--exec"; a leading one is
|
|
9
|
+
// not a legal start for the derived resource names.
|
|
10
|
+
const CREW_RE = /^[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?$/;
|
|
11
|
+
// Matches the upstream template's PermissionsBoundaryArn AllowedPattern.
|
|
12
|
+
const BOUNDARY_RE =
|
|
13
|
+
/^arn:aws(-[a-z0-9]+)*:iam::[0-9]{12}:policy\/kirocrew-crew-boundary$/;
|
|
14
|
+
const ECR_REPO_RE =
|
|
15
|
+
/^arn:aws(-[a-z0-9]+)*:ecr:[a-z0-9-]{1,32}:[0-9]{12}:repository\/[a-zA-Z0-9._/-]+$/;
|
|
16
|
+
|
|
17
|
+
/** CloudWatch Logs retention values the upstream template permits. */
|
|
18
|
+
const ALLOWED_RETENTION_DAYS = [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Properties for {@link FargateCrew}.
|
|
22
|
+
*/
|
|
23
|
+
export interface FargateCrewProps {
|
|
24
|
+
/**
|
|
25
|
+
* Crew name: 1–32 chars, lower-case alphanumeric with inner hyphens, never
|
|
26
|
+
* leading or trailing. Every resource name is DERIVED from it — the task
|
|
27
|
+
* definition rebuilds the role ARNs and log-group name from the crew name
|
|
28
|
+
* and refuses a document whose ARNs disagree, so a rename here is a launch
|
|
29
|
+
* refusal, not a silent mismatch.
|
|
30
|
+
*/
|
|
31
|
+
readonly crew: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Days a crew's task logs are kept before CloudWatch expires them. Must be
|
|
35
|
+
* one of the CloudWatch retention values.
|
|
36
|
+
*
|
|
37
|
+
* @default 30
|
|
38
|
+
*/
|
|
39
|
+
readonly logRetentionDays?: number;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* ARN of the pre-created shared crew permissions boundary
|
|
43
|
+
* (`arn:aws:iam::<account>:policy/kirocrew-crew-boundary`).
|
|
44
|
+
*
|
|
45
|
+
* Optional by design: this is a DECLARED degraded mode. Unlike the EC2 lane
|
|
46
|
+
* (whose boundary is created once by launcher code), no creator exists for
|
|
47
|
+
* the crew boundary yet, so rather than reference a policy nothing creates
|
|
48
|
+
* this is omitted until that creator lands. When set, it caps what these
|
|
49
|
+
* roles can ever do regardless of attached policies.
|
|
50
|
+
*
|
|
51
|
+
* @default - no boundary (declared degraded mode)
|
|
52
|
+
*/
|
|
53
|
+
readonly permissionsBoundaryArn?: string;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* ARN of the private ECR repository holding the crew image. Leave unset when
|
|
57
|
+
* the image is pulled from a public registry (the decided delivery, ECR
|
|
58
|
+
* Public), which needs no execution-role pull grant. When set, the pull grant
|
|
59
|
+
* is scoped to this one repository.
|
|
60
|
+
*
|
|
61
|
+
* @default - public registry; no pull grant
|
|
62
|
+
*/
|
|
63
|
+
readonly ecrRepositoryArn?: string;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* An S3 backup bucket to grant the TASK role write access to, so the running
|
|
67
|
+
* crew container can push `kirocrew snapshot` bundles off-box on its own
|
|
68
|
+
* schedule. The task role (not the execution role) gets this, because the
|
|
69
|
+
* push runs inside the container. Omit to disable off-box backup.
|
|
70
|
+
*
|
|
71
|
+
* @default - no off-box backup grant
|
|
72
|
+
*/
|
|
73
|
+
readonly backupBucket?: ICrewBackupBucket;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Per-crew Fargate scaffolding for ONE KiroCrew remote crew: the two roles a
|
|
78
|
+
* task carries and the log group it writes to.
|
|
79
|
+
*
|
|
80
|
+
* One per crew, alongside the shared {@link FargateCrewBase}. Deleting this
|
|
81
|
+
* removes exactly one crew's identity and logs and leaves the cluster and its
|
|
82
|
+
* siblings untouched. Port of the upstream `kirocrew-fargate-crew` template.
|
|
83
|
+
*
|
|
84
|
+
* Security invariants preserved from upstream:
|
|
85
|
+
* - The **task role** (the running container's identity, and the blast radius
|
|
86
|
+
* one agent turn reaches) is created with NO policies and MUST NEVER be
|
|
87
|
+
* granted `secretsmanager:GetSecretValue` — the model credential is already
|
|
88
|
+
* in the container env, so the grant buys nothing while letting one turn read
|
|
89
|
+
* every crew's secret.
|
|
90
|
+
* - The **execution role** reads secrets scoped to `kirocrew/crew/<crew>/*`
|
|
91
|
+
* only, with individually-listed actions (no prefix wildcards).
|
|
92
|
+
* - Both assume-role policies carry an `aws:SourceAccount` condition so the
|
|
93
|
+
* roles are not assumable on behalf of an unrelated stack's task.
|
|
94
|
+
*/
|
|
95
|
+
export class FargateCrew extends Construct {
|
|
96
|
+
/** Role ECS assumes BEFORE the container starts (secret fetch + log stream). */
|
|
97
|
+
public readonly executionRole: iam.Role;
|
|
98
|
+
/** Identity the RUNNING container carries. Created with no policies. */
|
|
99
|
+
public readonly taskRole: iam.Role;
|
|
100
|
+
/** The crew's log group (`/kirocrew/crew/<crew>`). */
|
|
101
|
+
public readonly logGroup: logs.LogGroup;
|
|
102
|
+
/** The crew this construct scaffolds. */
|
|
103
|
+
public readonly crew: string;
|
|
104
|
+
/** The one secret ARN pattern the execution role may read. */
|
|
105
|
+
public readonly secretArnPattern: string;
|
|
106
|
+
|
|
107
|
+
constructor(scope: Construct, id: string, props: FargateCrewProps) {
|
|
108
|
+
super(scope, id);
|
|
109
|
+
|
|
110
|
+
const crew = props.crew;
|
|
111
|
+
if (!CREW_RE.test(crew)) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
'crew must be 1-32 chars, lower-case alphanumeric with inner hyphens ' +
|
|
114
|
+
`(never leading/trailing); got '${crew}'`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
const retention = props.logRetentionDays ?? 30;
|
|
118
|
+
if (!ALLOWED_RETENTION_DAYS.includes(retention)) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`logRetentionDays must be one of ${ALLOWED_RETENTION_DAYS.join(', ')}`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
if (props.permissionsBoundaryArn && !BOUNDARY_RE.test(props.permissionsBoundaryArn)) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
'permissionsBoundaryArn must be a kirocrew-crew-boundary policy ARN',
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (props.ecrRepositoryArn && !ECR_REPO_RE.test(props.ecrRepositoryArn)) {
|
|
129
|
+
throw new Error('ecrRepositoryArn must be a valid ECR repository ARN');
|
|
130
|
+
}
|
|
131
|
+
this.crew = crew;
|
|
132
|
+
|
|
133
|
+
const boundary = props.permissionsBoundaryArn
|
|
134
|
+
? iam.ManagedPolicy.fromManagedPolicyArn(this, 'Boundary', props.permissionsBoundaryArn)
|
|
135
|
+
: undefined;
|
|
136
|
+
|
|
137
|
+
// Log group. Name is FIXED (logConfiguration is one of the four fields
|
|
138
|
+
// RunTask cannot override), derived as /kirocrew/crew/<crew>.
|
|
139
|
+
this.logGroup = new logs.LogGroup(this, 'LogGroup', {
|
|
140
|
+
logGroupName: `/kirocrew/crew/${crew}`,
|
|
141
|
+
retention: retentionToEnum(retention),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const secretArnPattern = `arn:${Aws.PARTITION}:secretsmanager:${Aws.REGION}:${Aws.ACCOUNT_ID}:secret:kirocrew/crew/${crew}/*`;
|
|
145
|
+
this.secretArnPattern = secretArnPattern;
|
|
146
|
+
const logGroupArn = `arn:${Aws.PARTITION}:logs:${Aws.REGION}:${Aws.ACCOUNT_ID}:log-group:/kirocrew/crew/${crew}:*`;
|
|
147
|
+
|
|
148
|
+
const assumedBy = new iam.ServicePrincipal('ecs-tasks.amazonaws.com', {
|
|
149
|
+
conditions: {
|
|
150
|
+
// Without this the role is assumable on behalf of any task in the
|
|
151
|
+
// account, including one an unrelated stack registered.
|
|
152
|
+
StringEquals: { 'aws:SourceAccount': Aws.ACCOUNT_ID },
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Execution role: what ECS assumes BEFORE the container starts, to fetch
|
|
157
|
+
// secrets and open the log stream. The container never holds this role,
|
|
158
|
+
// which is why the secret read lives here and not on the task role.
|
|
159
|
+
this.executionRole = new iam.Role(this, 'ExecutionRole', {
|
|
160
|
+
roleName: `kirocrew-crew-${crew}-exec`,
|
|
161
|
+
assumedBy,
|
|
162
|
+
permissionsBoundary: boundary,
|
|
163
|
+
});
|
|
164
|
+
this.executionRole.addToPolicy(
|
|
165
|
+
new iam.PolicyStatement({
|
|
166
|
+
// Scoped to THIS crew's secret namespace. The trailing wildcard covers
|
|
167
|
+
// only the six-char suffix Secrets Manager appends; it does not widen
|
|
168
|
+
// the crew segment, so this role cannot read a sibling's secret.
|
|
169
|
+
actions: ['secretsmanager:GetSecretValue'],
|
|
170
|
+
resources: [secretArnPattern],
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
173
|
+
this.executionRole.addToPolicy(
|
|
174
|
+
new iam.PolicyStatement({
|
|
175
|
+
// CreateLogGroup is absent on purpose: the group is created by this
|
|
176
|
+
// construct, so a task that could create one could write outside the
|
|
177
|
+
// namespace it is retained and deleted under.
|
|
178
|
+
actions: ['logs:CreateLogStream', 'logs:PutLogEvents'],
|
|
179
|
+
resources: [logGroupArn],
|
|
180
|
+
}),
|
|
181
|
+
);
|
|
182
|
+
if (props.ecrRepositoryArn) {
|
|
183
|
+
// GetAuthorizationToken is account-wide by the service's own design (it
|
|
184
|
+
// names no repository); the layer/image reads are scoped to the repo.
|
|
185
|
+
this.executionRole.addToPolicy(
|
|
186
|
+
new iam.PolicyStatement({
|
|
187
|
+
actions: ['ecr:GetAuthorizationToken'],
|
|
188
|
+
resources: ['*'],
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
this.executionRole.addToPolicy(
|
|
192
|
+
new iam.PolicyStatement({
|
|
193
|
+
actions: [
|
|
194
|
+
'ecr:BatchCheckLayerAvailability',
|
|
195
|
+
'ecr:BatchGetImage',
|
|
196
|
+
'ecr:GetDownloadUrlForLayer',
|
|
197
|
+
],
|
|
198
|
+
resources: [props.ecrRepositoryArn],
|
|
199
|
+
}),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Task role: the identity the RUNNING container carries. Created with NO
|
|
204
|
+
// policies at all — the intended starting state. It must NEVER be granted
|
|
205
|
+
// secretsmanager:GetSecretValue (see class docstring).
|
|
206
|
+
this.taskRole = new iam.Role(this, 'TaskRole', {
|
|
207
|
+
roleName: `kirocrew-crew-${crew}-task`,
|
|
208
|
+
assumedBy,
|
|
209
|
+
permissionsBoundary: boundary,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Off-box backup: the RUNNING container pushes snapshots, so the grant goes
|
|
213
|
+
// on the task role. It stays scoped to the backup bucket + its KMS key —
|
|
214
|
+
// this does not reintroduce any secret-read the task role is denied.
|
|
215
|
+
if (props.backupBucket) {
|
|
216
|
+
props.backupBucket.grantWrite(this.taskRole);
|
|
217
|
+
props.backupBucket.grantRead(this.taskRole);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
for (const taggable of [this.logGroup, this.executionRole, this.taskRole]) {
|
|
221
|
+
Tags.of(taggable).add('kirocrew:managed', 'true');
|
|
222
|
+
Tags.of(taggable).add('kirocrew:crew', crew);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Namespace a crew secret must be created under for the exec role to read it. */
|
|
227
|
+
public get secretNamePrefix(): string {
|
|
228
|
+
return `kirocrew/crew/${this.crew}/`;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function retentionToEnum(days: number): logs.RetentionDays {
|
|
233
|
+
const map: Record<number, logs.RetentionDays> = {
|
|
234
|
+
1: logs.RetentionDays.ONE_DAY,
|
|
235
|
+
3: logs.RetentionDays.THREE_DAYS,
|
|
236
|
+
5: logs.RetentionDays.FIVE_DAYS,
|
|
237
|
+
7: logs.RetentionDays.ONE_WEEK,
|
|
238
|
+
14: logs.RetentionDays.TWO_WEEKS,
|
|
239
|
+
30: logs.RetentionDays.ONE_MONTH,
|
|
240
|
+
60: logs.RetentionDays.TWO_MONTHS,
|
|
241
|
+
90: logs.RetentionDays.THREE_MONTHS,
|
|
242
|
+
120: logs.RetentionDays.FOUR_MONTHS,
|
|
243
|
+
150: logs.RetentionDays.FIVE_MONTHS,
|
|
244
|
+
180: logs.RetentionDays.SIX_MONTHS,
|
|
245
|
+
365: logs.RetentionDays.ONE_YEAR,
|
|
246
|
+
};
|
|
247
|
+
return map[days];
|
|
248
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
|
2
|
+
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
3
|
+
import * as s3 from 'aws-cdk-lib/aws-s3';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* CPU architecture for the KiroCrew EC2 instance. Selects the matching
|
|
7
|
+
* Amazon Linux 2023 AMI and the pinned Node.js / kiro-cli download.
|
|
8
|
+
*/
|
|
9
|
+
export enum CrewArchitecture {
|
|
10
|
+
/** 64-bit ARM (Graviton). The upstream default. */
|
|
11
|
+
ARM64 = 'arm64',
|
|
12
|
+
/** 64-bit x86. */
|
|
13
|
+
X86_64 = 'x86_64',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* How the KiroCrew source is delivered to the instance at first boot.
|
|
18
|
+
*
|
|
19
|
+
* Exactly one mode is active. When {@link sourceBucket} is set the instance
|
|
20
|
+
* downloads a source tarball from S3 (and is granted read on just that one
|
|
21
|
+
* object); otherwise it shallow-clones {@link kirocrewRef} from
|
|
22
|
+
* {@link kirocrewRepo}.
|
|
23
|
+
*/
|
|
24
|
+
export interface CrewSource {
|
|
25
|
+
/**
|
|
26
|
+
* S3 bucket holding the source tarball (`kirocrew-src.tar.gz`). When set,
|
|
27
|
+
* the instance role is granted `s3:GetObject` on this object ONLY. Leave
|
|
28
|
+
* unset to clone from git instead.
|
|
29
|
+
*
|
|
30
|
+
* @default - clone from git (see kirocrewRepo / kirocrewRef)
|
|
31
|
+
*/
|
|
32
|
+
readonly sourceBucket?: string;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* S3 key of the source tarball within {@link sourceBucket}. Required when
|
|
36
|
+
* `sourceBucket` is set; ignored otherwise.
|
|
37
|
+
*
|
|
38
|
+
* @default - none
|
|
39
|
+
*/
|
|
40
|
+
readonly sourceKey?: string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Git repository to clone when no S3 source is configured.
|
|
44
|
+
*
|
|
45
|
+
* @default 'https://github.com/kirodotdev/KiroCrew.git'
|
|
46
|
+
*/
|
|
47
|
+
readonly kirocrewRepo?: string;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Git ref (branch or tag) to install when cloning.
|
|
51
|
+
*
|
|
52
|
+
* Pin this to a released tag for reproducible, version-controlled deploys —
|
|
53
|
+
* the upstream launcher defaults to `main`, which drifts. This construct
|
|
54
|
+
* defaults to `main` only to match upstream; SET IT to a tag in production.
|
|
55
|
+
*
|
|
56
|
+
* @default 'main'
|
|
57
|
+
*/
|
|
58
|
+
readonly kirocrewRef?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Exposes the gateway's webhook port to ONE source security group.
|
|
63
|
+
*
|
|
64
|
+
* There is deliberately no CIDR form: the brain box is never internet-
|
|
65
|
+
* reachable by contract. The named source SG (e.g. an ingest Lambda's SG, or a
|
|
66
|
+
* reverse proxy that fronts the loopback gateway) is the only peer allowed to
|
|
67
|
+
* reach the port.
|
|
68
|
+
*
|
|
69
|
+
* NOTE: the KiroCrew gateway binds loopback (`127.0.0.1`) only — it exposes no
|
|
70
|
+
* routable listener. This rule opens the security group so a consumer-owned
|
|
71
|
+
* reverse proxy / tunnel on the box can be reached from the source SG; actually
|
|
72
|
+
* serving the webhook on a routable interface is the consumer's concern (see
|
|
73
|
+
* README "Private dual-stack brain" and the webhook Decisions-for-review).
|
|
74
|
+
*/
|
|
75
|
+
export interface WebhookIngress {
|
|
76
|
+
/**
|
|
77
|
+
* Imported security group allowed to reach the webhook port. Passed as an
|
|
78
|
+
* `ISecurityGroup` (imported) — this construct never creates it.
|
|
79
|
+
*/
|
|
80
|
+
readonly source: ec2.ISecurityGroup;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* TCP port the ingress rule opens. Defaults to the dashboard/gateway port so
|
|
84
|
+
* a reverse proxy fronting the loopback gateway is reachable; override to
|
|
85
|
+
* target a consumer proxy on a different port.
|
|
86
|
+
*
|
|
87
|
+
* @default - the resolved dashboardPort (5476)
|
|
88
|
+
*/
|
|
89
|
+
readonly port?: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Always-on runtime settings for the hosted crew, threaded into the crew
|
|
94
|
+
* `config.json` at boot. All fields are optional and default to the current
|
|
95
|
+
* gateway behaviour; omitting {@link RemoteCrewInstanceProps.crewRuntime}
|
|
96
|
+
* entirely reproduces today's `kirocrew setup --agent-only` + `kirocrew
|
|
97
|
+
* gateway` exactly.
|
|
98
|
+
*
|
|
99
|
+
* Verified against KiroCrew v0.6.0: autopilot maps to `agent.approval_mode`,
|
|
100
|
+
* idle-close maps to `session.timeout_secs`, and the conductor roster ships
|
|
101
|
+
* with `setup --agent-only` (custom members are source-delivered JSON under
|
|
102
|
+
* `~/.kiro/agents/`).
|
|
103
|
+
*/
|
|
104
|
+
export interface CrewRuntime {
|
|
105
|
+
/**
|
|
106
|
+
* Enable Autopilot: the hosted crew auto-approves tool calls that pass its
|
|
107
|
+
* security checks (deny rules and sensitive-path blocks still apply). Sets
|
|
108
|
+
* `agent.approval_mode` to `"auto"` in config.json.
|
|
109
|
+
*
|
|
110
|
+
* @default false (interactive; gateway default)
|
|
111
|
+
*/
|
|
112
|
+
readonly autopilot?: boolean;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Keep the 24/7 brain session alive between events by disabling the idle
|
|
116
|
+
* session sweep. Sets `session.timeout_secs` to `0` (documented: "0 disables
|
|
117
|
+
* the idle sweep").
|
|
118
|
+
*
|
|
119
|
+
* @default false (default 3600s idle timeout applies)
|
|
120
|
+
*/
|
|
121
|
+
readonly disableIdleClose?: boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Properties for {@link RemoteCrewInstance}.
|
|
126
|
+
*/
|
|
127
|
+
export interface RemoteCrewInstanceProps {
|
|
128
|
+
/**
|
|
129
|
+
* VPC to launch the instance into.
|
|
130
|
+
*/
|
|
131
|
+
readonly vpc: ec2.IVpc;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* ARN of an IAM permissions boundary applied to the instance role.
|
|
135
|
+
*
|
|
136
|
+
* The instance runs a prompt-injectable agent that executes arbitrary
|
|
137
|
+
* tools, so its role MUST carry a boundary that caps blast radius. This is
|
|
138
|
+
* required, not optional.
|
|
139
|
+
*/
|
|
140
|
+
readonly permissionsBoundaryArn: string;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Subnet selection for the instance. A public (IGW-routed) subnet needs a
|
|
144
|
+
* public IP for egress; a private (NAT-routed) subnet does not — see
|
|
145
|
+
* {@link associatePublicIp}.
|
|
146
|
+
*
|
|
147
|
+
* @default - one public subnet in the VPC
|
|
148
|
+
*/
|
|
149
|
+
readonly vpcSubnets?: ec2.SubnetSelection;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* EC2 instance type.
|
|
153
|
+
*
|
|
154
|
+
* @default - m7g.2xlarge (arm64) / m7i.2xlarge (x86_64), matching the
|
|
155
|
+
* upstream "Development" size tier
|
|
156
|
+
*/
|
|
157
|
+
readonly instanceType?: ec2.InstanceType;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* CPU architecture. Must match {@link instanceType} when that is set.
|
|
161
|
+
*
|
|
162
|
+
* @default CrewArchitecture.ARM64
|
|
163
|
+
*/
|
|
164
|
+
readonly architecture?: CrewArchitecture;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* gp3 root volume size in GiB (20–1000). The volume is always encrypted.
|
|
168
|
+
*
|
|
169
|
+
* @default 60
|
|
170
|
+
*/
|
|
171
|
+
readonly volumeSizeGb?: number;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Attach a public IP to the instance ENI. Required for egress on IGW-only
|
|
175
|
+
* subnets; leave off (false) for NAT-routed private subnets, where it is
|
|
176
|
+
* unused attack surface.
|
|
177
|
+
*
|
|
178
|
+
* @default true
|
|
179
|
+
*/
|
|
180
|
+
readonly associatePublicIp?: boolean;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Assign an IPv6 address to the instance's primary ENI and permit IPv6
|
|
184
|
+
* egress on the security group.
|
|
185
|
+
*
|
|
186
|
+
* Enables a dual-stack posture: combined with `associatePublicIp: false` and
|
|
187
|
+
* a private, IPv6-capable subnet, the instance egresses over IPv6 (via the
|
|
188
|
+
* VPC's Egress-Only Internet Gateway) with no public IPv4. CDK's
|
|
189
|
+
* `allowAllOutbound` renders IPv4 `0.0.0.0/0` egress only, so this also adds
|
|
190
|
+
* an explicit all-traffic IPv6 egress rule.
|
|
191
|
+
*
|
|
192
|
+
* This construct does NOT provision subnet IPv6 CIDRs, an Egress-Only
|
|
193
|
+
* Internet Gateway, or any route — those are the consumer VPC's
|
|
194
|
+
* responsibility. The selected subnet(s) MUST already carry IPv6 CIDRs.
|
|
195
|
+
*
|
|
196
|
+
* @default false
|
|
197
|
+
*/
|
|
198
|
+
readonly enableIpv6?: boolean;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Discovery tag value written as `kirocrew:instance`. Must match
|
|
202
|
+
* `[a-zA-Z0-9-]{1,51}`.
|
|
203
|
+
*
|
|
204
|
+
* @default 'kirocrew'
|
|
205
|
+
*/
|
|
206
|
+
readonly stackTag?: string;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* TCP port the gateway serves the dashboard on (loopback only; reached via
|
|
210
|
+
* SSM port-forward). Recorded in the instance registry as the remote port.
|
|
211
|
+
*
|
|
212
|
+
* @default 5476
|
|
213
|
+
*/
|
|
214
|
+
readonly dashboardPort?: number;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Optional SSH ingress CIDR. When set (and no wider than /16), opens tcp/22
|
|
218
|
+
* from that CIDR as a fallback. Omit for SSM-only access (recommended).
|
|
219
|
+
*
|
|
220
|
+
* @default - no inbound; SSM-only
|
|
221
|
+
*/
|
|
222
|
+
readonly allowSshCidr?: string;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Open the gateway/webhook port to ONE source security group only (never a
|
|
226
|
+
* CIDR). Independent of {@link allowSshCidr} — both, either, or neither may
|
|
227
|
+
* be set; unset leaves the SG no-inbound (the default).
|
|
228
|
+
*
|
|
229
|
+
* @default - no webhook ingress
|
|
230
|
+
*/
|
|
231
|
+
readonly webhookIngress?: WebhookIngress;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Secrets Manager ARN of the Bearer token that authenticates the native
|
|
235
|
+
* webhook (`POST /api/hooks/agent`). At boot the instance fetches the secret
|
|
236
|
+
* and writes it as `hooks.webhook_token` in the crew `config.json` — the
|
|
237
|
+
* token is never baked into userData, env literals, or code. The instance
|
|
238
|
+
* role is granted `secretsmanager:GetSecretValue` on THIS ARN only.
|
|
239
|
+
*
|
|
240
|
+
* Required when {@link webhookIngress} is set: a reachable webhook with no
|
|
241
|
+
* auth is a defect, not a default, so synth fails if ingress is opened
|
|
242
|
+
* without a token.
|
|
243
|
+
*
|
|
244
|
+
* NOTE: the KiroCrew gateway binds loopback (`127.0.0.1`) only and exposes no
|
|
245
|
+
* routable webhook listener — see the webhook Decisions-for-review in the PR.
|
|
246
|
+
* This wires the AUTH (token-in-config); routable exposure of the loopback
|
|
247
|
+
* route is a consumer reverse-proxy / tunnel concern.
|
|
248
|
+
*
|
|
249
|
+
* @default - webhook auth not configured (loopback / SSM only)
|
|
250
|
+
*/
|
|
251
|
+
readonly webhookTokenSecretArn?: string;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Always-on runtime settings (Autopilot, no-idle-close) for the hosted crew,
|
|
255
|
+
* threaded into config.json at boot. Omit for the current gateway defaults.
|
|
256
|
+
*
|
|
257
|
+
* @default - current gateway behaviour (interactive, 3600s idle timeout)
|
|
258
|
+
*/
|
|
259
|
+
readonly crewRuntime?: CrewRuntime;
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* How the KiroCrew source reaches the instance (S3 tarball or git clone).
|
|
263
|
+
*
|
|
264
|
+
* @default - clone kirodotdev/KiroCrew@main
|
|
265
|
+
*/
|
|
266
|
+
readonly source?: CrewSource;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Minutes to wait for the gateway to become healthy before the stack fails
|
|
270
|
+
* and rolls back (cold boot + dnf + Node + vite build + pip).
|
|
271
|
+
*
|
|
272
|
+
* @default 25
|
|
273
|
+
*/
|
|
274
|
+
readonly bootstrapTimeoutMinutes?: number;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* An S3 backup bucket to push crew snapshots to on a schedule. When set, the
|
|
278
|
+
* instance role is granted write, a systemd timer runs
|
|
279
|
+
* `kirocrew snapshot --purpose backup` and uploads the newest (redaction-
|
|
280
|
+
* scrubbed) bundle, and a `kirocrew-restore-from-s3` helper is installed for
|
|
281
|
+
* rebuilding a replacement instance. Omit to disable off-box backup.
|
|
282
|
+
*
|
|
283
|
+
* @default - no off-box backup
|
|
284
|
+
*/
|
|
285
|
+
readonly backupBucket?: ICrewBackupBucket;
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* systemd OnCalendar expression for the backup timer (see
|
|
289
|
+
* `man systemd.time`). Only used when {@link backupBucket} is set.
|
|
290
|
+
*
|
|
291
|
+
* @default 'daily'
|
|
292
|
+
*/
|
|
293
|
+
readonly backupSchedule?: string;
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* S3 key prefix under which snapshots are stored in the backup bucket.
|
|
297
|
+
* Only used when {@link backupBucket} is set. A trailing slash is added if
|
|
298
|
+
* absent.
|
|
299
|
+
*
|
|
300
|
+
* @default 'crew-snapshots/'
|
|
301
|
+
*/
|
|
302
|
+
readonly backupPrefix?: string;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* The subset of {@link CrewBackupBucket} the EC2/Fargate constructs need. Kept
|
|
307
|
+
* as an interface so a consumer can pass their own bucket wrapper.
|
|
308
|
+
*/
|
|
309
|
+
export interface ICrewBackupBucket {
|
|
310
|
+
/** The destination bucket name. */
|
|
311
|
+
readonly bucket: s3.IBucket;
|
|
312
|
+
/** Grant a principal write access to snapshots (bucket + KMS). */
|
|
313
|
+
grantWrite(grantee: iam.IGrantable): void;
|
|
314
|
+
/** Grant a principal read access to snapshots (bucket + KMS). */
|
|
315
|
+
grantRead(grantee: iam.IGrantable): void;
|
|
316
|
+
}
|