diff --git a/apps/web/e2e/nodes.spec.ts b/apps/web/e2e/nodes.spec.ts index 5c762775..5cc21d0d 100644 --- a/apps/web/e2e/nodes.spec.ts +++ b/apps/web/e2e/nodes.spec.ts @@ -172,6 +172,8 @@ test("offers a plaintext HTTP node command with a warning only when allow_insecu const field = add.getByLabel("One-time enrollment command", { exact: true }); await expect(field).toHaveValue(/curl [^\n]* 'http:\/\/10\.0\.0\.5:8080\/node-install\/node-install\.pyz' /); await expect(field).toHaveValue(/ --source-url 'http:\/\/10\.0\.0\.5:8080' --core-url 'http:\/\/10\.0\.0\.5:8080' /); + // The switch travels with the command: the installer is told to accept the plain-HTTP Core origin. + await expect(field).toHaveValue(/ --core-url 'http:\/\/10\.0\.0\.5:8080' --allow-insecure-origin /); }); test("removes a node after confirmation", async ({ page, request }) => { diff --git a/apps/web/src/features/sandbox/NodeEnrollment.tsx b/apps/web/src/features/sandbox/NodeEnrollment.tsx index 5a855cfd..62bc410e 100644 --- a/apps/web/src/features/sandbox/NodeEnrollment.tsx +++ b/apps/web/src/features/sandbox/NodeEnrollment.tsx @@ -140,7 +140,7 @@ export function NodeEnrollment({ client, consoleConfig, deployment, nodes, open, // Expired only once a read begun after the expiry found no node for the command. const expired = lapsed && fresh && checked !== null && checked.startedAt >= expiresAt && checked.nodes === nodes; const command = enrollment && provider && available && publicUrl && (registered || !expired) && !ready - ? nodeInstallCommand({ token: enrollment.token, coreUrl: publicUrl, sourceUrl: publicUrl, provider, installationId: deployment.installation_id, scriptDigest: consoleConfig.node_installer_sha256 }) : ""; + ? nodeInstallCommand({ token: enrollment.token, coreUrl: publicUrl, sourceUrl: publicUrl, provider, installationId: deployment.installation_id, scriptDigest: consoleConfig.node_installer_sha256, allowInsecureOrigin: insecure }) : ""; const nodeId = node?.id ?? null; const polling = open && enrollment !== null && !ready && (registered || !expired); const check = useCallback(async () => { diff --git a/apps/web/src/features/sandbox/enrollment-command.test.ts b/apps/web/src/features/sandbox/enrollment-command.test.ts index 180c4e1b..d7343497 100644 --- a/apps/web/src/features/sandbox/enrollment-command.test.ts +++ b/apps/web/src/features/sandbox/enrollment-command.test.ts @@ -19,6 +19,19 @@ printf '==> Verifying node installer...\\n' && printf '%s %s\\n' '${digest}' "$d/node-install.pyz" | sha256sum -c --status && printf '%s\\n' 'secret'\\''onetime' | $s \${s:+--preserve-env=http_proxy,https_proxy,no_proxy,HTTP_PROXY,HTTPS_PROXY,NO_PROXY} python3 "$d/node-install.pyz" \${NO_COLOR+--no-color} --enrollment-token-stdin --source-url 'https://console.example' --core-url 'https://core.example' --provider 'docker' --installation-id '7f3c2a90-5b1e-4c2d-9e3f-0a1b2c3d4e5f')`); }); + it("appends --allow-insecure-origin right after --core-url when the switch is on", () => { + const command = nodeInstallCommand({ token: "secret'onetime", coreUrl: "http://10.0.0.5:8080", sourceUrl: "http://10.0.0.5:8080", provider: "docker", installationId: "7f3c2a90-5b1e-4c2d-9e3f-0a1b2c3d4e5f", scriptDigest: digest, allowInsecureOrigin: true }); + expect(command).toContain("--core-url 'http://10.0.0.5:8080' --allow-insecure-origin --provider 'docker'"); + // The flag is forwarded once, and only in the installer's own argument list. + expect(command.match(/--allow-insecure-origin/g)).toHaveLength(1); + expect(command.endsWith("--provider 'docker' --installation-id '7f3c2a90-5b1e-4c2d-9e3f-0a1b2c3d4e5f')")).toBe(true); + }); + it("leaves the command byte-for-byte unchanged when the switch is off or omitted", () => { + const args = { token: "secret'onetime", coreUrl: "https://core.example", sourceUrl: "https://console.example", provider: "docker" as const, installationId: "7f3c2a90-5b1e-4c2d-9e3f-0a1b2c3d4e5f", scriptDigest: digest }; + expect(nodeInstallCommand({ ...args, allowInsecureOrigin: false })).toBe(install()); + expect(nodeInstallCommand(args)).toBe(install()); + expect(nodeInstallCommand({ ...args, allowInsecureOrigin: false })).not.toContain("--allow-insecure-origin"); + }); it("creates the exact uninstall commands, with no token", () => { const uninstall = () => nodeUninstallCommand({ sourceUrl: "https://console.example", installationId: "7f3c2a90-5b1e-4c2d-9e3f-0a1b2c3d4e5f", scriptDigest: digest }); expect(uninstall()).toBe(` (umask 077; d=$(mktemp -d) || exit; trap 'rm -rf "$d"' EXIT; s=; [ "$(id -u)" -eq 0 ] || s=sudo diff --git a/apps/web/src/features/sandbox/enrollment-command.ts b/apps/web/src/features/sandbox/enrollment-command.ts index f67f7d30..ac873d66 100644 --- a/apps/web/src/features/sandbox/enrollment-command.ts +++ b/apps/web/src/features/sandbox/enrollment-command.ts @@ -25,12 +25,15 @@ const runInstaller = `$s \${s:+--preserve-env=http_proxy,https_proxy,no_proxy,HT /** * Adds this host as a node. The one-time token reaches the installer only on * standard input (`printf` is a shell builtin), never in an argument, the - * environment or sudo's command line. + * environment or sudo's command line. `allowInsecureOrigin` forwards the + * installer's `--allow-insecure-origin`, which lets a plain-HTTP Core origin + * enroll; it stays off unless the caller explicitly asks for it, so the default + * command is byte-for-byte unchanged. */ -export function nodeInstallCommand({ token, coreUrl, sourceUrl, provider, installationId, scriptDigest }: { - token: string; coreUrl: string; sourceUrl: string; provider: "docker" | "microsandbox"; installationId: string; scriptDigest: string; +export function nodeInstallCommand({ token, coreUrl, sourceUrl, provider, installationId, scriptDigest, allowInsecureOrigin = false }: { + token: string; coreUrl: string; sourceUrl: string; provider: "docker" | "microsandbox"; installationId: string; scriptDigest: string; allowInsecureOrigin?: boolean; }): string { - return `${nodeInstaller(sourceUrl, scriptDigest)}printf '%s\\n' ${quote(token)} | ${runInstaller} --enrollment-token-stdin --source-url ${quote(sourceUrl)} --core-url ${quote(coreUrl)} --provider ${quote(provider)} --installation-id ${quote(installationId)})`; + return `${nodeInstaller(sourceUrl, scriptDigest)}printf '%s\\n' ${quote(token)} | ${runInstaller} --enrollment-token-stdin --source-url ${quote(sourceUrl)} --core-url ${quote(coreUrl)}${allowInsecureOrigin ? " --allow-insecure-origin" : ""} --provider ${quote(provider)} --installation-id ${quote(installationId)})`; } /**