@quiltdata/benchling-webhook 0.9.0-20251127T201515Z → 0.9.0-20251129T071202Z

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.
Files changed (37) hide show
  1. package/README.md +111 -5
  2. package/dist/bin/benchling-webhook.js +6 -0
  3. package/dist/bin/benchling-webhook.js.map +1 -1
  4. package/dist/bin/cli.js +1 -0
  5. package/dist/bin/cli.js.map +1 -1
  6. package/dist/bin/commands/deploy.d.ts +2 -0
  7. package/dist/bin/commands/deploy.d.ts.map +1 -1
  8. package/dist/bin/commands/deploy.js +82 -0
  9. package/dist/bin/commands/deploy.js.map +1 -1
  10. package/dist/bin/commands/logs.d.ts.map +1 -1
  11. package/dist/bin/commands/logs.js +20 -3
  12. package/dist/bin/commands/logs.js.map +1 -1
  13. package/dist/bin/commands/status.d.ts +1 -0
  14. package/dist/bin/commands/status.d.ts.map +1 -1
  15. package/dist/bin/commands/status.js +55 -5
  16. package/dist/bin/commands/status.js.map +1 -1
  17. package/dist/lib/benchling-webhook-stack.d.ts.map +1 -1
  18. package/dist/lib/benchling-webhook-stack.js +18 -19
  19. package/dist/lib/benchling-webhook-stack.js.map +1 -1
  20. package/dist/lib/http-api-gateway.d.ts +2 -0
  21. package/dist/lib/http-api-gateway.d.ts.map +1 -1
  22. package/dist/lib/http-api-gateway.js +79 -9
  23. package/dist/lib/http-api-gateway.js.map +1 -1
  24. package/dist/lib/index.d.ts +0 -1
  25. package/dist/lib/index.d.ts.map +1 -1
  26. package/dist/lib/index.js +2 -4
  27. package/dist/lib/index.js.map +1 -1
  28. package/dist/lib/waf-web-acl.d.ts +51 -0
  29. package/dist/lib/waf-web-acl.d.ts.map +1 -0
  30. package/dist/lib/waf-web-acl.js +192 -0
  31. package/dist/lib/waf-web-acl.js.map +1 -0
  32. package/dist/package.json +6 -3
  33. package/package.json +6 -3
  34. package/dist/lib/rest-api-gateway.d.ts +0 -32
  35. package/dist/lib/rest-api-gateway.d.ts.map +0 -1
  36. package/dist/lib/rest-api-gateway.js +0 -268
  37. package/dist/lib/rest-api-gateway.js.map +0 -1
