使用 Kubernetes CSR 自定义 CA 集成

该特性正在积极研发中,目前尚处于 experimental 阶段。

这个特性需要 Kubernetes 版本 >= 1.18。

此任务演示如何使用集成了 Kubernetes CSR API 的自定义证书颁发机构来制备工作负载证书,不同的工作负载可以获取不同的证书签名者签名的证书, 每个证书签名者本质上是一个不同的 CA。可以预期的是,如果工作负载的证书是同一个证书签名者颁发的, 则这些工作负载可以与 MTLS 通信,而不同签名者签名的工作负载则不能这样。这个特性利用了 Chiron,这是一个与 Istiod 关联的轻量级组件,使用 Kubernetes CSR API 签署证书。

在本例中,我们使用开源 cert-manager。Cert-manager 从 1.4 版本开始已增加了对 Kubernetes CertificateSigningRequests 实验性支持

在 Kubernetes 集群中部署自定义 CA 控制器

  1. 按照安装文档部署 cert-manager。

    确保启用了 --feature-gates=ExperimentalCertificateSigningRequestControllers=true 特性门控。

    1. $ helm repo add jetstack https://charts.jetstack.io
    2. $ helm repo update
    3. $ helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set featureGates="ExperimentalCertificateSigningRequestControllers=true" --set installCRDs=true
  2. 为 cert-manager 创建三个自签名的集群签发器:istio-systemfoobar。 注:也可以使用命名空间签发器和其他类型的签发器。

    1. $ cat <<EOF > ./selfsigned-issuer.yaml
    2. apiVersion: cert-manager.io/v1
    3. kind: ClusterIssuer
    4. metadata:
    5. name: selfsigned-bar-issuer
    6. spec:
    7. selfSigned: {}
    8. ---
    9. apiVersion: cert-manager.io/v1
    10. kind: Certificate
    11. metadata:
    12. name: bar-ca
    13. namespace: cert-manager
    14. spec:
    15. isCA: true
    16. commonName: bar
    17. secretName: bar-ca-selfsigned
    18. issuerRef:
    19. name: selfsigned-bar-issuer
    20. kind: ClusterIssuer
    21. group: cert-manager.io
    22. ---
    23. apiVersion: cert-manager.io/v1
    24. kind: ClusterIssuer
    25. metadata:
    26. name: bar
    27. spec:
    28. ca:
    29. secretName: bar-ca-selfsigned
    30. ---
    31. apiVersion: cert-manager.io/v1
    32. kind: ClusterIssuer
    33. metadata:
    34. name: selfsigned-foo-issuer
    35. spec:
    36. selfSigned: {}
    37. ---
    38. apiVersion: cert-manager.io/v1
    39. kind: Certificate
    40. metadata:
    41. name: foo-ca
    42. namespace: cert-manager
    43. spec:
    44. isCA: true
    45. commonName: foo
    46. secretName: foo-ca-selfsigned
    47. issuerRef:
    48. name: selfsigned-foo-issuer
    49. kind: ClusterIssuer
    50. group: cert-manager.io
    51. ---
    52. apiVersion: cert-manager.io/v1
    53. kind: ClusterIssuer
    54. metadata:
    55. name: foo
    56. spec:
    57. ca:
    58. secretName: foo-ca-selfsigned
    59. ---
    60. apiVersion: cert-manager.io/v1
    61. kind: ClusterIssuer
    62. metadata:
    63. name: selfsigned-istio-issuer
    64. spec:
    65. selfSigned: {}
    66. ---
    67. apiVersion: cert-manager.io/v1
    68. kind: Certificate
    69. metadata:
    70. name: istio-ca
    71. namespace: cert-manager
    72. spec:
    73. isCA: true
    74. commonName: istio-system
    75. secretName: istio-ca-selfsigned
    76. issuerRef:
    77. name: selfsigned-istio-issuer
    78. kind: ClusterIssuer
    79. group: cert-manager.io
    80. ---
    81. apiVersion: cert-manager.io/v1
    82. kind: ClusterIssuer
    83. metadata:
    84. name: istio-system
    85. spec:
    86. ca:
    87. secretName: istio-ca-selfsigned
    88. EOF
    89. $ kubectl apply -f ./selfsigned-issuer.yaml

