feat(devtools): Aurora Serverless v2 scale-to-zero + NAT-free Lambda connectivity - #640
feat(devtools): Aurora Serverless v2 scale-to-zero + NAT-free Lambda connectivity#640seanspeaks wants to merge 2 commits into
Conversation
…connectivity Implements ADR-033. Adds two independent, opt-in, default-off capabilities on database.postgres so a Frigg-owned Aurora can idle at ~$0. Scale-to-zero (minCapacity: 0): - Validator accepts minCapacity 0 OR [0.5,128]; (0,0.5) rejected. - Scaling config uses `?? 0.5` / `?? 4` (the old `|| 0.5` turned a requested 0 back into 0.5) and emits SecondsUntilAutoPause (default 300, range 300-86400) when minCapacity === 0. - Warns when minCapacity:0 is pinned to an engine older than 13.15/14.12/15.7/16.3. Connectivity mode (connectivity: 'vpc' default | 'public'): - 'public' places Aurora in public subnets (PubliclyAccessible), opens 5432 to allowedCidrs (default ['0.0.0.0/0']) via CidrIp — one ingress rule per CIDR — instead of the Lambda security group, and enforces TLS (sslmode=require). - The VpcBuilder decouples the Lambda VPC attachment + NAT from Aurora's networking: it still builds the VPC/public subnets/DB subnet group Aurora needs but skips the NAT Gateway and VPC endpoints and clears vpcConfig, so the composer leaves provider.vpc unset and the Lambda keeps default internet egress. Also threads the new fields through the legacy-config translation, documents them in app-definition types, and fixes the stale maxCapacity default JSDoc (1 -> 4). Default behavior is unchanged: 'vpc' connectivity + minCapacity 0.5 compose a byte-for-byte identical template. Adds template-shape unit tests (validator + generated CloudFormation) covering both capabilities and the no-regression path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JDh45c1vm91ySYtvVv9Z65
✅ Deploy Preview for friggframework-org canceled.
|
| const allowedCidrs = | ||
| Array.isArray(dbConfig.allowedCidrs) && dbConfig.allowedCidrs.length > 0 | ||
| ? dbConfig.allowedCidrs | ||
| : ['0.0.0.0/0']; |
There was a problem hiding this comment.
Security issue: Empty array allowedCidrs: [] is treated as missing and defaults to ['0.0.0.0/0'], opening the database to the entire internet when the user may have intended to block all access.
const allowedCidrs =
Array.isArray(dbConfig.allowedCidrs) && dbConfig.allowedCidrs.length > 0
? dbConfig.allowedCidrs
: ['0.0.0.0/0'];If a user explicitly sets allowedCidrs: [], this condition evaluates to false and falls through to the default ['0.0.0.0/0'], creating a wide-open security rule instead of no rules.
Fix: Either validate that allowedCidrs cannot be empty in the validator, or handle empty arrays explicitly to create no ingress rules (though that would make the database unreachable).
| const allowedCidrs = | |
| Array.isArray(dbConfig.allowedCidrs) && dbConfig.allowedCidrs.length > 0 | |
| ? dbConfig.allowedCidrs | |
| : ['0.0.0.0/0']; | |
| const allowedCidrs = | |
| Array.isArray(dbConfig.allowedCidrs) | |
| ? dbConfig.allowedCidrs | |
| : ['0.0.0.0/0']; | |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
…tor hardening Adversarial review follow-ups on the Aurora scale-to-zero / NAT-free work: - [HIGH] Public subnets are now internet-routable. Skipping NAT dropped the IGW default route + subnet associations (they were only emitted as a side effect of the NAT build), leaving the public Aurora endpoint unreachable. In public mode the VpcBuilder now calls createPublicRouting for a Frigg-created (stack) VPC — guarded by the presence of FriggInternetGateway so a discovered/existing VPC (whose subnets already route to an IGW) is left alone. - [SEC] Empty allowedCidrs with connectivity=public is now a validation ERROR instead of silently falling back to 0.0.0.0/0. - Fixed the stale aurora-builder.test.js assertion for the new minCapacity error. - Added a loud warning for connectivity=public with discover/use-existing (Frigg can't move an existing cluster to public subnets, and the Lambda is detached); useExistingAurora now applies sslmode=require in public mode. - Added minCapacity <= maxCapacity cross-check. - connectivity=public now requires vpc.enable=true (clear error, not a downstream "requires 2 public subnets" throw). - Warn when secondsUntilAutoPause is set with minCapacity !== 0 (ignored). - VPC_ENABLED is 'false' in public mode so /health doesn't misreport isInVpc. - Tightened allowedCidrs validation to real IPv4 numeric ranges (octets <=255, prefix <=32); documented IPv4-only. - Corrected the .config.* translation comment (it is a mirror, not the load-bearing 0-propagation path). Default vpc path unchanged. New + existing aurora-builder tests green; vpc-builder.test.js failures are pre-existing base-branch red (unchanged). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JDh45c1vm91ySYtvVv9Z65
|



Summary
Implements ADR-033. Adds two independent, opt-in, default-off capabilities on
database.postgresso a Frigg-owned Aurora Serverless v2 cluster can idle at ~$0 instead of the current ~$75/mo floor (0.5 ACU always-on + an always-on NAT Gateway). Existing app definitions compose a byte-for-byte identical template.1. Scale-to-zero (
minCapacity: 0)minCapacityof0(Aurora Serverless v2 scale-to-zero, GA Nov 2024) or[0.5, 128];(0, 0.5)remains invalid. Also cross-checksminCapacity <= maxCapacity.MinCapacity: dbConfig.minCapacity ?? 0.5/MaxCapacity: dbConfig.maxCapacity ?? 4(the old|| 0.5turned a requested0back into0.5).minCapacity === 0, emitsSecondsUntilAutoPausefrom a new optionalsecondsUntilAutoPause(default 300, valid300–86400; a warning fires if it's set whileminCapacity !== 0).minCapacity: 0is pinned to an engine older than Aurora PG 13.15/14.12/15.7/16.3. The default 15.13 qualifies.2. Connectivity mode (
connectivity: 'vpc'default |'public')'vpc'(default, unchanged): Aurora in private subnets, Lambda attached to the VPC, ingress from the Lambda SG.'public'(NAT-free): Aurora in public subnets withPubliclyAccessible: true; ingress opens 5432 to a newallowedCidrsoption (default['0.0.0.0/0']) viaCidrIp— one ingress rule per CIDR — instead ofSourceSecurityGroupId;DATABASE_URLcarriessslmode=require; the Lambda is left OUT of the VPC (no NAT Gateway, no VPC endpoints) but the public subnets still get an Internet Gateway default route + associations so the endpoint is reachable;VPC_ENABLED=false. Requiresvpc.enable: true(validated).connectivity: 'public'+minCapacity: 0→ a Frigg-owned Aurora that idles at $0 with no NAT.The decoupling seam: the composer sets
provider.vpconly whenVpcBuilderreturns a truthyvpcConfig(infrastructure-composer.js:126). In public mode theVpcBuilderstill builds the VPC / public subnets / DB subnet group / IGW public routing Aurora requires, but skips the NAT Gateway + VPC endpoints and setsresult.vpcConfig = null— soprovider.vpcstays unset and the Lambda is never VPC-attached. The public-subnet IGW routing is emitted only for a Frigg-created (stack) VPC (guarded byFriggInternetGatewaypresence); a discovered/existing VPC is left alone.Files changed
packages/devtools/infrastructure/domains/database/aurora-builder.js— validator (minCapacity 0, min≤max, secondsUntilAutoPause + ignore-warning, connectivity, allowedCidrs numeric validation, empty-in-public error, public+vpc.enable rule, discover/use-existing warnings, engine warning), scaling config, public-mode subnet/PubliclyAccessibleselection, per-CIDRCidrIpingress, TLS inDATABASE_URL(create-new/discover/use-existing paths), config-mirror translation.packages/devtools/infrastructure/domains/networking/vpc-builder.js— public mode: emit IGW public-subnet routing (stack VPC only), skip NAT Gateway + VPC endpoints, clearvpcConfig, setVPC_ENABLED=false.packages/devtools/infrastructure/domains/shared/types/app-definition.js— documents the four new fields; fixes stalemaxCapacitydefault JSDoc (1 → 4).packages/devtools/infrastructure/domains/database/aurora-builder.test.js— updated the minCapacity error-message assertion.packages/devtools/infrastructure/__tests__/aurora-scale-to-zero-connectivity.test.js— new template-shape + validator tests.docs/architecture-decisions/033-...md— the ADR.Tests
./node_modules/.bin/jest infrastructure/__tests__/aurora-scale-to-zero-connectivity.test.js infrastructure/domains/database/aurora-builder.test.jsfrompackages/devtools: 76 passed / 76 (2 suites).MinCapacity === 0(mutation-checked.not.toBe(0.5)),SecondsUntilAutoPause300/custom,MaxCapacity4.CidrIpingress (default 0.0.0.0/0, noSourceSecurityGroupId),PubliclyAccessible: truein public subnets, no NAT Gateway, IGW public route + route table + both subnet associations present (no NAT route),provider.vpcunset,VPC_ENABLED=false,sslmode=require, combined $0-idle path.vpcdefault):MinCapacity0.5 / noSecondsUntilAutoPause, ingress viaSourceSecurityGroupId(FriggAuroraIngressRuleunchanged), not publicly accessible,provider.vpcset,VPC_ENABLED=true, nosslmode.Mutation-verified:
?? 0.5→|| 0.5, removingvpcConfig = null, removingcreatePublicRouting, and forcingVPC_ENABLED='true'each break the intended test(s).Review findings — status
Fixed: public-subnet IGW routing decoupled from NAT (HIGH); empty
allowedCidrsin public mode now an error (SEC); staleaurora-builder.test.jsassertion; discover/use-existing public warning +useExistingAuroraTLS;minCapacity <= maxCapacity;connectivity:'public'requiresvpc.enable;secondsUntilAutoPauseignored-warning;VPC_ENABLED=falsein public mode; numeric IPv4 CIDR validation (documented IPv4-only); corrected the.config.*translation comment.Deferred / accepted (per reviewer):
sslmode=require(notverify-full) is ADR-acceptable; theFriggLambdaSecurityGroup/vpcSecurityGroupIdscoupling is correct today. IPv6 (CidrIpv6) is explicitly out of scope forallowedCidrs(documented).🤖 Generated with Claude Code
Generated by Claude Code
📦 Published PR as canary version:
2.0.0--canary.640.5140601.0✨ Test out this PR locally via:
npm install @friggframework/admin-scripts@2.0.0--canary.640.5140601.0 npm install @friggframework/core@2.0.0--canary.640.5140601.0 npm install @friggframework/devtools@2.0.0--canary.640.5140601.0 npm install @friggframework/eslint-config@2.0.0--canary.640.5140601.0 npm install @friggframework/prettier-config@2.0.0--canary.640.5140601.0 npm install @friggframework/schemas@2.0.0--canary.640.5140601.0 npm install @friggframework/serverless-plugin@2.0.0--canary.640.5140601.0 npm install @friggframework/test@2.0.0--canary.640.5140601.0 npm install @friggframework/ui@2.0.0--canary.640.5140601.0 # or yarn add @friggframework/admin-scripts@2.0.0--canary.640.5140601.0 yarn add @friggframework/core@2.0.0--canary.640.5140601.0 yarn add @friggframework/devtools@2.0.0--canary.640.5140601.0 yarn add @friggframework/eslint-config@2.0.0--canary.640.5140601.0 yarn add @friggframework/prettier-config@2.0.0--canary.640.5140601.0 yarn add @friggframework/schemas@2.0.0--canary.640.5140601.0 yarn add @friggframework/serverless-plugin@2.0.0--canary.640.5140601.0 yarn add @friggframework/test@2.0.0--canary.640.5140601.0 yarn add @friggframework/ui@2.0.0--canary.640.5140601.0