cert-manager 和 mTLS 介绍

cert-manager 将证书和证书颁发者添加为 Kubernetes 集群中的资源类型,并简化了获取、更新和使用这些证书的过程。相比 istio 自带的 CA 管理,cert-manager 具有以下优势:支持多种证书颁发机构,方面实现中间 CA 证书的轮换等。

istio 网格的一大特点,就是可实现自动 mTLS(Mutual Transport Layer Security),完成网格内的流量加密,有助于缩小云原生部署的攻击面。istio开启mTLS的设置方式有:

  • 通过初始化配置“enableAutoMtls”选项设置,默认为 true,示例配置如下:
1
2
3
4
5
6
7
8
apiVersion: v1
kind: ConfigMap
metadata:
  name: istio
  namespace: istio-system
data:
  mesh: |-
    enableAutoMtls: true  # 开启自动mTLS
  • PeerAuthentication:指定某个命名空间或者某个工作负载是否开启 mTLS,mode值可为STRICT、PERMISSIVE、DISABLE、UNSET
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# foo命名空间可以接受mTLS和纯文本流量
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: foo
spec:
  mtls:
    mode: PERMISSIVE  
---
# foo空间下finance服务只接受mTLS流量
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: finance
  namespace: foo
spec:
  selector:
    matchLabels:
      app: finance
  mtls:
    mode: STRICT
  • DestinationRule:指定某个工作负载是否开启 mTLS,mode值可为ISTIO_MUTUAL、MUTUAL、SIMPLE、DISABLE
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 请求ratings服务时,需要使用istio mTLS
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: ratings-istio-mtls
spec:
  host: ratings.prod.svc.cluster.local
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL

什么是TLS

传输层安全 (TLS) 是互联网上广泛使用的一种加密协议。TLS 的前身是 SSL,在客户端-服务器连接中对服务器进行身份验证,并对客户和服务器之间的通信进行加密,以便外部各方无法窥视通信。TLS协议主要组成:

  • 公钥和私钥
  • TLS 证书

mTLS是如何运作的

通常在 TLS 中,服务器有一个 TLS 证书和一个公钥/私钥对,而客户端没有。典型的 TLS 流程是这样运作的:

  • 客户端连接到服务器
  • 服务器出示其 TLS 证书
  • 客户端验证服务器的证书
  • 客户端和服务器通过加密的 TLS 连接交换信息 TLS流程

在 mTLS 中,客户端和服务器都有一个证书,并且双方都使用它们的公钥/私钥对进行身份验证。与常规 TLS 相比,mTLS 中有一些额外步骤来验证双方:

  • 客户端连接到服务器
  • 服务器出示其 TLS 证书
  • 客户端验证服务器的证书
  • 客户端出示其 TLS 证书
  • 服务器验证客户端的证书
  • 服务器授予访问权限
  • 客户端和服务器通过加密的 TLS 连接交换信息 mTLS流程

openssl 模拟证书流程

这里使用 openssl 模拟一个 CA 证书机构,生成 CA 端的私钥和证书,以及签发服务端证书。

创建 CA 端的私钥和证书

创建证书需要时需要一个配置,包含证书的全局设置、主题信息、扩展字段等,示例配置 ca.conf:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
[ req ]
default_bits = 2048 #公钥长度
prompt = no # 不提示输入
utf8 = yes  # 以 utf8 格式编码
encrypt_key = no # 私钥不加密
distinguished_name  = req_dn # 证书主题信息
req_extensions = req_ext    # 证书扩展字段
x509_extensions = req_ext   # x509 证书扩展字段

[ req_dn ]
O = Istio      # 组织
CN = Root CA    # 通用名称

[ req_ext ]
subjectKeyIdentifier = hash # 证书标识符
basicConstraints = critical, CA:true    # 基本约束,是否是 CA 证书
keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, keyCertSign # 密钥用法

参考openssl-ca-keyusage中的说明,列举了几个使用场景下keyUsage如何设置:

  • 自签名 CA:值设置为“critical, cRLSign, digitalSignature, keyCertSign”
  • 中间CA:值设置为“critical, cRLSign, digitalSignature, keyCertSign”
  • VPN/Web Server非CA证书:值设置为“critical, nonRepudiation, digitalSignature, keyEncipherment, keyAgreement”
  • VPN Client非CA证书:值设置为“critical, nonRepudiation, digitalSignature, keyEncipherment”

keyUsage中部分值说明:

  • critical:表示字段是否为重要字段,如果一个扩展被标记为 critical,那么任何不理解该扩展的实体都必须拒绝该证书
  • keyCertSign:用于验证证书上的签名
  • digitalSignature:证书可用于应用数字签名,数字签名通常用于具有完整性的实体认证和数据源认证
  • nonRepudiation:证书可用于签署上述数据,但证书公钥可用于提供不可否认服务;这可以防止签名实体错误地拒绝某些操作
  • keyEncipherment:证书可用于加密对称密钥,然后将该密钥传输到目标;目标解密密钥,使用它来加密和解密实体之间的数据
  • cRLSign:证书可用于验证撤销信息(如CRL)上的签名
  • keyAgreement:证书允许使用密钥协议来建立具有目标的对称密钥,可以使用对称密钥对实体之间发送的数据进行加密和解密

