你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

管理 Service Fabric 群集中的证书

本文介绍如何管理用于保护 Azure Service Fabric 群集中的通信的证书。 它是对 Service Fabric 群集安全性的补充和对 Service Fabric 中基于 X.509 证书的身份验证的说明。

先决条件

在开始之前,你应该熟悉基本的安全性概念和 Service Fabric 公开的用于配置群集安全性的控制措施。

免责声明

本文将证书管理的理论方面与涵盖服务、技术等具体细节的动手示例相结合。 由于此处的大部分受众是 Microsoft 内部员工,因此本文提到的服务、技术和产品是特定于 Azure 的。 如果某些特定于 Microsoft 的细节不适用于你的情况,请按需在末尾的评论区寻求详细说明或相关指导。

定义证书管理

正如你在本文的姊妹篇 Service Fabric 群集中基于 X.509 证书的身份验证中了解到的那样,证书是一个加密对象,它实质上会将非对称密钥对与描述其所表示的实体的属性绑定。

但是,证书也是一个“易变质”对象,因为它的生存期有限并且容易遭到攻击。 从安全的角度来看,意外泄露或成功攻击可能会使证书变得无用。 此特性意味着需要定期更改证书或针对响应安全事件更改证书。

证书管理的另一个方面(也是一个完全独立的主题)是保护证书私钥或机密,这些私钥或机密保护那些涉及证书获得和预配过程的实体的标识。

我们将“证书管理”描述为用于获取证书并将它们安全可靠地传输到需要这些证书的位置的流程和过程。

某些管理操作(例如注册、策略设置和授权控制)不在本文的讨论范围内。 其他操作(例如预配、续订、重新生成密钥或吊销)仅与 Service Fabric 偶然相关。 尽管如此,本文还是会大致介绍一下这些内容,因为了解这些操作可以帮助你正确保护群集。

你的直接目标很可能是尽可能地实现证书管理自动化,以便确保群集的不间断可用性。 由于该过程无需用户接触,因此还需要提供安全性保证。 使用 Service Fabric 群集,可以实现此目标。

本文的其余部分首先解构证书管理,然后重点介绍如何启用自动滚动更新。

具体来说,它涵盖以下主题:

  • 关于所有者和平台之间的属性分离的假设
  • 从证书颁发到使用这一过程的长管道
  • 证书轮换:原因、方式和时间
  • 哪些方面可能会出错?

本文不涵盖以下主题:

  • 保护和管理域名
  • 注册到证书
  • 设置授权控件以强制实施证书颁发。

有关这些主题的信息,请参阅你最喜欢的公钥基础结构 (PKI) 服务的注册机构 (RA)。 如果你是 Microsoft 内部读取者,可以联系 Azure 安全。

证书管理中涉及的角色和实体

Service Fabric 群集中的安全方法是“群集所有者声明它,Service Fabric 运行时强制执行它”的一个例子。这意味着参与群集运行的所有证书、密钥或其他标识凭据几乎都不来自该服务本身。 它们都是由群集所有者声明的。 群集所有者还负责将证书预配到群集中,根据需要续订这些证书,并且始终帮助确保其安全性。

更具体地说,群集所有者必须确保:

  • 在群集清单的 NodeType 节中声明的证书可以在该类型的每个节点上找到(根据出示规则)。
  • 如前一个要点中声明的证书在安装时包含其相应的私钥。
  • 在出示规则中声明的证书应能够通过验证规则

Service Fabric 自身将承担以下职责:

  • 定位与群集定义中的声明匹配的证书
  • 根据需要向 Service Fabric 控制的实体授予对相应私钥的访问权限
  • 严格按照已制定的安全性最佳做法和群集定义来验证证书
  • 在证书即将过期或无法执行基本的证书验证步骤时发出警报
  • (在一定程度上)验证主机的基础配置是否满足群集定义中与证书相关的方面的要求

由此可见,证书管理负担(以活动操作的形式)完全落在群集所有者身上。 后续部分将详细介绍每个管理操作,包括可用机制及其对群集的影响。

证书的历程