@@ -0,0 +1,51 @@
1
+ import * as wafv2 from "aws-cdk-lib/aws-wafv2";
2
+ import * as logs from "aws-cdk-lib/aws-logs";
3
+ import { Construct } from "constructs";
4
+ export interface WafWebAclProps {
5
+ /**
6
+ * Comma-separated list of allowed IP addresses/CIDR blocks
7
+ *
8
+ * Empty string means no IP filtering (discovery mode - COUNT all requests).
9
+ * Non-empty string enables IP filtering (security mode - BLOCK unknown IPs).
10
+ *
11
+ * @example "192.168.1.0/24,10.0.0.0/8"
12
+ * @default ""
13
+ */
14
+ readonly ipAllowList?: string;
15
+ }
16
+ /**
17
+ * WAF Web ACL for Benchling webhook IP filtering
18
+ *
19
+ * Provides defense-in-depth security at AWS edge with two rules:
20
+ * 1. Health check exception - Always allow /health, /health/ready, /health/live
21
+ * 2. IP allowlist - Allow requests from configured IP ranges
22
+ *
23
+ * **Automatic Mode Selection:**
24
+ * - Empty IP allowlist → COUNT mode (discovery phase - logs requests but doesn't block)
25
+ * - Non-empty IP allowlist → BLOCK mode (security phase - blocks unknown IPs)
26
+ *
27
+ * This allows customers to deploy initially without knowing Benchling IPs,
28
+ * discover them from CloudWatch logs, then add them to enable blocking mode.
29
+ */
30
+ export declare class WafWebAcl extends Construct {
31
+ readonly webAcl: wafv2.CfnWebACL;
32
+ readonly ipSet: wafv2.CfnIPSet;
33
+ readonly logGroup: logs.ILogGroup;
34
+ constructor(scope: Construct, id: string, props?: WafWebAclProps);
35
+ /**
36
+ * Parse IP allowlist string into array of CIDR blocks
37
+ *
38
+ * - Splits by comma
39
+ * - Trims whitespace
40
+ * - Adds /32 suffix if not present
41
+ * - Filters out empty entries
42
+ *
43
+ * @param allowList Comma-separated list of IPs/CIDR blocks
44
+ * @returns Array of CIDR blocks
45
+ *
46
+ * @example
47
+ * parseIpAllowList("192.168.1.0/24, 10.0.0.1") → ["192.168.1.0/24", "10.0.0.1/32"]
48
+ */
49
+ private parseIpAllowList;
50
+ }
51
+ //# sourceMappingURL=waf-web-acl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waf-web-acl.d.ts","sourceRoot":"","sources":["../../lib/waf-web-acl.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,IAAI,MAAM,sBAAsB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,WAAW,cAAc;IAC3B;;;;;;;;OAQG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CACjC;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,SAAU,SAAQ,SAAS;IACpC,SAAgB,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC;IACxC,SAAgB,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC;IACtC,SAAgB,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC;gBAE7B,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,GAAE,cAAmB;IA2HpE;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,gBAAgB;CAU3B"}
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.WafWebAcl = void 0;
37
+ const cdk = __importStar(require("aws-cdk-lib"));
38
+ const wafv2 = __importStar(require("aws-cdk-lib/aws-wafv2"));
39
+ const logs = __importStar(require("aws-cdk-lib/aws-logs"));
40
+ const constructs_1 = require("constructs");
41
+ /**
42
+ * WAF Web ACL for Benchling webhook IP filtering
43
+ *
44
+ * Provides defense-in-depth security at AWS edge with two rules:
45
+ * 1. Health check exception - Always allow /health, /health/ready, /health/live
46
+ * 2. IP allowlist - Allow requests from configured IP ranges
47
+ *
48
+ * **Automatic Mode Selection:**
49
+ * - Empty IP allowlist → COUNT mode (discovery phase - logs requests but doesn't block)
50
+ * - Non-empty IP allowlist → BLOCK mode (security phase - blocks unknown IPs)
51
+ *
52
+ * This allows customers to deploy initially without knowing Benchling IPs,
53
+ * discover them from CloudWatch logs, then add them to enable blocking mode.
54
+ */
55
+ class WafWebAcl extends constructs_1.Construct {
56
+ constructor(scope, id, props = {}) {
57
+ super(scope, id);
58
+ // Parse IP allowlist with CIDR notation normalization
59
+ const ipAllowList = this.parseIpAllowList(props.ipAllowList || "");
60
+ // Automatic mode selection based on IP allowlist
61
+ // - Empty allowlist: Allow mode (discovery - logs all requests, no blocking)
62
+ // - Has IPs: Block mode (security - blocks unknown IPs)
63
+ const isDiscoveryMode = ipAllowList.length === 0;
64
+ const mode = isDiscoveryMode ? "Allow" : "Block";
65
+ console.log(`WAF mode: ${mode} (${isDiscoveryMode ? "discovery - no IPs configured, all traffic allowed" : `security - ${ipAllowList.length} IP ranges configured`})`);
66
+ // Create IP Set for allowlist
67
+ this.ipSet = new wafv2.CfnIPSet(this, "IPSet", {
68
+ name: "BenchlingWebhookIPSet",
69
+ scope: "REGIONAL",
70
+ ipAddressVersion: "IPV4",
71
+ addresses: ipAllowList,
72
+ description: "Allowed IP addresses for Benchling webhooks",
73
+ });
74
+ // CloudWatch log group for WAF logs
75
+ this.logGroup = new logs.LogGroup(this, "WafLogGroup", {
76
+ logGroupName: "/aws/waf/benchling-webhook",
77
+ retention: logs.RetentionDays.ONE_WEEK,
78
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
79
+ });
80
+ // Create Web ACL with two rules
81
+ // Default action: Allow (discovery mode) or Block (security mode)
82
+ const defaultActionConfig = isDiscoveryMode
83
+ ? { allow: {} }
84
+ : { block: {} };
85
+ this.webAcl = new wafv2.CfnWebACL(this, "WebACL", {
86
+ name: "BenchlingWebhookWebACL",
87
+ scope: "REGIONAL",
88
+ defaultAction: defaultActionConfig,
89
+ description: "WAF for Benchling webhook IP filtering with automatic discovery mode. " +
90
+ `Mode: ${mode} (${isDiscoveryMode ? "empty allowlist - all traffic allowed" : `${ipAllowList.length} IPs configured`})`,
91
+ rules: [
92
+ // Rule 1: Health check exception (Priority 10)
93
+ // Always allow health check endpoints regardless of IP
94
+ {
95
+ name: "HealthCheckException",
96
+ priority: 10,
97
+ statement: {
98
+ orStatement: {
99
+ statements: [
100
+ {
101
+ byteMatchStatement: {
102
+ fieldToMatch: { uriPath: {} },
103
+ positionalConstraint: "EXACTLY",
104
+ searchString: "/health",
105
+ textTransformations: [{ priority: 0, type: "NONE" }],
106
+ },
107
+ },
108
+ {
109
+ byteMatchStatement: {
110
+ fieldToMatch: { uriPath: {} },
111
+ positionalConstraint: "EXACTLY",
112
+ searchString: "/health/ready",
113
+ textTransformations: [{ priority: 0, type: "NONE" }],
114
+ },
115
+ },
116
+ {
117
+ byteMatchStatement: {
118
+ fieldToMatch: { uriPath: {} },
119
+ positionalConstraint: "EXACTLY",
120
+ searchString: "/health/live",
121
+ textTransformations: [{ priority: 0, type: "NONE" }],
122
+ },
123
+ },
124
+ ],
125
+ },
126
+ },
127
+ action: { allow: {} },
128
+ visibilityConfig: {
129
+ sampledRequestsEnabled: true,
130
+ cloudWatchMetricsEnabled: true,
131
+ metricName: "HealthCheckException",
132
+ },
133
+ },
134
+ // Rule 2: IP allowlist (Priority 20)
135
+ // Allow requests from configured IP ranges
136
+ // Note: When IP allowlist is empty, this rule matches no IPs,
137
+ // so all non-health requests fall through to default action (COUNT or BLOCK)
138
+ {
139
+ name: "IPAllowlist",
140
+ priority: 20,
141
+ statement: {
142
+ ipSetReferenceStatement: {
143
+ arn: this.ipSet.attrArn,
144
+ },
145
+ },
146
+ action: { allow: {} },
147
+ visibilityConfig: {
148
+ sampledRequestsEnabled: true,
149
+ cloudWatchMetricsEnabled: true,
150
+ metricName: "IPAllowlist",
151
+ },
152
+ },
153
+ ],
154
+ visibilityConfig: {
155
+ sampledRequestsEnabled: true,
156
+ cloudWatchMetricsEnabled: true,
157
+ metricName: "BenchlingWebhookWebACL",
158
+ },
159
+ });
160
+ // Configure WAF logging to CloudWatch
161
+ new wafv2.CfnLoggingConfiguration(this, "WafLogging", {
162
+ resourceArn: this.webAcl.attrArn,
163
+ logDestinationConfigs: [this.logGroup.logGroupArn],
164
+ });
165
+ }
166
+ /**
167
+ * Parse IP allowlist string into array of CIDR blocks
168
+ *
169
+ * - Splits by comma
170
+ * - Trims whitespace
171
+ * - Adds /32 suffix if not present
172
+ * - Filters out empty entries
173
+ *
174
+ * @param allowList Comma-separated list of IPs/CIDR blocks
175
+ * @returns Array of CIDR blocks
176
+ *
177
+ * @example
178
+ * parseIpAllowList("192.168.1.0/24, 10.0.0.1") → ["192.168.1.0/24", "10.0.0.1/32"]
179
+ */
180
+ parseIpAllowList(allowList) {
181
+ return allowList
182
+ .split(",")
183
+ .map((ip) => ip.trim())
184
+ .filter((ip) => ip.length > 0)
185
+ .map((ip) => {
186
+ // Ensure CIDR notation (add /32 if not specified)
187
+ return ip.includes("/") ? ip : `${ip}/32`;
188
+ });
189
+ }
190
+ }
191
+ exports.WafWebAcl = WafWebAcl;
192
+ //# sourceMappingURL=waf-web-acl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waf-web-acl.js","sourceRoot":"","sources":["../../lib/waf-web-acl.ts"],"names":[],"mappings":"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"}
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quiltdata/benchling-webhook",
3
- "version": "0.9.0-20251127T201515Z",
3
+ "version": "0.9.0-20251129T071202Z",
4
4
  "description": "AWS CDK deployment for Benchling webhook processing using Fargate - Deploy directly with npx",