执行命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 生成私钥
openssl genrsa -out ca.key
# 生成自签名证书
openssl req -x509 -new -nodes -key ca.key -days 365 -out ca.pem -config ca.conf
# 查看证书内容, -noout 不用输出证书原内容
openssl x509 -noout -in ca.pem -text
# ca.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            59:50:54:0d:75:87:7f:b0:fa:29:ff:56:6b:2d:ca:c7:97:b5:ef:a7
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: O = Istio, CN = Root CA
        Validity
            Not Before: Oct 26 06:22:02 2023 GMT
            Not After : Oct 25 06:22:02 2024 GMT
        Subject: O = Istio, CN = Root CA
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:b2:62:ea:81:76:70:f8:6b:1b:b6:da:14:f3:df:
                    0f:00:25:05:c3:b3:dd:07:de:86:01:0f:9a:32:ea:
                    b0:8b:86:9c:20:d9:f5:cf:cb:91:ff:9c:0d:24:4b:
                    5e:e1:a2:83:f0:f2:2a:95:f1:33:b9:be:d2:f0:08:
                    4a:2b:76:33:af:d7:3c:4e:ef:00:9a:43:bf:51:44:
                    e0:54:a1:6b:3f:bd:95:62:0c:95:fe:83:4a:51:62:
                    5a:39:c9:70:3b:a1:5d:19:cd:f6:25:8f:d9:99:e2:
                    8f:98:3d:99:ba:6b:92:1a:59:25:ea:7f:72:06:b7:
                    11:fe:b2:0a:ee:a3:25:99:29:ba:91:3c:77:94:54:
                    3b:6e:17:81:9d:e6:37:99:fd:f9:d6:7d:1f:a4:a6:
                    39:f7:88:c0:63:d2:fb:0b:6a:cf:e9:6c:6b:1b:63:
                    8e:8e:66:95:f5:ec:b7:28:b2:4e:76:e4:94:bb:57:
                    ed:43:c2:52:4e:b3:07:0b:ba:7f:b9:8a:bf:08:02:
                    97:f3:09:b6:9d:6a:ed:ca:42:70:f4:e2:dc:86:5c:
                    9b:82:28:d7:8b:01:19:26:1b:82:58:cf:e4:00:1f:
                    c8:5c:db:85:28:f5:ef:11:34:a1:58:53:48:be:f2:
                    c9:c6:01:88:60:3d:e3:70:d0:29:ba:fb:e4:d2:e9:
                    f5:3d
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                57:B0:DA:BD:B4:4A:AB:F0:7E:82:80:04:16:EA:AF:BC:6A:A9:A2:7D
            X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Key Usage: critical
                Digital Signature, Non Repudiation, Key Encipherment, Certificate Sign
    Signature Algorithm: sha256WithRSAEncryption
         37:f9:ba:09:40:80:6d:3c:35:d5:07:01:4c:b8:74:bf:80:16:
         e6:0e:98:ee:28:36:0c:97:c6:74:99:25:07:a8:e3:f3:5e:51:
         0b:e7:68:39:58:99:5c:24:be:2e:a7:b6:1f:d7:b6:95:99:0e:
         70:1b:15💿ec:0e:c5:00:57:12:17:16:c0:85:16:97:05:f3:
         3a:ad:fd:c1:83:d3:26:0e:a8:db:67:35:22:43:00:dc:64:e3:
         50:e0:46:64:63:1b:0b:33:bd:79:36:0c:d0:e9:5a:36:1a:b4:
         80:8f:fb:c7:11:2a:ff:be:67:45:f8:70:40:c8:5a:39:69:df:
         8c:6b:11:f5:19:aa:a1:23:59:1c:5b:81:c0:f4:f9:18:aa:7b:
         5e:01:3b:f7:41:32:83:63:37:eb:0b:e3:0d:c7:72:e3:4f:5a:
         ff:66:ad:3a:eb:9f:58:2c:08:26:c4:84:ee:b0:7e:bd:0d🇩🇪
         b0:21:d3:75:56:2d:f6:df:4d:64:67:50:de:1b:2c:b6:71:0e:
         cd:cb:82:8d:2e:01:4e:30:ab:9c:3b:a6:24:76:96:cc:e7:ca:
         94:05:bd:ea:ab:65:ad:9b:94:46:98:1c:70:f8:d7:78:16:8f:
         67:7e:ba:bd:5f:0d:1f:46:62:8b:4c:b0:47:23:6e:f7:37:37:
         c7:ac:80:2b

证书内容中包含了证书版本、序列号、签名算法和公钥等信息。

模拟服务端证书签发

下面使用 CA 模拟 istio 中证书的签发过程,签发时 sidecar 中 istio-agent 中会生成私钥和证书签名请求,证书签名请求配置跟上面类似,示例配置 csr.conf:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
[ req ]
default_bits = 2048
prompt = no
utf8 = yes
encrypt_key = no
distinguished_name  = req_dn
req_extensions = req_ext
x509_extensions = req_ext

[ req_dn ]
O = Istio
CN = productpage

[ req_ext ]
subjectKeyIdentifier = hash
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, keyCertSign

