编写用于双向 TLS(查看器)验证的 CloudFront 连接函数 - Amazon CloudFront
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

编写用于双向 TLS(查看器)验证的 CloudFront 连接函数

借助 CloudFront 连接函数,您可以为 mTLS 证书验证和自定义身份验证逻辑编写轻量级 JavaScript 函数。您的连接函数代码可在全球范围内的 CloudFront 边缘站点验证客户端证书、实施设备特定的身份验证规则、处理证书吊销场景,以及做出允许/拒绝 TLS 连接的决定。

连接函数提供了一种强大的方式,可让您通过自己的业务逻辑来扩展 CloudFront 的内置证书验证功能。与处理 HTTP 数据的查看器请求和查看器响应函数不同,连接函数在 TLS 层运行,并且有权获取证书信息、客户端 IP 地址和 TLS 连接详细信息。这使得它们非常适合实施零信任安全模型、设备身份验证系统和超出标准 PKI 验证范围的自定义证书验证策略。

您的连接函数代码在安全的隔离环境中运行,启动时间以亚毫秒级计,并且可扩展以每秒处理数百万次连接。运行时已针对证书验证工作负载进行优化,并提供了与 CloudFront KeyValueStore 的内置集成以用于实时数据查找操作,从而满足证书吊销列表检查和设备允许列表验证等复杂的身份验证场景的需求。

要获得帮助以编写有效的连接函数代码,请参阅以下主题。有关完整的代码示例和分步教程,请参阅本指南中的教程部分,并浏览 CloudFront 控制台中提供的连接函数示例。

CloudFront 连接函数使用案例和用途

在编写 CloudFront 连接函数之前,请仔细确定需要实施哪种类型的证书验证或身份验证逻辑。连接函数专为需要超出标准 PKI 证书检查范围的自定义验证的特定使用案例而设计。了解您的使用案例可以帮助您设计高效代码,以满足安全要求并保持最佳性能。

常见连接函数使用案例包括:

  • 证书吊销处理:实施自定义策略来处理已吊销的证书,包括证书轮换的宽限期、内部设备的可信网络异常或已吊销证书可能需要临时访问权限的紧急访问场景。

  • 可选的 mTLS 支持:使用不同的身份验证策略处理 mTLS 和非 mTLS 连接,让您能够提升支持证书的客户端的安全性,同时保持与传统客户端的兼容性。

  • 基于 IP 的身份验证:将证书验证与客户端 IP 地址检查相结合以提升安全性,例如,限制来自特定地理区域、企业网络或已知恶意 IP 范围的访问。

  • 多租户证书验证:实施租户特定的验证规则,其中根据客户端证书颁发者或主题属性应用不同的证书颁发机构或验证标准。

  • 基于时间的访问控制:实施基于时间的限制,即证书仅在特定时间、维护时段或业务时段有效,即使证书本身尚未过期也是如此。

连接函数在 CloudFront 执行标准证书验证(信任链验证、到期检查和签名验证)之后且在 TLS 连接建立之前运行。该执行时序既可让您灵活地添加自定义验证标准,又可充分利用 CloudFront 的内置证书验证功能。您的函数会收到标准验证的结果,并且可根据标准和自定义标准来明智地决定是允许还是拒绝连接。

在设计连接函数时,请考虑验证逻辑对性能产生的影响。函数的执行限制为 5 毫秒,因此应加快复杂操作的执行速度。使用 KeyValueStore 进行快速数据查找而不是复杂的计算,并构建验证逻辑以对无效证书实施快速失效机制。

CloudFront 连接函数事件结构和响应格式

CloudFront 连接函数接收的事件结构与查看器请求和查看器响应函数的不同。连接函数接收的不是 HTTP 请求/响应数据,而是可用于做出身份验证决策的证书和连接信息。

连接函数的事件结构

连接函数会接收一个包含证书和连接信息的事件对象。该函数的事件结构如下所示:

{ "clientCertificate": { "certificates": { "leaf": { "serialNumber": "string", "issuer": "string", "subject": "string", "validity": { "notBefore": "string", "notAfter": "string", }, "sha256Fingerprint": "string" } } }, "clientIp": "string", "endpoint": "string", "distributionId": "string", "connectionId": "string" }

以下是事件对象结构的示例:

{ "clientCertificate": { "certificates": { "leaf": { "serialNumber": "00:9e:2a:af:16:56:e5:47:25:7d:2e:38:c3:f9:9d:57:fa", "issuer": "C=US, O=Ram, OU=Edge, ST=WA, CN=mTLS-CA, L=Snoqualmie", "subject": "C=US, O=Ram, OU=Edge, ST=WA, CN=mTLS-CA, L=Snoqualmie", "validity": { "notBefore": "2025-09-10T23:43:10Z", "notAfter": "2055-09-11T00:43:02Z" }, "sha256Fingerprint": "_w6bJ7aOAlGOj7NUhJxTfsfee-ONg_xop3_PTgTJpqs=" } } }, "clientIp": "127.0.0.1", "endpoint": "d3lch071jze0cb.cloudfront.net", "distributionId": "E1NXS4MQZH501R", "connectionId": "NpvTe1925xfj24a67sPQr7ae42BIq03FGhJJKfrQYWZcWZFp96SIIg==" }

