[istio源碼分析][citadel] citadel之istio_ca

1. 前言

轉(zhuǎn)載請說明原文出處, 尊重他人勞動成果!

源碼位置: https://github.com/nicktming/istio
分支: tming-v1.3.6 (基于1.3.6版本)

本文將分析istio中的負責證書方面事情的組件citadel, 源碼位置在istio/security, 具體將分析citadelistio_ca的作用.

對證書https不了解的可以參考 https://www.cnblogs.com/pzblog/p/9088286.html.

2. 初識istio_ca

[root@master istio_ca]# pwd
/root/go/src/istio.io/istio/security/cmd/istio_ca
[root@master istio_ca]# ls
istio_ca  main.go  run.sh
[root@master istio_ca]# kubectl get sa --all-namespaces
NAMESPACE         NAME                   SECRETS   AGE
default           bookinfo-details       1         14d
default           bookinfo-productpage   1         14d
default           bookinfo-ratings       1         14d
default           bookinfo-reviews       1         14d
default           default                1         39d
istio-system      default                1         15d
kube-node-lease   default                1         23d
kube-public       default                1         39d
kube-system       coredns                1         39d
kube-system       default                1         39d
tming             default                1         15d
[root@master istio_ca]# kubectl get cm -n istio-system
NAME                     DATA   AGE
istio                    2      15d
istio-sidecar-injector   2      15d
[root@master istio_ca]# cat run.sh 
#!/usr/bin/env bash
export CITADEL_ENABLE_NAMESPACES_BY_DEFAULT=true
config=""
config="$config --append-dns-names=true"
config="$config --grpc-port=8060"
config="$config --citadel-storage-namespace=istio-system"
config="$config --custom-dns-names=istio-pilot-service-account.istio-system:istio-pilot.istio-system"
config="$config --monitoring-port=15014"
config="$config --self-signed-ca=true"
config="$config --workload-cert-ttl=2160h"
./istio_ca $config
unset CITADEL_ENABLE_NAMESPACES_BY_DEFAULT 

[root@master istio_ca]# ./run.sh 
2020-02-04T08:02:23.012518Z     info    The custom-defined DNS name list is [istio-pilot-service-account.istio-system:istio-pilot.istio-system]
2020-02-04T08:02:23.018847Z     info    Use self-signed certificate as the CA certificate
2020-02-04T08:02:23.019753Z     info    ControlZ available at 127.0.0.1:9876
2020-02-04T08:02:23.027905Z     info    Failed to get secret (error: secrets "istio-ca-secret" not found), will create one
2020-02-04T08:02:23.495396Z     info    Using self-generated public key: -----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
2020-02-04T08:02:23.502515Z     info    The Citadel's public key is successfully written into configmap istio-security in namespace istio-system.
2020-02-04T08:02:23.502545Z     info    rootCertRotator Set up back off time 2m21s to start rotator.
2020-02-04T08:02:23.502558Z     info    Creating Kubernetes controller to write issued keys and certs into secret ...
2020-02-04T08:02:23.502619Z     info    rootCertRotator Jitter is enabled, wait 2m21s before starting root cert rotator.
2020-02-04T08:02:23.602779Z     info    adding registry entry "k8s.cluster.local" -> "k8s.cluster.local"
2020-02-04T08:02:23.602831Z     info    added client certificate authenticator
2020-02-04T08:02:23.603288Z     info    Citadel monitor has started.
2020-02-04T08:02:23.603298Z     info    Citadel has started
2020-02-04T08:02:23.603332Z     info    monitor Monitor server started.
2020-02-04T08:02:23.604169Z     info    Starting GRPC server on port 8060
2020-02-04T08:02:23.610077Z     info    adding registry entry "spiffe://cluster.local/ns/default/sa/bookinfo-details" -> "spiffe://cluster.local/ns/default/sa/bookinfo-details"
...
2020-02-04T08:02:23.786537Z     info    k8sController   Secret tming/istio.default is created successfully
...

查看所有secret, 可以每個serviceaccount都額外增加了一個類型為istio.io/key-and-certsecret, 名字就是istio.加上serviceaccount的名字.

[root@master ~]# kubectl get secret --all-namespaces
NAMESPACE         NAME                                           TYPE                                  DATA   AGE
...
default           istio.bookinfo-details                         istio.io/key-and-cert                 3      98s
default           istio.bookinfo-productpage                     istio.io/key-and-cert                 3      96s
default           istio.bookinfo-ratings                         istio.io/key-and-cert                 3      96s
default           istio.bookinfo-reviews                         istio.io/key-and-cert                 3      97s
default           istio.default                                  istio.io/key-and-cert                 3      39d
istio-system      istio.default                                  istio.io/key-and-cert                 3      95s
kube-node-lease   istio.default                                  istio.io/key-and-cert                 3      23d
kube-public       istio.default                                  istio.io/key-and-cert                 3      39d
kube-system       istio.coredns                                  istio.io/key-and-cert                 3      39d
kube-system       istio.default                                  istio.io/key-and-cert                 3      39d
tming             istio.default                                  istio.io/key-and-cert                 3      98s
...
[root@master ~]# 

3. pki

pki的地址在security/pki.

3.1 NewIstioCA