执行命令签发:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# 生成私钥
openssl genrsa -out workload.key
# 生成证书签名请求
openssl req -new -key workload.key -out workload.csr -config csr.conf
# 查看证书签名请求内容
openssl req -noout -text -in workload.csr
# workload.csr
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: O = Istio, CN = productpage
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:a7:9d:ad:93:cc:ee:88:aa:f6:8a:c6:c5:c7:76:
                    9b:43:2f:2c:d7:7c:f7:ac:ee:16:20:a4:8f:68:03:
                    79:d4:f5🇩🇪f9:d7:ee:e2:c1:4d:91:88:8e:23:8a:
                    fd:61:b6:0b:a8:51:69:14:af:14:df:17:b1:fd:e4:
                    ba:56:4b:51:3e:10:6f:e3:1d:ea:c9:23:80:72:bd:
                    80:1a:bc:06:76:1b:28:e1:72:2f:aa:bb:fa:41:fe:
                    8f:33:65:1a:46:e0:b2:1d:6a:07:7e:6c:a6:3b:bc:
                    ac:d1:b1:5c:8f:bd:a3:02:7c:78:f4:5d:19:80:1c:
                    cc:15:13:43:bb:46:a8:f1:e8:74:e4:11:1c:6a:40:
                    fb:8d:11:ea:3a:73:d9:0e:af:54:81:4d:b4:22:3f:
                    45:7d:ee:1b:66:16:d0:e6:fd:15:55:79:d8:26:d4:
                    54:fc:41:27:6c:7b:89:7d:51:f6:0b:ee:ff:7d:38:
                    12:a6:3d:5e:f8:36:a3:bd:34:b0:2a:0c:31:62:f7:
                    68:0b:fb:3d:96:7f:54💿92:75:18:c6:22:fb:22:
                    d9:94:d6:27:1a:ac:cb:f4:4f:e6:8d:7d:2e:c9:9f:
                    37:84:aa:19:40:1d:89:04:94:91:c5:b1:e8:d2:36:
                    de:c5:81:c9:92:ac:f3:a9:37:22:99:93:cd:2d:be:
                    8b:c1
                Exponent: 65537 (0x10001)
        Attributes:
        Requested Extensions:
            X509v3 Subject Key Identifier:
                8F:B8:8B:0F:B8:27:B0:25:CB:0B:D5:BF:21:CB:3E:E2:6A:B1:99:08
            X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Key Usage: critical
                Digital Signature, Non Repudiation, Key Encipherment, Certificate Sign
    Signature Algorithm: sha256WithRSAEncryption
         55:3d:4f:5a:7b:4a:af:22:ec:c1:1d:0c:58:d1:76:8a:ae:c7:
         2b:f7:42:0e:42:2a:47:f5:9e:e7:97:58:20:22:75:28:e9:0e:
         8a💿74:3c:5e:3b:20:fd:c2:f6:f7:d9:34:7f:57:69:87:22:
         d4:77:70:e6:70:e4:40:eb:88:72:3f:f7:cd:e0:71:19:be:f2:
         d4:6a:89:8b:d9:e7:63:32:0c:1b:04:e2:65:6e:f2:cb:4a:25:
         88:35:02:9b:aa:13:61:a4:45:bd:c6:62:13:26:56:89:7b:46:
         31:33:53:47:df:63:dc:97:79:70:62:c1:59:66:e5:02:bc:98:
         c7:43:12:c3:ad:04:45:ce:cd:79:0b:0e:d9:3f:e6:1a:00:5c:
         67:f6:01:05:7a:c2:6b:58:03:ce:80:51:6d:0b:08:64:af:72:
         e7:8b:a7:0a:18:21:f8:6f:5d:a2:c2:6d:de:1e:b0:39:a1:18:
         16:80:e7:72:d8:2e:71:06:38:16:d9:b8:fa:fb:99:77:4a:f1:
         b9:17:df:a0:39:9d:b8:53:19:34:8b:ac:60:27:a4:79:50:75:
         f7:51:29:d1:d2:3b:87💿b3:08:2d:33:90:d6:a7:87:73:aa:
         8f:09:3c:68:4a:09:3e:dd:a7:8f:98:b3:d3:b7:d7:61:5d:bc:
         c5:55:b9:98

# 签发服务端证书
openssl x509 -req -in workload.csr -CA ca.pem -CAkey ca.key \
    -CAcreateserial -out workload.pem -days 365 \
    -extensions req_ext -extfile csr.conf -sha256
# 查看证书内容,跟 ca.pem 的内容类似,证书中的公钥为 workload.csr 中的公钥
openssl x509 -noout -text -in workload.pem
# 验证证书
openssl verify -CAfile ca.pem workload.pem

istio 集成 cert-manager

集成cert-manager后,集群获取证书整体逻辑如下图: 请求cert-manager获取证书流程图

安装和集成

参考 使用 cert-manager 加密服务网格 即可,这里使用的 istio 示例 插入 CA 证书 中生成的私钥和证书,然后创建 secret:

1
2
kubectl create namespace istio-system 
kubectl -nistio-system create secret tls ca-key-pair --cert=certs/root-cert.pem --key=certs/root-key.pem

这里使用的 root 根证书,如果需要多集群部署,则可以使用中间证书。接着创建 istio-csr 需要的 Issuer 和 Certificate:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# issuer 会被 Certificate 的 issuerRef 引用
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: istiod-tls
  namespace: istio-system
spec:
  ca:
    secretName: ca-key-pair # 提供的私钥和证书
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: istiod-tls
  namespace: istio-system
spec:
  commonName: istiod.istio-system.svc
  dnsNames:
  - istiod
  - istiod.istio-system
  - istiod.istio-system.svc
  - istiod.istio-system.svc.cluster
  - istiod.istio-system.svc.cluster.local
  uris:
  - spiffe://cluster.local/ns/istio-system/sa/istiod-service-account
  secretName: istiod-tls
  duration: 43200h  #5y
  renewBefore: 12h
  privateKey:
    rotationPolicy: Always
    algorithm: RSA
    size: 2048
  revisionHistoryLimit: 1
  issuerRef:
    name: istiod-tls
    kind: Issuer
    group: cert-manager.io

这里名称没用 istio-ca 默认名称,需要在 istio-csr deployment 中修改设置为“–issuer-name=istiod-tls”。

查看 envoy 中获取的证书

sidecar 中 envoy 是通过 sds 协议从 istio-agent 中获取的证书,而 istio-agent 是从 cert-manager 中获取的,下面从 envoy 查看获取到证书信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
kubectl get pods -nsample
NAME                             READY   STATUS    RESTARTS   AGE
sleep-9454cc476-2qxzs            2/2     Running   0          23h
helloworld-v2-79d5467d55-9hdk7   2/2     Running   0          22h
# 获取证书链
istioctl proxy-config secret helloworld-v2-79d5467d55-9hdk7.sample -o json | jq -r \
'.dynamicActiveSecrets[0].secret.tlsCertificate.certificateChain.inlineBytes' | base64 --decode > chain.pem
# 里面有两个证书,其中第二个是根证书,此命令只会输出第一个证书内容
openssl x509 -noout -text -in chain.pem
# 获取根证书,跟 chain.pem 中第二个证书一样
istioctl proxy-config secret helloworld-v2-79d5467d55-9hdk7.sample -o json | jq -r \
'.dynamicActiveSecrets[1].secret.validationContext.trustedCa.inlineBytes' | base64 --decode > root.pem
# 查看根证书内容
openssl x509 -noout -text -in root.pem

