const crypto = require("crypto"); const { nodeIdentity, signedRequestPayloadDigest } = require("./node-signing"); function agentIdentity(seedPrefix, agent) { const identity = nodeIdentity(seedPrefix, agent); return { ...identity, publicKeyFingerprint: `sha256:${crypto .createHash("sha256") .update(identity.publicKey) .digest("hex")}`, }; } function agentWorkflowSignatureMessage({ tenant, project, agent, requestKind, process: processId, task = "", payloadDigest, nonce, issuedAtEpochSeconds, }) { const parts = [ "clusterflux-agent-workflow-signature:v2", tenant, project, agent, requestKind, processId, task, payloadDigest, nonce, String(issuedAtEpochSeconds), ]; return Buffer.concat( parts.flatMap((part) => [ Buffer.from(`${Buffer.byteLength(part)}:`), Buffer.from(part), Buffer.from("\n"), ]) ); } function signedAgentWorkflowProof(identity, request, options = {}) { const nonce = options.nonce || `${request.type}-${process.pid}-${Date.now()}-${crypto .randomBytes(8) .toString("hex")}`; const issuedAtEpochSeconds = options.issuedAtEpochSeconds || Math.floor(Date.now() / 1000); const processId = request.type === "launch_task" ? request.task_spec?.process : request.process; const task = request.type === "launch_task" ? request.task_spec?.task_instance : request.task || ""; const signature = crypto.sign( null, agentWorkflowSignatureMessage({ tenant: request.tenant, project: request.project, agent: request.actor_agent, requestKind: request.type, process: processId, task, payloadDigest: signedRequestPayloadDigest(request), nonce, issuedAtEpochSeconds, }), identity.privateKeyObject ); return { nonce, issued_at_epoch_seconds: issuedAtEpochSeconds, signature: `ed25519:${signature.toString("base64")}`, }; } function signedAgentWorkflowRequest(identity, request, options = {}) { const unsignedRequest = { ...request, agent_public_key_fingerprint: options.publicKeyFingerprint || identity.publicKeyFingerprint, }; return { ...unsignedRequest, agent_signature: signedAgentWorkflowProof(identity, unsignedRequest, options), }; } module.exports = { agentIdentity, agentWorkflowSignatureMessage, signedAgentWorkflowProof, signedAgentWorkflowRequest, };