使用 Anthos Service Mesh 保护和加密 Anthos 集群之间的通信

本文档向管理 Kubernetes 集群的网络、平台和安全工程师介绍如何使用 Anthos Service Mesh 入站和出站网关处理集群间的外部通信。本文档介绍如何使用 Anthos Service Mesh 加密和帮助保护从一个 Kubernetes 集群上部署的工作负载到另一个 Kubernetes 集群中运行的工作负载的出站流量。所述技术展示了如何将不同的证书用于集群间的双向加密通信。

本文档中的指南源于客户的要求,即将特定的根证书授权机构 (CA) 用于集群间通信。金融服务或医疗保健等严格监管的市场可能会有此类要求。本文档中的指南还允许使用 Kubernetes 集群以外的端点,例如财务清算提供商或敏感数据的 API 接口。本指南对于需要遵守严格的安全和审核政策的组织尤为有用。

您可以操作和处理加密通信,而无需涉及集群中运行的工作负载。如需了解如何配置您自己的集群,请按照配套教程进行操作。

简介

企业刚开始采用 Kubernetes 时,通常从单个集群起步,大部分通信都在集群中。很快,跨命名空间的交互变得越来越重要,此时,CalicoCillium 等网络政策提供商可提供帮助。但是,随着容器环境的扩大,确保外部服务与 Kubernetes 集群中运行的容器之间的通信安全变得更为重要。

网络政策在处理传统安全概念(例如创建集群内部防火墙规则)方面表现出色,但在集群外部作用有限。您可以仅允许访问特定 IP 地址,但无法控制内容或身份。因此,您需要更全面的概念,以帮助加密发送到其他外部服务的流量。下图提供了一种方法。

使用私有(密钥)证书和公共证书加密流量。

在应用领域,通常使用 TLS(传输层安全协议)来进行加密。这意味着您可以使用私有(密钥)证书和公共证书加密流量,如上图所示。接收方持有公共证书,用于验证信息来自可信来源。HTTPS 网络流量使用 TLS 来帮助确保客户端与网络服务器之间的通信是安全和加密的。在这种情况下,公共证书来自可信颁发者(例如 Google Trust Services),也称为 CA,是公钥基础架构 (PKI) 的一部分。TLS 验证服务器的身份,但不会验证客户端的身份。

如果客户端本身也必须通过验证,则需要双向身份验证 (mTLS)。如果发送方和接收方都必须向对方标识自己的身份,则使用 mTLS,如下图所示。

使用双向身份验证 (mTLS) 来加密流量。

此方法通常用于需要一层额外的安全保障的应用。在金融行业以及对于个人身份信息 (PII),监管机构通常要求 mTLS。

Anthos Service Mesh

Anthos Service Mesh 是 Google 管理的服务网格解决方案,它基于 OSS Istio。这意味着它与 Istio API 完全兼容。Anthos Service Mesh 可以在平台级别(而不是应用代码内)提供 mTLS 功能,这意味着您不需要对每个服务进行重新编码即可将其应用于服务。证书轮替等操作也是 Anthos Service Mesh 的一部分。本文档重点介绍 mTLS 和 Anthos Service Mesh 的外部通信功能。它还有许多其他功能,例如故障注入、高级负载平衡和错误处理。

服务网格(例如 Anthos Service Mesh)通过 Sidecar 代理 (Envoy) 路由所有流量,从而使开发者免于执行枯燥但重要的任务,例如加密和证书处理。通过使用透明的代理,服务网格可以支持强大的 L7 功能,例如根据标头信息路由 HTTP 和 HTTPS 调用。但是,Anthos Service Mesh 还支持流量封装和加密,这有助于提高安全性。

示例配置:集群间的 MySQL 通信

如果您希望不同集群中的服务之间进行安全可信的通信,则可以使用此场景。在此示例中,MySQL 客户端应用与另一个 Kubernetes 集群中运行的 MySQL 服务器数据库工作负载通信,如下图所示。

MySQL 客户端应用与另一个 Kubernetes 集群中运行的 MySQL 服务器数据库工作负载通信。

虽然服务网格通常在 OSI L7 上运行,但您也可以使用其部分功能来控制 L4 通信。

