fix: require internal token for device pod executor

This commit is contained in:
Codex
2026-05-29 16:28:34 +08:00
parent b974372ab6
commit 5ebadee19f
7 changed files with 84 additions and 24 deletions
+24 -9
View File
@@ -4,6 +4,7 @@ import { createServer } from "node:http";
import test from "node:test";
const bunCommand = process.env.HWLAB_TEST_BUN_COMMAND || process.env.HWLAB_BUN_COMMAND || (process.versions.bun ? process.execPath : "bun");
const INTERNAL_TOKEN = "test-internal-token";
test("device pod executor exposes cloud-api authority boundary and method guards", async () => {
const service = await startDevicePod("device-pod-test");
@@ -42,9 +43,17 @@ test("device pod executor exposes cloud-api authority boundary and method guards
assert.equal(externalJob.status, 403);
assert.equal((await externalJob.json()).error.code, "cloud_api_authority_required");
const internalJob = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs`, {
const headerOnlyJob = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs`, {
method: "POST",
headers: { "content-type": "application/json", "x-hwlab-internal-service": "hwlab-cloud-api" },
body: JSON.stringify({ intent: "workspace.ls" })
});
assert.equal(headerOnlyJob.status, 403);
assert.equal((await headerOnlyJob.json()).error.code, "cloud_api_authority_required");
const internalJob = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs`, {
method: "POST",
headers: internalHeaders({ "content-type": "application/json" }),
body: JSON.stringify({ jobId: "job_devicepod_test", intent: "workspace.ls", traceId: "trc_device_pod_test", operationId: "op_device_pod_test" })
});
assert.equal(internalJob.status, 409);
@@ -54,13 +63,13 @@ test("device pod executor exposes cloud-api authority boundary and method guards
assert.equal(internalJobPayload.outputUrl, "/v1/device-pods/device-pod-test/jobs/job_devicepod_test/output");
const storedJob = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs/job_devicepod_test`, {
headers: { "x-hwlab-internal-service": "hwlab-cloud-api" }
headers: internalHeaders()
});
assert.equal(storedJob.job.id, "job_devicepod_test");
assert.equal(storedJob.status, "blocked");
const storedOutput = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs/job_devicepod_test/output`, {
headers: { "x-hwlab-internal-service": "hwlab-cloud-api" }
headers: internalHeaders()
});
assert.equal(storedOutput.job.id, "job_devicepod_test");
assert.equal(storedOutput.output.summary, "HWLAB_CLOUD_API_INTERNAL_URL is not configured for gateway dispatch");
@@ -73,7 +82,7 @@ test("device pod executor exposes cloud-api authority boundary and method guards
const cancelStored = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs/job_devicepod_test/cancel`, {
method: "POST",
headers: { "x-hwlab-internal-service": "hwlab-cloud-api" }
headers: internalHeaders()
});
assert.equal(cancelStored.status, 200);
assert.equal((await cancelStored.json()).job.id, "job_devicepod_test");
@@ -114,7 +123,7 @@ test("device-pod executor bounds gateway output text", async () => {
try {
const jobResponse = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs`, {
method: "POST",
headers: { "content-type": "application/json", "x-hwlab-internal-service": "hwlab-cloud-api" },
headers: internalHeaders({ "content-type": "application/json" }),
body: JSON.stringify({
jobId: "job_bounded_output_test",
intent: "workspace.ls",
@@ -149,7 +158,7 @@ test("device pod executor dispatches internal jobs through cloud-api gateway ada
const dispatches = [];
const cloudApi = createServer(async (request, response) => {
const body = await requestJson(request);
dispatches.push({ method: request.method, url: request.url, internalService: request.headers["x-hwlab-internal-service"], body });
dispatches.push({ method: request.method, url: request.url, internalService: request.headers["x-hwlab-internal-service"], internalToken: request.headers["x-hwlab-internal-token"], body });
response.writeHead(200, { "content-type": "application/json; charset=utf-8" });
response.end(JSON.stringify({
jsonrpc: "2.0",
@@ -183,7 +192,7 @@ test("device pod executor dispatches internal jobs through cloud-api gateway ada
try {
const jobResponse = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs`, {
method: "POST",
headers: { "content-type": "application/json", "x-hwlab-internal-service": "hwlab-cloud-api" },
headers: internalHeaders({ "content-type": "application/json" }),
body: JSON.stringify({
jobId: "job_devicepod_gateway_test",
intent: "workspace.ls",
@@ -220,6 +229,7 @@ test("device pod executor dispatches internal jobs through cloud-api gateway ada
assert.equal(output.output.auditId, "aud_devicepod_test");
assert.equal(dispatches.length, 1);
assert.equal(dispatches[0].internalService, "hwlab-device-pod");
assert.equal(dispatches[0].internalToken, INTERNAL_TOKEN);
assert.equal(dispatches[0].body.params.gatewaySessionId, "gws_devicepod_test");
assert.equal(dispatches[0].body.params.input.cwd, "F:\\Work\\Project");
assert.match(dispatches[0].body.params.input.command, /^node tools\\device-host-cli\.mjs --profile-json-b64 /u);
@@ -254,7 +264,7 @@ test("device-pod executor maps v0.1 CLI options to device-host-cli argv", async
try {
const jobResponse = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs`, {
method: "POST",
headers: { "content-type": "application/json", "x-hwlab-internal-service": "hwlab-cloud-api" },
headers: internalHeaders({ "content-type": "application/json" }),
body: JSON.stringify({
jobId: "job_options_test",
intent: "debug.download",
@@ -293,6 +303,7 @@ async function startDevicePod(devicePodId, extraEnv = {}) {
HWLAB_DEVICE_POD_PORT: String(port),
HWLAB_DEVICE_POD_ID: devicePodId,
HWLAB_ENVIRONMENT: "v02",
HWLAB_DEVICE_POD_INTERNAL_TOKEN: INTERNAL_TOKEN,
...extraEnv
},
stdio: ["ignore", "pipe", "pipe"]
@@ -314,7 +325,7 @@ async function waitForJobStatus(port, devicePodId, jobId, status) {
let last;
for (let attempt = 0; attempt < 30; attempt += 1) {
last = await fetchJson(`http://127.0.0.1:${port}/v1/device-pods/${devicePodId}/jobs/${jobId}/output`, {
headers: { "x-hwlab-internal-service": "hwlab-cloud-api" }
headers: internalHeaders()
});
if (last.status === status) return last;
await new Promise((resolve) => setTimeout(resolve, 50));
@@ -323,6 +334,10 @@ async function waitForJobStatus(port, devicePodId, jobId, status) {
return last;
}
function internalHeaders(extra = {}) {
return { ...extra, "x-hwlab-internal-service": "hwlab-cloud-api", "x-hwlab-internal-token": INTERNAL_TOKEN };
}
async function requestJson(request) {
const chunks = [];
for await (const chunk of request) chunks.push(chunk);
+7 -2
View File
@@ -11,6 +11,7 @@ const port = parsePort(process.env.HWLAB_DEVICE_POD_PORT, parsePort(process.env.
const devicePodId = process.env.HWLAB_DEVICE_POD_ID || DEFAULT_DEVICE_POD_ID;
const environment = process.env.HWLAB_ENVIRONMENT || process.env.HWLAB_GITOPS_PROFILE || "dev";
const cloudApiInternalUrl = normalizeBaseUrl(process.env.HWLAB_CLOUD_API_INTERNAL_URL || process.env.HWLAB_CLOUD_API_URL);
const internalToken = textOr(process.env.HWLAB_DEVICE_POD_INTERNAL_TOKEN, "");
const dispatchTimeoutMs = numberOr(process.env.HWLAB_DEVICE_POD_GATEWAY_DISPATCH_TIMEOUT_MS, 120000);
const DEVICE_JOB_OUTPUT_MAX_BYTES = 12000;
const jobs = new Map();
@@ -236,6 +237,7 @@ async function dispatchGatewayJob({ job, route }) {
accept: "application/json",
"content-type": "application/json",
"x-hwlab-internal-service": SERVICE_ID,
"x-hwlab-internal-token": internalToken,
"x-trace-id": job.traceId,
"x-request-id": `req_${job.id}`
},
@@ -318,8 +320,11 @@ function parseDevicePodPath(pathname) {
}
function isInternalCaller(request) {
const header = request.headers["x-hwlab-internal-service"];
return String(Array.isArray(header) ? header[0] : header ?? "").trim() === "hwlab-cloud-api";
if (!internalToken) return false;
const serviceHeader = request.headers["x-hwlab-internal-service"];
const tokenHeader = request.headers["x-hwlab-internal-token"];
return String(Array.isArray(serviceHeader) ? serviceHeader[0] : serviceHeader ?? "").trim() === "hwlab-cloud-api"
&& String(Array.isArray(tokenHeader) ? tokenHeader[0] : tokenHeader ?? "").trim() === internalToken;
}
function sourcePayload() {