import { dirname } from "node:path"; import { buildInstallationContract, type InstallationRole, type InstallationSuffix, type InstallationVariable, } from "./contracts.js"; import { resolveRuntimeBindings, type RuntimeBindings } from "./bindings.js"; import { DWH_TRANSPORTS, validateWorkspaceDescriptor, type DwhTransport, type WorkspaceDescriptor, } from "./schema.js"; import { WorkspaceSecretStore, type WorkspaceSecretMaterialization, } from "./secret-store.js"; export type WorkspaceSecretInput = "password" | "textarea"; export interface WorkspaceSecretRequirement { id: string; variable: string; connector: "dwh" | "evidence"; label: string; description: string; input: WorkspaceSecretInput; required: boolean; } export interface WorkspaceRuntimeBindingLease { bindings: RuntimeBindings; release(): void; } interface RequirementDefinition { id: string; label: string; description: string; input: WorkspaceSecretInput; } const DEFINITIONS: Readonly>> = { "DWH.PASSWORD_FILE": { id: "dwh.password", label: "Data warehouse password", description: "Password used by the selected data warehouse connection.", input: "password", }, "DWH.API_KEY_FILE": { id: "dwh.api_key", label: "Data warehouse API key", description: "API key sent to the configured data warehouse REST endpoint.", input: "password", }, "DWH.SSH_PRIVATE_KEY_FILE": { id: "dwh.ssh_private_key", label: "SSH private key", description: "Private key used to open the configured SSH tunnel to the data warehouse.", input: "textarea", }, "EVIDENCE.SIGNED_URLS_FILE": { id: "evidence.signed_urls", label: "Evidence signed URLs", description: "Signed URLs that authorize ThothII to retrieve the workspace Evidence sources.", input: "textarea", }, "EVIDENCE.ACCESS_KEY_FILE": { id: "evidence.access_key", label: "Evidence access key", description: "Access-key identifier used for the configured S3 Evidence source.", input: "password", }, "EVIDENCE.SECRET_KEY_FILE": { id: "evidence.secret_key", label: "Evidence secret key", description: "Secret access key used for the configured S3 Evidence source.", input: "password", }, "EVIDENCE.SESSION_TOKEN_FILE": { id: "evidence.session_token", label: "Evidence session token", description: "Optional temporary session token used with the S3 Evidence credentials.", input: "password", }, }; const REQUIRED_DWH_SECRETS: Readonly> = { postgres_direct: ["PASSWORD_FILE"], rest_api: ["API_KEY_FILE"], ssh_tunnel: ["PASSWORD_FILE", "SSH_PRIVATE_KEY_FILE"], }; function isTransport(value: string | undefined): value is DwhTransport { return value !== undefined && (DWH_TRANSPORTS as readonly string[]).includes(value); } function selectedTransport( descriptor: WorkspaceDescriptor, variables: readonly InstallationVariable[], env: NodeJS.ProcessEnv, ): DwhTransport { const transportVariable = variables.find(({ role, suffix }) => role === "DWH" && suffix === "TRANSPORT"); const value = transportVariable === undefined ? undefined : env[transportVariable.name]; return isTransport(value) && descriptor.dwh.supported_transports.includes(value) ? value : descriptor.dwh.supported_transports[0]; } function requirementFor( variable: InstallationVariable, required: boolean, ): WorkspaceSecretRequirement | undefined { const definition = DEFINITIONS[`${variable.role}.${variable.suffix}`]; if (definition === undefined) return undefined; return { ...definition, variable: variable.name, connector: variable.role === "DWH" ? "dwh" : "evidence", required, }; } /** * Discover the credential fields for the connector and authentication mechanisms selected by * this installation. Paths, hostnames and trust files remain installation configuration rather * than user-entered secrets. */ export function discoverWorkspaceSecretRequirements( workspace: WorkspaceDescriptor, env: NodeJS.ProcessEnv, ): WorkspaceSecretRequirement[] { const descriptor = validateWorkspaceDescriptor(workspace); const variables = buildInstallationContract(descriptor).variables; const transport = selectedTransport(descriptor, variables, env); const restHasNoAuthentication = transport === "rest_api" && descriptor.diagnostics?.dwh_rest?.auth === "none"; const requiredDwh = new Set(restHasNoAuthentication ? [] : REQUIRED_DWH_SECRETS[transport]); const requirements: WorkspaceSecretRequirement[] = []; for (const variable of variables) { if (variable.role === "DWH") { if (variable.transports !== undefined && !variable.transports.includes(transport)) continue; const requirement = requirementFor(variable, requiredDwh.has(variable.suffix)); if (requirement !== undefined && requirement.required) requirements.push(requirement); continue; } const requirement = requirementFor(variable, variable.suffix !== "SESSION_TOKEN_FILE"); if (requirement !== undefined) requirements.push(requirement); } return requirements; } /** * Materialize only the selected workspace credentials, translate them to the existing file-based * harness contract, and bind cleanup to the returned lease. */ export function resolveRuntimeBindingsWithWorkspaceSecrets( workspace: WorkspaceDescriptor, env: NodeJS.ProcessEnv, secretRoots: readonly string[], store: WorkspaceSecretStore, ): WorkspaceRuntimeBindingLease { const descriptor = validateWorkspaceDescriptor(workspace); const requirements = discoverWorkspaceSecretRequirements(descriptor, env); let materialization: WorkspaceSecretMaterialization | undefined; try { materialization = store.materialize( descriptor.workspace.id, requirements.map(({ id }) => id), ); const effectiveEnvironment: NodeJS.ProcessEnv = { ...env }; const materializedRoots = new Set(); for (const requirement of requirements) { const path = materialization.files.get(requirement.id); if (path === undefined) continue; effectiveEnvironment[requirement.variable] = path; materializedRoots.add(dirname(path)); } const bindings = resolveRuntimeBindings( descriptor, effectiveEnvironment, [...secretRoots, ...materializedRoots], ); let released = false; return { bindings, release: () => { if (released) return; released = true; materialization?.release(); }, }; } catch (error) { materialization?.release(); throw error; } }