要使此概念发挥作用,需要满足以下要求:

  • 应用和集群之间的通信必须加密。
  • 客户端和服务器通信需要相互验证 (mTLS)。
  • 无需更改客户端和服务器工作负载。

虽然您可以将 MySQL 数据库设置为仅接受加密连接,但此设置需要更改数据库客户端,而您可能没有足够的权限这样做。

您可以使用 Anthos Service Mesh 通过多种方法来满足这些要求。一种方法是在集群之间创建一个共享 Istio 控制层面,并让服务彼此通信,因为它们属于同一个逻辑服务网格。对于支持 Anthos 的 GKE 集群,您可以在单项目多项目设置中使用 Anthos Service Mesh 来实现此方法。

但是,由于要求集群间通信有单独的信任链,您可以采用使用 mTLS 的出站网关 <–> 入站网关方法。

出站和入站网关是位于网格边界上的 Envoy 代理。

您可以配置网关以控制进出服务网格的流量。此机制也适用于非 Kubernetes 端点并允许您为加密通信使用不同的证书。

配置 Anthos Service Mesh 出站流量和入站流量

在上述场景中,您通过在相应集群间使用出站和入站网关来处理安全的集群间通信。

什么是出站网关?

出站流量是流出服务网格的流量。出站网关为该流量提供受控的出口点。

如果不进行其他配置,对于已注入 Sidecar 代理的 pod,目标为网格外部的服务(例如公共 API 服务)的流量将从 pod 路由到 Sidecar 代理。在 GKE 集群(以及大多数其他 Kubernetes 集群)中,节点 IP 地址使用 NAT 来转换 Sidecar 代理流量,然后这些流量直接流向服务的外部地址。下图展示了此配置。

客户端调用代表外部服务的服务器端。

在该图中,客户端调用代表外部服务的服务器端。对于网格,此流量是出站流量,因此您需要在客户端(例如 MySQL 客户端)配置出站网关。

您配置出站网关,将调用转发到外部服务。外部服务处理请求并返回响应后,再次通过出站网关返回到客户端代理,并最终返回到发出调用的 pod(例如 MySQL 客户端)。

什么是入站网关?

入站流量是流入服务网格的流量。入站网关向外部环境(即服务网格外部)公开服务,并处理应如何访问这些服务。它相当于 Kubernetes Ingress 对象。

借助入站网关,您可以定义一个受控的入口点,流量将通过此入口点流入网格。首先,流量通过负载平衡器(通过定义入站网关服务创建)进入。然后,请求被转发到 Sidecar 代理,并从代理转发到 pod。pod 处理请求,然后使用反方向路由返回响应。下图展示了此配置。

流量通过负载平衡器进入,请求被转发到 Sidecar 代理和 pod。

此流量是到另一个集群的网格 (VPC 2) 的入站流量。因此,您需要在服务器端配置入站网关来处理这些调用。

入站网关的服务器端配置会将调用转发到内部服务。内部服务处理请求后,响应将反向遍历路由,通过入站网关返回到客户端。

结合出站和入站功能以实现双向 TLS

如前所述,您需要为客户端定义充当服务网格的受控出口点的出站网关。为确保通过网关离开网格的流量被 mTLS 加密,您可以使用 TLS 源技术。配置出站网关,以便为流向外部服务的流量执行 TLS 源。

当从客户端离开服务网格的流量经过加密时,您需要确保服务器端能够向客户端标识自己的身份并破解加密的流量。

为此,您可以将入站网关作为网格的单一入口点。配置入站网关,使其接受双向加密的流量。

网格架构概览

下图展示了在无需对应用或服务器进行任何更改的情况下为 MySQL 场景实现此概念的必要条件。

在 VPC 1 中,您可以看到运行 MySQL 客户端的客户端集群正在访问服务器。服务器位于 VPC 2 中。

客户端比服务器端有更多配置,因为您需要进行更多流量匹配和路由,以确保应用使用出站网关。但是,此配置是零日操作,这意味着您只需执行一次。此配置实现后,维护起来非常容易。

使用 Kubernetes 实现此概念的一个好处是,所有配置项都存储在 YAML 文件中。这意味着可以在有版本控制的代码库中使用整个构造,这使您能够跟踪更改并在必要时轻松还原。

客户端

本小节介绍客户端配置。您在图中看到的每个元素在网格中都有独特的功能,可用于控制流量如何通过出站网关路由到其目标 MySQL 服务器。

