History

fix(server): redact managed endpoint output

Julius Marminge committed 4 months ago

Commit bf555bd1 parent 5b770972 files · +64 −3Browse files

Unified · Split

2 files
  1. apps/server/src/cloud/ManagedEndpointRuntime.test.ts +63 −1
  2. apps/server/src/cloud/ManagedEndpointRuntime.ts +1 −2
apps/server/src/cloud/ManagedEndpointRuntime.test.ts+63−1
@@ -65,6 +65,7 @@ function makeHandle(input: { · 64 unmodified lines
65 readonly isRunning?: () => boolean;65 readonly isRunning?: () => boolean;
66 readonly isRunningEffect?: ChildProcessSpawner.ChildProcessHandle["isRunning"];66 readonly isRunningEffect?: ChildProcessSpawner.ChildProcessHandle["isRunning"];
67 readonly exitCode?: Effect.Effect<ChildProcessSpawner.ExitCode>;67 readonly exitCode?: Effect.Effect<ChildProcessSpawner.ExitCode>;
68 readonly output?: string;
68}) {69}) {
69 return ChildProcessSpawner.makeHandle({70 return ChildProcessSpawner.makeHandle({
70 pid: ChildProcessSpawner.ProcessId(input.pid),71 pid: ChildProcessSpawner.ProcessId(input.pid),
@@ -78,7 +79,10 @@ function makeHandle(input: { · 7 unmodified lines
78 stdin: Sink.drain,79 stdin: Sink.drain,
79 stdout: Stream.empty,80 stdout: Stream.empty,
80 stderr: Stream.empty,81 stderr: Stream.empty,
81 all: Stream.empty,82 all:
83 input.output === undefined
84 ? Stream.empty
85 : Stream.make(new TextEncoder().encode(input.output)),
82 getInputFd: () => Sink.drain,86 getInputFd: () => Sink.drain,
83 getOutputFd: () => Stream.empty,87 getOutputFd: () => Stream.empty,
84 });88 });
@@ -262,6 +266,64 @@ describe("CloudManagedEndpointRuntime", () => { · 177 unmodified lines
262 }).pipe(Effect.provide(Logger.layer([logger], { mergeWithExisting: false })));266 }).pipe(Effect.provide(Logger.layer([logger], { mergeWithExisting: false })));
263 });267 });
264 268
269 it.effect("does not copy relay client output into log annotations", () => {
270 const connectorToken = "connector-token-sentinel";
271 const signedUrl = "https://user:password@example.com/private?token=secret#fragment";
272 const output = `ERR failed request ${signedUrl} ${connectorToken}`;
273 const logMessages: unknown[] = [];
274 let resolveObserved!: () => void;
275 const observed = new Promise<void>((resolve) => {
276 resolveObserved = resolve;
277 });
278 const logger = Logger.make(({ message }) => {
279 logMessages.push(message);
280 if (Array.isArray(message) && message[0] === "Relay client reported a transport warning") {
281 resolveObserved();
282 }
283 });
284 const spawner = ChildProcessSpawner.make(() =>
285 Effect.gen(function* () {
286 const handle = makeHandle({
287 pid: 350,
288 output,
289 onKill: () => undefined,
290 });
291 yield* Effect.addFinalizer(() => handle.kill().pipe(Effect.ignore));
292 return handle;
293 }),
294 );
295
296 return Effect.gen(function* () {
297 const runtime = yield* buildCloudManagedEndpointRuntime(spawner);
298 yield* runtime.applyConfig({
299 providerKind: "cloudflare_tunnel",
300 connectorToken,
301 tunnelId: "tunnel-1",
302 });
303 yield* Effect.promise(() => observed);
304
305 const warning = logMessages.find(
306 (message) =>
307 Array.isArray(message) && message[0] === "Relay client reported a transport warning",
308 );
309 expect(warning).toBeDefined();
310 if (!Array.isArray(warning)) return;
311 expect(warning[1]).toMatchObject({
312 pid: 350,
313 tunnelId: "tunnel-1",
314 outputLength: output.length,
315 });
316 expect(warning[1]).not.toHaveProperty("output");
317 const diagnosticText = Object.values(warning[1] as Record<string, unknown>)
318 .map(String)
319 .join("\n");
320 expect(diagnosticText).not.toContain(connectorToken);
321 expect(diagnosticText).not.toContain(signedUrl);
322 expect(diagnosticText).not.toContain("user:password");
323 expect(diagnosticText).not.toContain("token=secret");
324 }).pipe(Effect.provide(Logger.layer([logger], { mergeWithExisting: false })));
325 });
326
265 it.effect("supervises the active connector and restarts it after process exit", () =>327 it.effect("supervises the active connector and restarts it after process exit", () =>
266 Effect.gen(function* () {328 Effect.gen(function* () {
267 const spawned: Array<number> = [];329 const spawned: Array<number> = [];
apps/server/src/cloud/ManagedEndpointRuntime.ts+1−2
@@ -183,12 +183,11 @@ export const make = Effect.gen(function* () { · 182 unmodified lines
183 Stream.map((line) => line.trim()),183 Stream.map((line) => line.trim()),
184 Stream.filter((line) => line.length > 0),184 Stream.filter((line) => line.length > 0),
185 Stream.runForEach((line) => {185 Stream.runForEach((line) => {
186 const output = line.replaceAll(connector.config.connectorToken, "<redacted>");
187 const attributes = {186 const attributes = {
188 pid: Number(connector.child.pid),187 pid: Number(connector.child.pid),
189 tunnelId: connector.config.tunnelId,188 tunnelId: connector.config.tunnelId,
190 tunnelName: connector.config.tunnelName,189 tunnelName: connector.config.tunnelName,
191 output,190 outputLength: line.length,
192 };191 };
193 switch (classifyRelayClientOutput(line)) {192 switch (classifyRelayClientOutput(line)) {
194 case "connected":193 case "connected":