从这里可以看到 envoy 获取根证书内容,即是创建 ca-key-pair secret 时传入的 root-key.pem。

cert-manager 逻辑分析

总结

  • pods 的 sidecar 中 istio-agent,调用 caAddress 配置的 istio-csr 的 grpc 服务,请求签名证书
  • istio-csr 中的 grpc 服务,会调用 cert-manager 的提供的 client,创建一个 CertificateRequest cr,等待其被签名
  • cert-manager 中的 controller 会监听 CertificateRequest 创建事件,获取相应的issuer并签名,然后将签名后的证书写入到 CertificateRequest 的 status 中,即设置 status.ca 为 CA 的证书,status.certificate 为签发的证书。
  • istio-csr 中的 grpc 服务中通过监听此 CertificateRequest,会将签名后的证书返回给 istio-agent。
  • sidecar 中的服务 istio-agent 会提供一个 xds 协议的 grpc server,用来提供证书和 ca 证书给 envoy
  • sidecar 中 envoy 通过 socket 地址“./var/run/secrets/workload-spiffe-uds/socket”连接 istio-agent,用 xds 协议获取 CA 证书和本地证书。
  • pods中客户端调用服务器时,原始http请求经过envoy,会

创建 pods 时的日志

cert-manager 有以下几个模块,这里主要分析 cert-manager 和 cert-manager-istio-csr 逻辑:

  • cert-manager
  • cert-manager-istio-csr
  • cert-manager-webhook
  • cert-manager-cainjector

在新建一个 pods 时,查看 cert-manager-istio-csr 的日志:

1
2
3
4
5
6
7
2023-10-23T06:58:52.327281Z  info  klog  cert-manager "msg"="created CertificateRequest" "identity"="spiffe://cluster.local/ns/sample/sa/sleep" "name"="istio-csr-mjvpt" "namespace"="istio-system"
2023-10-23T06:58:52.335491Z  info  klog  cert-manager "msg"="waiting for CertificateRequest to become ready" "identity"="spiffe://cluster.local/ns/sample/sa/sleep" "name"="istio-csr-mjvpt" "namespace"="istio-system"
2023-10-23T06:58:52.335529Z  info  klog  cert-manager "msg"="waiting for CertificateRequest to become ready" "identity"="spiffe://cluster.local/ns/sample/sa/sleep" "name"="istio-csr-mjvpt" "namespace"="istio-system"
2023-10-23T06:58:52.346034Z  info  klog  cert-manager "msg"="waiting for CertificateRequest to become ready" "identity"="spiffe://cluster.local/ns/sample/sa/sleep" "name"="istio-csr-mjvpt" "namespace"="istio-system"
2023-10-23T06:58:52.384642Z  info  klog  cert-manager "msg"="signed CertificateRequest" "identity"="spiffe://cluster.local/ns/sample/sa/sleep" "name"="istio-csr-mjvpt" "namespace"="istio-system"
2023-10-23T06:58:52.385999Z  info  klog  grpc-server "msg"="workload CertificateRequest signed" "identities"="spiffe://cluster.local/ns/sample/sa/sleep" "serving-addr"="0.0.0.0:6443"
2023-10-23T06:58:52.393159Z  info  klog  cert-manager "msg"="deleted CertificateRequest" "identity"="spiffe://cluster.local/ns/sample/sa/sleep" "name"="istio-csr-mjvpt" "namespace"="istio-system"

查看 cert-manager 的日志:

1
2
3
4
5
6
7
8
I1023 06:58:52.346517       1 controller.go:154] cert-manager/certificaterequests-issuer-ca "msg"="syncing item" "key"="istio-system/istio-csr-mjvpt"
I1023 06:58:52.346571       1 sync.go:83] cert-manager/certificaterequests-issuer-ca "msg"="fetching issuer object referenced by CertificateRequest" "resource_kind"="CertificateRequest" "resource_name"="istio-csr-mjvpt" "resource_namespace"="istio-system" "resource_version"="v1"
I1023 06:58:52.346595       1 sync.go:98] cert-manager/certificaterequests-issuer-ca "msg"="ensuring issuer type matches this controller" "resource_kind"="CertificateRequest" "resource_name"="istio-csr-mjvpt" "resource_namespace"="istio-system" "resource_version"="v1"
I1023 06:58:52.346615       1 sync.go:125] cert-manager/certificaterequests-issuer-ca "msg"="validating CertificateRequest resource object" "resource_kind"="CertificateRequest" "resource_name"="istio-csr-mjvpt" "resource_namespace"="istio-system" "resource_version"="v1"
I1023 06:58:52.346634       1 sync.go:132] cert-manager/certificaterequests-issuer-ca "msg"="invoking sign function as existing certificate does not exist" "resource_kind"="CertificateRequest" "resource_name"="istio-csr-mjvpt" "resource_namespace"="istio-system" "resource_version"="v1"
I1023 06:58:52.371870       1 ca.go:132] cert-manager/certificaterequests-issuer-ca/sign "msg"="certificate issued" "resource_kind"="CertificateRequest" "resource_name"="istio-csr-mjvpt" "resource_namespace"="istio-system" "resource_version"="v1"
I1023 06:58:52.372022       1 sync.go:178] cert-manager/certificaterequests-issuer-ca/updateStatus "msg"="updating resource due to change in status" "diff"=["Conditions: []v1.CertificateRequestCondition[1] != []v1.CertificateRequestCondition[2]","Certificate: []uint8[0] != []uint8[1533]","CA: []uint8[0] != []uint8[1805]"] "resource_kind"="CertificateRequest" "resource_name"="istio-csr-mjvpt" "resource_namespace"="istio-system" "resource_version"="v1"
I1023 06:58:52.383236       1 controller.go:174] cert-manager/certificaterequests-issuer-ca "msg"="finished processing work item" "key"="istio-system/istio-csr-mjvpt"