让我们快速概述一下在 Service Fabric 群集环境中证书从颁发到使用的整个过程:

  1. 域所有者向 PKI 的 RA 注册他们希望与后续证书关联的域或使用者。 这些证书继而构成该域或使用者的所有权证明。

  2. 域所有者还在 RA 中指定获得授权的请求者的标识,即有权请求注册具有指定域或使用者的证书的实体。

  3. 然后,获得授权的请求者通过机密管理服务注册到证书中。 在 Azure 中,所选的机密管理服务为 Azure Key Vault,它可以安全地存储机密和证书并允许获得授权的实体检索它们。 Key Vault 还会根据关联证书策略中的配置续订证书并重新生成密钥。 Key Vault 使用 Microsoft Entra ID 作为标识提供者。

  4. 获得授权的检索者(也称为预配代理)会从密钥保管库中检索证书(包括其私钥),并将其安装在用于托管群集的计算机上。

  5. Service Fabric 服务(在每个节点上以提升的权限运行)将证书访问权限授予那些获得允许的 Service Fabric 实体;这些实体由本地组指定,在 ServiceFabricAdministrators 和 ServiceFabricAllowedUsers 之间进行拆分。

  6. Service Fabric 运行时会访问并使用证书来建立联合身份验证,或者对获得授权的客户端发出的入站请求进行身份验证。

  7. 预配代理监视密钥保管库证书,并在检测到续订时触发预配流程。 然后,群集所有者会根据需要更新群集定义,以指示更新证书的意图。

  8. 预配代理或群集所有者还负责清除和删除未使用的证书。

就本文而言,上述序列中的前两个步骤大多是不相关的。 它们唯一的关联是,证书的使用者公用名称是在群集定义中声明的 DNS 名称。

证书颁发和预配流程如下图所示:

对于通过指纹声明的证书

Diagram of provisioning certificates that are declared by thumbprint.

对于通过使用者公用名称声明的证书

Diagram of provisioning certificates that are declared by subject common name.

证书注册

Key Vault 文档中详细介绍了证书注册这一主题。 此处提供了一个概要,用于实现连续性和更容易进行参考。

在继续使用 Azure 的情况下,如果使用 Key Vault 作为机密管理服务,则获得授权的证书请求者必须至少具有密钥保管库所有者授予的对密钥保管库的证书管理权限。 随后,请求者按如下方式注册到证书:

  • 请求者在 Key Vault 中创建证书策略,该策略指定证书的域/使用者、所需的颁发者、密钥类型和长度、预期的密钥用法,等等。 有关详细信息,请参阅 Azure Key Vault 中的证书

  • 请求者使用在上一步中指定的策略在同一保管库中创建证书。 该证书继而生成一个密钥对作为保管库对象和一个使用私钥签名的证书签名请求,然后将其转发给指定的颁发者进行签名。

  • 在颁发者(也称为证书颁发机构 (CA))使用签名证书进行答复后,系统会将结果合并到密钥保管库中,然后可在以下位置使用证书数据:

    • {vaultUri}/certificates/{name} 下:包含公钥和元数据的证书
    • {vaultUri}/keys/{name} 下:证书的私钥,可用于加密操作(包装/解包、签名/验证)
    • {vaultUri}/secrets/{name} 下:包含私钥的证书,可作为不受保护的 PFX 或 PEM 文件下载。

请回想一下,密钥保管库中的证书包含按时间顺序排列的共享策略的证书实例列表。 证书版本将根据此策略的生存期和续订属性创建。 强烈建议不要让保管库证书共享使用者、域或 DNS 名称,因为在一个群集中,若要从不同的保管库证书预配证书实例,并且这些证书的使用者相同,但其他属性(例如颁发者、密钥用法等)却存在实质性的差异,则可能会造成中断。 此时,密钥保管库中存在一个可供使用的证书。 现在让我们探索该过程的其余部分。

证书预配

我们提到过“预配代理”,这是一个实体,用于从密钥保管库检索证书(包括其私钥),并将其安装在群集的每个主机上。 (请回想一下,Service Fabric 不预配证书。)

