socketduct 0.1.24 → 0.1.26
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/AGENTS.md +2 -2
- package/changelog.d/20261010130509-bound-log-frame-compression-memory.md +1 -0
- package/docs/runbooks/jsonl-integrity-rollout.md +8 -6
- package/package.json +2 -2
- package/scripts/test.js +9 -14
- package/src/client/multiplexed-transport-set.js +20 -2
- package/src/logging/compressed-log-writer.js +1 -1
- package/src/logging/zstd-frame-codec.js +15 -1
- package/src/reverse/client.js +44 -1
- package/src/reverse/hub.js +10 -1
- package/src/reverse/runtime.js +11 -2
- package/test/e2e/gateway-process.test.js +93 -92
- package/test/e2e/logger-shutdown-process.test.js +40 -37
- package/test/e2e/reverse-client-recovery-process.test.js +107 -103
- package/test/e2e/reverse-gateway-process.test.js +328 -318
- package/test/integration/initial-target-recovery.test.js +333 -334
- package/test/integration/logging-cli.test.js +121 -119
- package/test/integration/logging-compressed-recovery.test.js +96 -95
- package/test/integration/multiplexed-compatibility.test.js +178 -177
- package/test/integration/multiplexed-corrections.test.js +360 -359
- package/test/integration/multiplexed-failures.test.js +253 -251
- package/test/integration/multiplexed-transport-set.test.js +334 -322
- package/test/integration/multiplexed-transport.test.js +135 -134
- package/test/integration/reverse-gateway-stream-id-reset.test.js +90 -89
- package/test/integration/reverse-gateway.test.js +2309 -2218
- package/test/integration/reverse-resume-continuity.test.js +219 -218
- package/test/integration/reverse-route-class.test.js +363 -278
- package/test/integration/socketduct-http-agent.test.js +314 -312
- package/test/integration/socketduct-https-agent.test.js +516 -485
- package/test/integration/stream-compression.test.js +862 -824
- package/test/integration/tcp-forwarding.test.js +2550 -2447
- package/test/integration/tls-forwarding.test.js +593 -592
- package/test/unit/auth/auth.test.js +43 -44
- package/test/unit/auth/static-token-store.test.js +9 -8
- package/test/unit/client/connect.test.js +41 -40
- package/test/unit/client/deferred-virtual-socket.test.js +95 -94
- package/test/unit/client/multiplexed-spool-budget.test.js +155 -154
- package/test/unit/client/multiplexed-transport-set.test.js +845 -732
- package/test/unit/client/socketduct-socket.test.js +768 -771
- package/test/unit/client/virtual-socket.test.js +198 -168
- package/test/unit/config/docker-gateway-files.test.js +224 -224
- package/test/unit/config/environment.test.js +472 -492
- package/test/unit/index.test.js +53 -52
- package/test/unit/logging/compressed-log-worker.test.js +23 -22
- package/test/unit/logging/compressed-log-writer.test.js +155 -154
- package/test/unit/logging/log-retention.test.js +52 -47
- package/test/unit/logging/log-segment-index.test.js +84 -81
- package/test/unit/logging/zstd-frame-codec.test.js +28 -19
- package/test/unit/protocol/compression.test.js +254 -298
- package/test/unit/protocol/frame.test.js +280 -276
- package/test/unit/protocol/v3-compression.test.js +21 -23
- package/test/unit/protocol/v3-frame.test.js +42 -41
- package/test/unit/release-configuration.test.js +15 -12
- package/test/unit/reverse/disconnect-attribution.test.js +65 -64
- package/test/unit/reverse-terminal-outcomes.test.js +53 -50
- package/test/unit/server/control-handler.test.js +232 -231
- package/test/unit/server/primary.test.js +51 -49
- package/test/unit/server/relay-runtime.test.js +280 -294
- package/test/unit/sessions/session.test.js +48 -62
- package/test/unit/spool/spool-file.test.js +401 -385
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {describe, expect, it, withTestCleanup} from "@velocious/testing"
|
|
2
2
|
import {createConnection, createServer} from "node:net"
|
|
3
3
|
import {mkdtempSync, rmSync} from "node:fs"
|
|
4
4
|
import {tmpdir} from "node:os"
|
|
5
5
|
import {join} from "node:path"
|
|
6
|
-
import test from "node:test"
|
|
7
6
|
|
|
8
7
|
import {encodeFrame, FrameDecoder} from "../../../src/protocol/frame.js"
|
|
9
8
|
import {encodeV3Control, encodeV3Frame, V3FrameType} from "../../../src/protocol/v3-frame.js"
|
|
@@ -47,142 +46,163 @@ async function startTargetServer() {
|
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
await target.close()
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
assert.deepEqual(runtime.relay.status(), {
|
|
72
|
-
activeTunnels: 0,
|
|
73
|
-
activeTargets: 0,
|
|
74
|
-
sessions: 0,
|
|
75
|
-
attachedSessions: 0,
|
|
76
|
-
detachedSessions: 0,
|
|
77
|
-
bufferedTargetBytes: 0,
|
|
78
|
-
targetConnectionAttempts: 0,
|
|
79
|
-
targetRetryTimers: 0
|
|
49
|
+
/**
|
|
50
|
+
* @param {import('node:net').Socket} socket - Connected relay socket.
|
|
51
|
+
* @param {Record<string, unknown> & {type: string}} header - Control frame header.
|
|
52
|
+
* @returns {Promise<Record<string, unknown>>} Decoded response header.
|
|
53
|
+
*/
|
|
54
|
+
async function sendControl(socket, header) {
|
|
55
|
+
const decoder = new FrameDecoder()
|
|
56
|
+
return await new Promise((resolve, reject) => {
|
|
57
|
+
socket.on("data", (chunk) => {
|
|
58
|
+
for (const frame of decoder.push(chunk)) {
|
|
59
|
+
resolve(frame.header)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
socket.once("error", reject)
|
|
63
|
+
const framedHeader = header.type === "session.create" || header.type === "session.resume"
|
|
64
|
+
? {...header, framing: header.framing ?? [2]}
|
|
65
|
+
: header
|
|
66
|
+
socket.write(encodeFrame(framedHeader))
|
|
80
67
|
})
|
|
68
|
+
}
|
|
81
69
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
70
|
+
describe("relay runtime", () => {
|
|
71
|
+
it("startRelayRuntime starts a token-authenticated allowlisted relay", async () => withTestCleanup(async (after) => {
|
|
72
|
+
const target = await startTargetServer()
|
|
73
|
+
const runtime = await startRelayRuntime({
|
|
74
|
+
mode: "relay",
|
|
75
|
+
relayHost: "127.0.0.1",
|
|
76
|
+
relayPort: 0,
|
|
77
|
+
resumeWindowMs: 30_000,
|
|
78
|
+
maxTargetBufferBytes: 1_048_576,
|
|
79
|
+
maxTotalBufferBytes: 16_777_216,
|
|
80
|
+
handshakeTimeoutMs: 10_000,
|
|
81
|
+
maxInputBytesPerConnection: 65_536,
|
|
82
|
+
authTokens: [{username: "edge", token: "secret-token"}],
|
|
83
|
+
targetAllowlist: [{host: target.host, port: target.port}],
|
|
84
|
+
allowInsecurePublic: false
|
|
88
85
|
})
|
|
89
|
-
})
|
|
90
86
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
target: {host: target.host, port: target.port}
|
|
96
|
-
})
|
|
87
|
+
after(async () => {
|
|
88
|
+
await runtime.close()
|
|
89
|
+
await target.close()
|
|
90
|
+
})
|
|
97
91
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
expect(runtime.relay.status()).toEqual({
|
|
93
|
+
activeTunnels: 0,
|
|
94
|
+
activeTargets: 0,
|
|
95
|
+
sessions: 0,
|
|
96
|
+
attachedSessions: 0,
|
|
97
|
+
detachedSessions: 0,
|
|
98
|
+
bufferedTargetBytes: 0,
|
|
99
|
+
targetConnectionAttempts: 0,
|
|
100
|
+
targetRetryTimers: 0
|
|
101
|
+
})
|
|
101
102
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
resumeWindowMs: 30_000,
|
|
110
|
-
maxTargetBufferBytes: 1_048_576,
|
|
111
|
-
maxTotalBufferBytes: 16_777_216,
|
|
112
|
-
handshakeTimeoutMs: 10_000,
|
|
113
|
-
maxInputBytesPerConnection: 65_536,
|
|
114
|
-
authTokens: [{username: "edge", token: "secret-token"}],
|
|
115
|
-
targetAllowlist: [{host: target.host, port: target.port}],
|
|
116
|
-
allowInsecurePublic: false
|
|
117
|
-
})
|
|
118
|
-
const connection = new MultiplexedSocketductConnection({
|
|
119
|
-
relay: {host: "127.0.0.1", port: runtime.relay.port, token: "secret-token"}
|
|
120
|
-
})
|
|
121
|
-
try {
|
|
122
|
-
const socket = connection.createConnection({host: target.host, port: target.port})
|
|
123
|
-
await new Promise((resolve, reject) => {
|
|
124
|
-
socket.once("connect", resolve)
|
|
125
|
-
socket.once("error", reject)
|
|
103
|
+
const socket = await new Promise((resolve, reject) => {
|
|
104
|
+
const connection = createConnection({host: "127.0.0.1", port: runtime.relay.port})
|
|
105
|
+
connection.once("error", reject)
|
|
106
|
+
connection.once("connect", () => {
|
|
107
|
+
connection.off("error", reject)
|
|
108
|
+
resolve(connection)
|
|
109
|
+
})
|
|
126
110
|
})
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
111
|
+
|
|
112
|
+
const response = await sendControl(socket, {
|
|
113
|
+
type: "session.create",
|
|
114
|
+
token: "secret-token",
|
|
115
|
+
name: "docker-test",
|
|
116
|
+
target: {host: target.host, port: target.port}
|
|
130
117
|
})
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
assert.equal(/** @type {{physicalAccepts: number}} */ (runtime.relay.status()).physicalAccepts, 1)
|
|
118
|
+
|
|
119
|
+
expect(response.type).toBe("session.created")
|
|
134
120
|
socket.destroy()
|
|
135
|
-
}
|
|
136
|
-
await connection.close()
|
|
137
|
-
await runtime.close()
|
|
138
|
-
await target.close()
|
|
139
|
-
}
|
|
140
|
-
})
|
|
121
|
+
}))
|
|
141
122
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
123
|
+
it("startRelayRuntime can select protocol v3 while v2 remains the default", async () => {
|
|
124
|
+
const target = await startTargetServer()
|
|
125
|
+
const runtime = await startRelayRuntime({
|
|
126
|
+
mode: "relay",
|
|
127
|
+
protocolVersion: 3,
|
|
128
|
+
relayHost: "127.0.0.1",
|
|
129
|
+
relayPort: 0,
|
|
130
|
+
resumeWindowMs: 30_000,
|
|
131
|
+
maxTargetBufferBytes: 1_048_576,
|
|
132
|
+
maxTotalBufferBytes: 16_777_216,
|
|
133
|
+
handshakeTimeoutMs: 10_000,
|
|
134
|
+
maxInputBytesPerConnection: 65_536,
|
|
135
|
+
authTokens: [{username: "edge", token: "secret-token"}],
|
|
136
|
+
targetAllowlist: [{host: target.host, port: target.port}],
|
|
137
|
+
allowInsecurePublic: false
|
|
138
|
+
})
|
|
139
|
+
const connection = new MultiplexedSocketductConnection({
|
|
140
|
+
relay: {host: "127.0.0.1", port: runtime.relay.port, token: "secret-token"}
|
|
141
|
+
})
|
|
142
|
+
try {
|
|
143
|
+
const socket = connection.createConnection({host: target.host, port: target.port})
|
|
144
|
+
await new Promise((resolve, reject) => {
|
|
145
|
+
socket.once("connect", resolve)
|
|
146
|
+
socket.once("error", reject)
|
|
147
|
+
})
|
|
148
|
+
const response = new Promise((resolve, reject) => {
|
|
149
|
+
socket.once("data", (chunk) => resolve(chunk.toString()))
|
|
150
|
+
socket.once("error", reject)
|
|
151
|
+
})
|
|
152
|
+
socket.write("v3-runtime")
|
|
153
|
+
expect(await response).toBe("v3-runtime")
|
|
154
|
+
expect(/** @type {{physicalAccepts: number}} */ (runtime.relay.status()).physicalAccepts).toBe(1)
|
|
155
|
+
socket.destroy()
|
|
156
|
+
} finally {
|
|
157
|
+
await connection.close()
|
|
158
|
+
await runtime.close()
|
|
159
|
+
await target.close()
|
|
160
|
+
}
|
|
156
161
|
})
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
|
|
163
|
+
it("startRelayRuntime enforces configured cumulative v3 unauthenticated input bytes", async () => {
|
|
164
|
+
const runtime = await startRelayRuntime({
|
|
165
|
+
mode: "relay",
|
|
166
|
+
protocolVersion: 3,
|
|
167
|
+
relayHost: "127.0.0.1",
|
|
168
|
+
relayPort: 0,
|
|
169
|
+
resumeWindowMs: 30_000,
|
|
170
|
+
maxTargetBufferBytes: 1_048_576,
|
|
171
|
+
maxTotalBufferBytes: 16_777_216,
|
|
172
|
+
handshakeTimeoutMs: 10_000,
|
|
173
|
+
maxInputBytesPerConnection: 32,
|
|
174
|
+
authTokens: [{username: "edge", token: "secret-token"}],
|
|
175
|
+
targetAllowlist: [{host: "127.0.0.1", port: 1}],
|
|
176
|
+
allowInsecurePublic: false
|
|
162
177
|
})
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
const socket = createConnection({host: "127.0.0.1", port: runtime.relay.port})
|
|
179
|
+
try {
|
|
180
|
+
await new Promise((resolve, reject) => {
|
|
181
|
+
socket.once("connect", resolve)
|
|
182
|
+
socket.once("error", reject)
|
|
183
|
+
})
|
|
184
|
+
const auth = encodeV3Frame({type: V3FrameType.AUTH, streamId: 0, sequence: 1}, encodeV3Control({
|
|
185
|
+
version: 3,
|
|
186
|
+
token: "secret-token",
|
|
187
|
+
connectionId: "runtime_input_limit_01",
|
|
188
|
+
generation: 1
|
|
189
|
+
}))
|
|
190
|
+
expect(auth.length > 33).toBeTruthy()
|
|
191
|
+
socket.resume()
|
|
192
|
+
const closed = new Promise((resolve) => socket.once("close", resolve))
|
|
193
|
+
socket.write(auth.subarray(0, 33))
|
|
194
|
+
await Promise.race([
|
|
195
|
+
closed,
|
|
196
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("v3 unauthenticated input limit was not enforced")), 250))
|
|
197
|
+
])
|
|
198
|
+
} finally {
|
|
199
|
+
socket.destroy()
|
|
200
|
+
await runtime.close()
|
|
201
|
+
}
|
|
202
|
+
})
|
|
182
203
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
() => startRelayRuntime({
|
|
204
|
+
it("startRelayRuntime requires tokens and target allowlist", async () => {
|
|
205
|
+
await expect(startRelayRuntime({
|
|
186
206
|
mode: "relay",
|
|
187
207
|
relayHost: "127.0.0.1",
|
|
188
208
|
relayPort: 0,
|
|
@@ -194,12 +214,9 @@ test("startRelayRuntime requires tokens and target allowlist", async () => {
|
|
|
194
214
|
authTokens: [],
|
|
195
215
|
targetAllowlist: [{host: "127.0.0.1", port: 1}],
|
|
196
216
|
allowInsecurePublic: false
|
|
197
|
-
})
|
|
198
|
-
/SOCKETDUCT_AUTH_TOKENS/
|
|
199
|
-
)
|
|
217
|
+
})).rejects.toThrow(/SOCKETDUCT_AUTH_TOKENS/)
|
|
200
218
|
|
|
201
|
-
|
|
202
|
-
() => startRelayRuntime({
|
|
219
|
+
await expect(startRelayRuntime({
|
|
203
220
|
mode: "relay",
|
|
204
221
|
relayHost: "127.0.0.1",
|
|
205
222
|
relayPort: 0,
|
|
@@ -211,35 +228,11 @@ test("startRelayRuntime requires tokens and target allowlist", async () => {
|
|
|
211
228
|
authTokens: [{username: "edge", token: "secret-token"}],
|
|
212
229
|
targetAllowlist: [],
|
|
213
230
|
allowInsecurePublic: false
|
|
214
|
-
})
|
|
215
|
-
/SOCKETDUCT_TARGET_ALLOWLIST/
|
|
216
|
-
)
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* @param {import('node:net').Socket} socket - Connected relay socket.
|
|
221
|
-
* @param {Record<string, unknown> & {type: string}} header - Control frame header.
|
|
222
|
-
* @returns {Promise<Record<string, unknown>>} Decoded response header.
|
|
223
|
-
*/
|
|
224
|
-
async function sendControl(socket, header) {
|
|
225
|
-
const decoder = new FrameDecoder()
|
|
226
|
-
return await new Promise((resolve, reject) => {
|
|
227
|
-
socket.on("data", (chunk) => {
|
|
228
|
-
for (const frame of decoder.push(chunk)) {
|
|
229
|
-
resolve(frame.header)
|
|
230
|
-
}
|
|
231
|
-
})
|
|
232
|
-
socket.once("error", reject)
|
|
233
|
-
const framedHeader = header.type === "session.create" || header.type === "session.resume"
|
|
234
|
-
? {...header, framing: header.framing ?? [2]}
|
|
235
|
-
: header
|
|
236
|
-
socket.write(encodeFrame(framedHeader))
|
|
231
|
+
})).rejects.toThrow(/SOCKETDUCT_TARGET_ALLOWLIST/)
|
|
237
232
|
})
|
|
238
|
-
}
|
|
239
233
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
() => startRelayRuntime({
|
|
234
|
+
it("startRelayRuntime refuses non-loopback public bind without TLS or opt-out", async () => {
|
|
235
|
+
await expect(startRelayRuntime({
|
|
243
236
|
mode: "relay",
|
|
244
237
|
relayHost: "0.0.0.0",
|
|
245
238
|
relayPort: 0,
|
|
@@ -251,12 +244,9 @@ test("startRelayRuntime refuses non-loopback public bind without TLS or opt-out"
|
|
|
251
244
|
authTokens: [{username: "edge", token: "secret-token"}],
|
|
252
245
|
targetAllowlist: [{host: "127.0.0.1", port: 1}],
|
|
253
246
|
allowInsecurePublic: false
|
|
254
|
-
})
|
|
255
|
-
/public host.*without TLS/
|
|
256
|
-
)
|
|
247
|
+
})).rejects.toThrow(/public host.*without TLS/)
|
|
257
248
|
|
|
258
|
-
|
|
259
|
-
() => startRelayRuntime({
|
|
249
|
+
await expect(startRelayRuntime({
|
|
260
250
|
mode: "relay",
|
|
261
251
|
relayHost: "192.168.1.1",
|
|
262
252
|
relayPort: 0,
|
|
@@ -268,125 +258,122 @@ test("startRelayRuntime refuses non-loopback public bind without TLS or opt-out"
|
|
|
268
258
|
authTokens: [{username: "edge", token: "secret-token"}],
|
|
269
259
|
targetAllowlist: [{host: "127.0.0.1", port: 1}],
|
|
270
260
|
allowInsecurePublic: false
|
|
271
|
-
})
|
|
272
|
-
/public host.*without TLS/
|
|
273
|
-
)
|
|
274
|
-
})
|
|
275
|
-
|
|
276
|
-
test("startRelayRuntime allows non-loopback public bind with explicit opt-out", async () => {
|
|
277
|
-
const target = await startTargetServer()
|
|
278
|
-
const runtime = await startRelayRuntime({
|
|
279
|
-
mode: "relay",
|
|
280
|
-
relayHost: "0.0.0.0",
|
|
281
|
-
relayPort: 0,
|
|
282
|
-
resumeWindowMs: 30_000,
|
|
283
|
-
maxTargetBufferBytes: 1_048_576,
|
|
284
|
-
maxTotalBufferBytes: 16_777_216,
|
|
285
|
-
handshakeTimeoutMs: 10_000,
|
|
286
|
-
maxInputBytesPerConnection: 65_536,
|
|
287
|
-
authTokens: [{username: "edge", token: "secret-token"}],
|
|
288
|
-
targetAllowlist: [{host: target.host, port: target.port}],
|
|
289
|
-
allowInsecurePublic: true
|
|
261
|
+
})).rejects.toThrow(/public host.*without TLS/)
|
|
290
262
|
})
|
|
291
263
|
|
|
292
|
-
|
|
293
|
-
await
|
|
294
|
-
await
|
|
295
|
-
|
|
264
|
+
it("startRelayRuntime allows non-loopback public bind with explicit opt-out", async () => withTestCleanup(async (after) => {
|
|
265
|
+
const target = await startTargetServer()
|
|
266
|
+
const runtime = await startRelayRuntime({
|
|
267
|
+
mode: "relay",
|
|
268
|
+
relayHost: "0.0.0.0",
|
|
269
|
+
relayPort: 0,
|
|
270
|
+
resumeWindowMs: 30_000,
|
|
271
|
+
maxTargetBufferBytes: 1_048_576,
|
|
272
|
+
maxTotalBufferBytes: 16_777_216,
|
|
273
|
+
handshakeTimeoutMs: 10_000,
|
|
274
|
+
maxInputBytesPerConnection: 65_536,
|
|
275
|
+
authTokens: [{username: "edge", token: "secret-token"}],
|
|
276
|
+
targetAllowlist: [{host: target.host, port: target.port}],
|
|
277
|
+
allowInsecurePublic: true
|
|
278
|
+
})
|
|
296
279
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
connection.once("connect", () => {
|
|
301
|
-
connection.off("error", reject)
|
|
302
|
-
resolve(connection)
|
|
280
|
+
after(async () => {
|
|
281
|
+
await runtime.close()
|
|
282
|
+
await target.close()
|
|
303
283
|
})
|
|
304
|
-
})
|
|
305
284
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
285
|
+
const socket = await new Promise((resolve, reject) => {
|
|
286
|
+
const connection = createConnection({host: "127.0.0.1", port: runtime.relay.port})
|
|
287
|
+
connection.once("error", reject)
|
|
288
|
+
connection.once("connect", () => {
|
|
289
|
+
connection.off("error", reject)
|
|
290
|
+
resolve(connection)
|
|
291
|
+
})
|
|
292
|
+
})
|
|
312
293
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
294
|
+
const response = await sendControl(socket, {
|
|
295
|
+
type: "session.create",
|
|
296
|
+
token: "secret-token",
|
|
297
|
+
name: "insecure-test",
|
|
298
|
+
target: {host: target.host, port: target.port}
|
|
299
|
+
})
|
|
316
300
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
mode: "relay",
|
|
321
|
-
relayHost: "127.0.0.1",
|
|
322
|
-
relayPort: 0,
|
|
323
|
-
resumeWindowMs: 30_000,
|
|
324
|
-
maxTargetBufferBytes: 1_048_576,
|
|
325
|
-
maxTotalBufferBytes: 16_777_216,
|
|
326
|
-
handshakeTimeoutMs: 10_000,
|
|
327
|
-
maxInputBytesPerConnection: 65_536,
|
|
328
|
-
authTokens: [{username: "edge", token: "secret-token"}],
|
|
329
|
-
targetAllowlist: [{host: target.host, port: target.port}],
|
|
330
|
-
allowInsecurePublic: false
|
|
331
|
-
})
|
|
301
|
+
expect(response.type).toBe("session.created")
|
|
302
|
+
socket.destroy()
|
|
303
|
+
}))
|
|
332
304
|
|
|
333
|
-
|
|
334
|
-
await
|
|
335
|
-
await
|
|
336
|
-
|
|
305
|
+
it("startRelayRuntime accepts loopback bind without TLS", async () => withTestCleanup(async (after) => {
|
|
306
|
+
const target = await startTargetServer()
|
|
307
|
+
const runtime = await startRelayRuntime({
|
|
308
|
+
mode: "relay",
|
|
309
|
+
relayHost: "127.0.0.1",
|
|
310
|
+
relayPort: 0,
|
|
311
|
+
resumeWindowMs: 30_000,
|
|
312
|
+
maxTargetBufferBytes: 1_048_576,
|
|
313
|
+
maxTotalBufferBytes: 16_777_216,
|
|
314
|
+
handshakeTimeoutMs: 10_000,
|
|
315
|
+
maxInputBytesPerConnection: 65_536,
|
|
316
|
+
authTokens: [{username: "edge", token: "secret-token"}],
|
|
317
|
+
targetAllowlist: [{host: target.host, port: target.port}],
|
|
318
|
+
allowInsecurePublic: false
|
|
319
|
+
})
|
|
337
320
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
connection.once("connect", () => {
|
|
342
|
-
connection.off("error", reject)
|
|
343
|
-
resolve(connection)
|
|
321
|
+
after(async () => {
|
|
322
|
+
await runtime.close()
|
|
323
|
+
await target.close()
|
|
344
324
|
})
|
|
345
|
-
})
|
|
346
325
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
326
|
+
const socket = await new Promise((resolve, reject) => {
|
|
327
|
+
const connection = createConnection({host: "127.0.0.1", port: runtime.relay.port})
|
|
328
|
+
connection.once("error", reject)
|
|
329
|
+
connection.once("connect", () => {
|
|
330
|
+
connection.off("error", reject)
|
|
331
|
+
resolve(connection)
|
|
332
|
+
})
|
|
333
|
+
})
|
|
353
334
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
335
|
+
const response = await sendControl(socket, {
|
|
336
|
+
type: "session.create",
|
|
337
|
+
token: "secret-token",
|
|
338
|
+
name: "loopback-test",
|
|
339
|
+
target: {host: target.host, port: target.port}
|
|
340
|
+
})
|
|
357
341
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const certs = generateTestCertificates(certDir)
|
|
362
|
-
|
|
363
|
-
const runtime = await startRelayRuntime({
|
|
364
|
-
mode: "relay",
|
|
365
|
-
relayHost: "127.0.0.1",
|
|
366
|
-
relayPort: 0,
|
|
367
|
-
resumeWindowMs: 30_000,
|
|
368
|
-
maxTargetBufferBytes: 1_048_576,
|
|
369
|
-
maxTotalBufferBytes: 16_777_216,
|
|
370
|
-
handshakeTimeoutMs: 10_000,
|
|
371
|
-
maxInputBytesPerConnection: 65_536,
|
|
372
|
-
authTokens: [{username: "edge", token: "secret-token"}],
|
|
373
|
-
targetAllowlist: [{host: target.host, port: target.port}],
|
|
374
|
-
relayTls: {certFile: certs.serverCertFile, keyFile: certs.serverKeyFile},
|
|
375
|
-
allowInsecurePublic: false
|
|
376
|
-
})
|
|
342
|
+
expect(response.type).toBe("session.created")
|
|
343
|
+
socket.destroy()
|
|
344
|
+
}))
|
|
377
345
|
|
|
378
|
-
|
|
379
|
-
await
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
})
|
|
346
|
+
it("startRelayRuntime starts a TLS relay with valid certificate files", async () => withTestCleanup(async (after) => {
|
|
347
|
+
const target = await startTargetServer()
|
|
348
|
+
const certDir = mkdtempSync(join(tmpdir(), "socketduct-tls-startup-"))
|
|
349
|
+
const certs = generateTestCertificates(certDir)
|
|
383
350
|
|
|
384
|
-
|
|
385
|
-
|
|
351
|
+
const runtime = await startRelayRuntime({
|
|
352
|
+
mode: "relay",
|
|
353
|
+
relayHost: "127.0.0.1",
|
|
354
|
+
relayPort: 0,
|
|
355
|
+
resumeWindowMs: 30_000,
|
|
356
|
+
maxTargetBufferBytes: 1_048_576,
|
|
357
|
+
maxTotalBufferBytes: 16_777_216,
|
|
358
|
+
handshakeTimeoutMs: 10_000,
|
|
359
|
+
maxInputBytesPerConnection: 65_536,
|
|
360
|
+
authTokens: [{username: "edge", token: "secret-token"}],
|
|
361
|
+
targetAllowlist: [{host: target.host, port: target.port}],
|
|
362
|
+
relayTls: {certFile: certs.serverCertFile, keyFile: certs.serverKeyFile},
|
|
363
|
+
allowInsecurePublic: false
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
after(async () => {
|
|
367
|
+
await runtime.close()
|
|
368
|
+
await target.close()
|
|
369
|
+
rmSync(certDir, {recursive: true, force: true})
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
expect(runtime.relay.port > 0).toBeTruthy()
|
|
373
|
+
}))
|
|
386
374
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
() => startRelayRuntime({
|
|
375
|
+
it("startRelayRuntime fails loudly on missing TLS certificate file", async () => {
|
|
376
|
+
await expect(startRelayRuntime({
|
|
390
377
|
mode: "relay",
|
|
391
378
|
relayHost: "127.0.0.1",
|
|
392
379
|
relayPort: 0,
|
|
@@ -399,36 +386,35 @@ test("startRelayRuntime fails loudly on missing TLS certificate file", async ()
|
|
|
399
386
|
targetAllowlist: [{host: "127.0.0.1", port: 1}],
|
|
400
387
|
relayTls: {certFile: "/nonexistent/cert.crt", keyFile: "/nonexistent/key.key"},
|
|
401
388
|
allowInsecurePublic: false
|
|
402
|
-
})
|
|
403
|
-
/ENOENT/
|
|
404
|
-
)
|
|
405
|
-
})
|
|
406
|
-
|
|
407
|
-
test("startRelayRuntime allows non-loopback bind with TLS configured", async () => {
|
|
408
|
-
const target = await startTargetServer()
|
|
409
|
-
const certDir = mkdtempSync(join(tmpdir(), "socketduct-tls-loopback-"))
|
|
410
|
-
const certs = generateTestCertificates(certDir)
|
|
411
|
-
|
|
412
|
-
const runtime = await startRelayRuntime({
|
|
413
|
-
mode: "relay",
|
|
414
|
-
relayHost: "0.0.0.0",
|
|
415
|
-
relayPort: 0,
|
|
416
|
-
resumeWindowMs: 30_000,
|
|
417
|
-
maxTargetBufferBytes: 1_048_576,
|
|
418
|
-
maxTotalBufferBytes: 16_777_216,
|
|
419
|
-
handshakeTimeoutMs: 10_000,
|
|
420
|
-
maxInputBytesPerConnection: 65_536,
|
|
421
|
-
authTokens: [{username: "edge", token: "secret-token"}],
|
|
422
|
-
targetAllowlist: [{host: target.host, port: target.port}],
|
|
423
|
-
relayTls: {certFile: certs.serverCertFile, keyFile: certs.serverKeyFile},
|
|
424
|
-
allowInsecurePublic: false
|
|
389
|
+
})).rejects.toThrow(/ENOENT/)
|
|
425
390
|
})
|
|
426
391
|
|
|
427
|
-
|
|
428
|
-
await
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
392
|
+
it("startRelayRuntime allows non-loopback bind with TLS configured", async () => withTestCleanup(async (after) => {
|
|
393
|
+
const target = await startTargetServer()
|
|
394
|
+
const certDir = mkdtempSync(join(tmpdir(), "socketduct-tls-loopback-"))
|
|
395
|
+
const certs = generateTestCertificates(certDir)
|
|
396
|
+
|
|
397
|
+
const runtime = await startRelayRuntime({
|
|
398
|
+
mode: "relay",
|
|
399
|
+
relayHost: "0.0.0.0",
|
|
400
|
+
relayPort: 0,
|
|
401
|
+
resumeWindowMs: 30_000,
|
|
402
|
+
maxTargetBufferBytes: 1_048_576,
|
|
403
|
+
maxTotalBufferBytes: 16_777_216,
|
|
404
|
+
handshakeTimeoutMs: 10_000,
|
|
405
|
+
maxInputBytesPerConnection: 65_536,
|
|
406
|
+
authTokens: [{username: "edge", token: "secret-token"}],
|
|
407
|
+
targetAllowlist: [{host: target.host, port: target.port}],
|
|
408
|
+
relayTls: {certFile: certs.serverCertFile, keyFile: certs.serverKeyFile},
|
|
409
|
+
allowInsecurePublic: false
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
after(async () => {
|
|
413
|
+
await runtime.close()
|
|
414
|
+
await target.close()
|
|
415
|
+
rmSync(certDir, {recursive: true, force: true})
|
|
416
|
+
})
|
|
432
417
|
|
|
433
|
-
|
|
418
|
+
expect(runtime.relay.port > 0).toBeTruthy()
|
|
419
|
+
}))
|
|
434
420
|
})
|