@mesh-tech/mesh-cli
Version:
CLI for Mesh platform development utilities
233 lines (230 loc) • 7.46 kB
JavaScript
// libs/mesh-cli/src/utils/temporal-codec.ts
import { execFileSync } from "child_process";
import { webcrypto as crypto } from "node:crypto";
// libs/mesh-cli/src/utils/log.ts
import chalk from "chalk";
function logPrefix(opts) {
const enabled = opts.envFlag === "1" || opts.envFlag !== "0" && !opts.isTTY;
return enabled ? `[${opts.now.toISOString().slice(11, 19)}] ` : "";
}
function prefix() {
return chalk.dim(
logPrefix({
isTTY: !!process.stderr.isTTY,
envFlag: process.env.MESH_LOG_TIMESTAMPS,
now: /* @__PURE__ */ new Date()
})
);
}
function logSuccess(message) {
console.error(prefix() + chalk.green("\u2713"), message);
}
function logWarn(message) {
console.error(prefix() + chalk.yellow("\u26A0"), message);
}
// libs/mesh-cli/src/utils/temporal-codec.ts
function resolveTemporalEncodingKeyFromK8s(namespace) {
const secretName = `${namespace}-temporal-encoding-key`;
try {
const b64 = execFileSync(
"kubectl",
[
"get",
"secret",
secretName,
"-n",
namespace,
"-o",
"jsonpath={.data.TEMPORAL_ENCODING_KEY}"
],
{ encoding: "utf-8", timeout: 1e4, stdio: ["pipe", "pipe", "pipe"] }
).trim();
if (!b64) return void 0;
return Buffer.from(b64, "base64").toString("utf-8");
} catch {
return void 0;
}
}
var ENCRYPTED_ENCODING = "binary/encrypted";
var IV_LENGTH = 12;
async function deriveKey(encodingKey) {
const keyData = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(encodingKey)
);
return crypto.subtle.importKey(
"raw",
keyData,
{ name: "AES-GCM" },
false,
["decrypt"]
);
}
async function aesGcmDecrypt(data, key) {
const iv = data.slice(0, IV_LENGTH);
const ciphertextWithTag = data.slice(IV_LENGTH);
const decrypted = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv },
key,
ciphertextWithTag
);
return new Uint8Array(decrypted);
}
async function createPayloadDecrypter(encodingKey) {
const key = await deriveKey(encodingKey);
return async (payload) => {
if (!payload?.metadata) return null;
const encodingBytes = payload.metadata["encoding"];
if (!encodingBytes) return null;
const encoding = new TextDecoder().decode(encodingBytes);
if (encoding !== ENCRYPTED_ENCODING) return null;
if (!payload.data) return "[encrypted payload missing data]";
let inner;
try {
const decrypted = await aesGcmDecrypt(payload.data, key);
inner = decodePayloadProtobuf(decrypted);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return `[undecryptable: ${msg}]`;
}
return decodeInnerPayload(inner);
};
}
async function createRawPayloadDecrypter(encodingKey) {
const key = await deriveKey(encodingKey);
return async (payload) => {
if (!payload?.metadata) return null;
const encodingBytes = payload.metadata["encoding"];
if (!encodingBytes) return null;
const encoding = new TextDecoder().decode(encodingBytes);
if (encoding !== ENCRYPTED_ENCODING) return null;
if (!payload.data) {
throw new Error("encrypted payload has no data to decrypt");
}
const decrypted = await aesGcmDecrypt(payload.data, key);
return decodePayloadProtobuf(decrypted);
};
}
function readVarint(buf, pos) {
let value = 0;
let shift = 0;
let next = pos;
while (next < buf.length) {
const byte = buf[next];
next += 1;
value |= (byte & 127) << shift;
if ((byte & 128) === 0) return { value, next };
shift += 7;
if (shift >= 32) throw new Error("varint too large");
}
throw new Error("truncated varint");
}
function readLengthDelimited(buf, pos) {
const { value: length, next: afterLen } = readVarint(buf, pos);
const end = afterLen + length;
if (end > buf.length) throw new Error("truncated length-delimited field");
return { bytes: buf.slice(afterLen, end), next: end };
}
function decodePayloadProtobuf(buf) {
const metadata = {};
let data;
let pos = 0;
while (pos < buf.length) {
const { value: tag, next: afterTag } = readVarint(buf, pos);
const fieldNumber = tag >>> 3;
const wireType = tag & 7;
pos = afterTag;
if (wireType === 2) {
const { bytes, next } = readLengthDelimited(buf, pos);
pos = next;
if (fieldNumber === 1) {
const entry = decodeMetadataEntry(bytes);
if (entry) metadata[entry.key] = entry.value;
} else if (fieldNumber === 2) {
data = bytes;
}
} else if (wireType === 0) {
pos = readVarint(buf, pos).next;
} else if (wireType === 1) {
pos += 8;
} else if (wireType === 5) {
pos += 4;
} else {
throw new Error(`unsupported wire type ${wireType} at pos ${pos}`);
}
}
return { metadata, data: data ?? new Uint8Array(0) };
}
function decodeMetadataEntry(buf) {
let key;
let value;
let pos = 0;
while (pos < buf.length) {
const { value: tag, next: afterTag } = readVarint(buf, pos);
const fieldNumber = tag >>> 3;
const wireType = tag & 7;
pos = afterTag;
if (wireType !== 2) {
if (wireType === 0) pos = readVarint(buf, pos).next;
else if (wireType === 1) pos += 8;
else if (wireType === 5) pos += 4;
else throw new Error(`unsupported metadata wire type ${wireType}`);
continue;
}
const { bytes, next } = readLengthDelimited(buf, pos);
pos = next;
if (fieldNumber === 1) key = new TextDecoder().decode(bytes);
else if (fieldNumber === 2) value = bytes;
}
if (key === void 0 || value === void 0) return null;
return { key, value };
}
function decodeInnerPayload(payload) {
const meta = payload.metadata ?? {};
const encodingBytes = meta["encoding"];
const encoding = encodingBytes ? new TextDecoder().decode(encodingBytes) : "unknown";
const data = payload.data ?? new Uint8Array(0);
if (encoding === "binary/null") return "null";
if (encoding === "json/plain") {
const text = new TextDecoder().decode(data);
try {
return JSON.stringify(JSON.parse(text), null, 2);
} catch {
return text;
}
}
if (encoding === "binary/plain") return `[binary, ${data.length} bytes]`;
return `[encoding=${encoding}, ${data.length} bytes]`;
}
function resolveEncodingKey(namespace) {
const fromK8s = resolveTemporalEncodingKeyFromK8s(namespace);
const key = fromK8s ?? process.env.TEMPORAL_ENCODING_KEY;
if (!key) {
logWarn(
`Could not read TEMPORAL_ENCODING_KEY from secret ${namespace}/${namespace}-temporal-encoding-key, and TEMPORAL_ENCODING_KEY is not set. Configure kubectl (namespace read access), or export TEMPORAL_ENCODING_KEY (e.g. from a \`mesh dev\` worker env).`
);
return void 0;
}
logSuccess(
fromK8s ? `Resolved encoding key from ${namespace}-temporal-encoding-key` : "Resolved encoding key from TEMPORAL_ENCODING_KEY env"
);
return key;
}
async function buildDecrypterForNamespace(namespace) {
const key = resolveEncodingKey(namespace);
if (!key) return void 0;
return createPayloadDecrypter(key);
}
async function buildRawDecrypterForNamespace(namespace) {
const key = resolveEncodingKey(namespace);
if (!key) return void 0;
return createRawPayloadDecrypter(key);
}
export {
buildDecrypterForNamespace,
buildRawDecrypterForNamespace,
createPayloadDecrypter,
createRawPayloadDecrypter,
resolveEncodingKey,
resolveTemporalEncodingKeyFromK8s
};