From 440e3a931dc7d7fac5770735394d7adc5dcb7a35 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 11 Jun 2026 20:09:09 +0000 Subject: [PATCH] fix: support HWLAB bootstrap admin secret --- scripts/src/hwlab-node.ts | 186 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 178 insertions(+), 8 deletions(-) diff --git a/scripts/src/hwlab-node.ts b/scripts/src/hwlab-node.ts index ec8001e8..b26f022e 100644 --- a/scripts/src/hwlab-node.ts +++ b/scripts/src/hwlab-node.ts @@ -6,7 +6,7 @@ import { runHwlabG14Command } from "./hwlab-g14"; import { hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane } from "./hwlab-node-lanes"; type SecretAction = "status" | "ensure" | "cleanup-owned-postgres" | "cleanup-obsolete"; -type SecretPreset = "openfga" | "master-server-admin-api-key" | "code-agent-provider" | "cloud-api-db" | "owned-postgres-cleanup" | "obsolete-secret-cleanup"; +type SecretPreset = "openfga" | "master-server-admin-api-key" | "bootstrap-admin" | "code-agent-provider" | "cloud-api-db" | "owned-postgres-cleanup" | "obsolete-secret-cleanup"; type DelegatedNodeDomain = "control-plane" | "git-mirror"; interface NodeSecretOptions { @@ -35,6 +35,10 @@ interface RuntimeSecretSpec { openFgaDbUser: string; openFgaDbHost: string; masterAdminApiKeySecret: string; + bootstrapAdminSecret: string; + bootstrapAdminPasswordHashKey: string; + bootstrapAdminSourceNamespace: string; + bootstrapAdminSourceSecret: string; cloudApiDbSecret: string; cloudApiDbKey: string; cloudApiDbName: string; @@ -52,6 +56,9 @@ interface RuntimeSecretSpec { const MASTER_ADMIN_API_KEY_ENV = "/root/.config/hwlab-v02/master-server-admin-api-key.env"; const MASTER_ADMIN_API_KEY_KEY = "api-key"; +const BOOTSTRAP_ADMIN_PASSWORD_HASH_KEY = "password-hash"; +const BOOTSTRAP_ADMIN_SOURCE_NAMESPACE = "hwlab-v02"; +const BOOTSTRAP_ADMIN_SOURCE_SECRET = "hwlab-v02-bootstrap-admin"; const OPENFGA_AUTHN_KEY = "authn-preshared-key"; const OPENFGA_DATASTORE_URI_KEY = "datastore-uri"; const OPENFGA_POSTGRES_PASSWORD_KEY = "postgres-password"; @@ -89,6 +96,8 @@ export function hwlabNodeHelp(): Record { "bun scripts/cli.ts hwlab nodes git-mirror status --node G14 --lane v03", "bun scripts/cli.ts hwlab nodes secret status --node G14 --lane v03 --name hwlab-v03-openfga", "bun scripts/cli.ts hwlab nodes secret ensure --node G14 --lane v03 --name hwlab-v03-master-server-admin-api-key --confirm", + "bun scripts/cli.ts hwlab nodes secret status --node G14 --lane v03 --name hwlab-v03-bootstrap-admin", + "bun scripts/cli.ts hwlab nodes secret ensure --node G14 --lane v03 --name hwlab-v03-bootstrap-admin --confirm", "bun scripts/cli.ts hwlab nodes secret status --node G14 --lane v03 --name hwlab-cloud-api-v03-db", "bun scripts/cli.ts hwlab nodes secret cleanup-owned-postgres --node G14 --lane v03 --dry-run", "bun scripts/cli.ts hwlab nodes secret cleanup-owned-postgres --node G14 --lane v03 --confirm", @@ -215,7 +224,7 @@ function rewriteDelegatedNodeString(value: string, scoped: ReturnType { ? spec.platformDb ? platformDbSecretStatusScript(options, spec) : openFgaSecretScript(options, spec) : options.preset === "master-server-admin-api-key" ? masterAdminApiKeySecretScript(options, spec) - : options.preset === "cloud-api-db" - ? spec.platformDb ? platformDbSecretStatusScript(options, spec) : cloudApiDbSecretScript(options, spec) - : options.preset === "owned-postgres-cleanup" - ? ownedPostgresCleanupScript(options, spec) - : codeAgentProviderSecretScript(options, spec); + : options.preset === "bootstrap-admin" + ? bootstrapAdminSecretScript(options, spec) + : options.preset === "cloud-api-db" + ? spec.platformDb ? platformDbSecretStatusScript(options, spec) : cloudApiDbSecretScript(options, spec) + : options.preset === "owned-postgres-cleanup" + ? ownedPostgresCleanupScript(options, spec) + : codeAgentProviderSecretScript(options, spec); const result = runTransScript(options.node, script, input, options.timeoutSeconds); const status = secretStatusFromText(statusText(result), result.exitCode === 0, result.exitCode, result.stderr, spec); const dryRunOk = options.action === "ensure" && options.dryRun && result.exitCode === 0; @@ -1282,6 +1311,107 @@ function masterAdminApiKeySecretScript(options: NodeSecretOptions, spec: Runtime ].join("\n"); } +function bootstrapAdminSecretScript(options: NodeSecretOptions, spec: RuntimeSecretSpec): string { + return [ + "set +e", + `namespace=${shellQuote(spec.namespace)}`, + `name=${shellQuote(spec.bootstrapAdminSecret)}`, + `source_namespace=${shellQuote(spec.bootstrapAdminSourceNamespace)}`, + `source_name=${shellQuote(spec.bootstrapAdminSourceSecret)}`, + `password_hash_key=${shellQuote(spec.bootstrapAdminPasswordHashKey)}`, + `cloud_api_deployment=${shellQuote(spec.cloudApiDeployment)}`, + `action_request=${shellQuote(options.action)}`, + `dry_run=${shellQuote(options.dryRun ? "true" : "false")}`, + `field_manager=${shellQuote(spec.fieldManager)}`, + "preset=bootstrap-admin", + "secret_exists_flag() { kubectl -n \"$1\" get secret \"$2\" >/dev/null 2>&1 && printf yes || printf no; }", + "secret_b64_key() { kubectl -n \"$1\" get secret \"$2\" -o \"go-template={{ index .data \\\"$3\\\" }}\" 2>/dev/null || true; }", + "decoded_length() { if [ -n \"$1\" ]; then printf '%s' \"$1\" | base64 -d 2>/dev/null | wc -c | tr -d ' '; else printf '0'; fi; }", + "before_exists=$(secret_exists_flag \"$namespace\" \"$name\")", + "before_hash_b64=$(secret_b64_key \"$namespace\" \"$name\" \"$password_hash_key\")", + "source_exists=$(secret_exists_flag \"$source_namespace\" \"$source_name\")", + "source_hash_b64=$(secret_b64_key \"$source_namespace\" \"$source_name\" \"$password_hash_key\")", + "before_hash_present=$([ -n \"$before_hash_b64\" ] && printf yes || printf no)", + "source_hash_present=$([ -n \"$source_hash_b64\" ] && printf yes || printf no)", + "before_hash_bytes=$(decoded_length \"$before_hash_b64\")", + "source_hash_bytes=$(decoded_length \"$source_hash_b64\")", + "action=observed", + "mutation=false", + "apply_exit=", + "rollout_restart_exit=", + "rollout_status_exit=", + "if [ \"$action_request\" = ensure ]; then", + " missing_target=false", + " [ \"$before_exists\" = yes ] && [ \"$before_hash_bytes\" -gt 0 ] || missing_target=true", + " missing_source=false", + " [ \"$source_exists\" = yes ] && [ \"$source_hash_bytes\" -gt 0 ] || missing_source=true", + " if [ \"$missing_source\" = true ]; then", + " action=source-missing-password-hash", + " apply_exit=44", + " elif [ \"$dry_run\" = true ]; then", + " if [ \"$missing_target\" = true ]; then action=would-copy-from-source; else action=kept; fi", + " elif [ \"$missing_target\" = false ]; then", + " action=kept", + " else", + " cat </tmp/hwlab-bootstrap-admin-rollout-restart.out 2>/tmp/hwlab-bootstrap-admin-rollout-restart.err", + " rollout_restart_exit=$?", + " if [ \"$rollout_restart_exit\" -eq 0 ]; then", + " kubectl -n \"$namespace\" rollout status \"deployment/$cloud_api_deployment\" --timeout=180s >/tmp/hwlab-bootstrap-admin-rollout-status.out 2>/tmp/hwlab-bootstrap-admin-rollout-status.err", + " rollout_status_exit=$?", + " fi", + " if [ -n \"$rollout_restart_exit\" ] && [ \"$rollout_restart_exit\" != 0 ]; then action=rollout-restart-failed", + " elif [ -n \"$rollout_status_exit\" ] && [ \"$rollout_status_exit\" != 0 ]; then action=rollout-status-failed", + " else action=copied-from-source; mutation=true; fi", + " else action=apply-failed; fi", + " fi", + "fi", + "after_exists=$(secret_exists_flag \"$namespace\" \"$name\")", + "after_hash_b64=$(secret_b64_key \"$namespace\" \"$name\" \"$password_hash_key\")", + "after_hash_present=$([ -n \"$after_hash_b64\" ] && printf yes || printf no)", + "after_hash_bytes=$(decoded_length \"$after_hash_b64\")", + "printf 'namespace\\t%s\\n' \"$namespace\"", + "printf 'secret\\t%s\\n' \"$name\"", + "printf 'key\\t%s\\n' \"$password_hash_key\"", + "printf 'preset\\t%s\\n' \"$preset\"", + "printf 'sourceNamespace\\t%s\\n' \"$source_namespace\"", + "printf 'sourceSecret\\t%s\\n' \"$source_name\"", + "printf 'action\\t%s\\n' \"$action\"", + "printf 'dryRun\\t%s\\n' \"$dry_run\"", + "printf 'mutation\\t%s\\n' \"$mutation\"", + "printf 'beforeExists\\t%s\\n' \"$before_exists\"", + "printf 'beforePasswordHashPresent\\t%s\\n' \"$before_hash_present\"", + "printf 'beforePasswordHashBytes\\t%s\\n' \"$before_hash_bytes\"", + "printf 'sourceExists\\t%s\\n' \"$source_exists\"", + "printf 'sourcePasswordHashPresent\\t%s\\n' \"$source_hash_present\"", + "printf 'sourcePasswordHashBytes\\t%s\\n' \"$source_hash_bytes\"", + "printf 'afterExists\\t%s\\n' \"$after_exists\"", + "printf 'afterPasswordHashPresent\\t%s\\n' \"$after_hash_present\"", + "printf 'afterPasswordHashBytes\\t%s\\n' \"$after_hash_bytes\"", + "printf 'cloudApiDeployment\\t%s\\n' \"$cloud_api_deployment\"", + "printf 'applyExitCode\\t%s\\n' \"$apply_exit\"", + "printf 'rolloutRestartExitCode\\t%s\\n' \"$rollout_restart_exit\"", + "printf 'rolloutStatusExitCode\\t%s\\n' \"$rollout_status_exit\"", + "if [ -n \"$apply_exit\" ] && [ \"$apply_exit\" != 0 ]; then exit \"$apply_exit\"; fi", + "if [ -n \"$rollout_restart_exit\" ] && [ \"$rollout_restart_exit\" != 0 ]; then exit \"$rollout_restart_exit\"; fi", + "if [ -n \"$rollout_status_exit\" ] && [ \"$rollout_status_exit\" != 0 ]; then exit \"$rollout_status_exit\"; fi", + ].join("\n"); +} + function codeAgentProviderSecretScript(options: NodeSecretOptions, spec: RuntimeSecretSpec): string { return [ "set +e", @@ -1633,6 +1763,46 @@ function secretStatusFromText(text: string, commandOk: boolean, exitCode: number summary: healthy ? `${fields.secret || spec.masterAdminApiKeySecret}/${MASTER_ADMIN_API_KEY_KEY} exists` : `${fields.secret || spec.masterAdminApiKeySecret}/${MASTER_ADMIN_API_KEY_KEY} missing`, }; } + if (fields.preset === "bootstrap-admin") { + const beforeHashBytes = numericField(fields.beforePasswordHashBytes); + const sourceHashBytes = numericField(fields.sourcePasswordHashBytes); + const afterHashBytes = numericField(fields.afterPasswordHashBytes); + const healthy = fields.afterExists === "yes" && + fields.afterPasswordHashPresent === "yes" && + typeof afterHashBytes === "number" && afterHashBytes > 0; + return { + ok: commandOk && healthy, + namespace: fields.namespace || spec.namespace, + secret: fields.secret || spec.bootstrapAdminSecret, + key: fields.key || spec.bootstrapAdminPasswordHashKey, + preset: "bootstrap-admin", + source: { + namespace: fields.sourceNamespace || spec.bootstrapAdminSourceNamespace, + secret: fields.sourceSecret || spec.bootstrapAdminSourceSecret, + exists: fields.sourceExists === "yes", + passwordHash: { keyPresent: fields.sourcePasswordHashPresent === "yes", valueBytes: sourceHashBytes }, + }, + action: fields.action || null, + dryRun: fields.dryRun === "true", + mutation: fields.mutation === "true", + before: { + exists: fields.beforeExists === "yes", + passwordHash: { keyPresent: fields.beforePasswordHashPresent === "yes", valueBytes: beforeHashBytes }, + }, + after: { + exists: fields.afterExists === "yes", + passwordHash: { keyPresent: fields.afterPasswordHashPresent === "yes", valueBytes: afterHashBytes }, + }, + cloudApiDeployment: fields.cloudApiDeployment || spec.cloudApiDeployment, + applyExitCode: numericField(fields.applyExitCode), + rolloutRestartExitCode: numericField(fields.rolloutRestartExitCode), + rolloutStatusExitCode: numericField(fields.rolloutStatusExitCode), + exitCode, + stderr: commandOk ? "" : stderr.trim().slice(0, 2000), + valuesRedacted: true, + summary: healthy ? `${fields.secret || spec.bootstrapAdminSecret}/${spec.bootstrapAdminPasswordHashKey} exists` : `${fields.secret || spec.bootstrapAdminSecret}/${spec.bootstrapAdminPasswordHashKey} missing`, + }; + } if (fields.preset === "code-agent-provider") { const beforeOpenaiBytes = numericField(fields.beforeOpenaiBytes); const beforeOpencodeBytes = numericField(fields.beforeOpencodeBytes);