连接函数响应格式

连接函数必须返回一个响应对象,该对象指示是允许还是拒绝连接。使用辅助方法做出连接决策:

function connectionHandler(connection) { // Helper methods to allow or deny connections if (/* some logic to determine if function should allow connection */) { connection.allow(); } else { connection.deny(); } }

与查看器请求和查看器响应函数不同,连接函数无法修改 HTTP 请求或响应。它们只能允许或拒绝 TLS 连接。

CloudFront 连接函数 JavaScript 运行时特征

CloudFront 连接函数使用 CloudFront Functions JavaScript 运行时 2.0,它提供一个专门针对证书验证工作负载进行了优化的安全的高性能环境。该运行时设计为在亚毫秒级内启动,并可在 CloudFront 的全球边缘网络中处理数百万次并发执行。

该运行时环境包括全面的 JavaScript 语言支持:

  • ECMAScript 2020(ES11)支持:现代 JavaScript 特征,包括可选链操作符(?.)、空值合并操作符(??)以及用于处理大型证书序列号的 BigInt

  • 内置对象:标准 JavaScript 对象,例如 Object、Array、JSON、Math 和 Date

  • 控制台日志记录:使用 console.log() 调试和监控证书验证决策。测试期间可实时查看日志,并且日志有助于排查开发过程中的验证逻辑问题

  • KeyValueStore 集成:可原生访问 CloudFront KeyValueStore 以实施超快速数据查询操作,并且支持实时证书吊销检查、设备允许列表验证以及租户特定的配置检索

连接函数已进行优化,可在证书验证场景中实现高性能。运行时会自动处理内存管理、垃圾回收和资源清理工作,以确保在数百万个并发连接中实现一致的性能。所有操作均设计为确定性执行且具备高性能,其中 KeyValueStore 查找操作通常在微秒级内完成。

运行时环境在各个函数执行之间完全隔离,确保不同的客户端连接之间不会发生数据泄漏。每次函数执行均以全新状态开始,并且无法访问之前的执行结果或来自其他连接的客户端数据。

CloudFront 连接函数辅助方法和 API

CloudFront 连接函数提供专用辅助方法,旨在简化证书验证决策流程并提升可观测性。这些方法已针对连接验证工作流进行优化,并与 CloudFront 的连接日志记录和监控系统无缝集成。

  • connection.allow():允许 TLS 连接继续工作。此方法将指示 CloudFront 完成 TLS 握手流程,并允许客户端建立连接。当证书验证通过且满足任何自定义身份验证逻辑时,使用此方法

  • connection.deny():拒绝 TLS 连接并终止握手。此方法会立即关闭连接并阻止任何 HTTP 流量流动。客户端将收到 TLS 连接错误。将此方法用于证书无效、身份验证失败或违反策略的场景

  • connection.logCustomData():将自定义数据添加到连接日志(最多 800 字节的 UTF-8 文本)。此方法可让您将验证结果、证书详细信息或决策依据写入 CloudFront 连接日志,以便进行安全监控、合规审计和问题排查

这些方法提供了简洁的声明式接口,可用于做出连接决策,并记录相关信息以用于监控与调试工作。允许/拒绝模式可确保函数的意图明确,并且 CloudFront 能够根据您的决策优化连接处理。自定义日志记录数据会立即在 CloudFront 连接日志中提供,并且可与日志分析工具结合使用,以实施安全监控并获得运营洞察。

在函数完成之前,始终调用 connection.allow() 或 connection.deny()。出于安全防护考虑,如果两种方法均未被调用,CloudFront 将默认拒绝连接。

CloudFront 连接函数 KeyValueStore 集成

CloudFront 连接函数可使用 CloudFront KeyValueStore 为证书验证场景执行超快速数据查找。KeyValueStore 对于连接函数而言尤其强大,因为它能提供全局、最终一致的数据访问,并在所有 CloudFront 边缘站点实现微秒级的查找速度。这使其非常适合维护证书吊销列表、设备允许列表、租户配置和其他需要在 TLS 握手过程中可供访问的验证数据。

KeyValueStore 集成专为高性能连接验证工作流而设计:

  • kvsHandle.exists(key):检查 KeyValueStore 中是否存在某个键,而无需检索其对应的值。这是处理二进制验证场景(如证书吊销检查)的最高效方法,仅需确认证书序列号是否存在于吊销列表中

  • kvsHandle.get(key):从 KeyValueStore 中检索一个值以用于更复杂的验证场景。当您需要访问与证书或设备标识符关联的配置数据、验证规则或元数据时,使用此选项

KeyValueStore 操作是异步的,且必须与 async/await 语法结合使用。KeyValueStore 的总大小限制为 10 MB,并且最多支持 1000 万个键值对。KeyValueStore 数据在所有边缘站点间保持最终一致性,更新通常在数秒内完成传播。

要获得最佳性能,可构建 KeyValueStore 键以最大限度地减少查找操作数。使用证书序列号作为键来进行简单吊销检查,或者为多 CA 环境创建复合键(包含颁发者哈希和序列号)。在设计数据结构时,请考虑在键复杂性和 KeyValueStore 容量之间进行权衡。

使用 async 和 await

连接函数支持使用 async/await 语法的异步操作,这在执行 KeyValueStore 操作或其他异步任务时至关重要。async/await 模式可确保您的函数等待 KeyValueStore 查找完成后再做出连接决策,同时保持 TLS 握手处理所需的高性能特征。

正确使用 async/await 对于连接函数至关重要,因为 KeyValueStore 操作的速度虽然非常快,但仍是需要跨 CloudFront 分布式基础设施进行协调的网络操作。运行时会自动处理 promise 解析,并确保您的函数在 5 毫秒的执行时限内完成。

例 :异步连接函数与 KeyValueStore
import cf from 'cloudfront'; async function connectionHandler(connection) { const kvsHandle = cf.kvs(); // Async operation to check KeyValueStore for certificate revocation const isRevoked = await kvsHandle.exists(connection.clientCertificate.certificates.leaf.serialNumber); if (isRevoked) { // Log the revocation decision with certificate details connection.logCustomData(`REVOKED_CERT:${connection.clientCertificate.certificates.leaf.serialNumber}:${connection.clientCertificate.certificates.leaf.issuer}`); console.log(`Denying connection for revoked certificate: ${connection.clientCertificate.certificates.leaf.serialNumber}`); return connection.deny(); } // Log successful validation for monitoring connection.logCustomData(`VALID_CERT:${connection.clientCertificate.certificates.leaf.serialNumber}`); console.log(`Allowing connection for valid certificate: ${connection.clientCertificate.certificates.leaf.serialNumber}`); return connection.allow(); }

在调用 KeyValueStore 方法或其他异步操作时,始终使用 async/await。连接函数运行时会自动处理 promise 解析,并确保在 TLS 握手处理的严格时间限制内实施正确的执行流程。避免使用 .then() 或回调模式,因为 async/await 可在连接函数环境中实现更清晰的错误处理和更佳的性能。

在设计异步连接函数时,需优化代码结构以最大限度地减少 KeyValueStore 操作的数量,并在验证逻辑中尽早执行这些操作。这可确保实现最佳性能,并降低高流量时段内发生超时问题的风险。考虑批量执行相关验证检查,并针对您的使用案例使用最高效的 KeyValueStore 方法 [exists() 与 get()]。

连接函数代码示例

以下示例演示针对不同验证场景的常见连接函数模式。使用这些示例作为您自己的连接函数实施的起点。

例 :设备证书验证

此示例演示如何针对 IoT 设备、游戏主机和其他客户端身份验证场景验证设备序列号和证书主题字段:

async function connectionHandler(connection) { // Custom validation: check device serial number format const serialNumber = connection.clientCertificate.certificates.leaf.serialNumber; if (!serialNumber.startsWith("DEV")) { connection.logCustomData(`INVALID_SERIAL:${serialNumber}`); return connection.deny(); } // Validate certificate subject contains required organizational unit const subject = connection.clientCertificate.certificates.leaf.subject; if (!subject.includes("OU=AuthorizedDevices")) { connection.logCustomData(`INVALID_OU:${subject}`); return connection.deny(); } // Allow connection for valid devices connection.logCustomData(`VALID_DEVICE:${serialNumber}`); return connection.allow(); }

此函数执行标准证书验证之外的多项验证检查,包括设备序列号格式和组织单元验证。

例 :支持混合身份验证的可选 mTLS

此示例演示如何使用不同的身份验证策略来处理 mTLS 连接和非 mTLS 连接:

async function connectionHandler(connection) { if (connection.clientCertificate) { // mTLS connection - enhanced validation for certificate holders const subject = connection.clientCertificate.certificates.leaf.subject; connection.logCustomData(`MTLS_SUCCESS:${subject}:${connection.clientIp}`); console.log(`mTLS connection from: ${subject}`); return connection.allow(); } else { // Non-mTLS connection - apply IP-based restrictions const clientIp = connection.clientIp; // Only allow non-mTLS from specific IP ranges if (clientIp.startsWith("203.0.113.") || clientIp.startsWith("198.51.100.")) { connection.logCustomData(`NON_MTLS_ALLOWED:${clientIp}`); console.log(`Non-mTLS connection allowed from: ${clientIp}`); return connection.allow(); } connection.logCustomData(`NON_MTLS_DENIED:${clientIp}`); return connection.deny(); } }

此函数可提升拥有证书的客户端的安全性,并保持与可信 IP 范围内的传统客户端的兼容性。