跳转到内容
搜索文档

Workers Logpush

最后更新 查看 MarkdownAgent 设置

AI Gateway 允许你安全地将日志导出到外部存储位置,在那里你可以解密和处理它们。 你可以在 Cloudflare 仪表板 设置中开启和关闭 Workers Logpush。此产品在 Workers Paid 计划上可用。有关定价信息,请参阅定价

本指南说明如何为 AI Gateway 设置 Workers Logpush、生成用于加密的 RSA 密钥对,以及在收到日志后解密日志。

每个 gateway 最多可存储 1000 万条日志。如果达到限制,新日志将停止保存,也不会通过 Workers Logpush 导出。要继续保存和导出日志,必须删除旧日志以释放空间。Workers Logpush 限制为 4 个作业,每条日志最大请求大小为 1 MB。

日志加密方式

我们采用混合加密模型以提高效率和安全性。首先,为每条日志生成 AES 密钥。此 AES 密钥实际加密大部分数据,因其在处理大型数据集时的速度和安全性而被选用。

现在,为了安全共享此 AES 密钥,我们使用 RSA 加密。过程如下:AES 密钥虽然轻量,但需要安全传输给接收方。我们使用接收方的 RSA 公钥加密此密钥。此步骤利用 RSA 在安全密钥分发方面的优势,确保只有拥有相应 RSA 私钥的人才能解密和使用 AES 密钥。

加密后,AES 加密的数据和 RSA 加密的 AES 密钥一起发送。到达后,接收方系统使用 RSA 私钥解密 AES 密钥。AES 密钥可访问后,解密主要数据负载就很简单了。

此方法结合了两者的优势:AES 的数据加密效率与 RSA 的安全密钥交换能力,确保在整个数据生命周期中最佳地维护数据完整性、机密性和性能。

设置 Workers Logpush

要为 AI Gateway 配置 Workers Logpush,请按照以下步骤操作:

1. 在本地生成 RSA 密钥对

你需要生成密钥对来加密和解密日志。此脚本将输出 RSA privateKey 和 publicKey。请安全保管私钥,它将用于解密日志。以下是使用 Node.js 和 OpenSSL 生成密钥的示例脚本。

JavaScriptjs
const crypto = require("crypto");

const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", {
	modulusLength: 4096,
	publicKeyEncoding: {
		type: "spki",
		format: "pem",
	},
	privateKeyEncoding: {
		type: "pkcs8",
		format: "pem",
	},
});

console.log(publicKey);
console.log(privateKey);

在终端上执行以下代码运行脚本。将 file name 替换为你的 JavaScript 文件名。

node {file name}
  1. 生成私钥: 使用以下命令生成 RSA 私钥:

    openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:4096
  2. 生成公钥: 生成私钥后,可以使用以下命令提取相应的公钥:

    openssl rsa -pubout -in private_key.pem -out public_key.pem

2. 将公钥上传到 gateway 设置

生成密钥对后,将公钥上传到 AI Gateway 设置。此密钥将用于加密日志。要启用 Workers Logpush,需要为该 gateway 启用日志记录。

3. 设置 Logpush

要设置 Logpush,请参阅 Logpush 文档。

4. 接收加密日志

配置 Workers Logpush 后,日志将使用你上传的公钥加密发送。要访问数据,你需要使用私钥解密。日志将发送到你选择的对象存储提供商。

5. 解密日志

要解密来自 AI Gateway 的加密日志正文和元数据,可以使用以下 Node.js 脚本或 OpenSSL:

要解密来自 AI Gateway 的加密日志正文和元数据,将日志下载到文件夹,在此示例中命名为 my_log.log.gz

然后将此 JavaScript 文件复制到同一文件夹,并将私钥放在顶部变量中。

JavaScriptjs
const privateKeyStr = `-----BEGIN RSA PRIVATE KEY-----
....
-----END RSA PRIVATE KEY-----`;

const crypto = require("crypto");
const privateKey = crypto.createPrivateKey(privateKeyStr);

const fs = require("fs");
const zlib = require("zlib");
const readline = require("readline");

async function importAESGCMKey(keyBuffer) {
	try {
		// Ensure the key length is valid for AES
		if ([128, 192, 256].includes(256)) {
			return await crypto.webcrypto.subtle.importKey(
				"raw",
				keyBuffer,
				{
					name: "AES-GCM",
					length: 256,
				},
				true, // Whether the key is extractable (true in this case to allow for export later if needed)
				["encrypt", "decrypt"], // Use for encryption and decryption
			);
		} else {
			throw new Error("Invalid AES key length. Must be 128, 12, or 256 bits.");
		}
	} catch (error) {
		console.error("Failed to import key:", error);
		throw error;
	}
}

async function decryptData(encryptedData, aesKey, iv) {
	const decryptedData = await crypto.subtle.decrypt(
		{ name: "AES-GCM", iv: iv },
		aesKey,
		encryptedData,
	);
	return new TextDecoder().decode(decryptedData);
}

async function decryptBase64(privateKey, data) {
	if (data.key === undefined) {
		return data;
	}

	const aesKeyBuf = crypto.privateDecrypt(
		{
			key: privateKey,
			oaepHash: "SHA256",
		},
		Buffer.from(data.key, "base64"),
	);
	const aesKey = await importAESGCMKey(aesKeyBuf);

	const decryptedData = await decryptData(
		Buffer.from(data.data, "base64"),
		aesKey,
		Buffer.from(data.iv, "base64"),
	);

	return decryptedData.toString();
}

async function run() {
	let lineReader = readline.createInterface({
		input: fs.createReadStream("my_log.log.gz").pipe(zlib.createGunzip()),
	});

	lineReader.on("line", async (line) => {
		line = JSON.parse(line);

		const { Metadata, RequestBody, ResponseBody, ...remaining } = line;

		console.log({
			...remaining,
			Metadata: await decryptBase64(privateKey, Metadata),
			RequestBody: await decryptBase64(privateKey, RequestBody),
			ResponseBody: await decryptBase64(privateKey, ResponseBody),
		});
		console.log("--");
	});
}

run();

在终端上执行以下代码运行脚本。将 file name 替换为你的 JavaScript 文件名。

node {file name}

该脚本读取加密日志文件 (my_log.log.gz),解密元数据、请求正文和响应正文,并打印解密后的数据。 请确保将 privateKey 变量替换为你在步骤 1 中生成的实际 RSA 私钥。

  1. 使用私钥解密加密日志文件。

假设日志使用公钥(例如 public_key.pem)加密,你可以使用私钥(private_key.pem)解密日志文件。

例如,如果加密日志在名为 encrypted_logs.bin 的文件中,可以这样解密:

openssl rsautl -decrypt -inkey private_key.pem -in encrypted_logs.bin -out decrypted_logs.txt
  • -decrypt 告诉 OpenSSL 我们要解密文件。
  • -inkey private_key.pem 指定用于解密日志的私钥。
  • -in encrypted_logs.bin 是加密日志文件。
  • -out decrypted_logs.txt 解密后的日志将保存到此文件。
  1. 查看解密后的日志 解密后,可以通过运行以下命令查看日志:
cat decrypted_logs.txt

此命令将在终端输出解密后的日志。

这篇文档对您有帮助吗?