import { createHash } from "node:crypto"; import { readFileSync } from "node:fs"; import type { UniDeskConfig } from "./config"; import { rootPath } from "./config"; import { startJob } from "./jobs"; import { runSshCommandCapture, type SshCaptureResult } from "./ssh"; const g14K3sRoute = "G14:k3s"; const namespace = "platform-infra"; const serviceName = "sub2api"; const fieldManager = "unidesk-platform-infra"; const manifestPath = rootPath("src", "components", "platform-infra", "sub2api", "sub2api.k8s.yaml"); const configPath = rootPath("config", "platform-infra", "sub2api.yaml"); const secretName = "sub2api-secrets"; const requiredSecretKeys = ["POSTGRES_PASSWORD", "ADMIN_PASSWORD", "JWT_SECRET", "TOTP_ENCRYPTION_KEY"] as const; interface Sub2ApiConfig { image: { repository: string; tag: string; pullPolicy: "Always" | "IfNotPresent" | "Never"; }; security: { urlAllowlist: { enabled: boolean; allowInsecureHttp: boolean; allowPrivateHosts: boolean; upstreamHosts: string[]; }; }; } export function platformInfraHelp(): unknown { return { command: "platform-infra sub2api plan|apply|status|validate|codex-pool", output: "json", usage: [ "bun scripts/cli.ts platform-infra sub2api plan", "bun scripts/cli.ts platform-infra sub2api apply --dry-run", "bun scripts/cli.ts platform-infra sub2api apply --confirm", "bun scripts/cli.ts platform-infra sub2api status [--full|--raw]", "bun scripts/cli.ts platform-infra sub2api validate [--full|--raw]", "bun scripts/cli.ts platform-infra sub2api codex-pool plan", "bun scripts/cli.ts platform-infra sub2api codex-pool sync --confirm", "bun scripts/cli.ts platform-infra sub2api codex-pool validate", ], description: "Operate the G14 k3s internal-only Sub2API deployment in the shared platform-infra namespace. This entry creates no Ingress, NodePort, LoadBalancer, hostPort, hostNetwork, ResourceQuota, LimitRange, or CPU/memory resource requests/limits.", target: { route: g14K3sRoute, namespace, service: serviceName, serviceDns: `${serviceName}.${namespace}.svc.cluster.local:8080`, exposure: "k3s-cluster-internal-only", resourceLimits: "unset-by-policy", versionConfigPath: configPath, }, codexPool: { usage: [ "bun scripts/cli.ts platform-infra sub2api codex-pool plan", "bun scripts/cli.ts platform-infra sub2api codex-pool sync --confirm", "bun scripts/cli.ts platform-infra sub2api codex-pool validate", ], module: "scripts/src/platform-infra-sub2api-codex.ts", }, }; } export async function runPlatformInfraCommand(config: UniDeskConfig, args: string[]): Promise> { const [target, action] = args; if (target !== "sub2api") return unsupported(args); if (action === "plan" || action === undefined) return plan(); if (action === "apply") return await apply(config, parseApplyOptions(args.slice(2))); if (action === "status") return await status(config, parseDisclosureOptions(args.slice(2))); if (action === "validate") return await validate(config, parseDisclosureOptions(args.slice(2))); if (action === "codex-pool") { const { runCodexPoolCommand } = await import("./platform-infra-sub2api-codex"); return await runCodexPoolCommand(config, args.slice(2)); } return unsupported(args); } interface ApplyOptions { dryRun: boolean; confirm: boolean; wait: boolean; } interface DisclosureOptions { full: boolean; raw: boolean; } interface PolicyCheck { name: string; ok: boolean; detail: string; } function unsupported(args: string[]): Record { return { ok: false, error: "unsupported-platform-infra-command", args, help: platformInfraHelp(), }; } function parseApplyOptions(args: string[]): ApplyOptions { validateOptions(args, new Set(["--dry-run", "--confirm", "--wait"])); if (args.includes("--dry-run") && args.includes("--confirm")) throw new Error("apply accepts only one of --dry-run or --confirm"); return { dryRun: args.includes("--dry-run") || !args.includes("--confirm"), confirm: args.includes("--confirm"), wait: args.includes("--wait"), }; } function parseDisclosureOptions(args: string[]): DisclosureOptions { validateOptions(args, new Set(["--full", "--raw"])); const raw = args.includes("--raw"); return { full: raw || args.includes("--full"), raw }; } function validateOptions(args: string[], booleanOptions: Set): void { for (const arg of args) { if (booleanOptions.has(arg)) continue; throw new Error(`unsupported option: ${arg}`); } } function readSub2ApiConfig(): Sub2ApiConfig { const parsed = Bun.YAML.parse(readFileSync(configPath, "utf8")) as unknown; if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`${configPath} must contain a YAML object`); const image = (parsed as { image?: unknown }).image; if (typeof image !== "object" || image === null || Array.isArray(image)) throw new Error(`${configPath}.image must be an object`); const record = image as Record; const repository = stringField(record, "repository", "image"); const tag = stringField(record, "tag", "image"); const pullPolicy = stringField(record, "pullPolicy", "image"); if (pullPolicy !== "Always" && pullPolicy !== "IfNotPresent" && pullPolicy !== "Never") throw new Error(`${configPath}.image.pullPolicy must be Always, IfNotPresent, or Never`); if (!/^[a-z0-9._/-]+(?::[0-9]+)?$/u.test(repository)) throw new Error(`${configPath}.image.repository has an unsupported format`); if (!/^[A-Za-z0-9._-]+$/u.test(tag)) throw new Error(`${configPath}.image.tag has an unsupported format`); const security = objectField(parsed as Record, "security", ""); const urlAllowlist = objectField(security, "urlAllowlist", "security"); const enabled = booleanField(urlAllowlist, "enabled", "security.urlAllowlist"); const allowInsecureHttp = booleanField(urlAllowlist, "allowInsecureHttp", "security.urlAllowlist"); const allowPrivateHosts = booleanField(urlAllowlist, "allowPrivateHosts", "security.urlAllowlist"); const upstreamHosts = stringArrayField(urlAllowlist, "upstreamHosts", "security.urlAllowlist"); return { image: { repository, tag, pullPolicy }, security: { urlAllowlist: { enabled, allowInsecureHttp, allowPrivateHosts, upstreamHosts, }, }, }; } function stringField(obj: Record, key: string, path: string): string { const value = obj[key]; if (typeof value !== "string" || value.length === 0) throw new Error(`${configPath}.${path}.${key} must be a non-empty string`); return value; } function objectField(obj: Record, key: string, path: string): Record { const value = obj[key]; const prefix = path.length > 0 ? `${path}.` : ""; if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${configPath}.${prefix}${key} must be an object`); return value as Record; } function booleanField(obj: Record, key: string, path: string): boolean { const value = obj[key]; if (typeof value !== "boolean") throw new Error(`${configPath}.${path}.${key} must be a boolean`); return value; } function stringArrayField(obj: Record, key: string, path: string): string[] { const value = obj[key]; if (!Array.isArray(value) || !value.every((item) => typeof item === "string" && item.trim().length > 0)) { throw new Error(`${configPath}.${path}.${key} must be an array of non-empty strings`); } return value.map((item) => item.trim()); } function imageRef(sub2api: Sub2ApiConfig): string { return `${sub2api.image.repository}:${sub2api.image.tag}`; } function manifest(): string { const sub2api = readSub2ApiConfig(); const template = readFileSync(manifestPath, "utf8"); const urlAllowlist = sub2api.security.urlAllowlist; const configHash = createHash("sha256") .update(JSON.stringify({ image: sub2api.image, security: sub2api.security, })) .digest("hex") .slice(0, 16); return template .replaceAll("__SUB2API_IMAGE__", imageRef(sub2api)) .replaceAll("__SUB2API_IMAGE_PULL_POLICY__", sub2api.image.pullPolicy) .replaceAll("__SUB2API_CONFIG_HASH__", configHash) .replaceAll("__SUB2API_SECURITY_URL_ALLOWLIST_ENABLED__", String(urlAllowlist.enabled)) .replaceAll("__SUB2API_SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP__", String(urlAllowlist.allowInsecureHttp)) .replaceAll("__SUB2API_SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS__", String(urlAllowlist.allowPrivateHosts)) .replaceAll("__SUB2API_SECURITY_URL_ALLOWLIST_UPSTREAM_HOSTS__", urlAllowlist.upstreamHosts.join(",")); } function plan(): Record { const sub2api = readSub2ApiConfig(); const yaml = manifest(); const policy = policyChecks(yaml); return { ok: policy.every((check) => check.ok), action: "platform-infra-sub2api-plan", target: { route: g14K3sRoute, namespace, manifestPath, configPath, fieldManager, serviceDns: `${serviceName}.${namespace}.svc.cluster.local:8080`, }, config: { image: imageRef(sub2api), pullPolicy: sub2api.image.pullPolicy, security: sub2api.security, }, decision: { owner: "UniDesk", namespace, reason: "Sub2API is an internal shared platform utility for G14 k3s workloads, so it belongs with platform infrastructure rather than a user workload namespace.", exposure: "ClusterIP only; no public ingress or node-level exposure.", resourcePolicy: "No Kubernetes CPU/memory requests or limits, matching issue #220.", imageVersionControl: "Sub2API image repository/tag/pullPolicy are controlled by config/platform-infra/sub2api.yaml in the UniDesk repository.", urlAllowlistControl: "Sub2API upstream URL validation options are controlled by config/platform-infra/sub2api.yaml and rendered to SECURITY_URL_ALLOWLIST_* env vars.", networkPolicy: "NetworkPolicy/allow-all is rendered with the deployment so kube-router cannot silently default-deny Sub2API cross-pod traffic.", dataStores: ["PostgreSQL 18", "Redis 8"], appPoolCaps: { databaseMaxOpenConns: 10, databaseMaxIdleConns: 2, redisPoolSize: 32, redisMinIdleConns: 2, }, }, policy, next: { dryRun: "bun scripts/cli.ts platform-infra sub2api apply --dry-run", apply: "bun scripts/cli.ts platform-infra sub2api apply --confirm", status: "bun scripts/cli.ts platform-infra sub2api status", validate: "bun scripts/cli.ts platform-infra sub2api validate", }, }; } async function apply(config: UniDeskConfig, options: ApplyOptions): Promise> { const yaml = manifest(); const policy = policyChecks(yaml); if (!policy.every((check) => check.ok)) { return { ok: false, action: "platform-infra-sub2api-apply", mode: "policy-blocked", policy, }; } if (options.confirm && !options.wait) { const job = startJob( "platform_infra_sub2api_apply", ["bun", "scripts/cli.ts", "platform-infra", "sub2api", "apply", "--confirm", "--wait"], "Apply G14 k3s platform-infra Sub2API manifests through the controlled UniDesk CLI", ); return { ok: true, action: "platform-infra-sub2api-apply", mode: "async-job", job, statusCommand: `bun scripts/cli.ts job status ${job.id} --tail-bytes 12000`, next: { status: `bun scripts/cli.ts job status ${job.id} --tail-bytes 12000`, rollout: "bun scripts/cli.ts platform-infra sub2api status", validate: "bun scripts/cli.ts platform-infra sub2api validate", }, }; } if (options.dryRun) { const result = await capture(config, g14K3sRoute, ["script"], dryRunScript(yaml)); const parsed = parseJsonOutput(result.stdout); return { ok: result.exitCode === 0 && boolField(parsed, "ok", false), action: "platform-infra-sub2api-apply", mode: "dry-run", policy, remote: parsed ?? compactCapture(result, { full: true }), }; } const result = await capture(config, g14K3sRoute, ["script"], applyScript(yaml)); const parsed = parseJsonOutput(result.stdout); return { ok: result.exitCode === 0 && boolField(parsed, "ok", false), action: "platform-infra-sub2api-apply", mode: "confirmed", policy, remote: parsed ?? compactCapture(result, { full: true }), next: { status: "bun scripts/cli.ts platform-infra sub2api status", validate: "bun scripts/cli.ts platform-infra sub2api validate", }, }; } async function status(config: UniDeskConfig, options: DisclosureOptions): Promise> { const sub2api = readSub2ApiConfig(); const result = await capture(config, g14K3sRoute, ["script"], statusScript(sub2api)); const parsed = parseJsonOutput(result.stdout); if (options.raw) { return { ok: result.exitCode === 0 && boolField(parsed, "ok", false), action: "platform-infra-sub2api-status", remote: compactCapture(result, { full: true }), parsed, }; } return { ok: result.exitCode === 0 && boolField(parsed, "ok", false), action: "platform-infra-sub2api-status", summary: parsed, remote: compactCapture(result, { full: options.full || result.exitCode !== 0 }), }; } async function validate(config: UniDeskConfig, options: DisclosureOptions): Promise> { const result = await capture(config, g14K3sRoute, ["script"], validateScript()); const parsed = parseJsonOutput(result.stdout); if (options.raw) { return { ok: result.exitCode === 0 && boolField(parsed, "ok", false), action: "platform-infra-sub2api-validate", remote: compactCapture(result, { full: true }), parsed, }; } return { ok: result.exitCode === 0 && boolField(parsed, "ok", false), action: "platform-infra-sub2api-validate", summary: parsed, remote: compactCapture(result, { full: options.full || result.exitCode !== 0 }), }; } function policyChecks(yaml: string): PolicyCheck[] { return [ { name: "no-ingress", ok: !/^\s*kind:\s*Ingress\s*$/mu.test(yaml), detail: "Sub2API must not be exposed through Kubernetes Ingress.", }, { name: "no-nodeport-or-loadbalancer", ok: !/^\s*type:\s*(NodePort|LoadBalancer)\s*$/mu.test(yaml), detail: "Services must remain ClusterIP/internal-only.", }, { name: "no-host-network", ok: !/^\s*hostNetwork:\s*true\s*$/mu.test(yaml), detail: "Pods must not join the host network.", }, { name: "no-host-port", ok: !/^\s*hostPort:\s*[0-9]+\s*$/mu.test(yaml), detail: "Pods must not expose host ports.", }, { name: "no-cpu-memory-resources", ok: !/^\s*(cpu|memory):\s*/mu.test(yaml), detail: "Issue #220 requires no Kubernetes CPU/memory requests or limits.", }, { name: "no-resource-quota-or-limit-range", ok: !/^\s*kind:\s*(ResourceQuota|LimitRange)\s*$/mu.test(yaml), detail: "The platform-infra namespace must not receive quota/default limit objects for this deployment.", }, { name: "expected-namespace", ok: new RegExp(`^\\s*name:\\s*${namespace}\\s*$`, "mu").test(yaml), detail: `Manifest declares namespace ${namespace}.`, }, { name: "allow-all-network-policy", ok: hasAllowAllNetworkPolicy(yaml), detail: `Manifest must include NetworkPolicy/allow-all in ${namespace} to keep kube-router from blocking Sub2API cross-pod traffic.`, }, ]; } function hasAllowAllNetworkPolicy(yaml: string): boolean { return yaml.split(/^---\s*$/mu).some((document) => { return /^\s*kind:\s*NetworkPolicy\s*$/mu.test(document) && /^\s*name:\s*allow-all\s*$/mu.test(document) && new RegExp(`^\\s*namespace:\\s*${namespace}\\s*$`, "mu").test(document) && /^\s*podSelector:\s*\{\}\s*$/mu.test(document) && /^\s*-\s*Ingress\s*$/mu.test(document) && /^\s*-\s*Egress\s*$/mu.test(document) && /^\s*ingress:\s*\n\s*-\s*\{\}\s*$/mu.test(document) && /^\s*egress:\s*\n\s*-\s*\{\}\s*$/mu.test(document); }); } function dryRunScript(yaml: string): string { const encoded = Buffer.from(yaml, "utf8").toString("base64"); return ` set -u tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT manifest="$tmp/sub2api.k8s.yaml" printf '%s' '${encoded}' | base64 -d > "$manifest" client_out="$tmp/client.out" client_err="$tmp/client.err" server_out="$tmp/server.out" server_err="$tmp/server.err" kubectl apply --dry-run=client -f "$manifest" >"$client_out" 2>"$client_err" client_rc=$? if kubectl get namespace ${namespace} >/dev/null 2>&1; then namespace_exists=true kubectl apply --server-side --dry-run=server --field-manager=${fieldManager} -f "$manifest" >"$server_out" 2>"$server_err" server_rc=$? else namespace_exists=false server_rc=0 printf '%s\\n' 'server dry-run skipped because namespace does not exist yet; first apply creates it before namespaced resources' >"$server_out" : >"$server_err" fi python3 - "$client_rc" "$server_rc" "$namespace_exists" "$client_out" "$client_err" "$server_out" "$server_err" <<'PY' import json import sys client_rc = int(sys.argv[1]) server_rc = int(sys.argv[2]) namespace_exists = sys.argv[3] == "true" paths = sys.argv[4:] def text(path): try: return open(path, encoding="utf-8").read() except FileNotFoundError: return "" payload = { "ok": client_rc == 0 and server_rc == 0, "namespace": "${namespace}", "namespaceExistsBeforeDryRun": namespace_exists, "clientDryRun": { "exitCode": client_rc, "stdout": text(paths[0])[-4000:], "stderr": text(paths[1])[-4000:], }, "serverDryRun": { "exitCode": server_rc, "disposition": "executed" if namespace_exists else "skipped-namespace-missing", "stdout": text(paths[2])[-4000:], "stderr": text(paths[3])[-4000:], }, } print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0 if payload["ok"] else 1) PY `; } function applyScript(yaml: string): string { const encoded = Buffer.from(yaml, "utf8").toString("base64"); return ` set -u tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT manifest="$tmp/sub2api.k8s.yaml" printf '%s' '${encoded}' | base64 -d > "$manifest" ns_out="$tmp/ns.out" ns_err="$tmp/ns.err" secret_out="$tmp/secret.out" secret_err="$tmp/secret.err" apply_out="$tmp/apply.out" apply_err="$tmp/apply.err" kubectl create namespace ${namespace} --dry-run=client -o yaml | kubectl apply --server-side --force-conflicts --field-manager=${fieldManager} -f - >"$ns_out" 2>"$ns_err" ns_rc=$? secret_action="unknown" secret_rc=0 if [ "$ns_rc" -eq 0 ]; then if kubectl -n ${namespace} get secret ${secretName} >/dev/null 2>&1; then secret_action="kept-existing" : >"$secret_out" : >"$secret_err" else rand_hex() { bytes="$1" if command -v openssl >/dev/null 2>&1; then openssl rand -hex "$bytes" else dd if=/dev/urandom bs="$bytes" count=1 2>/dev/null | od -An -tx1 | tr -d ' \\n' fi } kubectl -n ${namespace} create secret generic ${secretName} \\ --from-literal=POSTGRES_PASSWORD="$(rand_hex 32)" \\ --from-literal=ADMIN_PASSWORD="$(rand_hex 16)" \\ --from-literal=JWT_SECRET="$(rand_hex 32)" \\ --from-literal=TOTP_ENCRYPTION_KEY="$(rand_hex 32)" \\ --dry-run=client -o yaml | kubectl apply --server-side --force-conflicts --field-manager=${fieldManager} -f - >"$secret_out" 2>"$secret_err" secret_rc=$? secret_action="created" fi fi apply_rc=1 if [ "$ns_rc" -eq 0 ] && [ "$secret_rc" -eq 0 ]; then kubectl apply --server-side --force-conflicts --field-manager=${fieldManager} -f "$manifest" >"$apply_out" 2>"$apply_err" apply_rc=$? else : >"$apply_out" printf '%s\\n' 'skipped because namespace or secret step failed' >"$apply_err" fi python3 - "$ns_rc" "$secret_rc" "$apply_rc" "$secret_action" "$ns_out" "$ns_err" "$secret_out" "$secret_err" "$apply_out" "$apply_err" <<'PY' import json import sys ns_rc = int(sys.argv[1]) secret_rc = int(sys.argv[2]) apply_rc = int(sys.argv[3]) secret_action = sys.argv[4] paths = sys.argv[5:] def text(path): try: return open(path, encoding="utf-8").read() except FileNotFoundError: return "" payload = { "ok": ns_rc == 0 and secret_rc == 0 and apply_rc == 0, "namespace": "${namespace}", "secret": { "name": "${secretName}", "action": secret_action, "requiredKeys": ${JSON.stringify(requiredSecretKeys)}, "valuesPrinted": False, }, "steps": { "namespace": {"exitCode": ns_rc, "stdout": text(paths[0])[-4000:], "stderr": text(paths[1])[-4000:]}, "secret": {"exitCode": secret_rc, "stdout": text(paths[2])[-4000:], "stderr": text(paths[3])[-4000:]}, "apply": {"exitCode": apply_rc, "stdout": text(paths[4])[-8000:], "stderr": text(paths[5])[-4000:]}, }, } print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0 if payload["ok"] else 1) PY `; } function statusScript(sub2api: Sub2ApiConfig): string { const expectedImage = imageRef(sub2api); const expectedUrlAllowlist = sub2api.security.urlAllowlist; return ` set -u tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT capture_json() { name="$1" shift "$@" -o json >"$tmp/$name.json" 2>"$tmp/$name.err" rc=$? printf '%s' "$rc" >"$tmp/$name.rc" } capture_json ns kubectl get namespace ${namespace} capture_json deployments kubectl -n ${namespace} get deployments -l app.kubernetes.io/part-of=platform-infra capture_json statefulsets kubectl -n ${namespace} get statefulsets -l app.kubernetes.io/part-of=platform-infra capture_json pods kubectl -n ${namespace} get pods -l app.kubernetes.io/part-of=platform-infra capture_json services kubectl -n ${namespace} get services -l app.kubernetes.io/part-of=platform-infra capture_json pvc kubectl -n ${namespace} get pvc -l app.kubernetes.io/part-of=platform-infra capture_json secrets kubectl -n ${namespace} get secret ${secretName} capture_json configmap kubectl -n ${namespace} get configmap sub2api-config capture_json networkpolicies kubectl -n ${namespace} get networkpolicy capture_json ingresses kubectl -n ${namespace} get ingress capture_json quotas kubectl -n ${namespace} get resourcequota capture_json limitranges kubectl -n ${namespace} get limitrange pod_name="$(kubectl -n ${namespace} get pod -l app.kubernetes.io/name=${serviceName},app.kubernetes.io/component=app -o jsonpath='{.items[0].metadata.name}' 2>"$tmp/pod-name.err" || true)" printf '%s' "$pod_name" >"$tmp/pod-name.txt" if [ -n "$pod_name" ]; then kubectl -n ${namespace} exec "$pod_name" -- sh -c 'printf "SECURITY_URL_ALLOWLIST_ENABLED=%s\\n" "$SECURITY_URL_ALLOWLIST_ENABLED"; printf "SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=%s\\n" "$SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP"; printf "SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS=%s\\n" "$SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS"; printf "SECURITY_URL_ALLOWLIST_UPSTREAM_HOSTS=%s\\n" "$SECURITY_URL_ALLOWLIST_UPSTREAM_HOSTS"' >"$tmp/pod-env.out" 2>"$tmp/pod-env.err" printf '%s' "$?" >"$tmp/pod-env.rc" else : >"$tmp/pod-env.out" printf '%s\\n' 'sub2api app pod not found' >"$tmp/pod-env.err" printf '%s' "1" >"$tmp/pod-env.rc" fi python3 - "$tmp" <<'PY' import json import os import sys tmp = sys.argv[1] def rc(name): try: return int(open(os.path.join(tmp, f"{name}.rc"), encoding="utf-8").read() or "1") except FileNotFoundError: return 1 def load(name): path = os.path.join(tmp, f"{name}.json") if not os.path.exists(path): return None try: return json.load(open(path, encoding="utf-8")) except json.JSONDecodeError: return None def items(name): data = load(name) if not isinstance(data, dict): return [] return data.get("items") or [] def text(name): path = os.path.join(tmp, name) try: return open(path, encoding="utf-8", errors="replace").read() except FileNotFoundError: return "" def deployment_summary(item): spec = item.get("spec") or {} status = item.get("status") or {} desired = spec.get("replicas", 1) available = status.get("availableReplicas", 0) init_containers = ((spec.get("template") or {}).get("spec") or {}).get("initContainers", []) containers = ((spec.get("template") or {}).get("spec") or {}).get("containers", []) return { "name": item["metadata"]["name"], "desired": desired, "readyReplicas": status.get("readyReplicas", 0), "availableReplicas": available, "updatedReplicas": status.get("updatedReplicas", 0), "ready": available >= desired, "images": [c.get("image") for c in containers], "initImages": [c.get("image") for c in init_containers], } def statefulset_summary(item): spec = item.get("spec") or {} status = item.get("status") or {} desired = spec.get("replicas", 1) ready = status.get("readyReplicas", 0) return { "name": item["metadata"]["name"], "desired": desired, "readyReplicas": ready, "currentReplicas": status.get("currentReplicas", 0), "updatedReplicas": status.get("updatedReplicas", 0), "ready": ready >= desired, "images": [c.get("image") for c in ((spec.get("template") or {}).get("spec") or {}).get("containers", [])], } def pod_summary(item): status = item.get("status") or {} container_statuses = status.get("containerStatuses") or [] return { "name": item["metadata"]["name"], "phase": status.get("phase"), "ready": all((cs.get("ready") is True) for cs in container_statuses) if container_statuses else False, "restarts": sum(int(cs.get("restartCount") or 0) for cs in container_statuses), "nodeName": (item.get("spec") or {}).get("nodeName"), "containers": [ { "name": cs.get("name"), "ready": cs.get("ready"), "restartCount": cs.get("restartCount"), "image": cs.get("image"), "state": list((cs.get("state") or {}).keys()), "stateDetail": cs.get("state") or {}, } for cs in container_statuses ], "initContainers": [ { "name": cs.get("name"), "ready": cs.get("ready"), "restartCount": cs.get("restartCount"), "image": cs.get("image"), "state": list((cs.get("state") or {}).keys()), "stateDetail": cs.get("state") or {}, } for cs in (status.get("initContainerStatuses") or []) ], "conditions": [ { "type": condition.get("type"), "status": condition.get("status"), "reason": condition.get("reason"), "message": condition.get("message"), } for condition in status.get("conditions", []) ], } def service_summary(item): spec = item.get("spec") or {} return { "name": item["metadata"]["name"], "type": spec.get("type", "ClusterIP"), "clusterIP": spec.get("clusterIP"), "ports": [ { "name": p.get("name"), "port": p.get("port"), "targetPort": p.get("targetPort"), "nodePort": p.get("nodePort"), } for p in spec.get("ports", []) ], } def pvc_summary(item): spec = item.get("spec") or {} status = item.get("status") or {} req = (spec.get("resources") or {}).get("requests") or {} return { "name": item["metadata"]["name"], "phase": status.get("phase"), "storageClassName": spec.get("storageClassName"), "requestedStorage": req.get("storage"), } def network_policy_summary(item): spec = item.get("spec") or {} return { "name": item["metadata"]["name"], "podSelector": spec.get("podSelector"), "policyTypes": spec.get("policyTypes") or [], "ingress": spec.get("ingress") or [], "egress": spec.get("egress") or [], } def is_allow_all_network_policy(item): spec = item.get("spec") or {} return ( item.get("metadata", {}).get("name") == "allow-all" and spec.get("podSelector") == {} and set(spec.get("policyTypes") or []) == {"Ingress", "Egress"} and spec.get("ingress") == [{}] and spec.get("egress") == [{}] ) def resource_findings(kind, collection): findings = [] for item in collection: spec = item.get("spec") or {} template_spec = ((spec.get("template") or {}).get("spec") or {}) if template_spec.get("hostNetwork") is True: findings.append({"kind": kind, "name": item["metadata"]["name"], "field": "hostNetwork"}) all_containers = [(container, "containers") for container in template_spec.get("containers", [])] + [(container, "initContainers") for container in template_spec.get("initContainers", [])] for container, container_group in all_containers: resources = container.get("resources") or {} if resources.get("requests"): findings.append({"kind": kind, "name": item["metadata"]["name"], "container": container.get("name"), "containerGroup": container_group, "field": "resources.requests"}) if resources.get("limits"): findings.append({"kind": kind, "name": item["metadata"]["name"], "container": container.get("name"), "containerGroup": container_group, "field": "resources.limits"}) for port in container.get("ports", []): if "hostPort" in port: findings.append({"kind": kind, "name": item["metadata"]["name"], "container": container.get("name"), "containerGroup": container_group, "field": "hostPort", "value": port.get("hostPort")}) return findings deployments = items("deployments") statefulsets = items("statefulsets") services = items("services") pods = items("pods") pvcs = items("pvc") networkpolicies = items("networkpolicies") secret = load("secrets") configmap = load("configmap") configmap_data = (configmap or {}).get("data") or {} secret_keys = sorted(((secret or {}).get("data") or {}).keys()) missing_secret_keys = [key for key in ${JSON.stringify(requiredSecretKeys)} if key not in secret_keys] service_violations = [] for svc in services: spec = svc.get("spec") or {} if spec.get("type", "ClusterIP") != "ClusterIP": service_violations.append({"name": svc["metadata"]["name"], "type": spec.get("type")}) for port in spec.get("ports", []): if "nodePort" in port: service_violations.append({"name": svc["metadata"]["name"], "nodePort": port.get("nodePort")}) resource_violations = resource_findings("Deployment", deployments) + resource_findings("StatefulSet", statefulsets) expected_image = "${expectedImage}" expected_url_allowlist = json.loads(${JSON.stringify(JSON.stringify(expectedUrlAllowlist))}) sub2api_deployment = next((deployment_summary(item) for item in deployments if item["metadata"]["name"] == "${serviceName}"), None) image_aligned = sub2api_deployment is not None and expected_image in sub2api_deployment.get("images", []) url_allowlist_runtime = { "enabled": configmap_data.get("SECURITY_URL_ALLOWLIST_ENABLED"), "allowInsecureHttp": configmap_data.get("SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP"), "allowPrivateHosts": configmap_data.get("SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS"), "upstreamHosts": configmap_data.get("SECURITY_URL_ALLOWLIST_UPSTREAM_HOSTS"), } pod_env = {} for line in text("pod-env.out").splitlines(): if "=" not in line: continue key, value = line.split("=", 1) pod_env[key] = value url_allowlist_pod_env = { "enabled": pod_env.get("SECURITY_URL_ALLOWLIST_ENABLED"), "allowInsecureHttp": pod_env.get("SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP"), "allowPrivateHosts": pod_env.get("SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS"), "upstreamHosts": pod_env.get("SECURITY_URL_ALLOWLIST_UPSTREAM_HOSTS"), } expected_url_allowlist_strings = { "enabled": str(expected_url_allowlist.get("enabled")).lower(), "allowInsecureHttp": str(expected_url_allowlist.get("allowInsecureHttp")).lower(), "allowPrivateHosts": str(expected_url_allowlist.get("allowPrivateHosts")).lower(), "upstreamHosts": ",".join(expected_url_allowlist.get("upstreamHosts") or []), } url_allowlist_configmap_aligned = url_allowlist_runtime == expected_url_allowlist_strings url_allowlist_pod_env_aligned = rc("pod-env") == 0 and url_allowlist_pod_env == expected_url_allowlist_strings url_allowlist_aligned = url_allowlist_configmap_aligned and url_allowlist_pod_env_aligned allow_all_network_policy = next((item for item in networkpolicies if item.get("metadata", {}).get("name") == "allow-all"), None) network_policy = { "requiredName": "allow-all", "exists": allow_all_network_policy is not None, "ok": allow_all_network_policy is not None and is_allow_all_network_policy(allow_all_network_policy), "policies": [network_policy_summary(item) for item in networkpolicies], } boundary = { "internalOnly": len(service_violations) == 0 and len(items("ingresses")) == 0, "serviceViolations": service_violations, "ingressCount": len(items("ingresses")), "resourceQuotaCount": len(items("quotas")), "limitRangeCount": len(items("limitranges")), "resourceViolations": resource_violations, } workload_ready = all(d["ready"] for d in map(deployment_summary, deployments)) and all(s["ready"] for s in map(statefulset_summary, statefulsets)) payload = { "ok": rc("ns") == 0 and workload_ready and image_aligned and url_allowlist_aligned and network_policy["ok"] and boundary["internalOnly"] and len(resource_violations) == 0 and boundary["resourceQuotaCount"] == 0 and boundary["limitRangeCount"] == 0 and len(missing_secret_keys) == 0, "namespace": "${namespace}", "namespaceExists": rc("ns") == 0, "deployments": [deployment_summary(item) for item in deployments], "statefulsets": [statefulset_summary(item) for item in statefulsets], "pods": [pod_summary(item) for item in pods], "services": [service_summary(item) for item in services], "pvcs": [pvc_summary(item) for item in pvcs], "networkPolicy": network_policy, "secret": { "name": "${secretName}", "exists": rc("secrets") == 0, "requiredKeys": ${JSON.stringify(requiredSecretKeys)}, "missingKeys": missing_secret_keys, "valuesPrinted": False, }, "imageControl": { "desiredImage": expected_image, "configPath": "config/platform-infra/sub2api.yaml", "aligned": image_aligned, "runningImages": sub2api_deployment.get("images", []) if sub2api_deployment else [], }, "security": { "urlAllowlist": { "configPath": "config/platform-infra/sub2api.yaml", "expected": expected_url_allowlist, "runtime": url_allowlist_runtime, "podEnv": url_allowlist_pod_env, "aligned": url_allowlist_aligned, "configMapAligned": url_allowlist_configmap_aligned, "podEnvAligned": url_allowlist_pod_env_aligned, "configMapExists": rc("configmap") == 0, "podEnvProbe": { "podName": text("pod-name.txt"), "exitCode": rc("pod-env"), "stderr": text("pod-env.err")[-1000:], }, } }, "boundary": boundary, "serviceDns": "${serviceName}.${namespace}.svc.cluster.local:8080", "next": { "apply": "bun scripts/cli.ts platform-infra sub2api apply --confirm", "validate": "bun scripts/cli.ts platform-infra sub2api validate", }, } print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0 if payload["ok"] else 1) PY `; } function validateScript(): string { return ` set -u tmp="$(mktemp -d)" probe_suffix="$(date +%s)-$$" pg_probe="unidesk-sub2api-netcheck-pg-$probe_suffix" redis_probe="unidesk-sub2api-netcheck-redis-$probe_suffix" cleanup() { kubectl -n ${namespace} delete pod "$pg_probe" "$redis_probe" --ignore-not-found=true --wait=false >/dev/null 2>&1 || true rm -rf "$tmp" } trap cleanup EXIT kubectl get --raw /api/v1/namespaces/${namespace}/services/${serviceName}:8080/proxy/health >"$tmp/health.body" 2>"$tmp/health.err" health_rc=$? kubectl get --raw /api/v1/namespaces/${namespace}/services/${serviceName}:8080/proxy/ >"$tmp/root.body" 2>"$tmp/root.err" root_rc=$? kubectl -n ${namespace} get networkpolicy allow-all -o json >"$tmp/network-policy.json" 2>"$tmp/network-policy.err" network_policy_rc=$? pg_pod="$(kubectl -n ${namespace} get pod -l app.kubernetes.io/name=sub2api-postgres -o jsonpath='{.items[0].metadata.name}' 2>"$tmp/pg-pod.err")" redis_pod="$(kubectl -n ${namespace} get pod -l app.kubernetes.io/name=sub2api-redis -o jsonpath='{.items[0].metadata.name}' 2>"$tmp/redis-pod.err")" if [ -n "$pg_pod" ]; then kubectl -n ${namespace} exec "$pg_pod" -- pg_isready -U sub2api -d sub2api -h 127.0.0.1 >"$tmp/pg.out" 2>"$tmp/pg.err" pg_rc=$? else pg_rc=1 printf '%s\\n' 'sub2api postgres pod not found' >"$tmp/pg.err" fi if [ -n "$redis_pod" ]; then kubectl -n ${namespace} exec "$redis_pod" -- redis-cli ping >"$tmp/redis.out" 2>"$tmp/redis.err" redis_rc=$? else redis_rc=1 printf '%s\\n' 'sub2api redis pod not found' >"$tmp/redis.err" fi if ! command -v timeout >/dev/null 2>&1; then printf '%s\\n' 'timeout command is required for bounded cross-pod probes' >"$tmp/pg-cross.err" printf '%s\\n' 'timeout command is required for bounded cross-pod probes' >"$tmp/redis-cross.err" : >"$tmp/pg-cross.out" : >"$tmp/redis-cross.out" pg_cross_rc=127 redis_cross_rc=127 else timeout 35s kubectl -n ${namespace} run "$pg_probe" --restart=Never --rm -i --image=postgres:18-alpine --image-pull-policy=IfNotPresent --command -- pg_isready -h sub2api-postgres -U sub2api -d sub2api -t 5 >"$tmp/pg-cross.out" 2>"$tmp/pg-cross.err" pg_cross_rc=$? timeout 35s kubectl -n ${namespace} run "$redis_probe" --restart=Never --rm -i --image=redis:8-alpine --image-pull-policy=IfNotPresent --command -- redis-cli -h sub2api-redis -p 6379 ping >"$tmp/redis-cross.out" 2>"$tmp/redis-cross.err" redis_cross_rc=$? fi python3 - "$tmp" "$health_rc" "$root_rc" "$pg_rc" "$redis_rc" "$network_policy_rc" "$pg_cross_rc" "$redis_cross_rc" <<'PY' import json import os import sys tmp = sys.argv[1] health_rc, root_rc, pg_rc, redis_rc, network_policy_rc, pg_cross_rc, redis_cross_rc = [int(value) for value in sys.argv[2:]] def text(name, limit=4000): path = os.path.join(tmp, name) try: data = open(path, encoding="utf-8", errors="replace").read() except FileNotFoundError: return "" return data[-limit:] def json_file(name): path = os.path.join(tmp, name) try: return json.load(open(path, encoding="utf-8")) except (FileNotFoundError, json.JSONDecodeError): return None def is_allow_all_network_policy(item): if not isinstance(item, dict): return False spec = item.get("spec") or {} return ( item.get("metadata", {}).get("name") == "allow-all" and spec.get("podSelector") == {} and set(spec.get("policyTypes") or []) == {"Ingress", "Egress"} and spec.get("ingress") == [{}] and spec.get("egress") == [{}] ) health_body = text("health.body", 2000) root_body = text("root.body", 2000) network_policy_obj = json_file("network-policy.json") network_policy_ok = network_policy_rc == 0 and is_allow_all_network_policy(network_policy_obj) payload = { "ok": health_rc == 0 and root_rc == 0 and pg_rc == 0 and redis_rc == 0 and network_policy_ok and pg_cross_rc == 0 and redis_cross_rc == 0, "namespace": "${namespace}", "serviceDns": "${serviceName}.${namespace}.svc.cluster.local:8080", "checks": { "allowAllNetworkPolicy": { "exitCode": network_policy_rc, "ok": network_policy_ok, "method": "kubectl -n ${namespace} get networkpolicy allow-all -o json", "policyTypes": ((network_policy_obj or {}).get("spec") or {}).get("policyTypes") if isinstance(network_policy_obj, dict) else None, "podSelector": ((network_policy_obj or {}).get("spec") or {}).get("podSelector") if isinstance(network_policy_obj, dict) else None, "ingress": ((network_policy_obj or {}).get("spec") or {}).get("ingress") if isinstance(network_policy_obj, dict) else None, "egress": ((network_policy_obj or {}).get("spec") or {}).get("egress") if isinstance(network_policy_obj, dict) else None, "stderr": text("network-policy.err", 2000), }, "sub2apiHealthViaKubernetesServiceProxy": { "exitCode": health_rc, "method": "kubectl get --raw /api/v1/namespaces/${namespace}/services/${serviceName}:8080/proxy/health", "bodyPreview": health_body, "stderr": text("health.err", 2000), }, "sub2apiRootViaKubernetesServiceProxy": { "exitCode": root_rc, "method": "kubectl get --raw /api/v1/namespaces/${namespace}/services/${serviceName}:8080/proxy/", "bodyBytes": len(root_body.encode("utf-8")), "bodyPreview": root_body[:400], "stderr": text("root.err", 2000), }, "postgresPgIsReady": { "exitCode": pg_rc, "stdout": text("pg.out", 2000), "stderr": text("pg.err", 2000), }, "postgresCrossPodPgIsReady": { "exitCode": pg_cross_rc, "method": "temporary postgres:18-alpine pod pg_isready -h sub2api-postgres -U sub2api -d sub2api -t 5, bounded by outer timeout", "stdout": text("pg-cross.out", 2000), "stderr": text("pg-cross.err", 2000), }, "redisPing": { "exitCode": redis_rc, "stdout": text("redis.out", 2000), "stderr": text("redis.err", 2000), }, "redisCrossPodPing": { "exitCode": redis_cross_rc, "method": "temporary redis:8-alpine pod redis-cli -h sub2api-redis -p 6379 ping, bounded by outer timeout", "stdout": text("redis-cross.out", 2000), "stderr": text("redis-cross.err", 2000), }, }, } print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0 if payload["ok"] else 1) PY `; } async function capture(config: UniDeskConfig, target: string, args: string[], input?: string): Promise { return await runSshCommandCapture(config, target, args, input); } function parseJsonOutput(stdout: string): Record | null { const trimmed = stdout.trim(); if (trimmed.length === 0) return null; const start = trimmed.indexOf("{"); const end = trimmed.lastIndexOf("}"); if (start === -1 || end === -1 || end <= start) return null; try { const parsed = JSON.parse(trimmed.slice(start, end + 1)) as unknown; return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed as Record : null; } catch { return null; } } function boolField(value: Record | null, key: string, defaultValue: boolean): boolean { if (value === null) return defaultValue; const field = value[key]; return typeof field === "boolean" ? field : defaultValue; } function compactCapture(result: SshCaptureResult, options: { full?: boolean } = {}): Record { const full = options.full ?? false; return { exitCode: result.exitCode, stdoutBytes: Buffer.byteLength(result.stdout, "utf8"), stderrBytes: Buffer.byteLength(result.stderr, "utf8"), stdoutTail: full || result.exitCode !== 0 ? result.stdout.slice(-8000) : "", stderrTail: full || result.exitCode !== 0 ? result.stderr.slice(-4000) : "", }; }