在这个过程中,会创建名为“istio-csr-mjvpt”的 CertificateRequest cr,并且完成之后默认会删除此 cr:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apiVersion: cert-manager.io/v1
kind: CertificateRequest
spec:
  duration: 24h0m0s
  extra:
    authentication.kubernetes.io/pod-name:
    - cert-manager-istio-csr-75b7d4bd76-d6lvz
    authentication.kubernetes.io/pod-uid:
    - 3e93c68d-af2d-4c65-8509-972085974bd1
  issuerRef:
    group: cert-manager.io
    kind: Issuer
    name: istiod-tls
  request: ...
status:
  ca: ... 
  certificate: ...
  conditions:
  - lastTransitionTime: "2023-10-28T19:08:57Z"
    message: Certificate request has been approved by cert-manager.io
    reason: cert-manager.io
    status: "True"
    type: Approved
  - lastTransitionTime: "2023-10-28T19:08:57Z"
    message: Certificate fetched from issuer successfully
    reason: Issued
    status: "True"
    type: Ready

istio-csr 源码

istio-csr 有 3 个主要组件:TLS 证书获取器、gRPC 服务器和 CA 包分发器。

  • TLS 证书获取器:负责获取 gRPC 服务器的 TLS 证书。它使用 cert-manager API 创建一个 csr,该资源将由 cert-manager 获取,并由配置的 issuer 签名。
  • gRPC 服务器:负责接收来自 istiod 的证书签名请求,并将签名后的证书发送回。因此,它使用 cert-manager CertificateRequest API 来获取已签名的证书。
  • CA 包分发器:负责在所有名称空间(使用 namespaceSelector 进行筛选)中创建和更新 istio-ca-root-cert ConfigMaps。

istio-csr 中相关代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// pkg/certmanager/certmanager.go
func (m *manager) Sign(ctx context.Context, identities string, csrPEM []byte, duration time.Duration, usages []cmapi.KeyUsage) (Bundle, error) {
  cr := &cmapi.CertificateRequest{
    ObjectMeta: metav1.ObjectMeta{
      GenerateName: "istio-csr-",
      Annotations: map[string]string{
        identityAnnotation: identities,
      },
    },
    Spec: cmapi.CertificateRequestSpec{
      Duration: &metav1.Duration{
        Duration: duration,
      },
      IsCA:      false,
      Request:   csrPEM,
      Usages:    usages,
      IssuerRef: m.opts.IssuerRef,
    },
  }

  for k, v := range m.opts.AdditionalAnnotations {
    cr.ObjectMeta.Annotations[k] = v
  }
  // 创建 CertificateRequest,这里的 clent 是 通过 client.CertmanagerV1().CertificateRequests(opts.Namespace) 初始化的
  cr, err := m.client.Create(ctx, cr, metav1.CreateOptions{})

  log.V(2).Info("created CertificateRequest")

  // 是否保留 cr,如果不保留,则在返回时删除
  if !m.opts.PreserveCertificateRequests {
    defer func() {
      // Use go routine to prevent blocking on Delete call.
      go func() {
        // Use the Background context so that this call is not cancelled by the
        // gRPC context closing.
        if err := m.client.Delete(context.Background(), cr.Name, metav1.DeleteOptions{}); err != nil {
          log.Error(err, "failed to delete CertificateRequest")
          return
        }

        log.V(2).Info("deleted CertificateRequest")
      }()
    }()
  }
  // 设置 watc 监听 certificaterequest,等待其被签名
  signedCR, err := m.waitForCertificateRequest(ctx, log, cr)
  if err != nil {
    return Bundle{}, fmt.Errorf("failed to wait for CertificateRequest %s/%s to be signed: %w",
      cr.Namespace, cr.Name, err)
  }

  log.V(2).Info("signed CertificateRequest")

  return Bundle{Certificate: signedCR.Status.Certificate, CA: signedCR.Status.CA}, nil
}

在 pods 的 sidecar 创建时,会请求到 istio-csr 的 grpc 服务,会执行 grpc server 的 CreateCertificate:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// pkg/server/server.go
func (s *Server) CreateCertificate(ctx context.Context, icr *securityapi.IstioCertificateRequest) (*securityapi.IstioCertificateResponse, error) {
  // authn incoming requests, and build concatenated identities for labelling
  identities, ok := s.authRequest(ctx, []byte(icr.Csr))
  if !ok {
    return nil, status.Error(codes.Unauthenticated, "request authenticate failure")
  }

  log := s.log.WithValues("identities", identities)

  // If requested duration is larger than the maximum value, override with the
  // maxiumum value.
  duration := time.Duration(icr.ValidityDuration) * time.Second
  if duration > s.opts.MaximumClientCertificateDuration {
    duration = s.opts.MaximumClientCertificateDuration
  }

  bundle, err := s.cm.Sign(ctx, identities, []byte(icr.Csr), duration, []cmapi.KeyUsage{cmapi.UsageClientAuth, cmapi.UsageServerAuth})
  if err != nil {
    log.Error(err, "failed to sign incoming client certificate signing request")
    return nil, status.Error(codes.Internal, "failed to sign certificate request")
  }

  certChain, err := s.parseCertificateBundle(bundle)
  if err != nil {
    log.Error(err, "failed to parse and verify signed certificate chain from issuer")
    return nil, status.Error(codes.Internal, "failed to parse and verify signed certificate from issuer")
  }

  // Build client response object
  response := &securityapi.IstioCertificateResponse{
    CertChain: certChain,
  }

  log.V(2).Info("workload CertificateRequest signed")

  // Return response to the client
  return response, nil
}