在本文的上下文中,群集将托管在 Azure 虚拟机 (VM) 或虚拟机规模集的集合上。 在 Azure 中,可使用以下机制将证书从保管库预配到 VM/VMSS。 与之前一样,这假定预配代理先前已由密钥保管库所有者授予对密钥保管库的获取权限。

  • 即席:操作员从密钥保管库中检索证书(以 PFX/PKCS #12 或 PEM 的形式),并将其安装在每个节点上。

    出于多种原因(包括从安全性到可用性在内的各种原因),不建议使用即席机制,我们在这里不对其进行进一步的讨论。 有关详细信息,请参阅 Azure 虚拟机规模集常见问题解答

  • 在部署过程中作为虚拟机规模集“机密”:计算服务代表操作员使用第一方标识从一个启用了模板部署的保管库检索证书,然后将其安装在虚拟机规模集的每个节点上,如 Azure 虚拟机规模集常见问题解答中所述。

    注意

    此方法仅允许预配进行了版本控制的机密。

  • 通过使用 Key Vault VM 扩展, 这可让你使用无版本声明来预配证书,并定期刷新观察到的证书。 在这种情况下,VM/VMSS 应该有一个托管标识,该标识已被授予对包含观察到的证书的密钥保管库的访问权限。

基于 VMSS/计算的预配具有安全性和可用性方面的优势,但也存在限制。 根据设计,它要求将证书声明为进行版本控制的机密。 此要求使基于 VMSS/计算的预配仅适用于使用由指纹声明的证书进行保护的群集。

相比之下,基于 Key Vault VM 扩展的预配始终会安装每个观察到的证书的最新版本。 这使得它仅适用于使用由使用者公共名称声明的证书进行保护的群集。 需要强调的是,不要对由实例(即通过指纹)声明的证书使用自动刷新预配机制(例如 Key Vault VM 扩展)。 失去可用性的风险非常大。

还存在其他预配机制,但此处提到的方法是 Azure Service Fabric 群集当前接受的选项。

证书的使用和监视

如前所述,Service Fabric 运行时负责查找和使用在群集定义中声明的证书。 Service Fabric 群集中基于 X.509 证书的身份验证一文详细介绍了 Service Fabric 如何实现出示和验证规则,此处不再赘述。 本文将介绍访问权限和权限授予,以及监视。

请回想一下,Service Fabric 中的证书用于多种目的,从联合层中的相互身份验证到管理终结点的传输层安全性 (TLS) 身份验证,不一而足。 这需要不同的组件或系统服务才能访问证书的私钥。 Service Fabric 运行时会定期扫描证书存储,并查找每个已知出示规则的匹配项。

对于每个匹配的证书,都会找到相应的私钥,并更新其自定义访问控制列表,以包括授予需要这些权限的标识的权限(通常为读取和执行)。

此过程被非正式地称为“ACLing”。 此过程约需 1 分钟,并且还介绍了“应用程序”证书,例如用于加密设置的证书或终结点证书。 ACLing 遵循出示规则,因此请务必记住:通过指纹声明的证书和未进行后续群集配置更新的自动刷新的证书将不可访问。

证书轮换

备注

Internet 工程任务组 (IETF) RFC 3647 正式将续订定义为颁发与要替换的证书具有相同属性的证书。 颁发者、使用者的公钥和信息将被保留。 重新生成密钥是指使用新的密钥对颁发证书,这对颁发者是否可以更改没有限制。 由于区别可能很重要(试想一下在颁发者固定的情况下通过使用者公用名称来声明证书),本文使用中性术语“轮换”来涵盖这两种场景。 请记住,当非正式地使用“续订”时,它指的是“重新生成密钥”。

如前所述,Key Vault 支持自动证书轮换。 也就是说,关联的证书策略定义在密钥保管库中轮换证书的时间点,不管在表示时采用的是过期前的天数还是总生存期的百分比。 必须在此时间点之后、现在的旧证书过期之前调用预配代理,然后才能将此新证书分发到群集的所有节点。

如果群集中当前正在使用的证书的到期日期早于预定的时间间隔,则 Service Fabric 会通过发出运行状况警告来协助此过程。 配置为观察密钥保管库证书的自动预配代理(Key Vault VM 扩展)会定期轮询密钥保管库,检测轮换,并检索和安装新证书。 通过 VM/VMSS 的“机密”功能进行预配时,需要一位获得授权的操作员使用与新证书对应的进行了版本控制的 Key Vault URI 来更新 VM/VMSS。

现已将轮换后的证书预配给了所有节点。 现在,假设应用于群集证书的轮换是由使用者公用名称声明的,让我们看看接下来会发生什么:

  • 对于群集内部以及与群集之间的新连接,Service Fabric 运行时会查找并选择最近颁发的匹配证书(“NotBefore”属性的最大值)。 这是对早期版本的 Service Fabric 运行时的更改。

  • 现有连接将保持活动状态,或者获允以自然方式过期或终止,并且系统会通知内部处理程序存在新的匹配项。

备注

目前,对于 7.2 CU4 版本及更高版本,Service Fabric 选择具有最大(最近颁发的)NotBefore 属性值的证书。 在低于 7.2 CU4 的版本中,Service Fabric 选择具有最大(最近过期)NotAfter 值的有效证书。

这将转换为以下重要观察结果:

  • 群集或托管的应用程序的可用性优先于用于轮换证书的指令。 群集最终会聚合到新证书,但不保证时间。 结果就是:

    • 对于观察者来说,轮换后的证书可能不会立即完全替换轮换前的证书。 强制立即替换当前使用的证书的唯一方法是重新启动主机。 重启 Service Fabric 节点是不够的,因为在群集中形成租用连接的内核模式组件将不受影响。 另请注意,重启 VM/VMSS 可能会导致暂时丢失可用性。 对于应用程序证书,只需重启各自的应用程序实例即可。

    • 引入不符合验证规则的重新生成密钥的证书可能会实质性地破坏群集。 这方面最常见的示例是出现意外的颁发者:群集证书是在颁发者固定的情况下通过使用者公用名称来声明的,但轮换后的证书是由新的或未声明的颁发者颁发的。

证书清除

目前,Azure 中没有可以显式删除证书的预配。 通常情况下,很难确定是否在给定的时间使用了给定的证书。 相比于群集证书,应用程序证书更加难以进行上述确定。 Service Fabric 本身(而不是预配代理)在任何情况下都不会删除由用户声明的证书。 对于标准预配机制来说,会出现以下情况:

  • 只要声明为 VM/VMSS 机密的证书在 VM/VMSS 定义中被引用并且可以从密钥保管库中检索,它们就会被预配。 如果删除了密钥保管库机密或证书,将使后续 VM/VMSS 部署失败。 同样,如果禁用密钥保管库中的机密版本,也会使引用该机密版本的 VM/VMSS 部署失败。

  • 通过 Key Vault VM 扩展预配的早期版本的证书不一定存在于 VM/VMSS 节点上。 代理仅检索并安装当前版本,它不会删除任何证书。 重置映像节点通常每月发生一次,它会将证书存储重置为 OS 映像的内容,因此将隐式删除早期版本。 但请考虑一下,纵向扩展虚拟机规模集将仅导致系统安装所观察证书的当前版本。 在已安装证书的情况下,请勿采用节点同质化操作。

简化管理:自动滚动更新示例

到目前为止,本文已经介绍了各种机制、限制,概述了复杂的规则和定义,并对中断进行了极端预测。 现在是时候设置自动证书管理来避免所有这些问题了。 让我们在平台即服务 (PaaS) v2 虚拟机规模集上运行的 Azure Service Fabric 群集的上下文中执行此操作,使用 Key Vault 进行机密管理,并利用托管标识,如下所示:

  • 证书的验证从指纹固定更改为使用者 + 颁发者固定。 来自特定颁发者的具有特定使用者的任何证书都同样受信任。
  • 证书的注册和获取在受信任的存储 (Key Vault) 中进行,并由代理(此处为 Key Vault VM 扩展)进行刷新。
  • 证书的预配不再在部署时进行,也不再基于版本(像 Azure 计算资源提供程序那样),而是使用无版本的 Key Vault URI 在部署后进行。
  • 通过用户分配的托管标识授予对密钥保管库的访问权限,这些托管标识是在部署期过程中创建并分配给虚拟机规模集的。
  • 部署后,代理(Key Vault VM 扩展)轮询并刷新在虚拟机规模集的每个节点上观察到的证书。 因此,证书轮换是完全自动化的,因为 Service Fabric 会自动获取最新的有效证书。

此序列可以完全脚本化和自动化,它可以在不让用户接触的情况下对某个已配置为证书自动滚动更新的群集进行初始部署。 后续部分提供了详细的步骤,其中包含混合使用 PowerShell cmdlet 和 JSON 模板的片段。 可以使用与 Azure 交互时所有支持的方法来实现相同的功能。

注意

此示例假定密钥保管库中已存在证书。 如本文前面所述,注册和续订 Key Vault 管理的证书需要先决条件手动步骤。 对于生产环境,请使用 Key Vault 管理的证书。 我们包含了一个特定于 Microsoft 内部 PKI 的示例脚本。

备注

证书自动滚动更新仅适用于 CA 颁发的证书。 使用自签名证书(包括那些在 Azure 门户中部署 Service Fabric 群集期间生成的证书)毫无意义,但如果声明颁发者指纹与叶证书的指纹相同,则对于本地或开发人员托管的部署仍然适用。

起点

为了简单起见,让我们假定采用以下开始状态:

  • Service Fabric 群集存在,并使用通过指纹声明的 CA 颁发的证书进行保护。
  • 此证书存储在密钥保管库中,并预配为虚拟机规模集机密。
  • 将使用同一证书将群集转换为基于通用名称的证书声明,以便可由使用者和颁发者对其进行验证。 如果不是这种情况,请获取用于此目的的 CA 颁发的证书,并通过指纹将其添加到群集定义中。 在 Azure 中为 Service Fabric 群集添加或删除证书中介绍了此过程。

下面是来自对应于此类状态的模板的 JSON 摘录。 此摘录省略了许多必需的设置,仅说明了与证书相关的内容。

  "resources": [
    {   ## VMSS definition
      "apiVersion": "[variables('vmssApiVersion')]",
      "type": "Microsoft.Compute/virtualMachineScaleSets",
      "name": "[variables('vmNodeTypeName')]",
      "location": "[variables('computeLocation')]",
      "properties": {
        "virtualMachineProfile": {
          "extensionProfile": {
            "extensions": [
            {
                "name": "[concat('ServiceFabricNodeVmExt','_vmNodeTypeName')]",
                "properties": {
                  "type": "ServiceFabricNode",
                  "autoUpgradeMinorVersion": true,
                  "publisher": "Microsoft.Azure.ServiceFabric",
                  "settings": {
                    "clusterEndpoint": "[reference(parameters('clusterName')).clusterEndpoint]",
                    "nodeTypeRef": "[variables('vmNodeTypeName')]",
                    "dataPath": "D:\\SvcFab",
                    "durabilityLevel": "Bronze",
                    "certificate": {
                        "thumbprint": "[parameters('primaryClusterCertificateTP')]",
                        "x509StoreName": "[parameters('certificateStoreValue')]"
                    }
                  },
                  "typeHandlerVersion": "1.1"
                }
            },}},
          "osProfile": {
            "adminPassword": "[parameters('adminPassword')]",
            "adminUsername": "[parameters('adminUsername')]",
            "secrets": [
            {
                "sourceVault": {
                    "id": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]"
                },
                "vaultCertificates": [
                {
                    "certificateStore": "[parameters('certificateStoreValue')]",
                    "certificateUrl": "[parameters('clusterCertificateUrlValue')]"
                },
            ]}]
        },
    },
    {   ## cluster definition
        "apiVersion": "[variables('sfrpApiVersion')]",
        "type": "Microsoft.ServiceFabric/clusters",
        "name": "[parameters('clusterName')]",
        "location": "[parameters('clusterLocation')]",
        "certificate": {
            "thumbprint": "[parameters('primaryClusterCertificateTP')]",
            "x509StoreName": "[parameters('certificateStoreValue')]"
        },
    }
  ]

上述代码实质上表示,在 Key Vault URI json [parameters('clusterCertificateUrlValue')] 中找到的具有指纹 json [parameters('primaryClusterCertificateTP')] 的证书已通过指纹声明为群集的唯一证书。

接下来,让我们将设置所需的其他资源,以确保实现证书自动滚动更新。

设置必备资源

如前所述,作为虚拟机规模集机密预配的证书将由 Microsoft.Compute 资源提供程序服务从密钥保管库中检索。 它通过以部署操作员身份使用其第一方标识来实现此目标。 此过程将更改自动注册。 将改为使用分配给虚拟机规模集并且拥有对该保管库中机密的 GET 权限的托管标识。

应同时部署下一个摘录。 它们被单独列出,仅供详细分析和说明。

首先定义一个用户分配的标识(包含默认值作为示例)。 有关详细信息,请参阅官方文档

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "userAssignedIdentityName": {
      "type": "string",
      "defaultValue": "sftstuaicus",
      "metadata": {
        "description": "User-assigned managed identity name"
      }
    },
  },
  "variables": {
      "vmssApiVersion": "2018-06-01",
      "sfrpApiVersion": "2018-02-01",
      "miApiVersion": "2018-11-30",
      "kvApiVersion": "2018-02-14",
      "userAssignedIdentityResourceId": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName'))]"
  },    
  "resources": [
    {
      "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
      "name": "[parameters('userAssignedIdentityName')]",
      "apiVersion": "[variables('miApiVersion')]",
      "location": "[resourceGroup().location]"
    },
  ]}