5
5
  "main": "dist/lib/index.js",
6
6
  "types": "dist/lib/index.d.ts",
@@ -18,8 +18,11 @@
18
18
  "build:clean": "rm -rf cdk.out dist */{*.js,*.d.ts}",
19
19
  "build:synth": "npx cdk synth",
20
20
  "build:typecheck": "tsc --noEmit",
21
- "deploy:dev": "ts-node bin/cli.ts deploy --stage dev --profile dev",
22
- "deploy:prod": "ts-node bin/cli.ts deploy --stage prod",
21
+ "build:lambda": "make lambda-bundle",
22
+ "build:lambda:check": "make check-bundle",
23
+ "build:lambda:clean": "make clean",
24
+ "deploy:dev": "make lambda-bundle && ts-node bin/cli.ts deploy --stage dev --profile dev",
25
+ "deploy:prod": "make lambda-bundle && ts-node bin/cli.ts deploy --stage prod",
23
26
  "deploy:notes": "bash scripts/release-notes.sh",
24
27
  "destroy": "ts-node bin/cli.ts destroy",
25
28
  "destroy:dev": "ts-node bin/cli.ts destroy --stage dev --profile dev",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quiltdata/benchling-webhook",
3
- "version": "0.9.0-20251127T201515Z",
3
+ "version": "0.9.0-20251129T071202Z",
4
4
  "description": "AWS CDK deployment for Benchling webhook processing using Fargate - Deploy directly with npx",