这个函数注册的接口信息描述为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// grpc.ServiceDesc 用来描述 istio 的证书服务
var IstioCertificateService_ServiceDesc = grpc.ServiceDesc{
  ServiceName: "istio.v1.auth.IstioCertificateService",
  HandlerType: (*IstioCertificateServiceServer)(nil),
  Methods: []grpc.MethodDesc{
    {
      MethodName: "CreateCertificate",
      Handler:    _IstioCertificateService_CreateCertificate_Handler,
    },
  },
  Streams:  []grpc.StreamDesc{},
  Metadata: "security/v1alpha1/ca.proto",
}

上面会调用 cert-manager 中的客户端,即 CertmanagerV1Client 的 CertificateRequests 函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// pkg/client/clientset/versioned/typed/certmanager/v1/certmanager_client.go
// istio-csr 中调用 "client.CertmanagerV1().CertificateRequests(opts.Namespace)"
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*CertmanagerV1Client, error) {
  config := *c
  if err := setConfigDefaults(&config); err != nil {
    return nil, err
  }
  client, err := rest.RESTClientForConfigAndClient(&config, h)
  if err != nil {
    return nil, err
  }
  return &CertmanagerV1Client{client}, nil
}

func (c *CertmanagerV1Client) CertificateRequests(namespace string) CertificateRequestInterface {
  return newCertificateRequests(c, namespace)
}

最后调用生成:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// pkg/client/clientset/versioned/typed/certmanager/v1/certificaterequest.go
// newCertificateRequests returns a CertificateRequests
func newCertificateRequests(c *CertmanagerV1Client, namespace string) *certificateRequests {
  return &certificateRequests{
    client: c.RESTClient(),
    ns:     namespace,
  }
}

// 接受一个 certificateRequest 的资源并创建
func (c *certificateRequests) Create(ctx context.Context, certificateRequest *v1.CertificateRequest, opts metav1.CreateOptions) (result *v1.CertificateRequest, err error) {
  result = &v1.CertificateRequest{}
  err = c.client.Post().
    Namespace(c.ns).
    Resource("certificaterequests").
    VersionedParams(&opts, scheme.ParameterCodec).
    Body(certificateRequest).
    Do(ctx).
    Into(result)
  return
}

cert-manager 相关源码

cert-manager 中的 controller 会监听 CertificateRequest 创建事件,获取相应的issuer并签名,然后将签名后的证书写入到 CertificateRequest 的 status 中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 这个控制器在 run 函数调用 worker 从 queueingController 获取的 queue 中循环拿到 obj,然后调用 queueingController 的 ProcessItem 进行处理
// pkg/controller/controller.go
func (c *controller) worker(ctx context.Context) {
  log := logf.FromContext(c.ctx)

  log.V(logf.DebugLevel).Info("starting worker")
  for {
      // queue 即 queueingController 的 Register 获取的
    obj, shutdown := c.queue.Get()
    if shutdown {
      break
    }

    var key string
    func() {
      defer c.queue.Done(obj)
      var ok bool
      if key, ok = obj.(string); !ok {
        return
      }
      log := log.WithValues("key", key)
      log.V(logf.DebugLevel).Info("syncing item")

          // 即 queueingController 的 ProcessItem
      err := c.syncHandler(ctx, key)
          // ...
      log.V(logf.DebugLevel).Info("finished processing work item")
      c.queue.Forget(obj)
    }()
  }
  log.V(logf.DebugLevel).Info("exiting worker loop")
}

证书请求控制器定义和逻辑如下,每次有证书请求事件时,就获取其引用的 issuer 进行签名,然后将签名后的证书和 ca 写入到 CertificateRequest 的 status 中:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// pkg/controller/certificaterequests/controller.go
// Controller 实现了 queueingController 来实现证书请求
type Controller struct {
  helper issuer.Helper
  cmClient cmclient.Interface
  fieldManager strin

  certificateRequestLister cmlisters.CertificateRequestLister
  secretLister internalinformers.SecretLister

  queue workqueue.RateLimitingInterface
  recorder record.EventRecorder
  // the issuer kind to react to when a certificate request is synced
  issuerType string
  issuerLister        cmlisters.IssuerLister
  clusterIssuerLister cmlisters.ClusterIssuerLister
  registerExtraInformers []RegisterExtraInformerFn

  // Issuer to call sign function
  issuerConstructor IssuerConstructor
  issuer            Issuer
}

func New(issuerType string, issuerConstructor IssuerConstructor, registerExtraInformers ...RegisterExtraInformerFn) *Controller {
  return &Controller{
    issuerType:             issuerType,
    issuerConstructor:      issuerConstructor,
    registerExtraInformers: registerExtraInformers,
  }
}