为每个集群创建验证秘钥

  1. $ kubectl get secret -n cert-manager -l controller.cert-manager.io/fao=true
  2. NAME TYPE DATA AGE
  3. bar-ca-selfsigned kubernetes.io/tls 3 3m36s
  4. foo-ca-selfsigned kubernetes.io/tls 3 3m36s
  5. istio-ca-selfsigned kubernetes.io/tls 3 3m38s

导出每个集群签发器的根证书

  1. $ export ISTIOCA=$(kubectl get clusterissuers istio-system -o jsonpath='{.spec.ca.secretName}' | xargs kubectl get secret -n cert-manager -o jsonpath='{.data.ca\.crt}' | base64 -d | sed 's/^/ /')
  2. $ export FOOCA=$(kubectl get clusterissuers foo -o jsonpath='{.spec.ca.secretName}' | xargs kubectl get secret -n cert-manager -o jsonpath='{.data.ca\.crt}' | base64 -d | sed 's/^/ /')
  3. $ export BARCA=$(kubectl get clusterissuers bar -o jsonpath='{.spec.ca.secretName}' | xargs kubectl get secret -n cert-manager -o jsonpath='{.data.ca\.crt}' | base64 -d | sed 's/^/ /')

使用默认的证书签名者信息部署 Istio

  1. 使用具有以下配置的 istioctl 在集群上部署 Istio,ISTIO_META_CERT_SIGNER 是工作负载所用的默认证书签名者。

    1. $ cat <<EOF > ./istio.yaml
    2. apiVersion: install.istio.io/v1alpha1
    3. kind: IstioOperator
    4. spec:
    5. values:
    6. pilot:
    7. env:
    8. EXTERNAL_CA: ISTIOD_RA_KUBERNETES_API
    9. meshConfig:
    10. defaultConfig:
    11. proxyMetadata:
    12. ISTIO_META_CERT_SIGNER: istio-system
    13. caCertificates:
    14. - pem: |
    15. $ISTIOCA
    16. certSigners:
    17. - clusterissuers.cert-manager.io/istio-system
    18. - pem: |
    19. $FOOCA
    20. certSigners:
    21. - clusterissuers.cert-manager.io/foo
    22. - pem: |
    23. $BARCA
    24. certSigners:
    25. - clusterissuers.cert-manager.io/bar
    26. components:
    27. pilot:
    28. k8s:
    29. env:
    30. - name: CERT_SIGNER_DOMAIN
    31. value: clusterissuers.cert-manager.io
    32. - name: PILOT_CERT_PROVIDER
    33. value: k8s.io/clusterissuers.cert-manager.io/istio-system
    34. overlays:
    35. - kind: ClusterRole
    36. name: istiod-clusterrole-istio-system
    37. patches:
    38. - path: rules[-1]
    39. value: |
    40. apiGroups:
    41. - certificates.k8s.io
    42. resourceNames:
    43. - clusterissuers.cert-manager.io/foo
    44. - clusterissuers.cert-manager.io/bar
    45. - clusterissuers.cert-manager.io/istio-system
    46. resources:
    47. - signers
    48. verbs:
    49. - approve
    50. EOF
    51. $ istioctl install --skip-confirmation -f ./istio.yaml
  2. 创建 barfoo 命名空间。

    1. $ kubectl create ns bar
    2. $ kubectl create ns foo
  3. bar 命名空间中部署 proxyconfig-bar.yaml,以便在 bar 命名空间中为工作负载定义证书签名者。

    1. $ cat <<EOF > ./proxyconfig-bar.yaml
    2. apiVersion: networking.istio.io/v1beta1
    3. kind: ProxyConfig
    4. metadata:
    5. name: barpc
    6. namespace: bar
    7. spec:
    8. environmentVariables:
    9. ISTIO_META_CERT_SIGNER: bar
    10. EOF
    11. $ kubectl apply -f ./proxyconfig-bar.yaml
  4. foo 命名空间中部署 proxyconfig-foo.yaml,以便在 foo 命名空间中为工作负载定义证书签名者。

    1. $ cat <<EOF > ./proxyconfig-foo.yaml
    2. apiVersion: networking.istio.io/v1beta1
    3. kind: ProxyConfig
    4. metadata:
    5. name: foopc
    6. namespace: foo
    7. spec:
    8. environmentVariables:
    9. ISTIO_META_CERT_SIGNER: foo
    10. EOF
    11. $ kubectl apply -f ./proxyconfig-foo.yaml
  5. foobar 命名空间中部署 httpbinsleep 样例应用程序。

    1. $ kubectl label ns foo istio-injection=enabled
    2. $ kubectl label ns bar istio-injection=enabled
    3. $ kubectl apply -f samples/httpbin/httpbin.yaml -n foo
    4. $ kubectl apply -f samples/sleep/sleep.yaml -n foo
    5. $ kubectl apply -f samples/httpbin/httpbin.yaml -n bar

