基于 Istio 的 ServiceMesh 方案中支持 Mutal TLS, 其实现依赖 Envoy 提供的 TLS 功能, TLS Transport Socket.

Envoy

对于服务端(upstream)而言, 相关配置在 listener.filter_chains[].transport_socket, 对应 DownstreamTlsContext, 用于约束客户端需要满足的要求, 主要信息为公钥.

对于客户端(downstream)而言, 相关配置在 cluster.transport_socket_match, 对应 UpstreamTlsContext, 用于阐明链接服务端时的私钥和 SNI.

以实际应用为例, 首先通过 istioctl 获取某个 Pod 的相关配置并保存到 uw.json.

istioctl proxy-config all user-web-default-658548cdd-97sgl -o json > uw.json

由于集群运行在 PERMISSIVE 模式下, 即同时允许 PLAINTEXT 和 Mutal TLS, 所以 Istio 为服务的 gRPC 端口 50051 创建了两个监听.

~ cat uw.json | jq '.configs[] | select(.["@type"] | test(".*ListenersConfigDump")) | .dynamic_listeners[].active_state.listener | select(.name == "virtualInbound") | .filter_chains[-2:][].name'
"0.0.0.0_50051"
"0.0.0.0_50051"
~ cat uw.json | jq '.configs[] | select(.["@type"] | test(".*ListenersConfigDump")) | .dynamic_listeners[].active_state.listener | select(.name == "virtualInbound") | .filter_chains[-2:][].filter_chain_match'
{
  "destination_port": 50051,
  "transport_protocol": "tls",
  "application_protocols": [
    "istio",
    "istio-peer-exchange",
    "istio-http/1.0",
    "istio-http/1.1",
    "istio-h2"
  ]
}
{
  "destination_port": 50051,
  "transport_protocol": "raw_buffer"
}
~ cat uw.json | jq '.configs[] | select(.["@type"] | test(".*ListenersConfigDump")) | .dynamic_listeners[].active_state.listener | select(.name == "virtualInbound") | .filter_chains[-2:][] | keys'
[
  "filter_chain_match",
  "filters",
  "name",
  "transport_socket"
]
[
  "filter_chain_match",
  "filters",
  "name"
]

其中 TLS 相关的核心内容是申明使用动态获取的证书(ROOTCA)来校验客户端.

cat uw.json | jq '.configs[] | select(.["@type"] | test(".*ListenersConfigDump")) | .dynamic_listeners[].active_state.listener | select(.name == "virtualInbound") | .filter_chains[-2].transport_socket.typed_config.common_tls_context.combined_validation_context'
{
  "default_validation_context": {
    "match_subject_alt_names": [
      {
        "prefix": "spiffe://cluster.local/"
      }
    ]
  },
  "validation_context_sds_secret_config": {
    "name": "ROOTCA",
    "sds_config": {
      "api_config_source": {
        "api_type": "GRPC",
        "grpc_services": [
          {
            "envoy_grpc": {
              "cluster_name": "sds-grpc"
            }
          }
        ],
        "set_node_on_first_message_only": true,
        "transport_api_version": "V3"
      },
      "initial_fetch_timeout": "0s",
      "resource_api_version": "V3"
    }
  }
}

Cluster 的配置中同样也有两份, 分别针对使用和不使用 TLS.

~ cat uw.json | jq '.configs[] | select(.["@type"] | test(".*ClustersConfigDump")) | .dynamic_active_clusters[].cluster | select(.name == "outbound|50051|default|uc.dev.svc.cluster.local") | .transport_socket_matches[].name'
"tlsMode-istio"
"tlsMode-disabled"
~ cat uw.json | jq '.configs[] | select(.["@type"] | test(".*ClustersConfigDump")) | .dynamic_active_clusters[].cluster | select(.name == "outbound|50051|default|uc.dev.svc.cluster.local") | .transport_socket_matches[].match'
{
  "tlsMode": "istio"
}
{}

