跳转到内容
搜索文档

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

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

这篇文档对您有帮助吗?