5
5
  "main": "dist/lib/index.js",
6
6
  "types": "dist/lib/index.d.ts",
@@ -18,8 +18,11 @@
18
18
  "build:clean": "rm -rf cdk.out dist */{*.js,*.d.ts}",
19
19
  "build:synth": "npx cdk synth",
20
20
  "build:typecheck": "tsc --noEmit",
21
- "deploy:dev": "ts-node bin/cli.ts deploy --stage dev --profile dev",
22
- "deploy:prod": "ts-node bin/cli.ts deploy --stage prod",
21
+ "build:lambda": "make lambda-bundle",
22
+ "build:lambda:check": "make check-bundle",
23
+ "build:lambda:clean": "make clean",
24
+ "deploy:dev": "make lambda-bundle && ts-node bin/cli.ts deploy --stage dev --profile dev",
25
+ "deploy:prod": "make lambda-bundle && ts-node bin/cli.ts deploy --stage prod",
23
26
  "deploy:notes": "bash scripts/release-notes.sh",
24
27
  "destroy": "ts-node bin/cli.ts destroy",
25
28
  "destroy:dev": "ts-node bin/cli.ts destroy --stage dev --profile dev",
@@ -1,32 +0,0 @@
1
- import * as apigateway from "aws-cdk-lib/aws-apigateway";
2
- import * as ec2 from "aws-cdk-lib/aws-ec2";
3
- import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2";
4
- import * as servicediscovery from "aws-cdk-lib/aws-servicediscovery";
5
- import * as logs from "aws-cdk-lib/aws-logs";
6
- import * as lambda from "aws-cdk-lib/aws-lambda";
7
- import { Construct } from "constructs";
8
- import { ProfileConfig } from "./types/config";
9
- export interface RestApiGatewayProps {
10
- readonly vpc: ec2.IVpc;
11
- readonly cloudMapService: servicediscovery.IService;
12
- readonly serviceSecurityGroup: ec2.ISecurityGroup;
13
- readonly config: ProfileConfig;
14
- readonly ecsService: elbv2.IApplicationLoadBalancerTarget | elbv2.INetworkLoadBalancerTarget;
15
- }
16
- export declare class RestApiGateway {
17
- readonly api: apigateway.RestApi;
18
- readonly vpcLink: apigateway.VpcLink;
19
- readonly nlb: elbv2.NetworkLoadBalancer;
20
- readonly logGroup: logs.ILogGroup;
21
- readonly authorizer?: lambda.Function;
22
- readonly authorizerLogGroup?: logs.ILogGroup;
23
- constructor(scope: Construct, id: string, props: RestApiGatewayProps);
24
- /**
25
- * Build IAM resource policy for IP whitelisting
26
- *
27
- * If ipAllowList is empty, allow all IPs.
28
- * Otherwise, only allow requests from specified IP addresses/CIDR blocks.
29
- */
30
- private buildResourcePolicy;
31
- }
32
- //# sourceMappingURL=rest-api-gateway.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"rest-api-gateway.d.ts","sourceRoot":"","sources":["../../lib/rest-api-gateway.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,UAAU,MAAM,4BAA4B,CAAC;AACzD,OAAO,KAAK,GAAG,MAAM,qBAAqB,CAAC;AAC3C,OAAO,KAAK,KAAK,MAAM,wCAAwC,CAAC;AAChE,OAAO,KAAK,gBAAgB,MAAM,kCAAkC,CAAC;AACrE,OAAO,KAAK,IAAI,MAAM,sBAAsB,CAAC;AAC7C,OAAO,KAAK,MAAM,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,WAAW,mBAAmB;IAChC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC;IACvB,QAAQ,CAAC,eAAe,EAAE,gBAAgB,CAAC,QAAQ,CAAC;IACpD,QAAQ,CAAC,oBAAoB,EAAE,GAAG,CAAC,cAAc,CAAC;IAClD,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC,8BAA8B,GAAG,KAAK,CAAC,0BAA0B,CAAC;CAChG;AAED,qBAAa,cAAc;IACvB,SAAgB,GAAG,EAAE,UAAU,CAAC,OAAO,CAAC;IACxC,SAAgB,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC;IAC5C,SAAgB,GAAG,EAAE,KAAK,CAAC,mBAAmB,CAAC;IAC/C,SAAgB,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC;IACzC,SAAgB,UAAU,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC7C,SAAgB,kBAAkB,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC;gBAExC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB;IA+MpE;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;CAmC9B"}
@@ -1,268 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.RestApiGateway = void 0;
37
- const cdk = __importStar(require("aws-cdk-lib"));
38
- const apigateway = __importStar(require("aws-cdk-lib/aws-apigateway"));
39
- const ec2 = __importStar(require("aws-cdk-lib/aws-ec2"));
40
- const elbv2 = __importStar(require("aws-cdk-lib/aws-elasticloadbalancingv2"));
41
- const logs = __importStar(require("aws-cdk-lib/aws-logs"));
42
- const lambda = __importStar(require("aws-cdk-lib/aws-lambda"));
43
- const iam = __importStar(require("aws-cdk-lib/aws-iam"));
44
- class RestApiGateway {
45
- constructor(scope, id, props) {
46
- // Access logs for REST API
47
- this.logGroup = new logs.LogGroup(scope, "ApiGatewayAccessLogs", {
48
- logGroupName: "/aws/apigateway/benchling-webhook-rest",
49
- retention: logs.RetentionDays.ONE_WEEK,
50
- removalPolicy: cdk.RemovalPolicy.DESTROY,
51
- });
52
- // Create Network Load Balancer for VPC Link integration
53
- // REST API Gateway requires NLB (not ALB) for private integration
54
- this.nlb = new elbv2.NetworkLoadBalancer(scope, "NetworkLoadBalancer", {
55
- vpc: props.vpc,
56
- internetFacing: false,
57
- vpcSubnets: {
58
- subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
59
- },
60
- });
61
- // Create target group for ECS service
62
- const targetGroup = new elbv2.NetworkTargetGroup(scope, "TargetGroup", {
63
- vpc: props.vpc,
64
- port: 8080,
65
- protocol: elbv2.Protocol.TCP,
66
- targetType: elbv2.TargetType.IP,
67
- healthCheck: {
68
- enabled: true,
69
- protocol: elbv2.Protocol.HTTP,
70
- path: "/health",
71
- interval: cdk.Duration.seconds(30),
72
- timeout: cdk.Duration.seconds(10),
73
- healthyThresholdCount: 2,
74
- unhealthyThresholdCount: 3,
75
- },
76
- deregistrationDelay: cdk.Duration.seconds(30),
77
- });
78
- // Add listener to NLB
79
- this.nlb.addListener("Listener", {
80
- port: 80,
81
- protocol: elbv2.Protocol.TCP,
82
- defaultTargetGroups: [targetGroup],
83
- });
84
- // Register ECS service with target group
85
- if ("attachToNetworkTargetGroup" in props.ecsService) {
86
- props.ecsService.attachToNetworkTargetGroup(targetGroup);
87
- }
88
- // Create VPC Link with NLB as target
89
- this.vpcLink = new apigateway.VpcLink(scope, "VpcLink", {
90
- targets: [this.nlb],
91
- vpcLinkName: "benchling-webhook-vpclink",
92
- description: "VPC Link for Benchling Webhook REST API",
93
- });
94
- // Parse IP allowlist from config
95
- const ipAllowList = props.config.security?.webhookAllowList
96
- ?.split(",")
97
- .map((ip) => ip.trim())
98
- .filter((ip) => ip.length > 0) || [];
99
- const verificationEnabled = props.config.security?.enableVerification !== false;
100
- const benchlingSecretArn = props.config.benchling.secretArn;
101
- if (!benchlingSecretArn) {
102
- throw new Error("Benchling secret ARN is required to configure the Lambda authorizer");
103
- }
104
- // Build resource policy for IP whitelisting
105
- const resourcePolicy = this.buildResourcePolicy(ipAllowList);
106
- // Lambda authorizer for webhook verification
107
- this.authorizerLogGroup = new logs.LogGroup(scope, "WebhookAuthorizerLogGroup", {
108
- retention: logs.RetentionDays.ONE_WEEK,
109
- removalPolicy: cdk.RemovalPolicy.DESTROY,
110
- });
111
- const bundlingCommands = [
112
- "set -euo pipefail",
113
- "export PIP_NO_BUILD_ISOLATION=1 PIP_ONLY_BINARY=:all: PIP_DISABLE_PIP_VERSION_CHECK=1 PIP_CACHE_DIR=/tmp/pipcache",
114
- "pip install -q --platform manylinux2014_x86_64 --implementation cp --python-version 3.11 --abi cp311 --only-binary=:all: -t /asset-output -r /asset-input/lambda/authorizer/requirements.txt -c /asset-input/lambda/authorizer/constraints.txt",
115
- "cp /asset-input/docker/src/lambda_authorizer.py /asset-output/index.py",
116
- ].join(" && ");
117
- const authorizerCode = process.env.NODE_ENV === "test"
118
- ? lambda.Code.fromInline("def handler(event, context):\n return {}")
119
- : lambda.Code.fromAsset(".", {
120
- bundling: {
121
- image: lambda.Runtime.PYTHON_3_11.bundlingImage,
122
- command: ["bash", "-c", bundlingCommands],
123
- },
124
- });
125
- this.authorizer = new lambda.Function(scope, "WebhookAuthorizerFunction", {
126
- runtime: lambda.Runtime.PYTHON_3_11,
127
- handler: "index.handler",
128
- memorySize: 128,
129
- timeout: cdk.Duration.seconds(10),
130
- architecture: lambda.Architecture.X86_64,
131
- description: "Benchling webhook signature verification (defense-in-depth)",
132
- environment: {
133
- BENCHLING_SECRET_ARN: benchlingSecretArn,
134
- },
135
- code: authorizerCode,
136
- logGroup: this.authorizerLogGroup,
137
- });
138
- this.authorizer.addToRolePolicy(new iam.PolicyStatement({
139
- actions: ["secretsmanager:GetSecretValue"],
140
- resources: [benchlingSecretArn],
141
- }));
142
- // Create REST API first (needed for proper authorizer permissions)
143
- this.api = new apigateway.RestApi(scope, "BenchlingWebhookRestAPI", {
144
- restApiName: "BenchlingWebhookRestAPI",
145
- description: "REST API for Benchling webhook integration with IP whitelisting (v1.0.0+)",
146
- policy: resourcePolicy,
147
- deployOptions: {
148
- stageName: "prod",
149
- accessLogDestination: new apigateway.LogGroupLogDestination(this.logGroup),
150
- accessLogFormat: apigateway.AccessLogFormat.jsonWithStandardFields({
151
- caller: true,
152
- httpMethod: true,
153
- ip: true,
154
- protocol: true,
155
- requestTime: true,
156
- resourcePath: true,
157
- responseLength: true,
158
- status: true,
159
- user: true,
160
- }),
161
- loggingLevel: apigateway.MethodLoggingLevel.INFO,
162
- dataTraceEnabled: true,
163
- },
164
- endpointConfiguration: {
165
- types: [apigateway.EndpointType.REGIONAL],
166
- },
167
- });
168
- // Create authorizer AFTER API is created (for proper source ARN permissions)
169
- const requestAuthorizer = verificationEnabled
170
- ? new apigateway.RequestAuthorizer(scope, "WebhookRequestAuthorizer", {
171
- handler: this.authorizer,
172
- identitySources: [
173
- apigateway.IdentitySource.header("webhook-id"),
174
- apigateway.IdentitySource.header("webhook-signature"),
175
- apigateway.IdentitySource.header("webhook-timestamp"),
176
- ],
177
- resultsCacheTtl: cdk.Duration.seconds(0),
178
- })
179
- : undefined;
180
- // Grant API Gateway permission to invoke Lambda authorizer from any method
181
- // The CDK RequestAuthorizer grants permission only for /authorizers/{id},
182
- // but API Gateway invokes from /{stage}/{method}/{path}, so we add explicit permission
183
- if (verificationEnabled && this.authorizer) {
184
- this.authorizer.addPermission("ApiGatewayInvokePermission", {
185
- principal: new iam.ServicePrincipal("apigateway.amazonaws.com"),
186
- action: "lambda:InvokeFunction",
187
- sourceArn: this.api.arnForExecuteApi("*", "/*", "*"),
188
- });
189
- }
190
- const createIntegration = (path) => new apigateway.Integration({
191
- type: apigateway.IntegrationType.HTTP_PROXY,
192
- integrationHttpMethod: "ANY",
193
- uri: `http://${this.nlb.loadBalancerDnsName}:80${path}`,
194
- options: {
195
- connectionType: apigateway.ConnectionType.VPC_LINK,
196
- vpcLink: this.vpcLink,
197
- },
198
- });
199
- const webhookMethodOptions = requestAuthorizer
200
- ? {
201
- authorizer: requestAuthorizer,
202
- authorizationType: apigateway.AuthorizationType.CUSTOM,
203
- }
204
- : undefined;
205
- // Webhook endpoints secured by Lambda authorizer
206
- const eventResource = this.api.root.addResource("event");
207
- eventResource.addMethod("ANY", createIntegration("/event"), webhookMethodOptions);
208
- const lifecycleResource = this.api.root.addResource("lifecycle");
209
- lifecycleResource.addMethod("ANY", createIntegration("/lifecycle"), webhookMethodOptions);
210
- const canvasResource = this.api.root.addResource("canvas");
211
- canvasResource.addMethod("ANY", createIntegration("/canvas"), webhookMethodOptions);
212
- // Health endpoints remain unauthenticated
213
- const healthResource = this.api.root.addResource("health");
214
- healthResource.addMethod("GET", createIntegration("/health"));
215
- healthResource.addResource("ready").addMethod("GET", createIntegration("/health/ready"));
216
- healthResource.addResource("live").addMethod("GET", createIntegration("/health/live"));
217
- // Output IP filtering status
218
- if (ipAllowList.length > 0) {
219
- console.log(`IP Whitelisting enabled: ${ipAllowList.length} CIDR blocks`);
220
- ipAllowList.forEach((ip) => console.log(` - ${ip}`));
221
- }
222
- else {
223
- console.log("IP Whitelisting disabled: all IPs allowed");
224
- }
225
- }
226
- /**
227
- * Build IAM resource policy for IP whitelisting
228
- *
229
- * If ipAllowList is empty, allow all IPs.
230
- * Otherwise, only allow requests from specified IP addresses/CIDR blocks.
231
- */
232
- buildResourcePolicy(ipAllowList) {
233
- if (ipAllowList.length === 0) {
234
- // No IP filtering - allow all
235
- return undefined;
236
- }
237
- return new iam.PolicyDocument({
238
- statements: [
239
- // Allow requests from whitelisted IPs
240
- new iam.PolicyStatement({
241
- effect: iam.Effect.ALLOW,
242
- principals: [new iam.AnyPrincipal()],
243
- actions: ["execute-api:Invoke"],
244
- resources: ["execute-api:/*"],
245
- conditions: {
246
- IpAddress: {
247
- "aws:SourceIp": ipAllowList,
248
- },
249
- },
250
- }),
251
- // Explicitly deny requests from non-whitelisted IPs
252
- new iam.PolicyStatement({
253
- effect: iam.Effect.DENY,
254
- principals: [new iam.AnyPrincipal()],
255
- actions: ["execute-api:Invoke"],
256
- resources: ["execute-api:/*"],
257
- conditions: {
258
- NotIpAddress: {
259
- "aws:SourceIp": ipAllowList,
260
- },
261
- },
262
- }),
263
- ],
264
- });
265
- }
266
- }
267
- exports.RestApiGateway = RestApiGateway;
268
- //# sourceMappingURL=rest-api-gateway.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"rest-api-gateway.js","sourceRoot":"","sources":["../../lib/rest-api-gateway.ts"],"names":[],"mappings":"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"}