func NewSelfSignedIstioCAOptions(ctx context.Context,
    rootCertGracePeriodPercentile int, caCertTTL, rootCertCheckInverval, certTTL,
    maxCertTTL time.Duration, org string, dualUse bool, namespace string,
    readCertRetryInterval time.Duration, client corev1.CoreV1Interface, rootCertFile string,
    enableJitter bool) (caOpts *IstioCAOptions, err error) {
    // 查看istio-system這個namespaces中是否存在istio-ca-secret這個secret
    caSecret, scrtErr := client.Secrets(namespace).Get(CASecret, metav1.GetOptions{})
    if scrtErr != nil && readCertRetryInterval > time.Duration(0) {
        // 重試一次
    }
    caOpts = &IstioCAOptions{
        ...
    }
    if scrtErr != nil {
        // 如果k8s中不存在 就自己生成key和cert 
        options := util.CertOptions{
            ...
        }
        // 用x509生成key和證書cert
        pemCert, pemKey, ckErr := util.GenCertKeyFromOptions(options)
        if ckErr != nil {
            return nil, fmt.Errorf("unable to generate CA cert and key for self-signed CA (%v)", ckErr)
        }
        // 把證書cert添加到rootCerts
        rootCerts, err := util.AppendRootCerts(pemCert, rootCertFile)
        if err != nil {
            return nil, fmt.Errorf("failed to append root certificates (%v)", err)
        }
        // 利用pemCert, pemKey, rootCerts 填充caOpts.KeyCertBundle
        if caOpts.KeyCertBundle, err = util.NewVerifiedKeyCertBundleFromPem(pemCert, pemKey, nil, rootCerts); err != nil {
            return nil, fmt.Errorf("failed to create CA KeyCertBundle (%v)", err)
        }
        // 在istio-system namespace中生成一個類型為istio.io/ca-root的secret
        // secret里面的數(shù)據(jù)是 根證書和根key
        secret := k8ssecret.BuildSecret("", CASecret, namespace, nil, nil, nil, pemCert, pemKey, istioCASecretType)
        if _, err = client.Secrets(namespace).Create(secret); err != nil {
            log.Errorf("Failed to write secret to CA (error: %s). Abort.", err)
            return nil, fmt.Errorf("failed to create CA due to secret write error")
        }
        log.Infof("Using self-generated public key: %v", string(rootCerts))
    } else {
        // 從已有的secret中讀取
        log.Infof("Load signing key and cert from existing secret %s:%s", caSecret.Namespace, caSecret.Name)
        rootCerts, err := util.AppendRootCerts(caSecret.Data[caCertID], rootCertFile)
        if err != nil {
            return nil, fmt.Errorf("failed to append root certificates (%v)", err)
        }
        if caOpts.KeyCertBundle, err = util.NewVerifiedKeyCertBundleFromPem(caSecret.Data[caCertID],
            caSecret.Data[caPrivateKeyID], nil, rootCerts); err != nil {
            return nil, fmt.Errorf("failed to create CA KeyCertBundle (%v)", err)
        }
        log.Infof("Using existing public key: %v", string(rootCerts))
    }
    // 將根證書保存到configmap中 名字為istio-system/istio-security
    if err = updateCertInConfigmap(namespace, client, caOpts.KeyCertBundle.GetRootCertPem()); err != nil {
        log.Errorf("Failed to write Citadel cert to configmap (%v). Node agents will not be able to connect.", err)
    } else {
        log.Infof("The Citadel's public key is successfully written into configmap istio-security in namespace %s.", namespace)
    }
    return caOpts, nil
}

1. 查看istio-system namespace中是否存在istio-ca-secret這個secret.
2. 如果不存在, 則會自己生成certkey, 該certkey會用于簽名那些所有的secret的. 并且存到istio-ca-secret中.
3. 如果存在, 則直接從已有的secret中讀取.
4. 無論存在不存在, 都需要根據(jù)pemCert, pemKey, rootCerts 填充caOpts.KeyCertBundle.
5. 將根證書保存到configmap中, 名字為istio-system/istio-security.

// security/pkg/pki/ca/ca.go
func updateCertInConfigmap(namespace string, client corev1.CoreV1Interface, cert []byte) error {
    certEncoded := base64.StdEncoding.EncodeToString(cert)
    cmc := configmap.NewController(namespace, client)
    return cmc.InsertCATLSRootCert(certEncoded)
}
// security/pkg/k8s/configmap/configmap.go
func (c *Controller) InsertCATLSRootCert(value string) error {
    configmap, err := c.core.ConfigMaps(c.namespace).Get(istioSecurityConfigMapName, metav1.GetOptions{})
    // 更新或生成該configmap
    return nil
}

查看istio-ca-secretistio-security.

