@appforge-ci/core 0.2.3 → 0.3.1
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/dist/agent-readiness.d.ts +240 -1
- package/dist/agent-readiness.d.ts.map +1 -1
- package/dist/agent-readiness.js +304 -1
- package/dist/agent-readiness.js.map +1 -1
- package/dist/agent-readiness.test.js +277 -19
- package/dist/agent-readiness.test.js.map +1 -1
- package/dist/api-client.d.ts +80 -3
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +79 -0
- package/dist/api-client.js.map +1 -1
- package/dist/api-client.test.js +22 -0
- package/dist/api-client.test.js.map +1 -1
- package/dist/appforge-verify.test.d.ts +2 -0
- package/dist/appforge-verify.test.d.ts.map +1 -0
- package/dist/appforge-verify.test.js +958 -0
- package/dist/appforge-verify.test.js.map +1 -0
- package/dist/attestation-keys.d.ts +20 -0
- package/dist/attestation-keys.d.ts.map +1 -0
- package/dist/attestation-keys.js +27 -0
- package/dist/attestation-keys.js.map +1 -0
- package/dist/attestation-keys.test.d.ts +2 -0
- package/dist/attestation-keys.test.d.ts.map +1 -0
- package/dist/attestation-keys.test.js +45 -0
- package/dist/attestation-keys.test.js.map +1 -0
- package/dist/attestation-signing.d.ts +226 -0
- package/dist/attestation-signing.d.ts.map +1 -0
- package/dist/attestation-signing.js +276 -0
- package/dist/attestation-signing.js.map +1 -0
- package/dist/attestation-signing.test.d.ts +2 -0
- package/dist/attestation-signing.test.d.ts.map +1 -0
- package/dist/attestation-signing.test.js +250 -0
- package/dist/attestation-signing.test.js.map +1 -0
- package/dist/attestation-workerd.test.d.ts +2 -0
- package/dist/attestation-workerd.test.d.ts.map +1 -0
- package/dist/attestation-workerd.test.js +183 -0
- package/dist/attestation-workerd.test.js.map +1 -0
- package/dist/attestation.d.ts +412 -0
- package/dist/attestation.d.ts.map +1 -0
- package/dist/attestation.js +353 -0
- package/dist/attestation.js.map +1 -0
- package/dist/attestation.test.d.ts +2 -0
- package/dist/attestation.test.d.ts.map +1 -0
- package/dist/attestation.test.js +218 -0
- package/dist/attestation.test.js.map +1 -0
- package/dist/build-vpn.d.ts +20 -0
- package/dist/build-vpn.d.ts.map +1 -0
- package/dist/build-vpn.js +50 -0
- package/dist/build-vpn.js.map +1 -0
- package/dist/fleet-vpn.d.ts +64 -0
- package/dist/fleet-vpn.d.ts.map +1 -0
- package/dist/fleet-vpn.js +88 -0
- package/dist/fleet-vpn.js.map +1 -0
- package/dist/fleet-vpn.test.d.ts +2 -0
- package/dist/fleet-vpn.test.d.ts.map +1 -0
- package/dist/fleet-vpn.test.js +69 -0
- package/dist/fleet-vpn.test.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas-repo-url.test.d.ts +2 -0
- package/dist/schemas-repo-url.test.d.ts.map +1 -0
- package/dist/schemas-repo-url.test.js +67 -0
- package/dist/schemas-repo-url.test.js.map +1 -0
- package/dist/schemas.d.ts +360 -7
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +148 -12
- package/dist/schemas.js.map +1 -1
- package/dist/ssh-deploy-key.d.ts +59 -0
- package/dist/ssh-deploy-key.d.ts.map +1 -0
- package/dist/ssh-deploy-key.js +124 -0
- package/dist/ssh-deploy-key.js.map +1 -0
- package/dist/ssh-deploy-key.test.d.ts +2 -0
- package/dist/ssh-deploy-key.test.d.ts.map +1 -0
- package/dist/ssh-deploy-key.test.js +117 -0
- package/dist/ssh-deploy-key.test.js.map +1 -0
- package/dist/types.d.ts +204 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -5
- package/dist/types.js.map +1 -1
- package/dist/vpn-attestation-workerd.test.d.ts +2 -0
- package/dist/vpn-attestation-workerd.test.d.ts.map +1 -0
- package/dist/vpn-attestation-workerd.test.js +184 -0
- package/dist/vpn-attestation-workerd.test.js.map +1 -0
- package/dist/vpn-attestation.d.ts +1015 -0
- package/dist/vpn-attestation.d.ts.map +1 -0
- package/dist/vpn-attestation.js +963 -0
- package/dist/vpn-attestation.js.map +1 -0
- package/dist/vpn-attestation.test.d.ts +2 -0
- package/dist/vpn-attestation.test.d.ts.map +1 -0
- package/dist/vpn-attestation.test.js +613 -0
- package/dist/vpn-attestation.test.js.map +1 -0
- package/dist/vpn-egress-presets.d.ts +142 -0
- package/dist/vpn-egress-presets.d.ts.map +1 -0
- package/dist/vpn-egress-presets.js +318 -0
- package/dist/vpn-egress-presets.js.map +1 -0
- package/dist/vpn-egress-presets.test.d.ts +2 -0
- package/dist/vpn-egress-presets.test.d.ts.map +1 -0
- package/dist/vpn-egress-presets.test.js +210 -0
- package/dist/vpn-egress-presets.test.js.map +1 -0
- package/dist/vpn-egress.d.ts +57 -0
- package/dist/vpn-egress.d.ts.map +1 -0
- package/dist/vpn-egress.js +126 -0
- package/dist/vpn-egress.js.map +1 -0
- package/dist/vpn-egress.test.d.ts +2 -0
- package/dist/vpn-egress.test.d.ts.map +1 -0
- package/dist/vpn-egress.test.js +133 -0
- package/dist/vpn-egress.test.js.map +1 -0
- package/dist/vpn-sandbox.d.ts +67 -0
- package/dist/vpn-sandbox.d.ts.map +1 -0
- package/dist/vpn-sandbox.js +22 -0
- package/dist/vpn-sandbox.js.map +1 -0
- package/dist/vpn-sandbox.test.d.ts +2 -0
- package/dist/vpn-sandbox.test.d.ts.map +1 -0
- package/dist/vpn-sandbox.test.js +34 -0
- package/dist/vpn-sandbox.test.js.map +1 -0
- package/dist/vpn-ssh.d.ts +138 -0
- package/dist/vpn-ssh.d.ts.map +1 -0
- package/dist/vpn-ssh.js +320 -0
- package/dist/vpn-ssh.js.map +1 -0
- package/dist/vpn-ssh.test.d.ts +2 -0
- package/dist/vpn-ssh.test.d.ts.map +1 -0
- package/dist/vpn-ssh.test.js +274 -0
- package/dist/vpn-ssh.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { createVpnTunnelSchema, updateVpnTunnelSchema, canonicalVpnEgressPolicy, egressSettingsForDisplay, egressSettingsFromRow, expandVpnEgressPolicy, findVpnEgressPreset, formatEgressRule, mergeEgressRules, normalizeEgressHost, normalizeEgressPresets, parseEgressAllowEntries, parseEgressAllowEntry, sharedTenancyPresetIds, validateEgressAllow, validateEgressPresets, validateEgressSettings, validateVpnEgressPolicy, vpnEgressPolicyHash, VPN_EGRESS_MAX_CUSTOM_RULES, VPN_EGRESS_MAX_PRESETS, VPN_EGRESS_MAX_RULES, VPN_EGRESS_PRESETS, } from "./index.js";
|
|
5
|
+
const sha = (s) => createHash("sha256").update(s).digest("hex");
|
|
6
|
+
test("registry: ids are unique and well-formed; every host passes the helper's own host rules; no bare *, no IP literal, no Unicode", () => {
|
|
7
|
+
assert.ok(VPN_EGRESS_PRESETS.length >= 5);
|
|
8
|
+
assert.equal(new Set(VPN_EGRESS_PRESETS.map((p) => p.id)).size, VPN_EGRESS_PRESETS.length);
|
|
9
|
+
for (const p of VPN_EGRESS_PRESETS) {
|
|
10
|
+
assert.match(p.id, /^[a-z0-9][a-z0-9-]*$/);
|
|
11
|
+
assert.ok(p.label.length > 0 && p.description.length > 0 && p.rules.length > 0, p.id);
|
|
12
|
+
for (const r of p.rules) {
|
|
13
|
+
assert.equal(normalizeEgressHost(r.host), r.host, `${p.id}: ${r.host} must already be in normal form`);
|
|
14
|
+
assert.notEqual(r.host, "*");
|
|
15
|
+
assert.doesNotMatch(r.host, /^[0-9.]+$/);
|
|
16
|
+
for (const port of r.ports ?? [])
|
|
17
|
+
assert.ok(Number.isInteger(port) && port >= 1 && port <= 65535);
|
|
18
|
+
}
|
|
19
|
+
// Each preset is a valid helper policy on its own.
|
|
20
|
+
assert.deepEqual(validateVpnEgressPolicy({ allow: p.rules.map(({ host, ports }) => ({ host, ...(ports ? { ports: [...ports] } : {}) })) }), [], p.id);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
test("registry: the hosts the Seatbelt spike observed in a cold Flutter android + ios build are all covered by the presets", () => {
|
|
24
|
+
const observed = [
|
|
25
|
+
"dl.google.com",
|
|
26
|
+
"repo.maven.apache.org",
|
|
27
|
+
"plugins.gradle.org",
|
|
28
|
+
"plugins-artifacts.gradle.org",
|
|
29
|
+
"services.gradle.org",
|
|
30
|
+
"storage.googleapis.com",
|
|
31
|
+
"pub.dev",
|
|
32
|
+
"github.com",
|
|
33
|
+
"release-assets.githubusercontent.com",
|
|
34
|
+
"cdn.cocoapods.org",
|
|
35
|
+
"cdn.jsdelivr.net",
|
|
36
|
+
];
|
|
37
|
+
const policy = expandVpnEgressPolicy({ egressPresets: ["flutter", "android-gradle", "ios-cocoapods"], egressAllow: [] });
|
|
38
|
+
const hosts = new Set(policy.allow.map((r) => r.host));
|
|
39
|
+
for (const h of observed)
|
|
40
|
+
assert.ok(hosts.has(h), `${h} missing`);
|
|
41
|
+
// ...and the presets add nothing the sample build did not need.
|
|
42
|
+
assert.deepEqual([...hosts].sort(), [...observed].sort());
|
|
43
|
+
});
|
|
44
|
+
test("registry: shared-tenancy hosts are flagged, so the UI can warn about the exfiltration channel", () => {
|
|
45
|
+
assert.deepEqual(sharedTenancyPresetIds(["flutter", "npm", "github", "ios-cocoapods", "android-gradle"]).sort(), [
|
|
46
|
+
"android-gradle",
|
|
47
|
+
"flutter",
|
|
48
|
+
"github",
|
|
49
|
+
"ios-cocoapods",
|
|
50
|
+
]);
|
|
51
|
+
assert.deepEqual(sharedTenancyPresetIds(["npm"]), []);
|
|
52
|
+
assert.deepEqual(sharedTenancyPresetIds(["no-such-preset"]), []);
|
|
53
|
+
const github = findVpnEgressPreset("github");
|
|
54
|
+
assert.ok(github.rules.every((r) => r.sharedTenancy === true));
|
|
55
|
+
});
|
|
56
|
+
test("golden: expanding settings gives these exact canonical bytes and hash (a preset change must be deliberate)", async () => {
|
|
57
|
+
const policy = expandVpnEgressPolicy({
|
|
58
|
+
egressPresets: ["npm", "flutter"],
|
|
59
|
+
egressAllow: [{ host: "Git.Corp.Example.com", ports: [8443] }, { host: "*.corp.example.com" }, { host: "pub.dev", ports: [8080] }],
|
|
60
|
+
});
|
|
61
|
+
const canonical = '{"v":1,"allow":[' +
|
|
62
|
+
'{"host":"*.corp.example.com","ports":[443]},' +
|
|
63
|
+
'{"host":"git.corp.example.com","ports":[8443]},' +
|
|
64
|
+
'{"host":"pub.dev","ports":[443,8080]},' +
|
|
65
|
+
'{"host":"registry.npmjs.org","ports":[443]},' +
|
|
66
|
+
'{"host":"registry.yarnpkg.com","ports":[443]},' +
|
|
67
|
+
'{"host":"storage.googleapis.com","ports":[443]}]}';
|
|
68
|
+
assert.equal(canonicalVpnEgressPolicy(policy), canonical);
|
|
69
|
+
assert.equal(await vpnEgressPolicyHash(policy), sha(canonical));
|
|
70
|
+
// The expansion is already in the merged form, so canonicalising it again changes nothing.
|
|
71
|
+
assert.deepEqual(mergeEgressRules(policy.allow), policy.allow);
|
|
72
|
+
});
|
|
73
|
+
test("golden: the full android + flutter + cocoapods policy is pinned", async () => {
|
|
74
|
+
const policy = expandVpnEgressPolicy({ egressPresets: ["ios-cocoapods", "android-gradle", "flutter"], egressAllow: [] });
|
|
75
|
+
const canonical = '{"v":1,"allow":[' +
|
|
76
|
+
'{"host":"cdn.cocoapods.org","ports":[443]},{"host":"cdn.jsdelivr.net","ports":[443]},{"host":"dl.google.com","ports":[443]},' +
|
|
77
|
+
'{"host":"github.com","ports":[443]},{"host":"plugins-artifacts.gradle.org","ports":[443]},{"host":"plugins.gradle.org","ports":[443]},' +
|
|
78
|
+
'{"host":"pub.dev","ports":[443]},{"host":"release-assets.githubusercontent.com","ports":[443]},{"host":"repo.maven.apache.org","ports":[443]},' +
|
|
79
|
+
'{"host":"services.gradle.org","ports":[443]},{"host":"storage.googleapis.com","ports":[443]}]}';
|
|
80
|
+
assert.equal(canonicalVpnEgressPolicy(policy), canonical);
|
|
81
|
+
assert.equal(await vpnEgressPolicyHash(policy), sha(canonical));
|
|
82
|
+
});
|
|
83
|
+
test("expansion is order- and duplicate-insensitive, and no settings at all is the tunnel-only policy", async () => {
|
|
84
|
+
const a = expandVpnEgressPolicy({ egressPresets: ["npm", "flutter", "npm"], egressAllow: [{ host: "a.example.com" }, { host: "A.example.com", ports: [443] }] });
|
|
85
|
+
const b = expandVpnEgressPolicy({ egressPresets: ["flutter", "npm"], egressAllow: [{ host: "a.example.com", ports: [443] }] });
|
|
86
|
+
assert.deepEqual(a, b);
|
|
87
|
+
assert.deepEqual(expandVpnEgressPolicy({ egressPresets: [], egressAllow: [] }), { allow: [] });
|
|
88
|
+
assert.equal(canonicalVpnEgressPolicy({ allow: [] }), '{"v":1,"allow":[]}');
|
|
89
|
+
});
|
|
90
|
+
test("expansion refuses what the helper would: an unknown preset, a bad host, an IP literal, a bare *", () => {
|
|
91
|
+
assert.throws(() => expandVpnEgressPolicy({ egressPresets: ["nope"], egressAllow: [] }), /unknown preset/);
|
|
92
|
+
for (const host of ["*", "10.0.0.1", "2130706433", "0x7f.1", "localhost", "a.com.", "*.com", "a b.com", "ünï.example.com", "a.example.com:443", "*.*.example.com"]) {
|
|
93
|
+
assert.throws(() => expandVpnEgressPolicy({ egressPresets: [], egressAllow: [{ host }] }), /invalid egress settings/, host);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
test("validateEgressAllow: bare *, IP literals, scheme/path/port in the host, trailing dot, Unicode and bad ports are all rejected, and no rejected value is echoed", () => {
|
|
97
|
+
const bad = ["*", "10.0.0.1", "127.0.0.1", "169.254.169.254", "::1", "2130706433", "0x7f.0.0.1", "https://a.example.com", "a.example.com/x", "a.example.com.", "a.example.com:443", "ünï.example.com", "", "*.example.*"];
|
|
98
|
+
for (const host of bad) {
|
|
99
|
+
const problems = validateEgressAllow([{ host }]);
|
|
100
|
+
assert.ok(problems.length > 0, `${JSON.stringify(host)} must be rejected`);
|
|
101
|
+
for (const p of problems) {
|
|
102
|
+
assert.match(p, /^egressAllow\[0\]/);
|
|
103
|
+
if (host.length > 3)
|
|
104
|
+
assert.ok(!p.includes(host), `problem echoes the value: ${p}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
assert.ok(validateEgressAllow([{ host: "a.example.com", ports: [0] }]).length > 0);
|
|
108
|
+
assert.ok(validateEgressAllow([{ host: "a.example.com", ports: [65536] }]).length > 0);
|
|
109
|
+
assert.ok(validateEgressAllow([{ host: "a.example.com", ports: [1.5] }]).length > 0);
|
|
110
|
+
assert.ok(validateEgressAllow([{ host: "a.example.com", ports: "443" }]).length > 0);
|
|
111
|
+
assert.ok(validateEgressAllow("a.example.com").length > 0);
|
|
112
|
+
assert.ok(validateEgressAllow([null]).length > 0);
|
|
113
|
+
assert.deepEqual(validateEgressAllow([{ host: "a.example.com" }, { host: "*.b.example.com", ports: [443, 8443] }]), []);
|
|
114
|
+
});
|
|
115
|
+
test("size limits: too many custom entries, too many presets, too many ports (also once merged)", () => {
|
|
116
|
+
const many = Array.from({ length: VPN_EGRESS_MAX_CUSTOM_RULES + 1 }, (_, i) => ({ host: `h${i}.example.com` }));
|
|
117
|
+
assert.match(validateEgressAllow(many)[0], /at most 100 entries/);
|
|
118
|
+
assert.deepEqual(validateEgressAllow(many.slice(0, VPN_EGRESS_MAX_CUSTOM_RULES)), []);
|
|
119
|
+
assert.match(validateEgressPresets(Array.from({ length: VPN_EGRESS_MAX_PRESETS + 1 }, () => "npm"))[0], /at most 16 presets/);
|
|
120
|
+
const ports17 = Array.from({ length: 17 }, (_, i) => 1000 + i);
|
|
121
|
+
assert.ok(validateEgressAllow([{ host: "a.example.com", ports: ports17 }]).length > 0);
|
|
122
|
+
// 16 ports in each of two entries for the same host merge to 17+ distinct ports.
|
|
123
|
+
const first16 = Array.from({ length: 16 }, (_, i) => 1000 + i);
|
|
124
|
+
const entries = [
|
|
125
|
+
{ host: "a.example.com", ports: first16 },
|
|
126
|
+
{ host: "A.example.com", ports: [2000] },
|
|
127
|
+
];
|
|
128
|
+
assert.deepEqual(validateEgressAllow(entries), []);
|
|
129
|
+
assert.match(validateEgressSettings({ egressAllow: entries })[0], /more than 16 ports once its presets and entries are merged/);
|
|
130
|
+
assert.throws(() => expandVpnEgressPolicy({ egressPresets: [], egressAllow: entries }), /merged/);
|
|
131
|
+
assert.ok(VPN_EGRESS_MAX_CUSTOM_RULES + VPN_EGRESS_PRESETS.reduce((n, p) => n + p.rules.length, 0) < VPN_EGRESS_MAX_RULES);
|
|
132
|
+
});
|
|
133
|
+
test("validateEgressPresets: unknown, malformed and non-array input is refused", () => {
|
|
134
|
+
assert.deepEqual(validateEgressPresets(["flutter", "npm"]), []);
|
|
135
|
+
assert.match(validateEgressPresets(["bogus"])[0], /unknown preset/);
|
|
136
|
+
assert.match(validateEgressPresets(["Flutter"])[0], /must be a preset id/);
|
|
137
|
+
assert.match(validateEgressPresets([1])[0], /must be a preset id/);
|
|
138
|
+
assert.match(validateEgressPresets("flutter")[0], /must be an array/);
|
|
139
|
+
assert.deepEqual(normalizeEgressPresets(["npm", "flutter", "npm"]), ["flutter", "npm"]);
|
|
140
|
+
});
|
|
141
|
+
test("parseEgressAllowEntry: host, host:port, and what is not an entry", () => {
|
|
142
|
+
assert.deepEqual(parseEgressAllowEntry("repo.example.com"), { rule: { host: "repo.example.com" } });
|
|
143
|
+
assert.deepEqual(parseEgressAllowEntry(" repo.example.com:8443 "), { rule: { host: "repo.example.com", ports: [8443] } });
|
|
144
|
+
assert.deepEqual(parseEgressAllowEntry("*.example.com:443"), { rule: { host: "*.example.com", ports: [443] } });
|
|
145
|
+
for (const bad of ["", "https://a.example.com", "a.example.com/x", "[::1]:443", "::1", "a.example.com:", "a.example.com:0", "a.example.com:70000", "a.example.com:12ab", "a b"]) {
|
|
146
|
+
assert.ok("error" in parseEgressAllowEntry(bad), JSON.stringify(bad));
|
|
147
|
+
}
|
|
148
|
+
const { rules, errors } = parseEgressAllowEntries(["a.example.com:443", "a.example.com:8443", "https://x"]);
|
|
149
|
+
assert.equal(rules.length, 2);
|
|
150
|
+
assert.equal(errors.length, 1);
|
|
151
|
+
assert.deepEqual(mergeEgressRules(rules), [{ host: "a.example.com", ports: [443, 8443] }]);
|
|
152
|
+
});
|
|
153
|
+
test("a long rejected entry is shortened in its error", () => {
|
|
154
|
+
const parsed = parseEgressAllowEntry(`https://${"a".repeat(300)}.example.com`);
|
|
155
|
+
assert.ok("error" in parsed && parsed.error.length < 140);
|
|
156
|
+
});
|
|
157
|
+
test("egressSettingsFromRow is strict: a corrupt column throws instead of reading as empty", () => {
|
|
158
|
+
assert.deepEqual(egressSettingsFromRow({ egressPresets: '["npm"]', egressAllow: '[{"host":"a.example.com"}]' }), {
|
|
159
|
+
egressPresets: ["npm"],
|
|
160
|
+
egressAllow: [{ host: "a.example.com" }],
|
|
161
|
+
});
|
|
162
|
+
assert.throws(() => egressSettingsFromRow({ egressPresets: "not json", egressAllow: "[]" }), /egressPresets/);
|
|
163
|
+
assert.throws(() => egressSettingsFromRow({ egressPresets: "[]", egressAllow: '{"host":"a"}' }), /egressAllow/);
|
|
164
|
+
assert.throws(() => egressSettingsFromRow({ egressPresets: "[]", egressAllow: null }), /egressAllow/);
|
|
165
|
+
// A well-formed JSON array of the wrong thing is refused at expansion, never trusted.
|
|
166
|
+
const s = egressSettingsFromRow({ egressPresets: "[1]", egressAllow: '["x"]' });
|
|
167
|
+
assert.throws(() => expandVpnEgressPolicy(s), /invalid egress settings/);
|
|
168
|
+
});
|
|
169
|
+
test("egressSettingsForDisplay is lenient and never throws", () => {
|
|
170
|
+
assert.deepEqual(egressSettingsForDisplay({ egressPresets: "nope", egressAllow: "nope" }), { egressPresets: [], egressAllow: [] });
|
|
171
|
+
assert.deepEqual(egressSettingsForDisplay({ egressPresets: '["npm",3]', egressAllow: '[{"host":"a.example.com","ports":[443,"x"]},7,{"ports":[1]}]' }), {
|
|
172
|
+
egressPresets: ["npm"],
|
|
173
|
+
egressAllow: [{ host: "a.example.com", ports: [443] }],
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
test("formatEgressRule shows the default port as the bare host", () => {
|
|
177
|
+
assert.deepEqual(formatEgressRule({ host: "a.example.com" }), ["a.example.com"]);
|
|
178
|
+
assert.deepEqual(formatEgressRule({ host: "a.example.com", ports: [443, 8443] }), ["a.example.com", "a.example.com:8443"]);
|
|
179
|
+
});
|
|
180
|
+
test("a preset host plus a custom entry for it is validated as merged: 443 + 16 custom ports is refused at save time, not at the first lease", () => {
|
|
181
|
+
const sixteen = Array.from({ length: 16 }, (_, i) => 8001 + i);
|
|
182
|
+
const settings = { egressPresets: ["github"], egressAllow: [{ host: "github.com", ports: sixteen }] };
|
|
183
|
+
const problems = validateEgressSettings(settings);
|
|
184
|
+
assert.equal(problems.length, 1);
|
|
185
|
+
assert.match(problems[0], /github\.com lists more than 16 ports once its presets and entries are merged/);
|
|
186
|
+
// The same settings are what expansion (the lease path) refuses, so save and lease now agree.
|
|
187
|
+
assert.throws(() => expandVpnEgressPolicy(settings), /merged/);
|
|
188
|
+
// 15 custom ports + the preset's 443 is exactly 16: accepted by both.
|
|
189
|
+
const fifteen = sixteen.slice(0, 15);
|
|
190
|
+
assert.deepEqual(validateEgressSettings({ ...settings, egressAllow: [{ host: "github.com", ports: fifteen }] }), []);
|
|
191
|
+
assert.doesNotThrow(() => expandVpnEgressPolicy({ ...settings, egressAllow: [{ host: "github.com", ports: fifteen }] }));
|
|
192
|
+
// Restating a preset's own port adds nothing.
|
|
193
|
+
assert.deepEqual(validateEgressSettings({ egressPresets: ["github"], egressAllow: [{ host: "github.com", ports: [443] }] }), []);
|
|
194
|
+
});
|
|
195
|
+
test("the tunnel schemas themselves reject bare *, IP literals and a bad preset, so a caller using them alone is safe", () => {
|
|
196
|
+
const base = {
|
|
197
|
+
name: "HQ",
|
|
198
|
+
gatewayEndpoint: "vpn.example.com:51820",
|
|
199
|
+
gatewayPublicKey: "A".repeat(43) + "=",
|
|
200
|
+
lanCidrs: ["10.20.0.0/16"],
|
|
201
|
+
};
|
|
202
|
+
assert.equal(createVpnTunnelSchema.safeParse({ ...base, egressAllow: [{ host: "a.example.com" }], egressPresets: ["npm"] }).success, true);
|
|
203
|
+
for (const host of ["*", "10.0.0.1"]) {
|
|
204
|
+
assert.equal(createVpnTunnelSchema.safeParse({ ...base, egressAllow: [{ host }] }).success, false, host);
|
|
205
|
+
assert.equal(updateVpnTunnelSchema.safeParse({ egressAllow: [{ host }] }).success, false, host);
|
|
206
|
+
}
|
|
207
|
+
assert.equal(updateVpnTunnelSchema.safeParse({ egressPresets: ["no-such-preset"] }).success, false);
|
|
208
|
+
assert.equal(updateVpnTunnelSchema.safeParse({ strictEgress: true }).success, true);
|
|
209
|
+
});
|
|
210
|
+
//# sourceMappingURL=vpn-egress-presets.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vpn-egress-presets.test.js","sourceRoot":"","sources":["../src/vpn-egress-presets.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict egress policy for the site-to-site VPN helper (`VpnJobConfig.egress`,
|
|
3
|
+
* see packages/vpn-proxy/PROTOCOL.md, "Strict egress"). Pure: validation, the
|
|
4
|
+
* canonical form and its SHA-256, so the control plane, the agent and the
|
|
5
|
+
* helper agree on exactly which policy a build ran under.
|
|
6
|
+
*
|
|
7
|
+
* This file is the TypeScript twin of `packages/vpn-proxy/internal/policy`.
|
|
8
|
+
* Both must accept and reject the same inputs and produce the same canonical
|
|
9
|
+
* bytes; `packages/vpn-proxy/testdata/egress-policy-vectors.json` is the shared
|
|
10
|
+
* proof and both test suites run it.
|
|
11
|
+
*
|
|
12
|
+
* Hosts are ASCII/punycode only (the control plane converts Unicode names
|
|
13
|
+
* before storing them): that keeps the two implementations free of IDNA and
|
|
14
|
+
* Unicode case-mapping differences.
|
|
15
|
+
*/
|
|
16
|
+
import type { VpnEgressPolicy } from "./types.js";
|
|
17
|
+
/** Mirrors `policy.MaxRules` / `policy.MaxPortsPerRule` / `policy.DefaultPort` / `policy.Version`. */
|
|
18
|
+
export declare const VPN_EGRESS_MAX_RULES = 512;
|
|
19
|
+
export declare const VPN_EGRESS_MAX_PORTS_PER_RULE = 16;
|
|
20
|
+
export declare const VPN_EGRESS_DEFAULT_PORT = 443;
|
|
21
|
+
export declare const VPN_EGRESS_POLICY_VERSION = 1;
|
|
22
|
+
/**
|
|
23
|
+
* Mirrors `config.MaxJobBytes`: the most stdin the helper reads for one job.
|
|
24
|
+
* It is sized so the largest valid policy (512 rules, 253-byte hosts, 16 ports
|
|
25
|
+
* each: about 190 KB as compact JSON) plus the rest of the job fits; the agent
|
|
26
|
+
* still checks the serialised job against it before spawning, because extra
|
|
27
|
+
* keys inside rules are ignored by validation but would count against the cap.
|
|
28
|
+
*/
|
|
29
|
+
export declare const VPN_JOB_MAX_BYTES: number;
|
|
30
|
+
/**
|
|
31
|
+
* Normalises one allow-list host: lowercase; either `name.tld` or `*.name.tld`
|
|
32
|
+
* with at least two labels after any `*.`; no IP literal in any spelling (a
|
|
33
|
+
* numeric last label is refused, so `10.0.0.1`, `2130706433`, `0x7f.1` all
|
|
34
|
+
* fail), no trailing dot, no port/scheme/path, no Unicode. Returns `null` when
|
|
35
|
+
* the host is not acceptable.
|
|
36
|
+
*/
|
|
37
|
+
export declare function normalizeEgressHost(raw: unknown): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Every problem with an egress policy (an empty array means valid). Problem
|
|
40
|
+
* strings name the field, like the helper's own validation
|
|
41
|
+
* (`egress.allow[3].host`), and never echo a rejected value.
|
|
42
|
+
*/
|
|
43
|
+
export declare function validateVpnEgressPolicy(policy: unknown): string[];
|
|
44
|
+
/**
|
|
45
|
+
* The exact bytes the helper hashes:
|
|
46
|
+
*
|
|
47
|
+
* {"v":1,"allow":[{"host":"*.googleapis.com","ports":[443]},{"host":"pub.dev","ports":[443]}]}
|
|
48
|
+
*
|
|
49
|
+
* Hosts lowercased; one entry per host (duplicates merge, ports unioned);
|
|
50
|
+
* sorted by host in byte order (so `*.` sorts before letters); ports ascending
|
|
51
|
+
* and deduplicated with the default 443 made explicit; no whitespace.
|
|
52
|
+
* Throws on an invalid policy (callers validate first and report the problems).
|
|
53
|
+
*/
|
|
54
|
+
export declare function canonicalVpnEgressPolicy(policy: VpnEgressPolicy): string;
|
|
55
|
+
/** Lowercase hex SHA-256 of `canonicalVpnEgressPolicy`: what the helper echoes as `policyHash`. */
|
|
56
|
+
export declare function vpnEgressPolicyHash(policy: VpnEgressPolicy): Promise<string>;
|
|
57
|
+
//# sourceMappingURL=vpn-egress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vpn-egress.d.ts","sourceRoot":"","sources":["../src/vpn-egress.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,sGAAsG;AACtG,eAAO,MAAM,oBAAoB,MAAM,CAAC;AACxC,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAChD,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,QAAa,CAAC;AAK5C;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAe/D;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,EAAE,CAiCjE;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAmBxE;AAED,mGAAmG;AACnG,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAIlF"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/** Mirrors `policy.MaxRules` / `policy.MaxPortsPerRule` / `policy.DefaultPort` / `policy.Version`. */
|
|
2
|
+
export const VPN_EGRESS_MAX_RULES = 512;
|
|
3
|
+
export const VPN_EGRESS_MAX_PORTS_PER_RULE = 16;
|
|
4
|
+
export const VPN_EGRESS_DEFAULT_PORT = 443;
|
|
5
|
+
export const VPN_EGRESS_POLICY_VERSION = 1;
|
|
6
|
+
/**
|
|
7
|
+
* Mirrors `config.MaxJobBytes`: the most stdin the helper reads for one job.
|
|
8
|
+
* It is sized so the largest valid policy (512 rules, 253-byte hosts, 16 ports
|
|
9
|
+
* each: about 190 KB as compact JSON) plus the rest of the job fits; the agent
|
|
10
|
+
* still checks the serialised job against it before spawning, because extra
|
|
11
|
+
* keys inside rules are ignored by validation but would count against the cap.
|
|
12
|
+
*/
|
|
13
|
+
export const VPN_JOB_MAX_BYTES = 256 * 1024;
|
|
14
|
+
/** Mirrors Go `route.Normalize`: LDH plus `_`, labels 1..63, total <= 253. */
|
|
15
|
+
const LABEL = /^[a-z0-9_-]{1,63}$/;
|
|
16
|
+
/**
|
|
17
|
+
* Normalises one allow-list host: lowercase; either `name.tld` or `*.name.tld`
|
|
18
|
+
* with at least two labels after any `*.`; no IP literal in any spelling (a
|
|
19
|
+
* numeric last label is refused, so `10.0.0.1`, `2130706433`, `0x7f.1` all
|
|
20
|
+
* fail), no trailing dot, no port/scheme/path, no Unicode. Returns `null` when
|
|
21
|
+
* the host is not acceptable.
|
|
22
|
+
*/
|
|
23
|
+
export function normalizeEgressHost(raw) {
|
|
24
|
+
if (typeof raw !== "string")
|
|
25
|
+
return null;
|
|
26
|
+
// ASCII only, BEFORE lowercasing: toLowerCase() folds the Kelvin sign (U+212A)
|
|
27
|
+
// to "k". Mirrors the Go implementation.
|
|
28
|
+
if (!/^[\x00-\x7f]*$/.test(raw))
|
|
29
|
+
return null;
|
|
30
|
+
const host = raw.toLowerCase();
|
|
31
|
+
const wild = host.startsWith("*.");
|
|
32
|
+
const name = wild ? host.slice(2) : host;
|
|
33
|
+
if (name === "" || name.length > 253 || name.endsWith(".") || name.includes("*"))
|
|
34
|
+
return null;
|
|
35
|
+
const labels = name.split(".");
|
|
36
|
+
if (labels.length < 2)
|
|
37
|
+
return null;
|
|
38
|
+
for (const l of labels)
|
|
39
|
+
if (!LABEL.test(l))
|
|
40
|
+
return null;
|
|
41
|
+
const last = labels[labels.length - 1];
|
|
42
|
+
if (last.startsWith("0x") || /^[0-9]+$/.test(last))
|
|
43
|
+
return null; // an IP in some spelling
|
|
44
|
+
return wild ? `*.${name}` : name;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Every problem with an egress policy (an empty array means valid). Problem
|
|
48
|
+
* strings name the field, like the helper's own validation
|
|
49
|
+
* (`egress.allow[3].host`), and never echo a rejected value.
|
|
50
|
+
*/
|
|
51
|
+
export function validateVpnEgressPolicy(policy) {
|
|
52
|
+
const problems = [];
|
|
53
|
+
if (policy === null || typeof policy !== "object" || Array.isArray(policy))
|
|
54
|
+
return ["egress: must be an object"];
|
|
55
|
+
const allow = policy.allow;
|
|
56
|
+
if (!Array.isArray(allow))
|
|
57
|
+
return ["egress.allow: must be an array"];
|
|
58
|
+
if (allow.length > VPN_EGRESS_MAX_RULES)
|
|
59
|
+
return [`egress.allow: may contain at most ${VPN_EGRESS_MAX_RULES} entries`];
|
|
60
|
+
allow.forEach((rule, i) => {
|
|
61
|
+
const at = `egress.allow[${i}]`;
|
|
62
|
+
if (rule === null || typeof rule !== "object" || Array.isArray(rule)) {
|
|
63
|
+
problems.push(`${at}: must be an object`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const { host, ports } = rule;
|
|
67
|
+
if (normalizeEgressHost(host) === null) {
|
|
68
|
+
problems.push(`${at}.host: must be a hostname like example.com or *.example.com (no IP literals, no bare *, no trailing dot)`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (ports === undefined || ports === null)
|
|
72
|
+
return;
|
|
73
|
+
if (!Array.isArray(ports)) {
|
|
74
|
+
problems.push(`${at}.ports: must be an array`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (ports.length > VPN_EGRESS_MAX_PORTS_PER_RULE) {
|
|
78
|
+
problems.push(`${at}.ports: may contain at most ${VPN_EGRESS_MAX_PORTS_PER_RULE} ports`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!ports.every((p) => typeof p === "number" && Number.isInteger(p) && p >= 1 && p <= 65535)) {
|
|
82
|
+
problems.push(`${at}.ports: each port must be 1-65535`);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
return problems;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The exact bytes the helper hashes:
|
|
89
|
+
*
|
|
90
|
+
* {"v":1,"allow":[{"host":"*.googleapis.com","ports":[443]},{"host":"pub.dev","ports":[443]}]}
|
|
91
|
+
*
|
|
92
|
+
* Hosts lowercased; one entry per host (duplicates merge, ports unioned);
|
|
93
|
+
* sorted by host in byte order (so `*.` sorts before letters); ports ascending
|
|
94
|
+
* and deduplicated with the default 443 made explicit; no whitespace.
|
|
95
|
+
* Throws on an invalid policy (callers validate first and report the problems).
|
|
96
|
+
*/
|
|
97
|
+
export function canonicalVpnEgressPolicy(policy) {
|
|
98
|
+
const problems = validateVpnEgressPolicy(policy);
|
|
99
|
+
if (problems.length > 0)
|
|
100
|
+
throw new Error(`invalid egress policy: ${problems.join("; ")}`);
|
|
101
|
+
const merged = new Map();
|
|
102
|
+
for (const rule of policy.allow) {
|
|
103
|
+
const host = normalizeEgressHost(rule.host);
|
|
104
|
+
let set = merged.get(host);
|
|
105
|
+
if (!set)
|
|
106
|
+
merged.set(host, (set = new Set()));
|
|
107
|
+
const ports = rule.ports && rule.ports.length > 0 ? rule.ports : [VPN_EGRESS_DEFAULT_PORT];
|
|
108
|
+
for (const p of ports)
|
|
109
|
+
set.add(p);
|
|
110
|
+
}
|
|
111
|
+
// Default Array.prototype.sort compares UTF-16 code units, which for ASCII is
|
|
112
|
+
// the same order as Go's byte-wise slices.Sort.
|
|
113
|
+
const hosts = [...merged.keys()].sort();
|
|
114
|
+
const entries = hosts.map((h) => {
|
|
115
|
+
const ports = [...merged.get(h)].sort((a, b) => a - b);
|
|
116
|
+
return `{"host":"${h}","ports":[${ports.join(",")}]}`;
|
|
117
|
+
});
|
|
118
|
+
return `{"v":${VPN_EGRESS_POLICY_VERSION},"allow":[${entries.join(",")}]}`;
|
|
119
|
+
}
|
|
120
|
+
/** Lowercase hex SHA-256 of `canonicalVpnEgressPolicy`: what the helper echoes as `policyHash`. */
|
|
121
|
+
export async function vpnEgressPolicyHash(policy) {
|
|
122
|
+
const bytes = new TextEncoder().encode(canonicalVpnEgressPolicy(policy));
|
|
123
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
124
|
+
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=vpn-egress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vpn-egress.js","sourceRoot":"","sources":["../src/vpn-egress.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vpn-egress.test.d.ts","sourceRoot":"","sources":["../src/vpn-egress.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { canonicalVpnEgressPolicy, normalizeEgressHost, validateVpnEgressPolicy, vpnEgressPolicyHash, VPN_EGRESS_MAX_PORTS_PER_RULE, VPN_EGRESS_MAX_RULES, VPN_JOB_MAX_BYTES, } from "./vpn-egress.js";
|
|
7
|
+
import { vpnEgressPolicySchema } from "./schemas.js";
|
|
8
|
+
// The Go implementation (packages/vpn-proxy/internal/policy) runs the same file.
|
|
9
|
+
const vectors = JSON.parse(readFileSync(fileURLToPath(new URL("../../vpn-proxy/testdata/egress-policy-vectors.json", import.meta.url)), "utf8"));
|
|
10
|
+
test("shared vectors: same validity, same canonical bytes, same hash as the Go helper", async () => {
|
|
11
|
+
assert.ok(vectors.length >= 20);
|
|
12
|
+
assert.ok(vectors.some((v) => v.valid) && vectors.some((v) => !v.valid));
|
|
13
|
+
for (const v of vectors) {
|
|
14
|
+
const policy = { allow: v.allow };
|
|
15
|
+
const problems = validateVpnEgressPolicy(policy);
|
|
16
|
+
if (!v.valid) {
|
|
17
|
+
assert.ok(problems.length > 0, `${v.name}: should be rejected`);
|
|
18
|
+
assert.throws(() => canonicalVpnEgressPolicy(policy), /invalid egress policy/, v.name);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
assert.deepEqual(problems, [], `${v.name}: should be valid`);
|
|
22
|
+
assert.equal(canonicalVpnEgressPolicy(policy), v.canonical, `${v.name}: canonical`);
|
|
23
|
+
assert.equal(createHash("sha256").update(v.canonical).digest("hex"), v.sha256, `${v.name}: vector hash`);
|
|
24
|
+
assert.equal(await vpnEgressPolicyHash(policy), v.sha256, `${v.name}: hash`);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
test("canonical form is insensitive to order, case, duplicates and explicit default ports", () => {
|
|
28
|
+
const a = canonicalVpnEgressPolicy({
|
|
29
|
+
allow: [{ host: "B.example.com", ports: [8443, 443] }, { host: "a.example.com" }, { host: "*.c.example.com", ports: [443] }],
|
|
30
|
+
});
|
|
31
|
+
const b = canonicalVpnEgressPolicy({
|
|
32
|
+
allow: [
|
|
33
|
+
{ host: "*.C.example.com" },
|
|
34
|
+
{ host: "a.example.com", ports: [443] },
|
|
35
|
+
{ host: "b.example.com", ports: [443] },
|
|
36
|
+
{ host: "b.example.com", ports: [8443] },
|
|
37
|
+
{ host: "A.EXAMPLE.COM" },
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
assert.equal(a, b);
|
|
41
|
+
assert.equal(a, '{"v":1,"allow":[{"host":"*.c.example.com","ports":[443]},{"host":"a.example.com","ports":[443]},{"host":"b.example.com","ports":[443,8443]}]}');
|
|
42
|
+
assert.equal(canonicalVpnEgressPolicy({ allow: [] }), '{"v":1,"allow":[]}');
|
|
43
|
+
assert.equal(canonicalVpnEgressPolicy({ allow: [{ host: "x.example.com", ports: [] }] }), '{"v":1,"allow":[{"host":"x.example.com","ports":[443]}]}');
|
|
44
|
+
});
|
|
45
|
+
test("the hash is the SHA-256 of the canonical bytes and changes with the policy", async () => {
|
|
46
|
+
const p = { allow: [{ host: "pub.dev" }] };
|
|
47
|
+
const h = await vpnEgressPolicyHash(p);
|
|
48
|
+
assert.match(h, /^[0-9a-f]{64}$/);
|
|
49
|
+
assert.equal(h, createHash("sha256").update(canonicalVpnEgressPolicy(p)).digest("hex"));
|
|
50
|
+
assert.notEqual(h, await vpnEgressPolicyHash({ allow: [{ host: "pub.dev", ports: [443, 80] }] }));
|
|
51
|
+
assert.notEqual(h, await vpnEgressPolicyHash({ allow: [] }));
|
|
52
|
+
await assert.rejects(() => vpnEgressPolicyHash({ allow: [{ host: "*" }] }), /invalid egress policy/);
|
|
53
|
+
});
|
|
54
|
+
test("normalizeEgressHost accepts hostnames and wildcards, refuses everything IP-shaped or overbroad", () => {
|
|
55
|
+
for (const [raw, want] of [
|
|
56
|
+
["pub.dev", "pub.dev"],
|
|
57
|
+
["Repo.Maven.Apache.ORG", "repo.maven.apache.org"],
|
|
58
|
+
["*.GoogleAPIs.com", "*.googleapis.com"],
|
|
59
|
+
["xn--bcher-kva.example", "xn--bcher-kva.example"],
|
|
60
|
+
["_dmarc.example.com", "_dmarc.example.com"],
|
|
61
|
+
]) {
|
|
62
|
+
assert.equal(normalizeEgressHost(raw), want, raw);
|
|
63
|
+
}
|
|
64
|
+
for (const bad of [
|
|
65
|
+
"", "*", "*.", "*.com", "intranet", "a.*.example.com", "*.*.example.com", "a*.example.com", "example.*",
|
|
66
|
+
"10.0.0.1", "*.10.0.0.1", "2130706433", "0x7f.0.0.1", "127.1", "::1", "[::1]", "example.com.", ".example.com",
|
|
67
|
+
"a..example.com", "https://example.com", "example.com/x", "example.com:443", "a@example.com", "a b.example.com",
|
|
68
|
+
"bücher.example", "EXAMPLE.com", "Kubernetes.io", "example.123", `${"a".repeat(64)}.example.com`,
|
|
69
|
+
`${"a.".repeat(130)}com`,
|
|
70
|
+
]) {
|
|
71
|
+
assert.equal(normalizeEgressHost(bad), null, JSON.stringify(bad));
|
|
72
|
+
}
|
|
73
|
+
for (const nonString of [undefined, null, 5, {}, []])
|
|
74
|
+
assert.equal(normalizeEgressHost(nonString), null);
|
|
75
|
+
});
|
|
76
|
+
test("validateVpnEgressPolicy reports every problem, names the field, never echoes the value", () => {
|
|
77
|
+
const secret = "SUPERSECRET";
|
|
78
|
+
const problems = validateVpnEgressPolicy({
|
|
79
|
+
allow: [
|
|
80
|
+
{ host: "ok.example.com" },
|
|
81
|
+
{ host: `${secret}/x` },
|
|
82
|
+
{ host: "10.0.0.1" },
|
|
83
|
+
{ host: "ok.example.com", ports: [0] },
|
|
84
|
+
{ host: "ok.example.com", ports: [65536] },
|
|
85
|
+
{ host: "ok.example.com", ports: [1.5] },
|
|
86
|
+
{ host: "ok.example.com", ports: ["443"] },
|
|
87
|
+
{ host: "ok.example.com", ports: "443" },
|
|
88
|
+
{ host: "ok.example.com", ports: Array.from({ length: VPN_EGRESS_MAX_PORTS_PER_RULE + 1 }, (_, i) => i + 1) },
|
|
89
|
+
null,
|
|
90
|
+
"pub.dev",
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
assert.equal(problems.length, 10);
|
|
94
|
+
assert.ok(problems[0].startsWith("egress.allow[1].host"));
|
|
95
|
+
assert.ok(problems.every((p) => p.startsWith("egress.allow[")));
|
|
96
|
+
assert.ok(!problems.some((p) => p.includes(secret)));
|
|
97
|
+
assert.deepEqual(validateVpnEgressPolicy({ allow: [] }), []);
|
|
98
|
+
assert.deepEqual(validateVpnEgressPolicy({ allow: [{ host: "pub.dev", ports: null }] }), []);
|
|
99
|
+
for (const bad of [null, undefined, 5, "x", [], {}, { allow: "pub.dev" }, { allow: null }]) {
|
|
100
|
+
assert.equal(validateVpnEgressPolicy(bad).length, 1, JSON.stringify(bad));
|
|
101
|
+
}
|
|
102
|
+
const tooMany = { allow: Array.from({ length: VPN_EGRESS_MAX_RULES + 1 }, (_, i) => ({ host: `h${i}.example.com` })) };
|
|
103
|
+
assert.equal(validateVpnEgressPolicy(tooMany).length, 1);
|
|
104
|
+
assert.deepEqual(validateVpnEgressPolicy({ allow: tooMany.allow.slice(0, VPN_EGRESS_MAX_RULES) }), []);
|
|
105
|
+
});
|
|
106
|
+
test("vpnEgressPolicySchema bounds shape and size", () => {
|
|
107
|
+
assert.ok(vpnEgressPolicySchema.safeParse({ allow: [] }).success);
|
|
108
|
+
assert.ok(vpnEgressPolicySchema.safeParse({ allow: [{ host: "pub.dev" }, { host: "*.example.com", ports: [443, 8443] }] }).success);
|
|
109
|
+
for (const bad of [
|
|
110
|
+
{}, { allow: "x" }, { allow: [{}] }, { allow: [{ host: "" }] }, { allow: [{ host: "a.example.com", ports: [0] }] },
|
|
111
|
+
{ allow: [{ host: "a.example.com", ports: [1.5] }] }, { allow: [{ host: "a.example.com", ports: [70000] }] },
|
|
112
|
+
{ allow: Array.from({ length: 513 }, () => ({ host: "a.example.com" })) },
|
|
113
|
+
]) {
|
|
114
|
+
assert.ok(!vpnEgressPolicySchema.safeParse(bad).success, JSON.stringify(bad).slice(0, 60));
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
test("wrapper shape: missing or null allow, and wrong-case keys, are invalid (the Go decoder agrees)", () => {
|
|
118
|
+
for (const bad of [{}, { allow: null }, { Allow: [] }, { allow: [{ Host: "pub.dev" }] }, { allow: [null] }]) {
|
|
119
|
+
assert.ok(validateVpnEgressPolicy(bad).length > 0, JSON.stringify(bad));
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
test("the largest valid policy fits in the helper's stdin cap with room for the rest of the job", () => {
|
|
123
|
+
const label = (c, n) => c.repeat(n);
|
|
124
|
+
const allow = Array.from({ length: VPN_EGRESS_MAX_RULES }, (_, i) => ({
|
|
125
|
+
host: `${String(i).padStart(3, "0")}${"a".repeat(60)}.${label("b", 63)}.${label("c", 63)}.${label("d", 61)}`,
|
|
126
|
+
ports: Array.from({ length: VPN_EGRESS_MAX_PORTS_PER_RULE }, (_, k) => 60000 + k),
|
|
127
|
+
}));
|
|
128
|
+
assert.equal(allow[0].host.length, 253);
|
|
129
|
+
assert.deepEqual(validateVpnEgressPolicy({ allow }), []);
|
|
130
|
+
const bytes = Buffer.byteLength(JSON.stringify({ egress: { allow } }));
|
|
131
|
+
assert.ok(bytes + 8 * 1024 < VPN_JOB_MAX_BYTES, `${bytes} bytes leaves too little room under ${VPN_JOB_MAX_BYTES}`);
|
|
132
|
+
});
|
|
133
|
+
//# sourceMappingURL=vpn-egress.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vpn-egress.test.js","sourceRoot":"","sources":["../src/vpn-egress.test.ts"],"names":[],"mappings":"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"}
|