流量路由只是所需功能的一部分。其他元素控制完全透明的流量加密,以确保通信始终安全。以下部分介绍了这些元素,帮助您进一步理解它们在此场景中的角色和功能。

显示流量如何通过出站网关路由到 MySQL 服务器的客户端配置。

服务条目

服务网格将自己的服务注册表创建到 Kubernetes 集群中。控制层面使用此注册表将 Sidecar 代理配置为路由表。在 Kubernetes 中运行的服务将被自动发现并添加到服务网格注册表中。不在 Kubernetes 集群中运行的服务无法被自动发现,但可以使用 ServiceEntry 来定义这些服务。这样,客户端就可以将条目用作主机名以连接到外部服务。

在 Anthos Service Mesh 中,完全限定域名 (FQDN) 用于标识所有服务。FQDN 是此构造中最重要的部分,因为证书基于主机名。虽然可以更改 FQDN,但这意味着您需要重新生成所有必需的证书。

为了进行通信,您必须将服务网格配置为侦听对外部服务的调用,以便正确路由流量。通过网格,您可以定义指向该外部服务的服务条目

此构造称为 MESH_EXTERNAL,非常适合这些使用场景。您可能还需要指定要查找的内容。由于这是 L4 使用场景,您只能控制 IP 地址和端口,因此您需要向网格告知协议及特定端口,在本例中为 TCP 和端口 3306(标准 MySQL 协议端口)。此外,服务器端对应组件(如上图所示)侦听端口 13306(服务器集群的出站网关)。最后,您需要指示服务条目捕获具有此端口标记的流量。

以下示例 YAML 服务条目展示了此配置:

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
 name: mysql-external
spec:
 hosts:
   - mysql.fqdn.example
 location: MESH_EXTERNAL
 ports:
   - number: 3306
     name: tcp
     protocol: TCP
   - number: 13306
     name: tls
     protocol: TLS
 resolution: DNS
 endpoints:
   - address: mysql.fqdn.example
     ports:
       tls: 13306

hosts 字段中,您可以设置外部服务的 FQDN,也可以将 location 字段指定为 MESH_EXTERNAL。您还必须指定外部服务使用的 ports 值,在本例中为 13306330613306 将是服务器端入站网关的公开端口。请务必在此服务条目中同时指定两者。对于协议,您必须指定 TLS,因为此连接提供基于 L4 的 TLS 通信。

定义服务条目后,网格可以侦听调用,并根据这些规则更改路由。

服务条目必须基于现有的 DNS 或 IP 地址条目,这表示 DNS 名称应该已经能被 DNS 服务器解析。例如,您可以在 Kubernetes 中使用核心 DNS 服务,并向其添加 kube-dns 中尚不存在的条目。您无法使用服务条目创建 DNS 条目。

虚拟服务

虚拟服务是用于影响流量路由模式的定义。您使用虚拟服务来确保 MySQL 客户端对服务条目的调用被路由到出站网关。因此,您可以设置虚拟服务,以根据截然不同的因素来路由流量。在 L7 使用场景中,这些因素不仅仅是流量路由。例如,您可以告诉虚拟服务在目标无法访问时如何反应。此示例使用此功能的一部分,以便将匹配的流量仅路由到出站网关供进一步处理。

使用虚拟服务将流量从 pod 通过代理路由到出站网关,然后从出站网关路由到外部服务。

上图显示了如何使用虚拟服务将流量从 pod 通过代理路由到出站网关,然后从出站网关路由到外部服务。

您还必须指定出站网关的端口(面向外部),默认为 15443。您创建出站网关后,系统会自动在出站网关上设置此端口。您可以选择任何其他可用端口,但需要修补网关才能打开所选端口。

以下代码段展示了一个虚拟服务定义示例:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: direct-mysql-through-egress-gateway
spec:
 hosts:
   - mysql.fqdn.example
 gateways:
   - istio-egressgateway-mysql
   - mesh
 tcp:
   - match:
       - gateways:
           - mesh
         port: 3306
     route:
       - destination:
           host: istio-egressgateway.istio-system.svc.cluster.local
           subset: mysql
           port:
             number: 15443
         weight: 100
   - match:
       - gateways:
           - istio-egressgateway-mysql
         port: 15443
     route:
       - destination:
           host: mysql.fqdn.example
           port:
             number: 13306
         weight: 100