验证相同命名空间内 httpbinsleep 之间的网络连通性

在部署工作负载时,它们会发送具有相关签名者信息的 CSR 请求。Istiod 将这些 CSR 请求转发到自定义 CA 进行签名。 自定义 CA 将使用正确的集群签发器在证书上签名。foo 命名空间下的工作负载将使用 foo 集群签发器, 而 bar 命名空间下的工作负载将使用 bar 集群签发器。要验证它们已经被正确的集群签发器进行了真正的签名, 我们可以验证相同命名空间下的工作负载可以通信,而不同命名空间下的工作负载不能通信。

  1. SLEEP_POD_FOO 环境变量设置为 sleep Pod 的名称。

    1. $ export SLEEP_POD_FOO=$(kubectl get pod -n foo -l app=sleep -o jsonpath={.items..metadata.name})
  2. 检查 foo 命名空间中 sleephttpbin 服务之间的网络连通性。

    1. $ kubectl exec "$SLEEP_POD_FOO" -n foo -c sleep -- curl http://httpbin.foo:8000/html
    2. <!DOCTYPE html>
    3. <html>
    4. <head>
    5. </head>
    6. <body>
    7. <h1>Herman Melville - Moby-Dick</h1>
    8. <div>
    9. <p>
    10. Availing himself of the mild...
    11. </p>
    12. </div>
    13. </body>
  3. 检查 foo 命名空间中的 sleep 服务与 bar 命名空间中的 httpbin 服务之间的网络连通性。

    1. $ kubectl exec "$SLEEP_POD_FOO" -n foo -c sleep -- curl http://httpbin.bar:8000/html
    2. upstream connect error or disconnect/reset before headers. reset reason: connection failure, transport failure reason: TLS error: 268435581:SSL routines:OPENSSL_internal:CERTIFICATE_VERIFY_FAILED

清理

  • 移除 istio-systemfoobar 命名空间:

    1. $ kubectl delete ns foo
    2. $ kubectl delete ns bar
    3. $ istioctl uninstall --purge -y
    4. $ helm delete -n cert-manager cert-manager
    5. $ kubectl delete ns istio-system cert-manager
    6. $ unset ISTIOCA FOOCA BARCA
    7. $ rm -rf istio.yaml proxyconfig-foo.yaml proxyconfig-bar.yaml selfsigned-issuer.yaml

使用此特性的原因

  • 自定义 CA 集成 - 通过在 Kubernetes CSR 请求中指定签名者名称,此特性允许 Istio 使用 Kubernetes CSR API 接口与自定义证书颁发机构集成。这确实需要自定义 CA 来实现一个 Kubernetes 控制器来观察 CertificateSigningRequestCertificate 资源并对其采取行动。

  • 更好的多租户 - 通过为不同工作负载指定不同的证书签名者,不同租户的工作负载所用的证书可以由不同的 CA 进行签名。