Skip to content
Cloudflare Docs
非官方翻译 - 此文档为非官方中文翻译版本,仅供参考。如有疑问请以 英文官方文档 为准。

Workers Logpush

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

本指南解释了如何为 AI 网关设置 Workers Logpush,生成用于加密的 RSA 密钥对,以及在接收到日志后如何解密日志。

您每个网关最多可以存储 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 网关配置 Workers Logpush,请按以下步骤操作:

1. 在本地生成 RSA 密钥对

您需要生成一个密钥对来加密和解密日志。此脚本将输出您的 RSA 私钥和公钥。保持私钥安全,因为它将用于解密日志。下面是使用 Node.js 和 OpenSSL 生成密钥的示例脚本。

JavaScript
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 文件名。

Terminal window
node {file name}

2. 将公钥上传到网关设置

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

3. 设置 Logpush

要设置 Logpush,请参阅 Logpush 快速开始

4. 接收加密日志

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

5. 解密日志

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

要解密来自 AI 网关的加密日志正文和元数据,请将日志下载到一个文件夹,在本例中名为 my_log.log.gz

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

JavaScript
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 {
// 确保密钥长度对 AES 有效
if ([128, 192, 256].includes(256)) {
return await crypto.webcrypto.subtle.importKey(
"raw",
keyBuffer,
{
name: "AES-GCM",
length: 256,
},
true, // 密钥是否可提取(在此情况下为 true,以便稍后需要时允许导出)
["encrypt", "decrypt"], // 用于加密和解密
);
} else {
throw new Error("无效的 AES 密钥长度。必须是 128、192 或 256 位。");
}
} catch (error) {
console.error("导入密钥失败:", 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 文件名。

Terminal window
node {file name}

The script reads the encrypted log file (my_log.log.gz), decrypts the metadata, request body, and response body, and prints the decrypted data. Ensure you replace the privateKey variable with your actual private RSA key that you generated in step 1.