包含 FQDN 网址的 hosts 字段用于专门向给定网址应用匹配规则。第一个 match 子句在网格级别上定义,这是一个保留关键字,应用于网格中的所有网关。第一个 route 块指示网格在匹配时为 true 时执行的操作。在此例中,将匹配的流量发送到出站网关。除路由权重外,此处还定义了出站端口。此代码块还提及了 subset 值,您稍后将进行定义。

第二个 match 子句应用于出站网关。附加到第二个 match 子句的第二个 route 块将网格配置为将流量发送到服务器集群入站网关,具体方法是在 host 字段中使用入站 FQDN 并指定端口 13306

在下一步中,您必须将证书注入编程到网关中,以使 mTLS 通信正常工作。

目标规则

现在流量已正确识别(服务条目)并从 pod 通过代理路由到网关(虚拟服务),下一步是加密流量。您可以使用目标规则来加密流量。服务网格中的这类规则在路由后应用于流量,并用于引入负载平衡和其他流量管理功能。

在路由后对流量应用目标规则。

在这种情况下,您可以使用目标规则来定义标准负载平衡模式并添加证书以使用 mTLS 通信启用端点。此步骤的具体执行方法是匹配 MySQL 服务器的 FQDN(通过服务器集群的入站网关公开)并定义 mTLS 规则。

以下定义是这种目标规则的一个示例:

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: egressgateway-for-mysql
spec:
  host: istio-egressgateway.istio-system.svc.cluster.local
  subsets:
    - name: mysql
      trafficPolicy:
        loadBalancer:
          simple: ROUND_ROBIN
        portLevelSettings:
          - port:
              number: 15443
            tls:
              mode: ISTIO_MUTUAL
              sni: mysql.fqdn.example

host 字段设置为出站网关的集群 FQDN。第一个目标规则使用 ISTIO_MUTUAL 模式(使用出站网关的 FQDN)执行流量的网格内部加密。在代码段中,您可以定义 subset,用于创建轮询负载平衡,并将端口设置(覆盖)为 15443。出站网关使用此端口发送流量。

请务必正确设置 tls 字段,因为它定义了网格内部政策 (ISTIO_MUTUAL)。在 sni(服务名称指示)字段下,添加服务器集群的入站网关的 FQDN。

第二个目标规则使用自定义提供的根 CA 证书对流量进行加密,然后再通过出站网关发送流量:

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
 name: originate-mtls-for-mysql
spec:
 host: mysql.fqdn.example
 trafficPolicy:
   loadBalancer:
     simple: ROUND_ROBIN
   portLevelSettings:
   - port:
       number: 13306
     tls:
       mode: MUTUAL
       credentialName: client-credential
       sni: mysql.fqdn.example

host 字段重新设置为外部 FQDN。trafficPolicy 字段将负载平衡器模式设置为 ROUND_ROBIN。它还将端口设置为 13306,并将 tls 模式设置为 MUTUAL,因为您现在使用的是自定义根 CA 证书并且对应组件(入站网关也使用 tls MUTUAL)必须使用相同的签名根 CA 证书标识自己。使用此端口,流量可以通过服务器集群的入站网关流入服务器集群并到达 MySQL 数据库。

使用自定义根 CA 证书的加密通常通过 Envoy Secret Discovery Service (SDS) 完成,并使用 Kubernetes 中的 Secret 来保存证书。使用 credentialName 字段将 Secret 名称添加到目标规则。

概括而言,流量的行为如下:

  • MySQL 以透明的方式向外部 FQDN 发送流量。网格注册表中已存在此 FQDN。
  • 使用目标内部规则通过内部网格证书对流量进行加密。
  • 虚拟服务将流量路由到网关。
  • 目标规则使用自定义根 CA 对流量进行加密(这与用于网格证书的网格 CA 不同)。
  • 在 mTLS 模式下通过出站网关转发流量。

服务器端

在此场景中,服务器端比客户端容易配置。服务器端只需要入站网关和虚拟服务条目来将流量路由到 MySQL 数据库服务器,如下图所示。

包含入站网关和将流量路由到 MySQL 服务器的虚拟服务条目的服务器端配置。

服务器集群入站网关

