mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 13:53:15 +00:00
AppService construct wires the per-env EC2 box and its internet path. The
seahaven-vpc and the internet-facing seahaven-com ALB are SHARED with the
on-prem seahaven-site stack, so everything VPC/ALB/zone-side is IMPORTED and
never owned/mutated; open-swe only ADDS its own resources.
Per env (open-swe-stack.ts → AppService):
- ARM64 EC2 box (t4g.medium dev / t4g.large prod) in private1 (us-east-1a,
in-AZ NAT egress). requireImdsv2, gp3-encrypted root, deleteOnTermination
(no RETAIN volume — replacement-tolerant; see ami-cache.ts).
userDataCausesReplacement; user-data rendered from deploy/ami/user-data.sh.
- Standalone instance SG: ingress ONLY from the shared ALB SG on :80; egress
via NAT. The ALB SG is opened to the box via a STANDALONE CfnSecurityGroupEgress
(the imported, on-prem-owned SG is never mutated).
- Target group → instance:80 (nginx is sole ingress; LangGraph :2024 stays
loopback). Health check GET /healthz.
- Two rules on the imported :443 listener, both → the TG:
* webhooks (priority 2 dev / 3 prod): host∈{openswe,hooks}-<env> AND /webhooks/*
* site (priority 10 dev / 11 prod): host=openswe-<env> (dashboard SPA + api)
Webhooks MUST sit below the on-prem host-agnostic /webhooks/* rule (priority 5)
or it would steal every webhook — first-match-by-ascending-priority.
- Route53 alias records (openswe[-dev] + hooks[-dev]) → shared ALB.
- 4 CloudWatch log groups at 30-day retention (IaC-owned; mirrors CW-agent config).
Security (/sh-security-review T12): iac-iam pass clean. Logic pass → 1 confirmed
medium fixed (OSWE-T12-01: nginx 1MB default client_max_body_size would 413 large
GitHub webhooks pre-signature-verification → set 25m on /webhooks/, 10m on
/dashboard/api/); hooks host scoped to /webhooks/* only (OSWE-T12-02 hygiene);
XFF-spoof candidate killed (no code trusts leftmost XFF). No confirmed
critical/high.
Synth-only; not deployed. AMI is the cdk.context.json placeholder until the baked
open-swe-base-arm64 id is pinned pre-deploy. tsc/synth(dev+prod)/jest(16) clean.
Next: T13 GPT-4.1 cross-review of the SG/listener diff before any deploy.
84 lines
3.5 KiB
TypeScript
84 lines
3.5 KiB
TypeScript
import * as cdk from "aws-cdk-lib";
|
|
import { Construct } from "constructs";
|
|
import { EnvName, prefix } from "./config";
|
|
import { AppService } from "./constructs/app-service";
|
|
import { ConfigStore } from "./constructs/config-store";
|
|
import { InstanceRole } from "./constructs/instance-role";
|
|
import {
|
|
AL2023_ARM64_SSM_CONTEXT_KEY,
|
|
cachedArm64AmazonLinux2023,
|
|
} from "./constructs/ami-cache";
|
|
|
|
export interface OpenSweStackProps extends cdk.StackProps {
|
|
/** open-swe environment — drives the `open-swe-<env>-*` resource naming. */
|
|
readonly envName: EnvName;
|
|
}
|
|
|
|
/**
|
|
* Per-env open-swe stack (`open-swe-dev` / `open-swe-prod`). Resource names are
|
|
* prefixed `open-swe-<env>-*`.
|
|
*
|
|
* Composes: the per-env least-privilege instance role (T6), the Secrets/SSM
|
|
* config store (T11), and the compute + ingress wiring (T12, AppService — EC2
|
|
* box, instance SG, target group, imported-listener rules, Route53 aliases,
|
|
* 30-day log groups). The shared VPC and ALB are imported, never owned. Synth is
|
|
* offline (AMI is the cdk.context.json-pinned placeholder until T12-deploy).
|
|
*/
|
|
export class OpenSweStack extends cdk.Stack {
|
|
public readonly instanceRole: InstanceRole;
|
|
public readonly configStore: ConfigStore;
|
|
public readonly appService: AppService;
|
|
|
|
constructor(scope: Construct, id: string, props: OpenSweStackProps) {
|
|
super(scope, id, props);
|
|
|
|
const envName = props.envName;
|
|
const p = prefix(envName);
|
|
|
|
cdk.Tags.of(this).add("project", "open-swe");
|
|
cdk.Tags.of(this).add("env", envName);
|
|
cdk.Tags.of(this).add("ManagedBy", "cdk");
|
|
|
|
// Per-env least-privilege EC2 instance role (open-swe-<env>-instance-role).
|
|
this.instanceRole = new InstanceRole(this, "Instance", envName);
|
|
|
|
// Secrets Manager + SSM Parameter Store shells the boot hook reads
|
|
// (deploy/seahaven/fetch-config.sh). Secret shells are value-less and
|
|
// populated out-of-band; IaC-managed SSM params carry real derivable values.
|
|
// The instance role already grants read on open-swe-<env>/* + /open-swe-<env>/*.
|
|
this.configStore = new ConfigStore(this, "Config", { envName });
|
|
|
|
// AMI cache discipline (see lib/constructs/ami-cache.ts). T3 is synth-only:
|
|
// resolve + surface the pinned AMI id ONLY when it is already cached in
|
|
// cdk.context.json, so synth never makes a live SSM call. T12 consumes
|
|
// `cachedArm64AmazonLinux2023()` for the actual ec2.Instance.
|
|
if (this.node.tryGetContext(AL2023_ARM64_SSM_CONTEXT_KEY) !== undefined) {
|
|
const amiId = cachedArm64AmazonLinux2023().getImage(this).imageId;
|
|
new cdk.CfnOutput(this, "PinnedAmiId", {
|
|
value: amiId,
|
|
description:
|
|
"Cached AL2023 ARM64 AMI id (pinned in cdk.context.json; consumed by the T12 EC2 instance).",
|
|
});
|
|
}
|
|
|
|
// T12: compute + ingress. Imports the shared seahaven-vpc + ALB and adds the
|
|
// env's EC2 box, instance SG, target group, listener rules, DNS, log groups.
|
|
this.appService = new AppService(this, "App", {
|
|
envName,
|
|
instanceRole: this.instanceRole.role,
|
|
});
|
|
|
|
new cdk.CfnOutput(this, "InstanceRoleArn", {
|
|
value: this.instanceRole.role.roleArn,
|
|
description: `${p} EC2 instance role ARN.`,
|
|
});
|
|
new cdk.CfnOutput(this, "InstanceId", {
|
|
value: this.appService.instance.instanceId,
|
|
description: `${p} EC2 instance id.`,
|
|
});
|
|
new cdk.CfnOutput(this, "TargetGroupArn", {
|
|
value: this.appService.targetGroup.targetGroupArn,
|
|
description: `${p} ALB target group ARN (→ instance:80 nginx).`,
|
|
});
|
|
}
|
|
}
|