// Register 初始化 controller,监听 CertificateRequest 并加入到 queue 中,并返回一组必须同步的函数
func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) {
  componentName := "certificaterequests-issuer-" + c.issuerType

  // construct a new named logger to be reused throughout the controller
  c.log = logf.FromContext(ctx.RootContext, componentName)

  // create a queue used to queue up items to be processed
  c.queue = workqueue.NewNamedRateLimitingQueue(controllerpkg.DefaultItemBasedRateLimiter(), componentName)

  secretsInformer := ctx.KubeSharedInformerFactory.Secrets()
  issuerInformer := ctx.SharedInformerFactory.Certmanager().V1().Issuers()
  c.issuerLister = issuerInformer.Lister()
  c.secretLister = secretsInformer.Lister()

  // obtain references to all the informers used by this controller
  certificateRequestInformer := ctx.SharedInformerFactory.Certmanager().V1().CertificateRequests()
  mustSync := []cache.InformerSynced{
    certificateRequestInformer.Informer().HasSynced,
    issuerInformer.Informer().HasSynced,
    secretsInformer.Informer().HasSynced,
  }
  for _, reg := range c.registerExtraInformers {
    ms, err := reg(ctx, c.log, c.queue)
    if err != nil {
      return nil, nil, fmt.Errorf("failed to register extra informer: %w", err)
    }
    mustSync = append(mustSync, ms...)
  }
  // ...
  c.certificateRequestLister = certificateRequestInformer.Lister()
  // 有证书请求事件时,加入到 queue 中
  certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue})
  issuerInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.handleGenericIssuer})
  // create an issuer helper for reading generic issuers
  c.helper = issuer.NewHelper(c.issuerLister, c.clusterIssuerLister)
  // Construct the issuer implementation with the built component context.
  c.issuer = c.issuerConstructor(ctx)

  c.log.V(logf.DebugLevel).Info("new certificate request controller registered",
    "type", c.issuerType)

  return c.queue, mustSync, nil
}

// ProcessItem 在上面的 worker 函数中调用,每当有新的证书请求时,即新的证书请求对象
func (c *Controller) ProcessItem(ctx context.Context, key string) error {
  log := logf.FromContext(ctx)
  dbg := log.V(logf.DebugLevel)

  namespace, name, err := cache.SplitMetaNamespaceKey(key)
  if err != nil {
    log.Error(err, "invalid resource key")
    return nil
  }

  cr, err := c.certificateRequestLister.CertificateRequests(namespace).Get(name)
  if err != nil {
    if k8sErrors.IsNotFound(err) {
      dbg.Info(fmt.Sprintf("certificate request in work queue no longer exists: %s", err))
      return nil
    }

    return err
  }

  ctx = logf.NewContext(ctx, logf.WithResource(log, cr))
  return c.Sync(ctx, cr)
}

// pkg/controller/certificaterequests/sync.go
func (c *Controller) Sync(ctx context.Context, cr *cmapi.CertificateRequest) (err error) {
  crCopy := cr.DeepCopy()
  defer func() {
    if saveErr := c.updateCertificateRequestStatusAndAnnotations(ctx, cr, crCopy); saveErr != nil {
      err = utilerrors.NewAggregate([]error{saveErr, err})
    }
  }()
    // ...
  dbg.Info("fetching issuer object referenced by CertificateRequest")

  issuerObj, err := c.helper.GetGenericIssuer(crCopy.Spec.IssuerRef, crCopy.Namespace)
  // ...
  log = logf.WithRelatedResource(log, issuerObj)
  dbg.Info("ensuring issuer type matches this controller")
  issuerType, err := apiutil.NameForIssuer(issuerObj)
  // ...
  dbg.Info("validating CertificateRequest resource object")

  if len(crCopy.Status.Certificate) > 0 {
    dbg.Info("certificate field is already set in status so skipping processing")
    return nil
  }

  dbg.Info("invoking sign function as existing certificate does not exist")

  // issuer 是调用 NewCA 创建的,
  resp, err := c.issuer.Sign(ctx, crCopy, issuerObj)
  if err != nil {
    log.Error(err, "error issuing certificate request")
    return err
  }

  // If the issuer has not returned any data we may be pending or failed. The
  // underlying issuer will have set the condition of pending or failed and we
  // should potentially wait for a re-sync.
  if resp == nil {
    return nil
  }

  // Update to status with the new given response.
  crCopy.Status.Certificate = resp.Certificate
  crCopy.Status.CA = resp.CA

  // invalid cert
  _, err = pki.DecodeX509CertificateBytes(crCopy.Status.Certificate)
  if err != nil {
    c.reporter.Failed(crCopy, err, "DecodeError", "Failed to decode returned certificate")
    return nil
  }

  // Set condition to Ready.
  c.reporter.Ready(crCopy)

  return ni

其中证书请求控制器有以下几种类型:

  • selfsigned
  • ca
  • venafi
  • acme
  • vault

ca 类型的初始化代码为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// pkg/controller/certificaterequests/ca/ca.go
type CA struct {
  issuerOptions controllerpkg.IssuerOptions
  secretsLister internalinformers.SecretLister

  reporter *crutil.Reporter

  // Used for testing to get reproducible resulting certificates
  templateGenerator templateGenerator
  signingFn         signingFn
}

func init() {
  // create certificate request controller for ca issuer
  controllerpkg.Register(CRControllerName, func(ctx *controllerpkg.ContextFactory) (controllerpkg.Interface, error) {
    return controllerpkg.NewBuilder(ctx, CRControllerName).
      For(certificaterequests.New(apiutil.IssuerCA, NewCA)).
      Complete()
  })
}

func NewCA(ctx *controllerpkg.Context) certificaterequests.Issuer {
  return &CA{
    issuerOptions: ctx.IssuerOptions,
    secretsLister: ctx.KubeSharedInformerFactory.Secrets().Lister(),
    reporter:      crutil.NewReporter(ctx.Clock, ctx.Recorder),
    templateGenerator: func(cr *cmapi.CertificateRequest) (*x509.Certificate, error) {
      if !utilfeature.DefaultMutableFeatureGate.Enabled(feature.DisallowInsecureCSRUsageDefinition) {
        return pki.DeprecatedCertificateTemplateFromCertificateRequestAndAllowInsecureCSRUsageDefinition(cr)
      }

      return pki.CertificateTemplateFromCertificateRequest(cr)
    },
    // Sign 中使用,用来签名证书
    signingFn: pki.SignCSRTemplate,
  }
}

func (c *CA) Sign(ctx context.Context, cr *cmapi.CertificateRequest, issuerObj cmapi.GenericIssuer) (*issuerpkg.IssueResponse, error) {
  secretName := issuerObj.GetSpec().CA.SecretName
  resourceNamespace := c.issuerOptions.ResourceNamespace(issuerObj)

  // get a copy of the CA certificate named on the Issuer
  caCerts, caKey, err := kube.SecretTLSKeyPairAndCA(ctx, c.secretsLister, resourceNamespace, issuerObj.GetSpec().CA.SecretName)
  // ...
  template, err := c.templateGenerator(cr)

  template.CRLDistributionPoints = issuerObj.GetSpec().CA.CRLDistributionPoints
  template.OCSPServer = issuerObj.GetSpec().CA.OCSPServers

  bundle, err := c.signingFn(caCerts, caKey, template)
  log.V(logf.DebugLevel).Info("certificate issued")

  return &issuerpkg.IssueResponse{
    Certificate: bundle.ChainPEM,
    CA:          bundle.CAPEM,
  }, nil
}

sidecar 逻辑分析

主要是利用 [SecretManagerClient] 来创建私钥和获取证书,可参考之前的 istio-agent 源码分析。 这里查看 envoy 配置中,具体哪里有用到证书相关配置。查看 helloworld sidecar 中 envoy 配置中,涉及 5000 端口入向流量的配置,其 tls 部分如下,此时当前 pod 作为服务端,使用 ROOTCA 证书验证客户端请求:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
{
    "filter_chain_match": {
     "destination_port": 5000,  // 对外监听的端口
     "transport_protocol": "tls",
     "application_protocols": [
      "istio",
      "istio-peer-exchange",
      "istio-http/1.0",
      "istio-http/1.1",
      "istio-h2"
     ]
    },
    "filters": [
     {
      "name": "envoy.filters.network.http_connection_manager",
      "typed_config": {
       "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
       "stat_prefix": "inbound_0.0.0.0_5000",
        "validate_clusters": false
       }...}
    ],
    "transport_socket": {  // 传输数据的底层套接字配置
     "name": "envoy.transport_sockets.tls",
     "typed_config": {
      "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext",
      "common_tls_context": {
       "tls_params": {},
       "alpn_protocols": [
        "h2",
        "http/1.1"
       ],
       "tls_certificate_sds_secret_configs": [ // 服务器端证书
        {
         "name": "default",  // 服务器证书名称
         "sds_config": {
          "api_config_source": {
           "api_type": "GRPC",
           "grpc_services": [
            {
             "envoy_grpc": {
              "cluster_name": "sds-grpc" // 获取服务器端证书的 SDS 服务器
             }
            }
           ],
           "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": [  // 匹配的 SAN 规则,用于验证服务器或客户端的证书
          {
           "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"  // 获取根证书的 SDS 服务器
             }
            }
           ],
           "set_node_on_first_message_only": true,
           "transport_api_version": "V3"
          },
          "initial_fetch_timeout": "0s",
          "resource_api_version": "V3"
         }
        }
       }
      },
      "require_client_certificate": true
     }
    },
    "name": "0.0.0.0_5000"
}

