feat(device-plane): add fail-closed deploy foundation
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import { timingSafeEqual } from "node:crypto";
|
||||
import { createServer } from "node:http";
|
||||
|
||||
import {
|
||||
assertSafeProjection,
|
||||
hashRestrictedIdentifier,
|
||||
normalizeDiscoverySignal,
|
||||
toSafeDiscoveryView,
|
||||
} from "../../../packages/device-protocol-contract/src/index.mjs";
|
||||
|
||||
export function createControlCoreApp({
|
||||
repository,
|
||||
gatewayToken = "",
|
||||
identifierPepper = "",
|
||||
discoveryIngestEnabled = false,
|
||||
} = {}) {
|
||||
if (!repository || typeof repository.health !== "function") {
|
||||
throw new TypeError("device_repository_required");
|
||||
}
|
||||
if (discoveryIngestEnabled) {
|
||||
if (typeof repository.upsertQuarantineDiscovery !== "function") {
|
||||
throw new TypeError("device_discovery_repository_required");
|
||||
}
|
||||
if (typeof gatewayToken !== "string" || gatewayToken.length < 32) {
|
||||
throw new TypeError("device_gateway_token_invalid");
|
||||
}
|
||||
if (typeof identifierPepper !== "string" || identifierPepper.length < 32) {
|
||||
throw new TypeError("device_identifier_pepper_invalid");
|
||||
}
|
||||
}
|
||||
|
||||
const server = createServer(async (request, response) => {
|
||||
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
||||
response.setHeader("Cache-Control", "no-store");
|
||||
response.setHeader("X-Content-Type-Options", "nosniff");
|
||||
|
||||
try {
|
||||
const requestUrl = new URL(
|
||||
request.url || "/",
|
||||
`http://${request.headers.host || "127.0.0.1"}`,
|
||||
);
|
||||
|
||||
if (request.method === "GET" && requestUrl.pathname === "/healthz") {
|
||||
const database = await repository.health();
|
||||
return writeJson(response, 200, {
|
||||
ok: true,
|
||||
service: "nodedc-device-control-core",
|
||||
database,
|
||||
discoveryIngest: discoveryIngestEnabled ? "enabled" : "disabled",
|
||||
commandTransport: "disabled",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
request.method === "POST"
|
||||
&& requestUrl.pathname === "/internal/v1/device-discoveries:observe"
|
||||
) {
|
||||
if (!discoveryIngestEnabled) {
|
||||
return writeJson(response, 404, {
|
||||
ok: false,
|
||||
error: "device_discovery_ingest_disabled",
|
||||
});
|
||||
}
|
||||
if (!matchesBearer(request.headers.authorization, gatewayToken)) {
|
||||
return writeJson(response, 401, {
|
||||
ok: false,
|
||||
error: "device_gateway_auth_required",
|
||||
});
|
||||
}
|
||||
|
||||
const input = await readJsonBody(request, 32 * 1024);
|
||||
const signal = normalizeDiscoverySignal(input);
|
||||
const identifierDigest = hashRestrictedIdentifier(
|
||||
signal.identifier,
|
||||
identifierPepper,
|
||||
);
|
||||
const safeView = assertSafeProjection(toSafeDiscoveryView(signal));
|
||||
const discovery = await repository.upsertQuarantineDiscovery({
|
||||
identifierDigest,
|
||||
safeView,
|
||||
});
|
||||
return writeJson(response, discovery.created ? 201 : 200, {
|
||||
ok: true,
|
||||
created: discovery.created,
|
||||
discovery: assertSafeProjection(discovery.value),
|
||||
});
|
||||
}
|
||||
|
||||
return writeJson(response, 404, {
|
||||
ok: false,
|
||||
error: "device_control_core_route_not_found",
|
||||
});
|
||||
} catch (error) {
|
||||
const status = Number(error?.statusCode || 400);
|
||||
return writeJson(
|
||||
response,
|
||||
Number.isInteger(status) && status >= 400 && status < 600
|
||||
? status
|
||||
: 500,
|
||||
{
|
||||
ok: false,
|
||||
error: safeErrorCode(error),
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
function matchesBearer(header, expected) {
|
||||
if (typeof header !== "string" || !header.startsWith("Bearer ")) return false;
|
||||
const actual = Buffer.from(header.slice("Bearer ".length), "utf8");
|
||||
const required = Buffer.from(expected, "utf8");
|
||||
return (
|
||||
actual.length === required.length
|
||||
&& required.length > 0
|
||||
&& timingSafeEqual(actual, required)
|
||||
);
|
||||
}
|
||||
|
||||
async function readJsonBody(request, maxBytes) {
|
||||
const chunks = [];
|
||||
let size = 0;
|
||||
for await (const chunk of request) {
|
||||
size += chunk.length;
|
||||
if (size > maxBytes) {
|
||||
const error = new Error("device_request_body_too_large");
|
||||
error.statusCode = 413;
|
||||
throw error;
|
||||
}
|
||||
chunks.push(chunk);
|
||||
}
|
||||
if (size === 0) throw new TypeError("device_request_body_required");
|
||||
try {
|
||||
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
||||
} catch {
|
||||
throw new TypeError("device_request_json_invalid");
|
||||
}
|
||||
}
|
||||
|
||||
function writeJson(response, status, body) {
|
||||
response.statusCode = status;
|
||||
return response.end(`${JSON.stringify(body)}\n`);
|
||||
}
|
||||
|
||||
function safeErrorCode(error) {
|
||||
const value = error instanceof Error ? error.message : "device_control_error";
|
||||
return /^[a-z0-9_:-]{1,128}$/.test(value)
|
||||
? value
|
||||
: "device_control_error";
|
||||
}
|
||||
Reference in New Issue
Block a user