[root@master ~]# kubectl get secret istio-ca-secret -o yaml -n istio-system
apiVersion: v1
data:
  ca-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMzVENDQWNXZ0F3SUJBZ0lRY005Q2w4OCttYThzTERnWFNFaGF3akFOQmdrcWhraUc5dzBCQVFzRkFEQVkKTVJZd0ZBWURWUVFLRXcxamJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJd01ESXdOREE0TURJeU0xb1hEVE13TURJdwpNVEE0TURJeU0xb3dHREVXTUJRR0ExVUVDaE1OWTJ4MWMzUmxjaTVzYjJOaGJEQ0NBU0l3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFKOVZrLythRXV1bHhvb1F1bzNSUnlrWUpuME9udlViZ1JyRHJXWEYKekxKYVZDVUthY1lxTE5vSjBXKzFES1dyZ05weFBKcE8vUUtQeGxOdnJtK004cllVOHc0NWI5VGFLdTNxSGwvdgp1UmkrSnJZT3ZWcGlZY3A4SEc2cnI5UWtIcGJjcm1OT2VOQ1lVZGdXQ2VkMW5tWjZjZGcrT0RIYkFzVkloTmRiCjBHNDc4RU1RM3FOaWFTdGVIRXJMSDRlU045Z2VVbkhmY2hCTVFnQXN4cjJza01wUEw2VWJSMHl2dzVXM3dpWngKbWpaN2ZUYURBcy9vZ3hTV1N6LzE2eG1lVzYvbEx4N1JQNVd6ZTlkWnBPOE9xUW5BV3dlL2puTTNrQ0lNbksrUgpPTVlBRjBiU1VLKzZiT004bmZVanM4RUVJdEJvdUluYmNwNFplZkhvSFlaNk10RUNBd0VBQWFNak1DRXdEZ1lEClZSMFBBUUgvQkFRREFnSUVNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUIKQUcxdEU3UnFCTHpzd3ovTStuYndoeFR2VE14eHJ2RDYxVDc1eTVPRUMxUnk4RmQxbnYyT09vR0UybjUxV0N4VAp0YzFERzAvWUNrbko1a3grWEo5TElDbkdkcHFxR2VnQkNrR1RlWmpPMlk0Zko5dEZ3SWdhbVQwYmNUVjJ3NmUwClVRdUxuR1hxaytVdUFiOHFHWUZObGVlMk1LOFV5aEM0SGlEN1Z2c2xXcUdmazdTOHNtWmR0cGUyMkY2RThrbDQKSFlYNHh4RWQyRTZxVlYxbFZsMTV1TGo2V3R3Z1V0dmZMR3RvMnlyOHN3cExETHZ4SVprdWd6azc1OE1LbUljNwowOEV3bUdNVnVvUUhEcnA4NDU1NkNwZGl2NDY0dUdWeW9PaDU1WWlPcGxidWtWQzZTRE9QbTlueTRRTThRV1ZKCmYyYkRXQkpoSmRXb1FXVElPWm1wek5vPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
  ca-key.pem: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBbjFXVC81b1M2NlhHaWhDNmpkRkhLUmdtZlE2ZTlSdUJHc090WmNYTXNscFVKUXBwCnhpb3MyZ25SYjdVTXBhdUEybkU4bWs3OUFvL0dVMit1YjR6eXRoVHpEamx2MU5vcTdlb2VYKys1R0w0bXRnNjkKV21KaHlud2NicXV2MUNRZWx0eXVZMDU0MEpoUjJCWUo1M1dlWm5weDJENDRNZHNDeFVpRTExdlFianZ3UXhEZQpvMkpwSzE0Y1Nzc2ZoNUkzMkI1U2NkOXlFRXhDQUN6R3ZheVF5azh2cFJ0SFRLL0RsYmZDSm5HYU5udDlOb01DCnoraURGSlpMUC9YckdaNWJyK1V2SHRFL2xiTjcxMW1rN3c2cENjQmJCNytPY3plUUlneWNyNUU0eGdBWFJ0SlEKcjdwczR6eWQ5U096d1FRaTBHaTRpZHR5bmhsNThlZ2Robm95MFFJREFRQUJBb0lCQUdHYzloeURjYy80TVpmbwpBOEphVWZRMUhXOUVBOUk1MVhCbUxOYkt4VXNHMThJUmpSZWdRdllaU2J2YitUR056bFVGUnBGcWpzcUE5b21yClEveUhKekt4eHU0UjloYzZ5VTRVUGlPY0k1T3ErdUJTUzJNU0hzTUVJZzhURTVjdHdhZSs3djliMWR4RlZPN0QKSWJJeGRxZGxvRlZRV1BFQ01jSlhXVHJ1dnRTbzRFeWQ5NlFzV0w5RFBvRkRiU0xULzJKM3lQTFJlUEhQK25mbQpiTEJZNStGcFI0TElGSHpjZ1VtdWhHdFA2QkQyTnM5VERLSWJINjd1OUZ5S05Ec0laRmZEZkY5NDJIbzNYUnhkCkpoaHpKdy9Kbkl2M0pGbzM2ekwxWHVjd0hkNnVIYlJoMGw5eER3Y3Z6MlFmYm9BN2hlSlAvUllRb3VLVGl1UHAKMVVWbXVaa0NnWUVBeElQYUNTRjQzMUNjQjlYNHpJRDhCaFBQY3E3K2cvbjZEMThiY1NWK2M1eHhGYytKU1FvbwpIRC9MZnJUWlNEa3lCbmV6eU5zbXRzY1pXTGpGblRZWFFaZDZVUkl4dUw3anU1emI3N2YwMWVMS1RWT3IwUk81CjlZMEF1UkEyaEp4Q0RObVdmYm5JcUxzTUJBOUhUNGE5T3BGME1KVi9DaGpPcElBSEdKMHV3L2NDZ1lFQXo1Q04KWWFQbzNwaGlFZnFyUElEMlRtUjdFNnZveGkxc0VmVjMvRk1EMEVUTkpidE5xL09KWElyOGFEV1lDeThMVDZteApXU3FDVG1tdDEvNTREa3hzVnM2NVJGUVVIc1pKSktkVVNTNXI0MkUxZElvcHBWMWZQcDdqTXFUb1l2QjQ3eWpMCnR2LzRvdnlzajdqYWJIZTZ4ZFF1bUhmZ1B6dndaaEllUmVuKy9YY0NnWUVBd2xqYjAxZmxJSVdxS2gyMU54c0IKSkVtSFNoWkM0K2JmSlVDYjlTUnRrSXpSVWc2ejZTWkFVTi9Pc3ZyTVFKOUFHQ0ZlRG5DZU12bG8yZE95ckM0SQpoZmYzSWlKcVJobVRROEozeVBZWEQwaUJaa1F2a2xCK0FwaDJkSS9TT3dnR1Vvc0dTRVRxYStUQWwyczh6U3VtCkRUdzR1cXUrdmcrV25oMTM1eUhjVkRVQ2dZQWVtM0k5czYzakpSVlJFV1d1eGxXTHVjVnNZUzB5REFDanBVN00KSm5HcGhIdTcxS0xmZUVvSkczV2M2RXBEVyt5UEVyYlVMekNIMzQyRERFTW4ySjBoTDlxaCtNUCt4YjZEYVNsRQpvVlBIYzg0dUlUR0M4UUhhZWhPR3BFcWdURldJN3JzYmdTTm81Vm1NMS9WZGlVcEJZY2g5TXpBZUl2aVFGSnZpCk0xOG1nUUtCZ0N0QVFNcytIK1RGcnFHaDZZN3RFNXBoM3pwZ2tsclp4ZEN2MDR4M2JDcy9EZDEyZDZKUElRSk0KQ0JGYXZibmgvNDhwZUJxR0NDRkxIZ1FVL0VUOTdRdFRPU3h3SWxHck9rR1BhUkR3eVc2M0hXWGV1RHBiRXhrbQpXUW9CcHBZYlpWSGlTcVVWaVR1NG5tNjRPQ0loNWVoa3dVS05YY0cvakFxSkdnUDhPYzEzCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
  cert-chain.pem: ""
  key.pem: ""
  root-cert.pem: ""
