fix: require internal token for device pod executor
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user