其中 sds-grpc cluster 配置为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
 "name": "sds-grpc",
 "type": "STATIC",
 "connect_timeout": "1s",
 "load_assignment": {
  "cluster_name": "sds-grpc",
  "endpoints": [
   {
    "lb_endpoints": [
     {
      "endpoint": {
       "address": {
        "pipe": { // path 为 sds 服务地址,即 pilot-agent 的 socket 地址
         "path": "./var/run/secrets/workload-spiffe-uds/socket"
        }
       }
      }
     }
    ]
   }
  ]
 },
 "typed_extension_protocol_options": {}
}

同时 helloworld sidecar 中 有配置 sleep cluster 信息如下,此时当前 pods 作为客户端,将 default 证书作为客户端证书,使用 ROOTCA 证书验证服务端的证书:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
{
 "version_info": "2023-10-24T05:54:24Z/30",
 "cluster": {
  "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
  "name": "outbound|80||sleep.sample.svc.cluster.local",
  "type": "EDS",
  "metadata": {
   "filter_metadata": {
    "istio": {
     "services": [
      {
       "namespace": "sample",
       "name": "sleep",
       "host": "sleep.sample.svc.cluster.local"
      }
     ]
    }
   }
  },
  "transport_socket_matches": [
   {
    "name": "tlsMode-istio",
    "match": {
     "tlsMode": "istio"
    },
    "transport_socket": {
     "name": "envoy.transport_sockets.tls",
     "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"
       ],
       "tls_certificate_sds_secret_configs": [
        {
         "name": "default",
         "sds_config": {
          "api_config_source": {
           "api_type": "GRPC",
           "grpc_services": [
            {
             "envoy_grpc": {
              "cluster_name": "sds-grpc"
             }
            }
           ]
          }
         }
        }
       ],
       "combined_validation_context": {
        "default_validation_context": {
         "match_subject_alt_names": [
          {
           "exact": "spiffe://cluster.local/ns/sample/sa/sleep"
          }
         ]
        },
        "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_.80_._.sleep.sample.svc.cluster.local"
     }
    }
   }
  ]
 },
 "last_updated": "2023-10-25T08:08:29.168Z"
}

总结就是,sidecar 从 cert-manager 获取了根证书和服务器证书,在 mTLS 请求中有两个角色:

  • 作为服务器时:下发当前证书给调用方,用于调用方验证身份;同时用根证书验证调用方身份
  • 作为客户端时:用根证书验证服务器身份;同时下发当前证书给服务器,用于服务器验证身份

mTLS 认证流程,可参考 HTTPS 双向认证

参考