从文档上来看, Istio 为 endpoint 设置了相关的 metadata, 进而动态匹配 match, 使用对应的配置.

~ cat uw.json | jq '.configs[] | select(.["@type"] | test(".*EndpointsConfigDump")) | .dynamic_endpoint_configs[].endpoint_config | select(.cluster_name == "outbound|50051|default|uc.dev.svc.cluster.local") | .endpoints[].lb_endpoints[].metadata'
{
  "filter_metadata": {
    "istio": {
      "workload": "uc-default;dev;uc;;cn-shanghai"
    },
    "envoy.transport_socket_match": {
      "tlsMode": "istio"
    }
  }
}

具体看 downstream 的 TLS 配置, 其中申明了:

  • 请求 SNI
  • 请求加密的私钥证书为 default
  • 使用公钥证书 ROOTCA 来校验服务端
  • 限制了服务端证书中的 spiffe
    ~ cat uw.json | jq '.configs[] | select(.["@type"] | test(".*ClustersConfigDump")) | .dynamic_active_clusters[].cluster | select(.name == "outbound|50051|default|uc.dev.svc.cluster.local") | .transport_socket_matches[0].transport_socket.typed_config'
    {
    "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
    "common_tls_context": {
      "tls_params": {
        "tls_minimum_protocol_version": "TLSv1_2",
        "tls_maximum_protocol_version": "TLSv1_3"
      },
      "alpn_protocols": [
        "istio-peer-exchange",
        "istio",
        "h2"
      ],
      "tls_certificate_sds_secret_configs": [
        {
          "name": "default",
          "sds_config": {
            "api_config_source": {
              "api_type": "GRPC",
              "grpc_services": [
                {
                  "envoy_grpc": {
                    "cluster_name": "sds-grpc"
                  }
                }
              ],
              "set_node_on_first_message_only": true,
              "transport_api_version": "V3"
            },
            "initial_fetch_timeout": "0s",
            "resource_api_version": "V3"
          }
        }
      ],
      "combined_validation_context": {
        "default_validation_context": {
          "match_subject_alt_names": [
            {
              "exact": "spiffe://cluster.local/ns/dev/sa/default"
            }
          ]
        },
        "validation_context_sds_secret_config": {
          "name": "ROOTCA",
          "sds_config": {
            "api_config_source": {
              "api_type": "GRPC",
              "grpc_services": [
                {
                  "envoy_grpc": {
                    "cluster_name": "sds-grpc"
                  }
                }
              ],
              "set_node_on_first_message_only": true,
              "transport_api_version": "V3"
            },
            "initial_fetch_timeout": "0s",
            "resource_api_version": "V3"
          }
        }
      }
    },
    "sni": "outbound_.50051_.default_.uc.dev.svc.cluster.local"
    }
    

Istio

上述提及的两个证书都是通过 SDS 动态从 cluster sds-grpc 获取的, 其对应的地址为 /var/run/secrets/worload-spiffe-uds/socket.

~ cat uw.json | jq '.configs[] | select(.["@type"] | test(".*ClustersConfigDump")) | .static_clusters[].cluster | select(.name == "sds-grpc")'
{
  "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
  "name": "sds-grpc",
  "type": "STATIC",
  "connect_timeout": "1s",
  "load_assignment": {
    "cluster_name": "sds-grpc",
    "endpoints": [
      {
        "lb_endpoints": [
          {
            "endpoint": {
              "address": {
                "pipe": {
                  "path": "./var/run/secrets/workload-spiffe-uds/socket"
                }
              }
            }
          }
        ]
      }
    ]
  },
  "typed_extension_protocol_options": {
    "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": {
      "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
      "explicit_http_config": {
        "http2_protocol_options": {}
      }
    }
  }
}

Sidecar 监听这个端口并启动了一个 SDS 服务, 其会按需向 istiod 发起 Certificate Signing Request(CSR) 生成对应证书.