From 1c859888f572c0f1e06d93cd5c6629933b4be423 Mon Sep 17 00:00:00 2001 From: Pedro Sanders Date: Tue, 3 Nov 2026 22:26:43 -0400 Subject: [PATCH] fix(infra): allow SSH from the run's VPC so the agent can reach the UUT The agent on the load generator drives the UUT over SSH at its private IP. The firewall allowed SSH only from the runner's public IP, so compose up on the UUT timed out on DigitalOcean. The VPC is the run's own; nothing outside it gains access. --- README.md | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- src/cli/formatPlan.ts | 2 +- src/infra/buildFirewall.ts | 11 ++++++++--- test/infra/createAllowSsh.unit.test.ts | 1 + test/infra/createProvision.unit.test.ts | 4 ++-- test/infra/helpers.unit.test.ts | 2 +- test/infra/run.unit.test.ts | 2 +- 9 files changed, 19 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 0ae8191..fee40c9 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ the end. In `infra.region` of `perfkit.yaml`, it creates, in this order: 3. two `ubuntu-24-04-x64` droplets in it, `perfkit--uut` and `perfkit--loadgen`, tagged `perfkit` and `perfkit-run-`; 4. a firewall, `perfkit-`: SIP (5060-5063 tcp/udp) and OTLP (4318 tcp) from the VPC only, - SSH (22) from this machine's public IP only (from `https://api.ipify.org`). + SSH (22) from this machine's public IP (from `https://api.ipify.org`) and from the VPC, where the agent on the load generator reaches the server under test. Both droplets get Docker Engine with the compose plugin, 25 MiB socket buffers (`net.core.rmem_max`, `wmem_max`, `rmem_default`, `wmem_default`) and 1048576 open files, through @@ -161,7 +161,7 @@ plan for Routr 2.31.6 (dry run, nothing created) region nyc3, new VPC (range assigned by DigitalOcean) uut c-8 ubuntu-24-04-x64 loadgen c-16 ubuntu-24-04-x64 - firewall SIP (5060-5063 tcp/udp) and OTLP (4318 tcp) from the VPC only; SSH (22) from 203.0.113.7 only + firewall SIP (5060-5063 tcp/udp) and OTLP (4318 tcp) from the VPC only; SSH (22) from 203.0.113.7 and the VPC only tags perfkit, perfkit-run-20261103T141210Z-bd3254a images fonoster/routr-edgeport:2.31.6@sha256:8bca…1866 fonoster/routr-echo:2.31.6@sha256:14a4…a43f diff --git a/package-lock.json b/package-lock.json index 97db267..c08c506 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "perfkit", - "version": "0.1.3", + "version": "0.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "perfkit", - "version": "0.1.3", + "version": "0.1.4", "license": "MIT", "dependencies": { "commander": "^14.0.3", diff --git a/package.json b/package.json index b9f379c..80b5025 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "perfkit", - "version": "0.1.3", + "version": "0.1.4", "description": "Benchmark harness for Fonoster projects: DigitalOcean runs, one report.json schema, one branded report page", "license": "MIT", "author": "Pedro Sanders ", diff --git a/src/cli/formatPlan.ts b/src/cli/formatPlan.ts index d66f23e..6f60a11 100644 --- a/src/cli/formatPlan.ts +++ b/src/cli/formatPlan.ts @@ -60,7 +60,7 @@ export function formatPlan( `${plan.loadgen.size.padEnd(sizeWidth)}${plan.loadgen.image}` ]), ...field("firewall", [ - `SIP (${SIP_PORTS} tcp/udp) and OTLP (${OTLP_PORT} tcp) from the VPC only; SSH (22) from ${plan.sshSource.replace(/\/32$/, "")} only` + `SIP (${SIP_PORTS} tcp/udp) and OTLP (${OTLP_PORT} tcp) from the VPC only; SSH (22) from ${plan.sshSource.replace(/\/32$/, "")} and the VPC only` ]), ...field("tags", [plan.tags.join(", ")]), ...field( diff --git a/src/infra/buildFirewall.ts b/src/infra/buildFirewall.ts index 279c520..657ff8c 100644 --- a/src/infra/buildFirewall.ts +++ b/src/infra/buildFirewall.ts @@ -8,8 +8,9 @@ const ANYWHERE = { addresses: ["0.0.0.0/0", "::/0"] }; /** * A run's firewall: SIP (tcp and udp) and OTLP (tcp) from the VPC's range - * only, SSH (22) from `sshSources` only, and every outbound connection. It - * applies to the run's droplets. + * only, SSH (22) from `sshSources` and from the VPC (the agent on the load + * generator drives the UUT over SSH at its private IP), and every outbound + * connection. It applies to the run's droplets. */ export function buildFirewall(input: { name: string; @@ -24,7 +25,11 @@ export function buildFirewall(input: { { protocol: "udp", ports: SIP_PORTS, sources: vpc }, { protocol: "tcp", ports: SIP_PORTS, sources: vpc }, { protocol: "tcp", ports: OTLP_PORT, sources: vpc }, - { protocol: "tcp", ports: "22", sources: { addresses: input.sshSources } } + { + protocol: "tcp", + ports: "22", + sources: { addresses: [...input.sshSources, input.vpcRange] } + } ], outbound_rules: [ { protocol: "tcp", ports: "all", destinations: ANYWHERE }, diff --git a/test/infra/createAllowSsh.unit.test.ts b/test/infra/createAllowSsh.unit.test.ts index 91d6b75..c202bf5 100644 --- a/test/infra/createAllowSsh.unit.test.ts +++ b/test/infra/createAllowSsh.unit.test.ts @@ -36,6 +36,7 @@ describe("createAllowSsh", () => { firewall.inbound_rules.find(({ ports }) => ports === "22")!; expect(ssh(after).sources.addresses).to.deep.equal([ "203.0.113.7/32", + "10.116.0.0/20", "198.51.100.20/32" ]); const strip = (firewall: typeof after) => ({ diff --git a/test/infra/createProvision.unit.test.ts b/test/infra/createProvision.unit.test.ts index b5412b9..4c34902 100644 --- a/test/infra/createProvision.unit.test.ts +++ b/test/infra/createProvision.unit.test.ts @@ -55,7 +55,7 @@ describe("createProvision", () => { ]); }); - it("should create the droplets in the VPC with the tags and a firewall open to SSH from this machine only", async () => { + it("should create the droplets in the VPC with the tags and a firewall open to SSH from this machine and the VPC only", async () => { // Arrange const { provision, client } = setup(); const plan = await makePlan(); @@ -83,7 +83,7 @@ describe("createProvision", () => { expect(firewall.droplet_ids).to.have.length(2); expect( firewall.inbound_rules.find(({ ports }) => ports === "22")?.sources - ).to.deep.equal({ addresses: ["203.0.113.7/32"] }); + ).to.deep.equal({ addresses: ["203.0.113.7/32", "10.116.0.0/20"] }); expect( firewall.inbound_rules .filter(({ ports }) => ports !== "22") diff --git a/test/infra/helpers.unit.test.ts b/test/infra/helpers.unit.test.ts index 8c4cf3e..8ce85a5 100644 --- a/test/infra/helpers.unit.test.ts +++ b/test/infra/helpers.unit.test.ts @@ -92,7 +92,7 @@ describe("buildFirewall", () => { "udp 5060-5063 10.1.0.0/20", "tcp 5060-5063 10.1.0.0/20", "tcp 4318 10.1.0.0/20", - "tcp 22 203.0.113.7/32" + "tcp 22 203.0.113.7/32,10.1.0.0/20" ]); expect( firewall.outbound_rules.map(({ protocol }) => protocol) diff --git a/test/infra/run.unit.test.ts b/test/infra/run.unit.test.ts index d04beb4..86f45b1 100644 --- a/test/infra/run.unit.test.ts +++ b/test/infra/run.unit.test.ts @@ -24,7 +24,7 @@ const PLAN = `plan for Routr 2.31.5 (dry run, nothing created) region nyc3, new VPC (range assigned by DigitalOcean) uut c-8 ubuntu-24-04-x64 loadgen c-16 ubuntu-24-04-x64 - firewall SIP (5060-5063 tcp/udp) and OTLP (4318 tcp) from the VPC only; SSH (22) from 203.0.113.7 only + firewall SIP (5060-5063 tcp/udp) and OTLP (4318 tcp) from the VPC only; SSH (22) from 203.0.113.7 and the VPC only tags perfkit, perfkit-run-${RUN_ID} images fonoster/routr-edgeport:2.31.5@sha256:4f1c…e2a0 fonoster/routr-echo:2.31.5@sha256:41ab…77d0