kind: Secret
metadata:
  name: istio-ca-secret
  ...
type: istio.io/ca-root
[root@master ~]# kubectl get cm istio-security -o yaml -n istio-system
apiVersion: v1
data:
  caTLSRootCert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMzVENDQWNXZ0F3SUJBZ0lRY005Q2w4OCttYThzTERnWFNFaGF3akFOQmdrcWhraUc5dzBCQVFzRkFEQVkKTVJZd0ZBWURWUVFLRXcxamJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJd01ESXdOREE0TURJeU0xb1hEVE13TURJdwpNVEE0TURJeU0xb3dHREVXTUJRR0ExVUVDaE1OWTJ4MWMzUmxjaTVzYjJOaGJEQ0NBU0l3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFKOVZrLythRXV1bHhvb1F1bzNSUnlrWUpuME9udlViZ1JyRHJXWEYKekxKYVZDVUthY1lxTE5vSjBXKzFES1dyZ05weFBKcE8vUUtQeGxOdnJtK004cllVOHc0NWI5VGFLdTNxSGwvdgp1UmkrSnJZT3ZWcGlZY3A4SEc2cnI5UWtIcGJjcm1OT2VOQ1lVZGdXQ2VkMW5tWjZjZGcrT0RIYkFzVkloTmRiCjBHNDc4RU1RM3FOaWFTdGVIRXJMSDRlU045Z2VVbkhmY2hCTVFnQXN4cjJza01wUEw2VWJSMHl2dzVXM3dpWngKbWpaN2ZUYURBcy9vZ3hTV1N6LzE2eG1lVzYvbEx4N1JQNVd6ZTlkWnBPOE9xUW5BV3dlL2puTTNrQ0lNbksrUgpPTVlBRjBiU1VLKzZiT004bmZVanM4RUVJdEJvdUluYmNwNFplZkhvSFlaNk10RUNBd0VBQWFNak1DRXdEZ1lEClZSMFBBUUgvQkFRREFnSUVNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUIKQUcxdEU3UnFCTHpzd3ovTStuYndoeFR2VE14eHJ2RDYxVDc1eTVPRUMxUnk4RmQxbnYyT09vR0UybjUxV0N4VAp0YzFERzAvWUNrbko1a3grWEo5TElDbkdkcHFxR2VnQkNrR1RlWmpPMlk0Zko5dEZ3SWdhbVQwYmNUVjJ3NmUwClVRdUxuR1hxaytVdUFiOHFHWUZObGVlMk1LOFV5aEM0SGlEN1Z2c2xXcUdmazdTOHNtWmR0cGUyMkY2RThrbDQKSFlYNHh4RWQyRTZxVlYxbFZsMTV1TGo2V3R3Z1V0dmZMR3RvMnlyOHN3cExETHZ4SVprdWd6azc1OE1LbUljNwowOEV3bUdNVnVvUUhEcnA4NDU1NkNwZGl2NDY0dUdWeW9PaDU1WWlPcGxidWtWQzZTRE9QbTlueTRRTThRV1ZKCmYyYkRXQkpoSmRXb1FXVElPWm1wek5vPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
kind: ConfigMap
metadata:
  name: istio-security
  ...
[root@master ~]# 

總結(jié)一下, 就是用x509生成了根cert 和 根key, 保存到了istio-ca-secret中, 一個可以疊加的root cert保存到了istio-security.

3.2 Sign

func (ca *IstioCA) Sign(csrPEM []byte, subjectIDs []string, requestedLifetime time.Duration, forCA bool) ([]byte, error) {
    // 用于簽名的證書和key 就是在NewSelfSignedIstioCAOptions中生成的key和cert
    signingCert, signingKey, _, _ := ca.keyCertBundle.GetAll()
    if signingCert == nil {
        return nil, caerror.NewError(caerror.CANotReady, fmt.Errorf("Istio CA is not ready")) // nolint
    }
    // 生成csr
    csr, err := util.ParsePemEncodedCSR(csrPEM)
    if err != nil {
        return nil, caerror.NewError(caerror.CSRError, err)
    }
    ...
    // 生成簽名證書
    certBytes, err := util.GenCertFromCSR(csr, signingCert, csr.PublicKey, *signingKey, subjectIDs, lifetime, forCA)
    ...
    block := &pem.Block{
        Type:  "CERTIFICATE",
        Bytes: certBytes,
    }
    cert := pem.EncodeToMemory(block)
    return cert, nil
}