接下来,授予此标识对密钥保管库机密的访问权限。 有关最新信息,请参阅官方文档

  "resources":
  [{
      "type": "Microsoft.KeyVault/vaults/accessPolicies",
      "name": "[concat(parameters('keyVaultName'), '/add')]",
      "apiVersion": "[variables('kvApiVersion')]",
      "properties": {
        "accessPolicies": [
          {
            "tenantId": "[reference(variables('userAssignedIdentityResourceId'), variables('miApiVersion')).tenantId]",
            "objectId": "[reference(variables('userAssignedIdentityResourceId'), variables('miApiVersion')).principalId]",
            "dependsOn": [
              "[variables('userAssignedIdentityResourceId')]"
            ],
            "permissions": {
              "secrets": [
                "get",
                "list"
              ]}}]}}]

在下一步中,你将执行以下操作:

  • 将用户分配的标识分配给虚拟机规模集。
  • 在创建托管标识并向其授予对密钥保管库的访问权限后声明虚拟机规模集的依赖项。
  • 声明 Key Vault VM 扩展,并要求它在启动时检索观察到的证书。 有关详细信息,请参阅适用于 Windows 的 Key Vault VM 扩展官方文档。
  • 更新 Service Fabric VM 扩展的定义,具体取决于 Key Vault VM 扩展,并将群集证书声明从指纹转换为公用名称。