入站网关公开了端口 13306。这可以是任何端口,但在此例中,为了方便识别,在标准 MySQL 端口前面添加了“1”。出于安全考虑,我们建议不要将标准 MySQL 端口 (3306) 直接暴露在互联网上。

由于默认 Istio 入站网关不侦听端口 13306,您需要添加此功能。以下示例代码段向网关添加端口 13306 补丁:

[{
  "op": "add",
  "path": "/spec/ports/0",
  "value": {
    "name": "tls-mysql",
    "protocol": "TCP",
    "targetPort": 13306,
    "port": 13306
  }
}]

您可以将此代码存储为 JSON 文件,并与 kubectl patch 命令一起使用,以将其应用于入站网关服务。

为了正确处理流量,必须在 MUTUAL 模式下设置入站网关。

此时,入站网关使用其凭据存储中的证书解密传入流量,并将流量发送到网格中。在网格中则使用网格内部证书来重新加密流量。下面的示例代码段展示了如何配置此网关:

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
 name: gateway-mysql
spec:
 selector:
   istio: ingressgateway # Istio default gateway implementation
 servers:
 - port:
     number: 13306
     name: tls-mysql
     protocol: TLS
   tls:
     mode: MUTUAL
     credentialName: mysql-credential
   hosts:
   - "mysql.fqdn.example"

在此示例中,selector 字段下使用标准 Istio 入站网关。您可以使用 servers 字段设置入站网关应预期的端口 number (13306) 和 protocol (TLS) 值。请务必为端口指定唯一名称。

定义 tls 并使用 credentialName 字段提供包含证书的 Secret,该证书由出站网关使用的相同根 CA 签名。证书必须存储在 Kubernetes Secret 中。

最后,您希望匹配指向 MySQL 数据库 FQDN 的流量。在 hosts 下设置的此 FQDN 的名称解析必须设置为入站网关的公共 IP 地址。

服务器集群虚拟服务

来自客户端集群(发起方)的出站网关的流量通过端口 13306 进入网格后,您必须标识此流量并确保其到达 MySQL 数据库服务器。为此,您可以定义一个虚拟服务:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: mysql-virtual-service
spec:
 hosts:
   - "mysql.fqdn.example"
 gateways:
   - gateway-mysql
 tcp:
   - route:
     - destination:
         port:
           number: 3306
         host: mysql.default.svc.cluster.local

要将流量发送到 MySQL 数据库服务,您需要使用 hosts 字段再次检查 FQDN 主机。此外,您必须使用 gateways 字段来配置应用此虚拟服务定义的位置。这是您在前面的 YAML 文件中定义的网关对象。由于这是 L4 流量,请设置 tcp 字段,并将 route 字段设置为指向 MySQL 数据库 Kubernetes 服务。您必须使用 Kubernetes 集群内部 FQDN 在 host 字段下指定服务名称。

MySQL 数据库在端口 `3306` 上收到客户端发送的请求。流量遍历 MySQL 数据库服务器的 Sidecar 代理。

MySQL 数据库可以通过端口 3306 接收客户端发送的请求。流量遍历 MySQL 数据库服务器的 Sidecar 代理。在 MySQL 数据库服务器看来,该请求是一个访问数据库的未加密本地请求。

服务器响应调用后,流量使用相同的路由返回到客户端。在客户端看来,本地数据库刚刚响应了其调用。

流量从客户端遍历到服务器的过程中,使用不同的证书进行了三次加密,这有助于保护客户端与服务器之间的通信。

第一次,在客户端的网格内部,使用网格 CA 签名的证书加密或解密流量。

第二次,离开网格时,在网关处,使用自定义根 CA 签名的证书加密流量。然后,在入站网关处,使用相同的自定义根 CA 签名的证书对流量进行身份验证和解密。

最后(第三)次,从入站网关遍历到 MySQL 服务器时,在服务器端的网格内部对流量进行加密或解密。同样地,此处使用网格 CA 的证书,因为这是在网格内部。

在此场景中,必须使用所述根 CA 对两个集群之间的通信进行加密。应用此配置后,可以独立于网格内部证书和应用本身,单独处理此部分。

通过这一额外的步骤,此配置使您可以轻松地定期轮替这些证书,而无需更改各自的 Kubernetes 集群的网格 CA。

后续步骤