可以看到簽名是用之前生成保存在ca.keyCertBundle的根cert的.

4. workloadsecret

該組件是專門為所有的serviceaccount生成和維護一個istio.io/key-and-cert類型的secret. 所有生成的這種類型的secret中保存著自己的key and cert, 并且它的證書都是通過保存在istio-ca-secret的根證書來進行簽名的.

func NewSecretController(ca certificateAuthority, enableNamespacesByDefault bool,
    certTTL time.Duration, gracePeriodRatio float32, minGracePeriod time.Duration,
    dualUse bool, core corev1.CoreV1Interface, forCA bool, pkcs8Key bool, namespaces []string,
    dnsNames map[string]*DNSNameEntry, istioCaStorageNamespace, rootCertFile string,
    selfSignedCa bool) (*SecretController, error) {
    ...
    c.saStore, c.saController =
        cache.NewInformer(saLW, &v1.ServiceAccount{}, time.Minute, cache.ResourceEventHandlerFuncs{
            AddFunc:    c.saAdded,
            DeleteFunc: c.saDeleted,
        })
    istioSecretSelector := fields.SelectorFromSet(map[string]string{"type": IstioSecretType}).String()
    scrtLW := listwatch.MultiNamespaceListerWatcher(namespaces, func(namespace string) cache.ListerWatcher {
        return &cache.ListWatch{
            ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
                options.FieldSelector = istioSecretSelector
                return core.Secrets(namespace).List(options)
            },
            WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
                options.FieldSelector = istioSecretSelector
                return core.Secrets(namespace).Watch(options)
            },
        }
    })
    c.scrtStore, c.scrtController =
        cache.NewInformer(scrtLW, &v1.Secret{}, secretResyncPeriod, cache.ResourceEventHandlerFuncs{
            DeleteFunc: c.scrtDeleted,
            UpdateFunc: c.scrtUpdated,
        })
    ...
    c.namespaceStore, c.namespaceController =
        cache.NewInformer(namespaceLW, &v1.Namespace{}, namespaceResyncPeriod, cache.ResourceEventHandlerFuncs{
            UpdateFunc: c.namespaceUpdated,
        })

    return c, nil
}
func (sc *SecretController) Run(stopCh chan struct{}) {
    go sc.scrtController.Run(stopCh)
    cache.WaitForCacheSync(stopCh, sc.scrtController.HasSynced)
    go sc.saController.Run(stopCh)
    go sc.namespaceController.Run(stopCh)
}

1. 可以看到有serviceaccount informer, secret informernamespace informer. 另外需要注意的是istioSecretSelector過濾的是類型為istio.io/key-and-certsecret.
2. ca certificateAuthority需要實現(xiàn)以下三個方法, IstioCA就是其中一個實現(xiàn)體.

type certificateAuthority interface {
    Sign(csrPEM []byte, subjectIDs []string, ttl time.Duration, forCA bool) ([]byte, error)
    SignWithCertChain(csrPEM []byte, subjectIDs []string, ttl time.Duration, forCA bool) ([]byte, error)
    GetCAKeyCertBundle() util.KeyCertBundle
}

3. 另外需要注意的是dnsNames, 這個在生成證書的時候會用到, 支持自定義域名, 可以看到運行中的參數(shù)有--custom-dns-names=istio-pilot-service-account.istio-system:istio-pilot.istio-system.

// security/pkg/k8s/controller/customdnsname.go
func ConstructCustomDNSNames(serviceAccounts []string, serviceNames []string,
    namespace string, customDNSNames string) map[string]*DNSNameEntry {
    result := make(map[string]*DNSNameEntry)
    for i, svcAccount := range serviceAccounts {
        result[svcAccount] = &DNSNameEntry{
            ServiceName: serviceNames[i],
            Namespace:   namespace,
        }
    }
    if len(customDNSNames) > 0 {
        customNames := strings.Split(customDNSNames, ",")
        log.Infof("The custom-defined DNS name list is %v", customNames)
        for _, customName := range customNames {
            nameDomain := strings.Split(customName, ":")
            if len(nameDomain) == 2 {
                override, ok := result[nameDomain[0]]
                if ok {
                    override.CustomDomains = append(override.CustomDomains, nameDomain[1])
                } else {
                    result[nameDomain[0]] = &DNSNameEntry{
                        ServiceName:   nameDomain[0],
                        CustomDomains: []string{nameDomain[1]},
                    }
                }
            } else {
                ...
            }
        }
    }
    return result
}

所以SecretController中的dnsName就是這么來的.

4.1 upsertSecret

作用: 為對應(yīng)的serviceaccount創(chuàng)建istio.io/key-and-certsecret.

func (sc *SecretController) upsertSecret(saName, saNamespace string) {
    // 根據(jù)serviceaccount name和namespace 構(gòu)造一個istio.io/key-and-cert類型的secret
    secret := k8ssecret.BuildSecret(saName, GetSecretName(saName), saNamespace, nil,
        nil, nil, nil, nil, IstioSecretType)
    // 查看該secret是否已經(jīng)存在于k8s中
    _, exists, err := sc.scrtStore.Get(secret)
    ...
    // 如果已經(jīng)存在 直接返回
    if exists {
        return
    }
    // k8s中不存在 則生成自己的key和證書 簽名是用根證書簽的
    chain, key, err := sc.generateKeyAndCert(saName, saNamespace)
    ...
    // 根證書
    rootCert := sc.ca.GetCAKeyCertBundle().GetRootCertPem()
    secret.Data = map[string][]byte{
        CertChainID:  chain,
        PrivateKeyID: key,
        RootCertID:   rootCert,
    }
    for i := 0; i < secretCreationRetry; i++ {
        // 保存到k8s中
        _, err = sc.core.Secrets(saNamespace).Create(secret)
        ...
    }
}

