Files
pikasTech-HWLAB/internal/cloud/admin-secrets-readmodel.ts
T
2026-07-01 15:24:16 +08:00

1025 lines
42 KiB
TypeScript

/*
* SPEC: HWLAB-ESO-UI-20260630 ESO UI draft-2026-06-30-p1-readonly-api.
* Responsibility: read-only, redacted admin secrets API model for Cloud Web.
*/
import { existsSync, readFileSync } from "node:fs";
import { createHash } from "node:crypto";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { sendJson } from "./server-http-utils.ts";
const CONTRACT_VERSION = "hwlab-admin-secrets-v1";
const PLAN_PERMISSION = "admin.secrets.plan";
const MAX_PLAN_BODY_BYTES = 32 * 1024;
const DEFAULT_CONFIG_RELATIVE_PATH = "config/hwlab-v03/secrets.yaml";
const DEFAULT_TARGET = Object.freeze({
node: "JD01",
lane: "v03",
runtimeNamespace: "hwlab-v03",
infraNamespace: "platform-infra",
publicUrl: "https://hwlab.pikapython.com",
source: "yaml-active-target"
});
const STATUS_PRIORITY = Object.freeze([
"SecretLeakBlocked",
"StoreUnavailable",
"SyncError",
"TargetMissing",
"RolloutPending",
"ValidationFailed",
"Ready",
"Unknown"
]);
const PLAN_FAILURE_PRIORITY = Object.freeze([
"rbac_denied",
"leak_blocked",
"store_scope_violation",
"store_unavailable",
"target_collision",
"sync_risk",
"validation_failed"
]);
const KUBERNETES_NAME_RE = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/u;
const REF_PATH_RE = /^[A-Za-z0-9._/-]+$/u;
const REF_KEY_RE = /^[A-Za-z0-9._-]+$/u;
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
export async function handleAdminSecretsHttp(request, response, url, options = {}) {
try {
const planRoute = request.method === "POST" && url.pathname === "/v1/admin/secrets/external-secrets/plan";
const auth = await authenticateAdmin(request, options, { requireAdmin: !planRoute });
if (!auth.ok) return sendJson(response, numericStatus(auth.httpStatus) ?? numericStatus(auth.status) ?? numericStatus(auth.statusCode) ?? 401, auth);
const readModel = buildAdminSecretsReadModel({ env: options.env ?? process.env });
if (readModel.leakBlocked) return sendJson(response, 502, readModel.payload);
if (request.method === "GET" && url.pathname === "/v1/admin/secrets/summary") {
return sendJson(response, 200, readModel.payload);
}
if (request.method === "GET" && url.pathname === "/v1/admin/secrets/external-secrets") {
return sendJson(response, 200, externalSecretsListPayload(readModel.payload, url));
}
if (planRoute) {
const permission = adminSecretsPlanPermission(auth.actor);
if (!permission.allowed) return sendJson(response, 403, planPermissionDeniedPayload(readModel.payload, permission));
const body = await readJsonRequestBody(request, MAX_PLAN_BODY_BYTES);
const plan = buildAdminSecretsPlan(readModel.payload, body, auth);
return sendJson(response, plan.httpStatus ?? 200, plan);
}
const detailMatch = url.pathname.match(/^\/v1\/admin\/secrets\/external-secrets\/([^/]+)\/([^/]+)$/u);
if (request.method === "GET" && detailMatch) {
const detail = externalSecretDetailPayload(readModel.payload, decodeURIComponent(detailMatch[1]), decodeURIComponent(detailMatch[2]));
return sendJson(response, detail.httpStatus ?? 200, detail);
}
if (request.method === "GET" && url.pathname === "/v1/admin/secrets/events") {
return sendJson(response, 200, eventsPayload(readModel.payload, url));
}
return sendJson(response, 404, secretsError("not_found", "Admin secrets route is not implemented", 404));
} catch (error) {
return sendJson(response, error.httpStatus ?? 500, secretsError(error.code ?? "admin_secrets_failed", error.message ?? "Admin secrets read model failed", error.httpStatus ?? 500, error.details));
}
}
export function buildAdminSecretsReadModel({ env = process.env } = {}) {
const { config, sourcePath, sourceRelativePath, sourceError } = readSecretPlaneConfig(env);
const secretPlane = record(config?.secretPlane);
const readModelConfig = record(secretPlane?.readModel);
const target = normalizeTarget(readModelConfig?.target, env, sourceRelativePath);
const evidence = normalizeEvidence(readModelConfig?.statusEvidence);
const store = normalizeStore(secretPlane?.store);
const externalSecrets = normalizeExternalSecrets(secretPlane, target, store, evidence, sourceRelativePath);
const events = buildEvents({ target, store, externalSecrets, evidence, sourceError });
const summaryStatus = sourceError ? "Unknown" : aggregateStatus([
...componentStatuses(evidence),
...externalSecrets.map((item) => item.status)
]);
const leakBlocked = hasLeakRisk({ target, store, externalSecrets, events });
const generatedAt = new Date().toISOString();
const common = {
ok: !leakBlocked,
status: leakBlocked ? "blocked" : "ok",
contractVersion: CONTRACT_VERSION,
valuesPrinted: false,
secretMaterialStored: false,
generatedAt,
target,
permissions: {
[PLAN_PERMISSION]: true,
plan: true,
apply: false,
valuesPrinted: false,
secretMaterialStored: false
},
source: {
kind: "yaml",
path: sourceRelativePath,
sourceRef: `${sourceRelativePath}#secretPlane`,
present: Boolean(sourcePath && !sourceError),
error: sourceError ? { code: "secret_plane_config_unavailable", message: sourceError } : null
}
};
if (leakBlocked) {
return {
leakBlocked: true,
payload: {
...common,
error: {
code: "SecretLeakBlocked",
message: "Admin secrets read model contained disallowed secret material indicators.",
layer: "admin-secrets-readmodel",
retryable: false,
valuesPrinted: false
}
}
};
}
return {
leakBlocked: false,
payload: {
...common,
summary: {
status: summaryStatus,
priority: STATUS_PRIORITY.indexOf(summaryStatus),
counts: {
externalSecrets: externalSecrets.length,
ready: externalSecrets.filter((item) => item.status === "Ready").length,
pending: externalSecrets.filter((item) => item.status === "RolloutPending" || item.status === "Unknown").length,
errors: externalSecrets.filter((item) => ["SecretLeakBlocked", "StoreUnavailable", "SyncError", "TargetMissing", "ValidationFailed"].includes(item.status)).length,
events: events.length
},
eso: {
controller: evidence.components.esoController,
webhook: evidence.components.esoWebhook,
certController: evidence.components.esoCertController
},
stores: [store],
consumerRollout: consumerRolloutSummary(externalSecrets),
latestEvidence: {
source: evidence.source,
issue: evidence.issue,
command: evidence.command,
validatedAt: evidence.validatedAt,
fingerprint: evidence.fingerprint,
valuesPrinted: false
}
},
externalSecrets,
events
}
};
}
export function buildAdminSecretsPlan(payload, requestBody = {}, auth = {}) {
const generatedAt = new Date().toISOString();
const permission = adminSecretsPlanPermission(auth.actor);
if (!permission.allowed) return planPermissionDeniedPayload(payload, permission, generatedAt);
const leak = findPlanSecretMaterial(requestBody);
if (leak) {
return {
...basePlanPayload(payload, generatedAt, permission),
ok: false,
status: "failed",
failureKind: "leak_blocked",
checks: [planCheck("leak-block", "Secret material blocked", "failed", "leak_blocked", "Plan request included a field that can carry secret material.")],
risks: [planRisk("leak_blocked", "blocked", "Plan request was rejected before planning because it contained a disallowed secret material field.")],
error: {
code: "SecretLeakBlocked",
message: "ExternalSecret plan requests must not include secret values.",
layer: "admin-secrets-plan",
retryable: false,
field: leak.path,
valuesPrinted: false
},
httpStatus: 422
};
}
const input = record(requestBody) ?? {};
const activeTarget = payload.target ?? DEFAULT_TARGET;
const targetInput = record(input.target) ?? {};
const target = {
node: (text(targetInput.node) || text(activeTarget.node) || DEFAULT_TARGET.node).toUpperCase(),
lane: text(targetInput.lane) || text(activeTarget.lane) || DEFAULT_TARGET.lane,
namespace: text(targetInput.namespace) || text(targetInput.runtimeNamespace) || text(activeTarget.runtimeNamespace) || DEFAULT_TARGET.runtimeNamespace
};
const mode = normalizePlanMode(input.mode);
const externalSecretName = text(input.externalSecretName) || text(input.name);
const targetSecretName = text(input.targetSecretName) || externalSecretName;
const refreshInterval = text(input.refreshInterval) || "1m";
const store = normalizePlanStore(input.store, payload, target);
const data = normalizePlanData(input.data);
const consumerRollout = normalizePlanConsumerRollout(input.consumerRollout);
const existingExternalSecret = payload.externalSecrets.find((item) => item.namespace === target.namespace && item.name === externalSecretName) ?? null;
const targetSecretCollision = payload.externalSecrets.find((item) => item.namespace === target.namespace && text(record(item.targetSecret)?.name) === targetSecretName) ?? null;
const affectedConsumers = affectedPlanConsumers(existingExternalSecret ?? targetSecretCollision);
const checks = [];
const risks = [];
const failures = new Set();
if (!externalSecretName || !KUBERNETES_NAME_RE.test(externalSecretName) || externalSecretName.length > 63) {
checks.push(planCheck("external-secret-name", "ExternalSecret name", "failed", "validation_failed", "Name must be a Kubernetes DNS label with at most 63 characters."));
failures.add("validation_failed");
} else {
checks.push(planCheck("external-secret-name", "ExternalSecret name", "passed", "ok", externalSecretName));
}
if (!targetSecretName || !KUBERNETES_NAME_RE.test(targetSecretName) || targetSecretName.length > 63) {
checks.push(planCheck("target-secret-name", "Target Secret name", "failed", "validation_failed", "Target Secret name must be a Kubernetes DNS label with at most 63 characters."));
failures.add("validation_failed");
} else {
checks.push(planCheck("target-secret-name", "Target Secret name", "passed", "ok", targetSecretName));
}
if (target.node !== text(activeTarget.node).toUpperCase() || target.lane !== text(activeTarget.lane) || target.namespace !== text(activeTarget.runtimeNamespace)) {
checks.push(planCheck("active-target", "Active target", "failed", "validation_failed", "Plan target must match the API active target."));
failures.add("validation_failed");
} else {
checks.push(planCheck("active-target", "Active target", "passed", "ok", `${target.node}/${target.lane}/${target.namespace}`));
}
const availableStore = findPlanStore(payload, store);
if (store.kind === "SecretStore" && store.namespace !== target.namespace) {
checks.push(planCheck("store-scope", "Store namespace scope", "failed", "store_scope_violation", "Namespace-scoped SecretStore cannot plan for a different namespace."));
risks.push(planRisk("store_scope_violation", "blocked", "Namespace-scoped Store is limited to the target namespace."));
failures.add("store_scope_violation");
} else {
checks.push(planCheck("store-scope", "Store namespace scope", "passed", "ok", store.kind === "ClusterSecretStore" ? `cluster store targets ${target.namespace}` : store.namespace));
}
if (!availableStore) {
checks.push(planCheck("store-available", "Store availability", "failed", "store_unavailable", `${store.kind}/${store.name} was not found in the active read model.`));
risks.push(planRisk("store_unavailable", "blocked", "Plan cannot confirm the requested Store from the active target."));
failures.add("store_unavailable");
} else {
checks.push(planCheck("store-available", "Store availability", "passed", "ok", `${store.kind}/${store.name}`));
}
if (!/^\d+[smhd]$/u.test(refreshInterval)) {
checks.push(planCheck("refresh-interval", "Refresh interval", "failed", "validation_failed", "Refresh interval must use s/m/h/d suffix."));
failures.add("validation_failed");
} else {
checks.push(planCheck("refresh-interval", "Refresh interval", "passed", "ok", refreshInterval));
}
for (const failure of validatePlanData(data, checks, risks)) failures.add(failure);
if (mode === "create" && (existingExternalSecret || targetSecretCollision)) {
const collisionName = existingExternalSecret?.name ?? text(record(targetSecretCollision?.targetSecret)?.name) ?? targetSecretName;
checks.push(planCheck("target-collision", "Target collision", "failed", "target_collision", `${target.namespace}/${collisionName} already exists in the read model.`));
risks.push(planRisk("target_collision", "blocked", "Create plan would collide with an existing ExternalSecret or target Secret."));
failures.add("target_collision");
} else {
checks.push(planCheck("target-collision", "Target collision", "passed", "ok", mode === "edit" ? "edit mode permits matching object" : "no collision detected"));
}
checks.push(planCheck("dry-run", "Dry-run boundary", "passed", "ok", "No Kubernetes, Vault, ESO, or GitOps writes are performed."));
checks.push(planCheck("secret-values", "Secret value boundary", "passed", "ok", "Plan request and response do not include secret values."));
risks.push(planRisk("dry_run_only", "info", "This plan is read-only and cannot apply, rotate, or roll back a Secret."));
if (consumerRollout.enabled && affectedConsumers.length === 0) risks.push(planRisk("consumer_rollout_marker", "info", "Consumer rollout marker is recorded as intent only; no consumer was resolved from the read model."));
const failureKind = firstFailureKind(failures);
const ok = !failureKind;
return {
...basePlanPayload(payload, generatedAt, permission),
ok,
status: ok ? "planned" : "failed",
planId: `esp_${sha256Text(stableJson({ target, mode, externalSecretName, targetSecretName, store, data, refreshInterval, consumerRollout })).slice(0, 16)}`,
failureKind: failureKind || null,
checks,
risks,
plan: {
operation: mode === "edit" ? "edit-external-secret-dry-run" : "create-external-secret-dry-run",
mode,
dryRun: true,
writes: false,
target,
externalSecret: {
name: externalSecretName,
namespace: target.namespace,
refreshInterval
},
targetSecret: {
name: targetSecretName,
namespace: target.namespace,
presence: targetSecretCollision ? "present" : "not-present",
valuesPrinted: false
},
store,
data: data.map((item) => ({
targetKey: item.targetKey,
sourceRef: item.sourceRef,
presence: "not-read-dry-run",
referenceFingerprint: item.referenceFingerprint,
valuesPrinted: false
})),
affectedConsumers,
consumerRollout,
valuesPrinted: false,
secretMaterialStored: false
}
};
}
function basePlanPayload(payload, generatedAt, permission) {
return {
contractVersion: CONTRACT_VERSION,
generatedAt,
target: payload.target,
permission,
valuesPrinted: false,
secretMaterialStored: false
};
}
function planPermissionDeniedPayload(payload, permission, generatedAt = new Date().toISOString()) {
return {
...basePlanPayload(payload, generatedAt, permission),
ok: false,
status: "failed",
failureKind: "rbac_denied",
checks: [planCheck("permission", PLAN_PERMISSION, "failed", "rbac_denied", `${PLAN_PERMISSION} is required.`)],
risks: [planRisk("rbac_denied", "blocked", "Current actor is not allowed to plan ExternalSecret changes.")],
error: {
code: "admin_secrets_plan_required",
message: `${PLAN_PERMISSION} permission is required.`,
layer: "admin-secrets-plan",
retryable: false,
valuesPrinted: false
},
httpStatus: 403
};
}
function adminSecretsPlanPermission(actor) {
const role = text(actor?.role);
const permissions = permissionSetFromActor(actor);
if (role === "admin") return { id: PLAN_PERMISSION, allowed: true, source: "admin-role", valuesPrinted: false };
if (permissions.has(PLAN_PERMISSION)) return { id: PLAN_PERMISSION, allowed: true, source: "actor-permission", valuesPrinted: false };
return { id: PLAN_PERMISSION, allowed: false, source: role || "anonymous", valuesPrinted: false };
}
function permissionSetFromActor(actor) {
const output = new Set();
for (const key of ["permissions", "capabilities"]) {
const value = actor?.[key];
if (Array.isArray(value)) for (const item of value) if (text(item)) output.add(text(item));
if (record(value)) for (const [name, status] of Object.entries(value)) if (status === true || status === "available" || status === "allowed") output.add(name);
}
return output;
}
async function readJsonRequestBody(request, limitBytes) {
let raw = "";
for await (const chunk of request) {
raw += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk ?? "");
if (Buffer.byteLength(raw, "utf8") > limitBytes) throw Object.assign(new Error("Plan request body is too large"), { httpStatus: 413, code: "plan_payload_too_large" });
}
if (!raw.trim()) return {};
try {
return JSON.parse(raw);
} catch (error) {
throw Object.assign(new Error("Plan request body must be valid JSON"), { httpStatus: 400, code: "plan_payload_parse_error", details: { reason: error.message } });
}
}
function normalizePlanMode(value) {
const mode = text(value).toLowerCase();
return mode === "edit" ? "edit" : "create";
}
function normalizePlanStore(value, payload, target) {
const input = record(value) ?? {};
const firstStore = record(payload.summary?.stores?.[0]) ?? {};
const kind = text(input.kind) === "SecretStore" ? "SecretStore" : "ClusterSecretStore";
const namespace = kind === "SecretStore" ? text(input.namespace) || target.namespace : text(input.namespace) || text(firstStore.namespace) || text(payload.target?.infraNamespace) || "platform-infra";
return {
kind,
name: text(input.name) || text(firstStore.name) || "hwlab-secret-plane-vault-cluster",
namespace,
scope: kind === "SecretStore" ? "namespace" : "cluster",
valuesPrinted: false
};
}
function findPlanStore(payload, store) {
const stores = Array.isArray(payload.summary?.stores) ? payload.summary.stores : [];
return stores.find((item) => {
if (text(item.kind) !== store.kind) return false;
if (text(item.name) !== store.name) return false;
if (store.kind === "SecretStore" && text(item.namespace) !== store.namespace) return false;
return true;
}) ?? null;
}
function normalizePlanData(value) {
const items = Array.isArray(value) ? value : [value].filter(Boolean);
return items.map((entry, index) => {
const item = record(entry) ?? {};
const sourceRef = record(item.sourceRef) ?? record(item.remoteRef) ?? {};
const pathValue = text(sourceRef.path) || text(item.path) || text(item.remotePath);
const property = text(sourceRef.property) || text(item.property);
const version = text(sourceRef.version) || text(item.version) || null;
const targetKey = text(item.targetKey);
return {
targetKey,
sourceRef: {
path: pathValue,
property,
version
},
referenceFingerprint: pathValue || property ? `sha256:${sha256Text(`${pathValue}:${property}:${version ?? ""}`).slice(0, 16)}` : null,
index
};
});
}
function normalizePlanConsumerRollout(value) {
const input = record(value) ?? {};
const enabled = input.enabled === true || text(input.intent) !== "";
const intent = text(input.intent) || (enabled ? "rollout-marker" : "none");
return {
enabled,
intent,
marker: enabled ? "plan-only" : "none",
valuesPrinted: false
};
}
function validatePlanData(data, checks, risks) {
const failures = new Set();
if (data.length === 0) {
checks.push(planCheck("source-ref", "Source reference", "failed", "sync_risk", "At least one sourceRef mapping is required."));
risks.push(planRisk("sync_risk", "blocked", "ESO sync cannot be planned without sourceRef path/property and target key."));
failures.add("sync_risk");
return failures;
}
for (const item of data) {
const label = `data[${item.index}]`;
if (!item.targetKey || !REF_KEY_RE.test(item.targetKey)) {
checks.push(planCheck(`${label}.targetKey`, "Target key", "failed", "validation_failed", "Target key must be non-empty and contain only letters, digits, dot, dash, or underscore."));
failures.add("validation_failed");
}
if (!item.sourceRef.path || !REF_PATH_RE.test(item.sourceRef.path)) {
checks.push(planCheck(`${label}.sourceRef.path`, "Source path", "failed", "sync_risk", "sourceRef.path is required and must be a safe remote reference path."));
risks.push(planRisk("sync_risk", "blocked", "Remote source path is missing or malformed."));
failures.add("sync_risk");
}
if (!item.sourceRef.property || !REF_KEY_RE.test(item.sourceRef.property)) {
checks.push(planCheck(`${label}.sourceRef.property`, "Source property", "failed", "sync_risk", "sourceRef.property is required and must be a safe property name."));
risks.push(planRisk("sync_risk", "blocked", "Remote source property is missing or malformed."));
failures.add("sync_risk");
}
if (item.sourceRef.version && !REF_KEY_RE.test(item.sourceRef.version)) {
checks.push(planCheck(`${label}.sourceRef.version`, "Source version", "failed", "validation_failed", "sourceRef.version contains unsupported characters."));
failures.add("validation_failed");
}
}
if (failures.size === 0) checks.push(planCheck("source-ref", "Source reference", "passed", "ok", `${data.length} mapping(s)`));
return failures;
}
function affectedPlanConsumers(secret) {
if (!secret || !Array.isArray(secret.consumers)) return [];
return secret.consumers.map((item) => ({
serviceId: text(item.serviceId) || "-",
kind: text(item.kind) || "-",
injectAs: text(item.injectAs) || "-",
envName: text(item.envName) || null,
rollout: text(item.rollout) || "Unknown",
revision: text(item.revision) || null,
valuesPrinted: false
}));
}
function firstFailureKind(failures) {
for (const kind of PLAN_FAILURE_PRIORITY) if (failures.has(kind)) return kind;
return "";
}
function planCheck(id, label, status, code, detail) {
return { id, label, status, code, detail, valuesPrinted: false };
}
function planRisk(code, severity, message) {
return { code, severity, message, valuesPrinted: false };
}
function findPlanSecretMaterial(value, pathParts = []) {
if (Array.isArray(value)) {
for (let index = 0; index < value.length; index += 1) {
const found = findPlanSecretMaterial(value[index], [...pathParts, String(index)]);
if (found) return found;
}
return null;
}
if (!value || typeof value !== "object") {
if (typeof value === "string" && looksLikeSecretMaterial(value) && !isPlanDescriptorPath(pathParts)) return { path: pathParts.join(".") || "$" };
return null;
}
for (const [key, item] of Object.entries(value)) {
const nextPath = [...pathParts, key];
if (isSecretValueKey(key) && typeof item === "string" && item.trim() && !isPlanDescriptorPath(nextPath)) return { path: nextPath.join(".") };
const found = findPlanSecretMaterial(item, nextPath);
if (found) return found;
}
return null;
}
function isSecretValueKey(key) {
return /^(value|raw|secretValue|decoded|base64|authorization|cookie|token|apiKey|password)$/u.test(key);
}
function isPlanDescriptorPath(pathParts) {
const key = pathParts[pathParts.length - 1];
if (["targetKey", "property", "version", "path", "remotePath", "externalSecretName", "targetSecretName", "name", "namespace", "kind", "intent", "mode", "node", "lane", "refreshInterval"].includes(key)) return true;
const joined = pathParts.join(".");
return /\.(sourceRef|remoteRef)\.(path|property|version)$/u.test(joined);
}
function looksLikeSecretMaterial(value) {
const item = text(value);
if (!item) return false;
if (/-----BEGIN [A-Z ]*PRIVATE KEY-----/u.test(item)) return true;
if (/^(hsk|ghp|github_pat|sk|xox[baprs])-[-A-Za-z0-9_]{16,}$/u.test(item)) return true;
if (/^[A-Za-z0-9+/]{48,}={0,2}$/u.test(item) && !item.includes("/")) return true;
return false;
}
function stableJson(value) {
return JSON.stringify(sortJson(value));
}
function sortJson(value) {
if (Array.isArray(value)) return value.map(sortJson);
if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortJson(value[key])]));
return value;
}
function externalSecretsListPayload(payload, url) {
const node = text(url.searchParams.get("node")).toUpperCase();
const lane = text(url.searchParams.get("lane")).toLowerCase();
const namespace = text(url.searchParams.get("namespace"));
const items = payload.externalSecrets.filter((item) => {
if (node && item.target.node !== node) return false;
if (lane && item.target.lane.toLowerCase() !== lane) return false;
if (namespace && item.namespace !== namespace) return false;
return true;
});
return {
ok: true,
status: "ok",
contractVersion: CONTRACT_VERSION,
valuesPrinted: false,
secretMaterialStored: false,
generatedAt: payload.generatedAt,
target: payload.target,
items,
count: items.length
};
}
function externalSecretDetailPayload(payload, namespace, name) {
const item = payload.externalSecrets.find((secret) => secret.namespace === namespace && secret.name === name);
if (!item) return secretsError("external_secret_not_found", "ExternalSecret read model entry was not found", 404, { namespace, name });
const events = filterSecretEvents(payload.events, { namespace, name }).slice(0, 200);
return {
ok: true,
status: "ok",
contractVersion: CONTRACT_VERSION,
valuesPrinted: false,
secretMaterialStored: false,
generatedAt: payload.generatedAt,
target: payload.target,
item,
events,
eventCount: events.length
};
}
function eventsPayload(payload, url) {
const limit = positiveInteger(url.searchParams.get("limit"), 50);
const namespace = text(url.searchParams.get("namespace"));
const name = text(url.searchParams.get("name"));
const items = filterSecretEvents(payload.events, { namespace, name })
.slice(0, Math.min(limit, 200));
return {
ok: true,
status: "ok",
contractVersion: CONTRACT_VERSION,
valuesPrinted: false,
secretMaterialStored: false,
generatedAt: payload.generatedAt,
target: payload.target,
events: items,
count: items.length
};
}
function filterSecretEvents(events, { namespace = "", name = "" } = {}) {
return events.filter((event) => {
if (namespace && event.namespace !== namespace) return false;
if (name && event.resourceName !== name) return false;
return true;
});
}
async function authenticateAdmin(request, options, { requireAdmin = true } = {}) {
const controller = options.accessController;
if (!controller || typeof controller.authenticate !== "function") return secretsError("access_controller_unavailable", "HWLAB access controller is not available", 503);
const auth = await controller.authenticate(request, { required: true });
if (!auth.ok) return auth;
if (requireAdmin && auth.actor?.role !== "admin") return secretsError("admin_required", "Only admin users can read admin secrets status", 403);
return auth;
}
function readSecretPlaneConfig(env) {
const configuredPath = text(env.HWLAB_ADMIN_SECRETS_CONFIG_PATH);
const candidates = [
configuredPath,
path.join(repoRoot, DEFAULT_CONFIG_RELATIVE_PATH),
path.join(process.cwd(), DEFAULT_CONFIG_RELATIVE_PATH),
path.join("/workspace/hwlab", DEFAULT_CONFIG_RELATIVE_PATH)
].filter(Boolean);
const sourcePath = candidates.find((candidate) => existsSync(candidate)) ?? candidates[0] ?? "";
const sourceRelativePath = sourcePath.includes(DEFAULT_CONFIG_RELATIVE_PATH) ? DEFAULT_CONFIG_RELATIVE_PATH : sourcePath;
if (!sourcePath || !existsSync(sourcePath)) return { config: null, sourcePath, sourceRelativePath, sourceError: `missing ${sourceRelativePath}` };
try {
return { config: parseAdminSecretsYaml(readFileSync(sourcePath, "utf8")), sourcePath, sourceRelativePath, sourceError: null };
} catch (error) {
return { config: null, sourcePath, sourceRelativePath, sourceError: `invalid yaml: ${error.message ?? String(error)}` };
}
}
function parseAdminSecretsYaml(raw) {
const root = {};
const stack = [{ indent: -1, value: root, pending: false, parent: null, key: "" }];
for (const [lineIndex, rawLine] of String(raw ?? "").split(/\r?\n/u).entries()) {
if (!rawLine.trim() || rawLine.trimStart().startsWith("#")) continue;
const indent = rawLine.match(/^ */u)?.[0].length ?? 0;
const content = rawLine.trim();
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) stack.pop();
let current = stack[stack.length - 1];
if (content.startsWith("- ")) {
current = ensureYamlArrayContainer(current);
const itemText = content.slice(2).trim();
if (!itemText) {
const item = {};
current.value.push(item);
stack.push({ indent, value: item, pending: false, parent: current.value, key: String(current.value.length - 1) });
continue;
}
const keyValue = splitYamlKeyValue(itemText);
if (keyValue) {
const item = {};
current.value.push(item);
assignYamlKeyValue(item, keyValue, lineIndex);
stack.push({ indent, value: item, pending: false, parent: current.value, key: String(current.value.length - 1) });
} else {
current.value.push(parseYamlScalar(itemText));
}
continue;
}
const parent = current.value;
if (!record(parent)) throw new Error(`unsupported yaml mapping at line ${lineIndex + 1}`);
const keyValue = splitYamlKeyValue(content);
if (!keyValue) throw new Error(`unsupported yaml line ${lineIndex + 1}`);
const assigned = assignYamlKeyValue(parent, keyValue, lineIndex);
if (assigned.pending) stack.push({ indent, value: assigned.value, pending: true, parent, key: keyValue.key });
}
return root;
}
function ensureYamlArrayContainer(entry) {
if (Array.isArray(entry.value)) return entry;
if (entry.pending && entry.parent && entry.key) {
const items = [];
entry.parent[entry.key] = items;
entry.value = items;
entry.pending = false;
return entry;
}
throw new Error("unsupported yaml sequence placement");
}
function splitYamlKeyValue(value) {
const match = String(value ?? "").match(/^([^:]+):(.*)$/u);
if (!match) return null;
return { key: match[1].trim(), value: match[2].trim() };
}
function assignYamlKeyValue(target, keyValue, lineIndex) {
if (!keyValue.key) throw new Error(`empty yaml key at line ${lineIndex + 1}`);
if (!keyValue.value) {
const child = {};
target[keyValue.key] = child;
return { value: child, pending: true };
}
target[keyValue.key] = parseYamlScalar(keyValue.value);
return { value: target[keyValue.key], pending: false };
}
function parseYamlScalar(value) {
const textValue = String(value ?? "").trim();
if (!textValue) return "";
if (textValue === "true") return true;
if (textValue === "false") return false;
if (textValue === "null" || textValue === "~") return null;
if (/^-?\d+(?:\.\d+)?$/u.test(textValue)) return Number(textValue);
if ((textValue.startsWith("\"") && textValue.endsWith("\"")) || (textValue.startsWith("'") && textValue.endsWith("'"))) return textValue.slice(1, -1);
return textValue;
}
function normalizeTarget(value, env, sourceRelativePath) {
const input = record(value);
const node = text(env.HWLAB_ADMIN_SECRETS_ACTIVE_NODE) || text(input?.node) || DEFAULT_TARGET.node;
const lane = text(env.HWLAB_ADMIN_SECRETS_ACTIVE_LANE) || text(input?.lane) || DEFAULT_TARGET.lane;
return {
node: node.toUpperCase(),
lane,
runtimeNamespace: text(input?.runtimeNamespace) || DEFAULT_TARGET.runtimeNamespace,
infraNamespace: text(input?.infraNamespace) || DEFAULT_TARGET.infraNamespace,
publicUrl: text(input?.publicUrl) || DEFAULT_TARGET.publicUrl,
source: text(input?.source) || DEFAULT_TARGET.source,
sourceRef: text(input?.sourceRef) || `${sourceRelativePath}#secretPlane.readModel.target`,
upstreamSourceRef: text(input?.upstreamSourceRef) || "config/platform-infra/secret-plane.yaml#activeTarget"
};
}
function normalizeEvidence(value) {
const input = record(value);
const components = record(input?.components);
return {
source: text(input?.source) || "pikasTech/unidesk#1282",
issue: text(input?.issue) || "pikasTech/HWLAB#2277",
command: text(input?.command) || "platform-infra secret-plane status --target JD01",
validatedAt: text(input?.validatedAt) || "2026-06-30T03:21:49Z",
fingerprint: text(input?.fingerprint),
status: normalizeStatus(input?.status, "Ready"),
components: {
esoController: normalizeStatus(components?.esoController, "Ready"),
esoWebhook: normalizeStatus(components?.esoWebhook, "Ready"),
esoCertController: normalizeStatus(components?.esoCertController, "Ready"),
secretStore: normalizeStatus(components?.secretStore, "Ready"),
clusterSecretStore: normalizeStatus(components?.clusterSecretStore, "Ready"),
sync: normalizeStatus(components?.sync, "Ready"),
consumer: normalizeStatus(components?.consumer, "Ready")
}
};
}
function normalizeStore(value) {
const input = record(value);
return {
kind: text(input?.kind) || "ClusterSecretStore",
name: text(input?.name) || "hwlab-secret-plane-vault-cluster",
namespace: text(input?.namespace) || "platform-infra",
scope: text(input?.scope) || (text(input?.kind) === "SecretStore" ? "namespace" : "cluster"),
status: "Ready",
reason: "JD01 secret-plane status evidence reports Store Ready",
valuesPrinted: false
};
}
function normalizeExternalSecrets(secretPlane, target, store, evidence, sourceRelativePath) {
const secrets = Array.isArray(secretPlane?.secrets) ? secretPlane.secrets : [];
return secrets.map((secret, index) => normalizeExternalSecret(secret, index, secretPlane, target, store, evidence, sourceRelativePath));
}
function normalizeExternalSecret(secret, index, secretPlane, target, store, evidence, sourceRelativePath) {
const item = record(secret) ?? {};
const statusEvidence = record(item.statusEvidence) ?? {};
const namespace = text(item.targetNamespace) || target.runtimeNamespace;
const data = Array.isArray(item.data) ? item.data.map((entry, dataIndex) => normalizeSecretData(entry, dataIndex)) : [];
const consumers = Array.isArray(item.consumers) ? item.consumers.map((consumer, consumerIndex) => normalizeConsumer(consumer, consumerIndex, evidence)) : [];
const fingerprint = text(statusEvidence.fingerprint) || evidence.fingerprint;
const status = normalizeStatus(statusEvidence.status, evidence.status);
const name = text(item.externalSecretName) || `external-secret-${index + 1}`;
const targetSecretName = text(item.targetSecretName) || name;
const lastUpdated = text(statusEvidence.lastUpdated) || text(statusEvidence.validatedAt) || evidence.validatedAt;
const createdAt = text(item.createdAt) || text(statusEvidence.createdAt) || null;
const cluster = text(item.cluster) || `${target.node}/${target.lane}`;
const reason = text(statusEvidence.reason) || `${evidence.source} validated ${cluster} read-only status`;
return {
id: text(item.id) || name,
name,
namespace,
cluster,
target,
store: { kind: store.kind, name: store.name, namespace: store.namespace, scope: store.scope, status: store.status },
status,
ready: status === "Ready",
reason,
message: "脱敏 read model only; sensitive material is never read or returned.",
refreshInterval: text(item.refreshInterval) || text(secretPlane?.refreshInterval) || "1m",
createdAt,
lastUpdated,
ageSource: createdAt || lastUpdated,
lastRefreshTime: lastUpdated,
conditions: normalizeConditions(item.conditions, { status, reason, lastUpdated }),
targetSecret: {
name: targetSecretName,
namespace,
keys: data.map((entry) => ({ key: entry.targetKey, presence: fingerprint ? "present" : "unknown", fingerprint, valuesPrinted: false })),
resourceVersion: text(statusEvidence.resourceVersion) || null,
fingerprint,
valuesPrinted: false
},
data,
consumers,
provenance: {
configSource: `${sourceRelativePath}#secretPlane.secrets[${index}]`,
statusSource: evidence.source,
issue: evidence.issue,
command: evidence.command,
validatedAt: evidence.validatedAt
},
valuesPrinted: false,
secretMaterialStored: false
};
}
function normalizeConditions(value, fallback) {
const source = Array.isArray(value) ? value : [];
const conditions = source.map((entry) => {
const item = record(entry) ?? {};
return {
type: text(item.type) || "Ready",
status: text(item.status) || fallback.status,
reason: text(item.reason) || fallback.reason,
lastTransitionTime: text(item.lastTransitionTime) || fallback.lastUpdated,
valuesPrinted: false
};
});
if (conditions.length > 0) return conditions;
return [{ type: "Ready", status: fallback.status, reason: fallback.reason, lastTransitionTime: fallback.lastUpdated, valuesPrinted: false }];
}
function normalizeSecretData(entry, index) {
const item = record(entry) ?? {};
const remoteRef = text(item.remoteRef);
return {
targetKey: text(item.targetKey) || `key-${index + 1}`,
remoteRef: {
path: redactRemoteRef(remoteRef),
property: text(item.property) || null,
version: text(item.version) || null,
fingerprint: remoteRef ? sha256Text(remoteRef).slice(0, 16) : null,
redacted: true
}
};
}
function normalizeConsumer(value, index, evidence) {
const input = record(value) ?? {};
const serviceId = text(input.serviceId) || `consumer-${index + 1}`;
return {
serviceId,
kind: text(input.kind) || "Deployment",
injectAs: text(input.injectAs) || "env",
envName: text(input.envName) || null,
status: evidence.components.consumer,
rollout: evidence.components.consumer === "Ready" ? "Ready" : "RolloutPending",
revision: text(input.revision) || null,
valuesPrinted: false
};
}
function buildEvents({ target, store, externalSecrets, evidence, sourceError }) {
const events = [];
if (sourceError) {
events.push(event("Unknown", "ConfigUnavailable", target.runtimeNamespace, "secret-plane", sourceError, target, evidence));
}
for (const secret of externalSecrets) {
events.push(event(secret.status, `${secret.status}ExternalSecret`, secret.namespace, secret.name, secret.reason, target, evidence, {
resourceKind: "ExternalSecret",
resourceName: secret.name
}));
events.push(event(store.status, `${store.status}Store`, store.namespace, store.name, "SecretStore / ClusterSecretStore readiness is exposed as redacted status.", target, evidence, {
resourceKind: store.kind,
resourceName: store.name
}));
}
return events.sort((left, right) => String(right.observedAt).localeCompare(String(left.observedAt)));
}
function event(status, reason, namespace, name, message, target, evidence, extra = {}) {
return {
status,
reason,
namespace,
resourceKind: extra.resourceKind || "SecretPlane",
resourceName: extra.resourceName || name,
message: summarizeMessage(message),
observedAt: evidence.validatedAt || new Date().toISOString(),
target,
traceId: null,
jobId: null,
source: evidence.source,
valuesPrinted: false
};
}
function componentStatuses(evidence) {
return Object.values(evidence.components);
}
function consumerRolloutSummary(externalSecrets) {
const consumers = externalSecrets.flatMap((item) => item.consumers);
return {
total: consumers.length,
ready: consumers.filter((item) => item.rollout === "Ready").length,
pending: consumers.filter((item) => item.rollout !== "Ready").length,
revision: consumers.find((item) => item.revision)?.revision ?? null
};
}
function aggregateStatus(statuses) {
for (const status of STATUS_PRIORITY) if (statuses.includes(status)) return status;
return "Unknown";
}
function normalizeStatus(value, fallback = "Unknown") {
const textValue = text(value);
if (STATUS_PRIORITY.includes(textValue)) return textValue;
if (/^(ready|ok|healthy|synced)$/iu.test(textValue)) return "Ready";
if (/^(notready|unavailable)$/iu.test(textValue)) return "StoreUnavailable";
if (/^(pending|progressing)$/iu.test(textValue)) return "RolloutPending";
if (/^(failed|error)$/iu.test(textValue)) return "SyncError";
return fallback;
}
function hasLeakRisk(value) {
const stack = [value];
while (stack.length > 0) {
const current = stack.pop();
if (Array.isArray(current)) {
stack.push(...current);
continue;
}
if (!current || typeof current !== "object") continue;
for (const [key, item] of Object.entries(current)) {
if (/^(value|raw|secretValue|decoded|base64|authorization|cookie|token|apiKey|password)$/u.test(key) && typeof item === "string" && item.trim()) return true;
if (item && typeof item === "object") stack.push(item);
}
}
return false;
}
function redactRemoteRef(value) {
const textValue = text(value);
if (!textValue) return null;
const parts = textValue.split("/").filter(Boolean);
if (parts.length <= 1) return `${textValue.slice(0, 4)}...`;
return `${parts[0]}/.../${parts[parts.length - 1]}`;
}
function summarizeMessage(value) {
const message = text(value);
return message.length > 220 ? `${message.slice(0, 217)}...` : message;
}
function secretsError(code, message, status = 500, details = {}) {
return {
ok: false,
status: "failed",
contractVersion: CONTRACT_VERSION,
valuesPrinted: false,
secretMaterialStored: false,
error: {
code,
message,
layer: "admin-secrets",
retryable: false,
valuesPrinted: false,
...details
},
httpStatus: status
};
}
function numericStatus(value) {
const number = Number(value);
return Number.isInteger(number) && number >= 100 && number <= 599 ? number : null;
}
function positiveInteger(value, fallback) {
const number = Number.parseInt(String(value ?? ""), 10);
return Number.isInteger(number) && number > 0 ? number : fallback;
}
function record(value) {
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
}
function text(value) {
if (value === null || value === undefined) return "";
return String(value).trim();
}
function sha256Text(value) {
return createHash("sha256").update(text(value)).digest("hex");
}