注意

这些更改是作为一个步骤进行的,因为它们属于同一资源的范围。

  "parameters": {
    "kvvmextPollingInterval": {
      "type": "string",
      "defaultValue": "3600",
      "metadata": {
        "description": "kv vm extension polling interval in seconds"
      }
    },
    "kvvmextLocalStoreName": {
      "type": "string",
      "defaultValue": "MY",
      "metadata": {
        "description": "kv vm extension local store name"
      }
    },
    "kvvmextLocalStoreLocation": {
      "type": "string",
      "defaultValue": "LocalMachine",
      "metadata": {
        "description": "kv vm extension local store location"
      }
    },
    "kvvmextObservedCertificates": {
      "type": "array",
      "defaultValue": [
                "https://sftestcus.vault.azure.net/secrets/sftstcncluster",
                "https://sftestcus.vault.azure.net/secrets/sftstcnserver"
            ],
      "metadata": {
        "description": "kv vm extension observed certificates versionless uri"
      }
    },
    "certificateCommonName": {
      "type": "string",
      "defaultValue": "cus.cluster.sftstcn.system.servicefabric.azure-int",
      "metadata": {
        "description": "Certificate Common name"
      }
    },
  },
  "resources": [
  {
    "apiVersion": "[variables('vmssApiVersion')]",
    "type": "Microsoft.Compute/virtualMachineScaleSets",
    "name": "[variables('vmNodeTypeName')]",
    "location": "[variables('computeLocation')]",
    "dependsOn": [
      "[variables('userAssignedIdentityResourceId')]",
      "[concat('Microsoft.KeyVault/vaults/', concat(parameters('keyVaultName'), '/accessPolicies/add'))]"
    ],
    "identity": {
      "type": "UserAssigned",
      "userAssignedIdentities": {
        "[variables('userAssignedIdentityResourceId')]": {}
      }
    },
    "virtualMachineProfile": {
      "extensionProfile": {
        "extensions": [
        {
          "name": "KVVMExtension",
          "properties": {
            "publisher": "Microsoft.Azure.KeyVault",
            "type": "KeyVaultForWindows",
            "typeHandlerVersion": "1.0",
            "autoUpgradeMinorVersion": true,
            "settings": {
                "secretsManagementSettings": {
                    "pollingIntervalInS": "[parameters('kvvmextPollingInterval')]",
                    "linkOnRenewal": false,
                    "observedCertificates": "[parameters('kvvmextObservedCertificates')]",
                    "requireInitialSync": true
                }
            }
          }
        },
        {
        "name": "[concat('ServiceFabricNodeVmExt','_vmNodeTypeName')]",
        "properties": {
          "type": "ServiceFabricNode",
          "provisionAfterExtensions" : [ "KVVMExtension" ],
          "publisher": "Microsoft.Azure.ServiceFabric",
          "settings": {
            "certificate": {
                "commonNames": [
                    "[parameters('certificateCommonName')]"
                ],
                "x509StoreName": "[parameters('certificateStoreValue')]"
            }
            },
            "typeHandlerVersion": "1.0"
          }
        },
  ] } ## extension profile
  },  ## VM profile
  "osProfile": {
    "adminPassword": "[parameters('adminPassword')]",
    "adminUsername": "[parameters('adminUsername')]",
  } 
  }
  ]