1. 根據(jù)serviceaccount namenamespace構(gòu)造一個istio.io/key-and-cert類型的secret.
2. 查看該secret是否已經(jīng)存在于k8s中.
3. 如果已經(jīng)存在, 直接返回.
4. 如果k8s中不存在, 則生成自己的key和證書, 簽名是用根證書簽的.

func (sc *SecretController) generateKeyAndCert(saName string, saNamespace string) ([]byte, []byte, error) {
    // 生成可以識別的名字 
    id := spiffe.MustGenSpiffeURI(saNamespace, saName)
    // 如果有自定義域名 則加入到id中
    if sc.dnsNames != nil {
        // Control plane components in same namespace.
        if e, ok := sc.dnsNames[saName]; ok {
            if e.Namespace == saNamespace {
                // Example: istio-pilot.istio-system.svc, istio-pilot.istio-system
                id += "," + fmt.Sprintf("%s.%s.svc", e.ServiceName, e.Namespace)
                id += "," + fmt.Sprintf("%s.%s", e.ServiceName, e.Namespace)
            }
        }
        if e, ok := sc.dnsNames[saName+"."+saNamespace]; ok {
            for _, d := range e.CustomDomains {
                id += "," + d
            }
        }
    }
    options := util.CertOptions{
        Host:       id,
        RSAKeySize: keySize,
        IsDualUse:  sc.dualUse,
        PKCS8Key:   sc.pkcs8Key,
    }
    // 生成key 和csr
    csrPEM, keyPEM, err := util.GenCSR(options)
    ...
    certChainPEM := sc.ca.GetCAKeyCertBundle().GetCertChainPem()
    // 獲得簽名后的證書
    certPEM, signErr := sc.ca.Sign(csrPEM, strings.Split(id, ","), sc.certTTL, sc.forCA)
    ...
    certPEM = append(certPEM, certChainPEM...)
    return certPEM, keyPEM, nil
}

5. 獲得根證書, 組裝信息把該secret保存到k8s中.

func (sc *SecretController) refreshSecret(scrt *v1.Secret) error {
    namespace := scrt.GetNamespace()
    saName := scrt.Annotations[ServiceAccountNameAnnotationKey]
    chain, key, err := sc.generateKeyAndCert(saName, namespace)
    if err != nil {
        return err
    }
    scrt.Data[CertChainID] = chain
    scrt.Data[PrivateKeyID] = key
    scrt.Data[RootCertID] = sc.ca.GetCAKeyCertBundle().GetRootCertPem()
    _, err = sc.core.Secrets(namespace).Update(scrt)
    return err
}

更新secret.

4.2 各informer的行為

informers.png

總體是上圖所示, 具體細節(jié)參考詳細代碼即可. 所以查看一個istio.io/key-and-cert類型的secret, data屬性有cert-chain.pem, key.pemroot-cert.pem.

[root@master istio_ca]# kubectl get secret istio.bookinfo-details -o yaml 
apiVersion: v1
data:
  cert-chain.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLVENDQWhHZ0F3SUJBZ0lSQUlzNzlyd0IvaUdISXppUFV4MUFPTEF3RFFZSktvWklodmNOQVFFTEJRQXcKR0RFV01CUUdBMVVFQ2hNTlkyeDFjM1JsY2k1c2IyTmhiREFlRncweU1EQXlNRFF3T0RBeU1qTmFGdzB5TURBMQpNRFF3T0RBeU1qTmFNQUF3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRGhET3hXCndXUVNMeDNjR0hsOHI1M3NRd1dGQTZBcC9PZVJ6YlpvSU5ySUVucithVVVmWHU3UGNtQ0xhNzVScy9UcENsTXgKeGpDTFU0aGZIWkk2cHBCS09xS2piZ3pIaldRbVpxTHdseWtEMWltMzNkTW9IekFaMHV3OFV0UDhpQS9GWUUzZAoxYnYxM3Q1eHlhaksxd2ZGUTF6bmt1YUhzNkdsMjZxcllzUHpSQUd4RHBVRk54T2l2cUJzUzZqQXJKWFplSWNBClFqMkl6cHArK1MvK2Z6alRNS2k4R3FiQ0M1VzZTUkpRMUxWbkIwT0RKd0JiSFYxUW1ML2FrbTNKaE9vQ1lFMzYKZG8wVzA0aWpERlZhM2E1c1NPZjdLcXNqcGZESHFZRDhXZFhhZThObmVxTVJ1aGpIdGF2cGRuQXR3YStMQXpKTApYU2drcTJ6UldBME5uRjB4QWdNQkFBR2pnWVV3Z1lJd0RnWURWUjBQQVFIL0JBUURBZ1dnTUIwR0ExVWRKUVFXCk1CUUdDQ3NHQVFVRkJ3TUJCZ2dyQmdFRkJRY0RBakFNQmdOVkhSTUJBZjhFQWpBQU1FTUdBMVVkRVFFQi93UTUKTURlR05YTndhV1ptWlRvdkwyTnNkWE4wWlhJdWJHOWpZV3d2Ym5NdlpHVm1ZWFZzZEM5ellTOWliMjlyYVc1bQpieTFrWlhSaGFXeHpNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJmaDRxY1F0NUFJRzlGcWF5dXB2SExsUTVYCnJDYmNFSlRGQWFjeUZVQVJuelk4aDBWV21hYTB4WnNJVXpGaklZd2ZLVU5DRWszUHhOQ25laTlEdVV2U2hOdlYKQlZnSnBaWUR0NS91ZnVlc3M5Q2FSMTdYMTNDTFJuRFJBTExWWXNaOVpFKzB6bWRLb1NkdmdSTUZVQ0N0QXgvYwpEaFVsU3F5eFpaSk8wU1hUT1JCNGFWK3dUZDdub0UrcEl6eEF3Q1VHc2U0TjNJK1B6WWVaQkd2Zmd6cXFLOGJ2CkdRWFlPcHdKTTdjc1pwWHlDRGtxSEt0UG81dlM2cXVXMnBPUWdrYkdnV2FaYWZ4aWtvWVBJZklQMFF1NVNjWjgKNXVDRXROQnY3c2xOcmV4amJhZnJBUzFpaFZ5c3R3bGdDc0NBM0FRNExqQnlzNjQyUUo4Mko0SDFEVkUzCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
  key.pem: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBNFF6c1ZzRmtFaThkM0JoNWZLK2Q3RU1GaFFPZ0tmem5rYzIyYUNEYXlCSjYvbWxGCkgxN3V6M0pnaTJ1K1ViUDA2UXBUTWNZd2kxT0lYeDJTT3FhUVNqcWlvMjRNeDQxa0ptYWk4SmNwQTlZcHQ5M1QKS0I4d0dkTHNQRkxUL0lnUHhXQk4zZFc3OWQ3ZWNjbW95dGNIeFVOYzU1TG1oN09ocGR1cXEyTEQ4MFFCc1E2VgpCVGNUb3I2Z2JFdW93S3lWMlhpSEFFSTlpTTZhZnZrdi9uODQwekNvdkJxbXdndVZ1a2tTVU5TMVp3ZERneWNBCld4MWRVSmkvMnBKdHlZVHFBbUJOK25hTkZ0T0lvd3hWV3QydWJFam4reXFySTZYd3g2bUEvRm5WMm52RFozcWoKRWJvWXg3V3I2WFp3TGNHdml3TXlTMTBvSkt0czBWZ05EWnhkTVFJREFRQUJBb0lCQVFEYS9qVlk4cFZMY0pmdwozY3dTUGQ1QjBySWpUblRqaHR0Y01UNkhzempTS2RHUGthYVdzVTFYaG1oV0kwRXV4aHZUVFozSk9KOXlaSlcyCllOTXp5WE50R0FPOVh0Q0d1cHF6RjNzRS9VTUhIYmE0MmQycEZEZzlXTmRTbUJMNmtQZ3Z5OWZadnl0SlJWZFcKaUxKOHQ2UXpHNnJqR1RyRTRGS3pUNytUU09kKzVtSDNGSGtiR05QUUpoZkJZNnRCR2k4bmFUNnhtUEpadjlhaQpXbVJpNUNjVkkrc2R4ajNOYXorcXNFRHJ3TTlLckcyTnMyQWxQejVrRndXQUEyY3U2N3BDUy9ZZFRtQkEzVGhICjIrQmp1dlBrL2pNRHhsU2IwNzZSa2JtZkZxcmY3OW9CMmxDWW42Z0Rybk5mNDFLSSs1ekJKN2w2OGo1aThqUnAKdmZBclExd3hBb0dCQU9iOFh4TnhMVFpGRGRablRUcGUwbmw5Vy9wZFpnQ2NQaE5FSzBNTHJGaWlaSzl2bS9TSAoydGEzTU1KbVlnSGpEa0NGWnh2cGRiY3BsY3BnNEIyTGh5UmtqcjA1NDZZSUplTkJhQTgzdElkdllRMm90OTMyCmQyOXlYUFdKRkowNnFBT0JZNEVOcUlzQlZuNFpJc1RpVzAwTGdNeHNHcyt5RE9zaGRueWhPdEhkQW9HQkFQbHMKQVl0ZDRQS3NkRTZYZi9PMlNTVEJWVHZuQnd2SWNwelRWbG02cnJVakFQd2RNSStOdmhoYmMrTDBEQk82RWRmRQo5dVpycE4xV3V5Mk0xVEpHU2dGdndpUWVLLzdOK3BzOXVzMEdmVnhIK2I2SEQ4bnJtWUhVWjVlR25pdysrZ3FwClFOZlFIWU9BU0FWRnl1MHB0anl1VGpDeVlwWW1Xck5lRWN5eVEwVmxBb0dBZWVSRDUrYVpqVUh6ZzJrUC8vVjUKN0ZLUzd4UEtlQmY3U2Y5M05QSThDS25wcUFxbHFlem5OdGVGQzVFcnR3TXl5aW1idDhjREw1enFSdG5JYXM3dQppZTNteFVSL05XYW5WNzEwUkZPSkdNOVZ6L2R3ejlqNFBmK0R2cTZRQ2tpaDBQZStvcU9xaFhBM1RHUEhUVTZHCkQ2bzZWYVhXb0RPOVRYZGpKM0dOc0tFQ2dZQUhBcVFnZUtrRDZSanp4SXBTSFVOOUJ2b1FUdlFCdnNhSjVkNjkKY3VQS0w0dXRpbHg5REd6VlhteXBhbGFVajF4RkJrSXlROEJFZ0ZXT2VERGQwdC90bm1pRWYxeVpNc3ppWkIvRgo5M0s2ajBOVHVaUVdCc3N2dlBxVVpiSTRhQ1M2Ky9yWWxmN2VYVktvNDBkSzF5dEtGVlFaUmtwREVoem1nYlJhClhvQkppUUtCZ1FDdDlSZll6cURRKzJ2eVFRUkhoRVhIaXpQbVRXZEN6M00xVERPSUpZcEI5VENUVE81cGwvYWcKbmVMamNjTng0czFPWm9qVDM4MXRFMndPNFRPQlRIMlJBTnBHcDBUbDRyL2RqSmdKVmpUa0Z4S0o0WG1OWEdiUApnWHBTdERWTTVrbDI1dUlKbXpVdVJSSTdIbEZmcnV4VnlYR3BUMTNUY2RHL01ib05vOE1Ianc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
  root-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMzVENDQWNXZ0F3SUJBZ0lRY005Q2w4OCttYThzTERnWFNFaGF3akFOQmdrcWhraUc5dzBCQVFzRkFEQVkKTVJZd0ZBWURWUVFLRXcxamJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJd01ESXdOREE0TURJeU0xb1hEVE13TURJdwpNVEE0TURJeU0xb3dHREVXTUJRR0ExVUVDaE1OWTJ4MWMzUmxjaTVzYjJOaGJEQ0NBU0l3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFKOVZrLythRXV1bHhvb1F1bzNSUnlrWUpuME9udlViZ1JyRHJXWEYKekxKYVZDVUthY1lxTE5vSjBXKzFES1dyZ05weFBKcE8vUUtQeGxOdnJtK004cllVOHc0NWI5VGFLdTNxSGwvdgp1UmkrSnJZT3ZWcGlZY3A4SEc2cnI5UWtIcGJjcm1OT2VOQ1lVZGdXQ2VkMW5tWjZjZGcrT0RIYkFzVkloTmRiCjBHNDc4RU1RM3FOaWFTdGVIRXJMSDRlU045Z2VVbkhmY2hCTVFnQXN4cjJza01wUEw2VWJSMHl2dzVXM3dpWngKbWpaN2ZUYURBcy9vZ3hTV1N6LzE2eG1lVzYvbEx4N1JQNVd6ZTlkWnBPOE9xUW5BV3dlL2puTTNrQ0lNbksrUgpPTVlBRjBiU1VLKzZiT004bmZVanM4RUVJdEJvdUluYmNwNFplZkhvSFlaNk10RUNBd0VBQWFNak1DRXdEZ1lEClZSMFBBUUgvQkFRREFnSUVNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUIKQUcxdEU3UnFCTHpzd3ovTStuYndoeFR2VE14eHJ2RDYxVDc1eTVPRUMxUnk4RmQxbnYyT09vR0UybjUxV0N4VAp0YzFERzAvWUNrbko1a3grWEo5TElDbkdkcHFxR2VnQkNrR1RlWmpPMlk0Zko5dEZ3SWdhbVQwYmNUVjJ3NmUwClVRdUxuR1hxaytVdUFiOHFHWUZObGVlMk1LOFV5aEM0SGlEN1Z2c2xXcUdmazdTOHNtWmR0cGUyMkY2RThrbDQKSFlYNHh4RWQyRTZxVlYxbFZsMTV1TGo2V3R3Z1V0dmZMR3RvMnlyOHN3cExETHZ4SVprdWd6azc1OE1LbUljNwowOEV3bUdNVnVvUUhEcnA4NDU1NkNwZGl2NDY0dUdWeW9PaDU1WWlPcGxidWtWQzZTRE9QbTlueTRRTThRV1ZKCmYyYkRXQkpoSmRXb1FXVElPWm1wek5vPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