虽然未在上述代码中显式列出,但请注意,密钥保管库证书 URL 已从虚拟机规模集的 OsProfile 部分中删除。

最后一步是更新群集定义,将证书声明从指纹更改为公用名称。 我们还将固定颁发者指纹:

  "parameters": {
    "certificateCommonName": {
      "type": "string",
      "defaultValue": "cus.cluster.sftstcn.system.servicefabric.azure-int",
      "metadata": {
        "description": "Certificate Common name"
      }
    },
    "certificateIssuerThumbprint": {
      "type": "string",
      "defaultValue": "1b45ec255e0668375043ed5fe78a09ff1655844d,d7fe717b5ff3593764f4d90654d86e8362ec26c8,3ac7c3cac8de0dd392c02789c8be97474f456960,96ea05926e2e42cc207e358668be2c316857fb5e",
      "metadata": {
        "description": "Certificate issuer thumbprints separated by comma"
      }
    },
  },
  "resources": [
    {
      "apiVersion": "[variables('sfrpApiVersion')]",
      "type": "Microsoft.ServiceFabric/clusters",
      "name": "[parameters('clusterName')]",
      "location": "[parameters('clusterLocation')]",
      "properties": {
        "certificateCommonNames": {
          "commonNames": [{
              "certificateCommonName": "[parameters('certificateCommonName')]",
              "certificateIssuerThumbprint": "[parameters('certificateIssuerThumbprint')]"
          }],
          "x509StoreName": "[parameters('certificateStoreValue')]"
        },
  ]

此时,你可以在单个部署中运行前面提到的更新。 Service Fabric 资源提供程序服务本身将按几个步骤拆分群集升级,如将群集证书从指纹转换为公用名称中所述。

分析和观察

本部分概述了本文中所介绍的概念和过程,并引吸引了人们对某些其他重要方面的兴趣。

关于证书预配

作为预配代理的 Key Vault VM 扩展以预定的频率连续运行。 如果无法检索观察到的证书,则会转到行中的下一目标,然后休眠至下一周期。 作为群集启动代理的 Service Fabric VM 扩展需要声明的证书,否则群集无法形成。 这反过来意味着,Service Fabric VM 扩展只能在成功检索群集证书之后运行,在此处由 json "provisionAfterExtensions" : [ "KVVMExtension" ]" 子句表示,以及由 Key Vault VM 扩展的 json "requireInitialSync": true 设置表示。

这是向 Key Vault VM 扩展指示:首次运行时(在部署或重启后)必须循环访问其观察到的证书,直到全部下载成功。 如果将此参数设置为“false”,则在检索群集证书失败的情况下,群集部署也会失败。 相反,如果在要求初始同步时所使用的已观察到的证书的列表不正确或无效,则会导致 Key Vault VM 扩展失败,并再次导致群集部署失败。

证书链接(已介绍)

你可能已注意到 Key Vault VM 扩展 linkOnRenewal 标志,以及它已设置为 false 这一事实。 此设置将探讨此标志控制的行为及其对群集功能的影响。 此行为特定于 Windows。

根据其定义

"linkOnRenewal": <Only Windows. This feature enables auto-rotation of SSL certificates, without necessitating a re-deployment or binding. e.g.: false>,

用于建立 TLS 连接的证书通常通过 Schannel 安全支持提供程序作为句柄获取。 也就是说,客户端不直接访问证书本身的私钥。 Schannel 支持以证书扩展 CERT_RENEWAL_PROP_ID 的形式重定向或链接凭据。

如果设置了此属性,则其值表示续订证书的指纹,因此 Schannel 将改为尝试加载链接的证书。 事实上,Schannel 会遍历此链接的(希望是非循环的)列表,直到它最后出现“最终”证书(一个没有续订标记的证书)。 如果使用得当,此功能可以很好地缓解因证书过期等原因导致的可用性损失。

在其他情况下,它可能导致难以诊断和缓解的中断。 Schannel 无条件地在其续订属性上执行证书遍历,而不考虑使用者、颁发者或其他任何特定属性,这些属性参与客户端生成的证书的验证。 生成的证书可能没有关联的私钥,或者该密钥尚未被 ACL 授予其预期使用者。

如果启用了链接,则 Key Vault VM 扩展在从密钥保管库检索观察到的证书时,将尝试查找匹配的现有证书,以便通过续订扩展属性来链接它们。 匹配仅基于使用者可选名称 (SAN),如果存在两个现有证书,则匹配有效,如以下示例所示:A: Certificate name (CN) = “Alice's accessories”, SAN = {“alice.universalexports.com”}, renewal = ‘’ B: CN = “Bob's bits”, SAN = {“bob.universalexports.com”, “bob.universalexports.net”}, renewal = ‘’

假设证书 C 通过 Key Vault VM 扩展检索:CN = “Mallory's malware”, SAN = {“alice.universalexports.com”, “bob.universalexports.com”, “mallory.universalexports.com”}

证书 A 的 SAN 列表完全包含在 C 中,因此 A.renewal = C.thumbprint。 证书 B 的 SAN 列表与 C 有一个共同的交集,但并不完全包含在其中,因此 B.renewal 将保留为空。

在证书 A 上,在此状态下调用 AcquireCredentialsHandle (Schannel) 的任何尝试实际上最终都会将 C 发送到远程方。 在使用 Service Fabric 的情况下,群集的传输子系统使用 Schannel 进行相互身份验证,因此上述行为直接影响群集的基本通信。 继续前面的示例,假设 A 是群集证书,接下来发生的情况取决于:

  • 如果 C 的私钥尚未被 ACL 授予 Service Fabric 运行时采用的帐户,你将看到获取私钥失败(SEC_E_UNKNOWN_CREDENTIALS 或类似项)。
  • 如果 C 的私钥是可访问的,你将看到其他节点返回的授权失败(CertificateNotMatched、未授权等等)。

在任一情况下,传输都会失败,群集可能会关闭。 具体表现各不相同。 更糟糕的是,链接取决于续订顺序,而续订顺序取决于 Key Vault VM 扩展中已观察到的证书的列表顺序、密钥保管库中的续订计划或甚至那些会改变检索顺序的暂时性错误。

若要缓解此类事件,我们建议你采取以下措施:

  • 不要混合使用不同保管库证书的使用者可选名称。 每个保管库证书应具有不同的用途,且其使用者和 SAN 应通过特异性反映出相关情况。

  • 在 SAN 列表中包括使用者公用名称(按原义,即 CN=<subject common name>)。

  • 如果不确定如何继续,请禁用通过 Key Vault VM 扩展预配的证书的续订链接。

    注意

    禁用链接是 Key Vault VM 扩展的顶级属性,不能为单个观察到的证书设置。

为什么应使用用户分配的托管标识? 使用它会产生什么影响?

从前面的 JSON 片段可以明显看出,需要对操作和更新进行特定的排序,才能保证转换成功并维护群集的可用性。 具体而言,虚拟机规模集资源会声明并使用其标识在单个(从用户的角度来看)更新中检索机密。

用于启动群集的 Service Fabric VM 扩展取决于 Key Vault VM 扩展是否完成,而这又取决于观察到的证书是否成功检索。

Key Vault VM 扩展使用虚拟机规模集的标识来访问密钥保管库,这意味着在部署虚拟机规模集之前,密钥保管库上的访问策略必须已更新。

若要释放托管标识的创建操作或将其分配给其他资源,部署操作员必须具有订阅或资源组中的必需角色 (ManagedIdentityOperator),以及管理模板中引用的其他资源所需的角色。

从安全性角度来看,回想一下,可以将虚拟机规模集视为与其 Azure 标识相关的安全边界。 这意味着托管在 VM 上的任何应用程序原则上都可以获得表示 VM 的访问令牌。 托管标识访问令牌是从未经身份验证的实例元数据服务终结点获取的。 如果将 VM 视为共享或多租户环境,则可能不会指示此检索群集证书的方法。 不过,它是适用于证书自动滚动更新的唯一预配机制。

故障排除和常见问题解答

问:如何以编程方式注册到 Key Vault 托管的证书?

找到 Key Vault 文档中的颁发者名称,然后将其替换为以下脚本:

  $issuerName=<depends on your PKI of choice>
	$clusterVault="sftestcus"
	$clusterCertVaultName="sftstcncluster"
	$clusterCertCN="cus.cluster.sftstcn.system.servicefabric.azure-int"
	Set-AzKeyVaultCertificateIssuer -VaultName $clusterVault -Name $issuerName -IssuerProvider $issuerName
	$distinguishedName="CN=" + $clusterCertCN
	$policy = New-AzKeyVaultCertificatePolicy `
	    -IssuerName $issuerName `
	    -SubjectName $distinguishedName `
	    -SecretContentType 'application/x-pkcs12' `
	    -Ekus "1.3.6.1.5.5.7.3.1", "1.3.6.1.5.5.7.3.2" `
	    -ValidityInMonths 4 `
	    -KeyType 'RSA' `
	    -RenewAtPercentageLifetime 75        
	Add-AzKeyVaultCertificate -VaultName $clusterVault -Name $clusterCertVaultName -CertificatePolicy $policy
	
	# poll the result of the enrollment
	Get-AzKeyVaultCertificateOperation -VaultName $clusterVault -Name $clusterCertVaultName 

问:如果证书是由未声明或未指定的颁发者颁发的,会发生什么情况? 在哪里可以获取指定 PKI 的活动颁发者的详尽列表?

如果证书声明指定了颁发者指纹,并且该证书的直接颁发者未包含在固定颁发者列表中,则该证书会被视为无效,无论其根是否受客户端信任。 因此,必须确保颁发者列表是最新的,这一点非常重要。 引入新的颁发者是一种罕见事件,应在开始颁发证书之前广而告之。

通常情况下,根据 IETF RFC 7382 的要求,PKI 会发布和维护认证实践声明。 除其他信息外,该声明还包括所有活动颁发者。 以编程方式检索此列表可能因 PKI 不同而异。

对于 Microsoft 内部 PKI,请务必查阅有关用于检索已获授权的颁发者的终结点和 SDK 的内部文档。 群集所有者有责任定期查看此列表,以确保其群集定义包括所有预期的颁发者。

问:是否支持多个 PKI?

是的。 你不能在群集清单中用相同的值声明多个 CN 项,但可以列出与同一 CN 对应的多个 PKI 中的颁发者。 建议不要这样做,证书透明度规范可能会阻止颁发此类证书。 然而,作为一种从一个 PKI 迁移到另一个 PKI 的方法,这是一种可接受的机制。

问:如果当前群集证书不是由 CA 颁发的,或者没有所需的使用者,该怎么办?

使用预期的使用者获取证书,并根据指纹将其作为次要证书添加到群集的定义中。 升级成功完成后,再次启动群集配置升级,以将证书声明转换为公用名称。