kind: Secret
metadata:
  annotations:
    istio.io/service-account.name: bookinfo-details
  name: istio.bookinfo-details
  namespace: default
 ...
type: istio.io/key-and-cert
[root@master istio_ca]# 

5. server

rootCmd = &cobra.Command{
        Use:   "istio_ca",
        Short: "Istio Certificate Authority (CA).",
        Args:  cobra.ExactArgs(0),
        Run: func(cmd *cobra.Command, args []string) {
            runCA()
        },
    }
func main() {
    if err := rootCmd.Execute(); err != nil {
        log.Errora(err)
        os.Exit(-1)
    }
}
func runCA() {
    ...
    var webhooks map[string]*controller.DNSNameEntry
    if opts.appendDNSNames {
        // 生成自定義的dnsname
        webhooks = controller.ConstructCustomDNSNames(webhookServiceAccounts,
            webhookServiceNames, opts.istioCaStorageNamespace, opts.customDNSNames)
    }
    // 創(chuàng)建k8s client
    cs, err := kubelib.CreateClientset(opts.kubeConfigFile, "")
    ...
    // 創(chuàng)建IstioCA
    ca := createCA(cs.CoreV1())
    stopCh := make(chan struct{})
    if !opts.serverOnly {
        // 創(chuàng)建SecretController
        sc, err := controller.NewSecretController(ca, opts.enableNamespacesByDefault,
            opts.workloadCertTTL, opts.workloadCertGracePeriodRatio, opts.workloadCertMinGracePeriod,
            opts.dualUse, cs.CoreV1(), opts.signCACerts, opts.pkcs8Keys, listenedNamespaces, webhooks,
            opts.istioCaStorageNamespace, opts.rootCertFile, opts.selfSignedCA)
        ...
        // 運行SecretController
        sc.Run(stopCh)
    } else {
        ...
    }
    if opts.grpcPort > 0 {
        ...
       }
    ...
}

1. 生成自定義的dnsname.
2. 創(chuàng)建IstioCA對象ca.
3. 創(chuàng)建SecretController對象sc并運行.

6. 參考

  1. istio 1.3.6源碼
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容