证书控制器chiron

证书控制器chiron

Chiron是与Istiod链接的轻量级组件,该组件使用Kubernetes CA API签署证书而无需维护其私钥。使用此功能具有以下优点:

  • 不像istiod,此功能不需要维护私有签名密钥,从而增强了安全性。

  • 简化了向TLS客户端的根证书分发。客户不再需要等待Istiod生成和分发其CA证书。

配置提供证书

cat < ./istio.yaml apiVersion: install.istio.io/v1alpha1 kind: IstioOperator spec: meshConfig: certificates:

  • secretName: dns.example1-service-account

    dnsNames: [example1.istio-system.svc, example1.istio-system]

  • secretName: dns.example2-service-account

    dnsNames: [example2.istio-system.svc, example2.istio-system]

    EOF

istioctl install -f ./istio.yaml

代码

初始化证书控制器

if err := s.initCertController(args); err != nil {
    return fmt.Errorf("error initializing certificate controller: %v", err)
}

具体逻辑

func (s *Server) initCertController(args *PilotArgs) error {
    var err error
    var secretNames, dnsNames, namespaces []string

    //获取网格配置
    meshConfig := s.environment.Mesh()
    if meshConfig.GetCertificates() == nil || len(meshConfig.GetCertificates()) == 0 {
        log.Info("No certificates specified, skipping K8S DNS certificate controller")
        return nil
    }

    k8sClient := s.kubeClient
    // 读取上述配置的certificates
    for _, c := range meshConfig.GetCertificates() {
        name := strings.Join(c.GetDnsNames(), ",")
        if len(name) == 0 { // 必须包含至少一个DNS name
            continue
        }
        if len(c.GetSecretName()) > 0 {
            // Chiron 将生成key和证书保存到secret
            secretNames = append(secretNames, c.GetSecretName())
            dnsNames = append(dnsNames, name)
            namespaces = append(namespaces, args.Namespace)
        }
    }

    // 设置和管理非pilot服务的证书.如果服务为空,则证书控制器将不执行任何操作。
    s.certController, err = chiron.NewWebhookController(defaultCertGracePeriodRatio, defaultMinCertGracePeriod,
        k8sClient.CoreV1(), k8sClient.AdmissionregistrationV1beta1(), k8sClient.CertificatesV1beta1(),
        defaultCACertPath, secretNames, dnsNames, namespaces)
    if err != nil {
        return fmt.Errorf("failed to create certificate controller: %v", err)
    }
    s.addStartFunc(func(stop <-chan struct{}) error {
        go func() {
            // 启动Chiron 管理certificates的生命周期
            s.certController.Run(stop)
        }()

        return nil
    })

    return nil
}

在启动时

func (wc *WebhookController) Run(stopCh <-chan struct{}) {
    // 创建证书
    for i, secretName := range wc.secretNames {
        err := wc.upsertSecret(secretName, wc.dnsNames[i], wc.serviceNamespaces[i])
        if err != nil {
            log.Errorf("error when upserting secret (%v) in ns (%v): %v", secretName, wc.serviceNamespaces[i], err)
        }
    }

    ...
}

创建WebhookController

func NewWebhookController(gracePeriodRatio float32, minGracePeriod time.Duration,
    core corev1.CoreV1Interface, admission admissionv1beta1.AdmissionregistrationV1beta1Interface,
    certClient certclient.CertificatesV1beta1Interface, k8sCaCertFile string,
    secretNames, dnsNames, serviceNamespaces []string) (*WebhookController, error) {

    ...

    c := &WebhookController{
        gracePeriodRatio:  gracePeriodRatio,
        minGracePeriod:    minGracePeriod,
        k8sCaCertFile:     k8sCaCertFile,
        core:              core,
        admission:         admission,
        certClient:        certClient,
        secretNames:       secretNames,
        dnsNames:          dnsNames,
        serviceNamespaces: serviceNamespaces,
        certUtil:          certutil.NewCertUtil(int(gracePeriodRatio * 100)),
    }

    // 读取CA.
    _, err := reloadCACert(c)
    if err != nil {
        return nil, err
    }
    if len(dnsNames) == 0 {
        log.Warn("the input services are empty, no services to manage certificates for")
    } else {
        // watch istio.io/dns-key-and-cert类型的secret
        istioSecretSelector := fields.SelectorFromSet(map[string]string{"type": IstioDNSSecretType}).String()
        scrtLW := listwatch.MultiNamespaceListerWatcher(serviceNamespaces, func(namespace string) cache.ListerWatcher {
            return &cache.ListWatch{
                ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
                    options.FieldSelector = istioSecretSelector
                    return core.Secrets(namespace).List(context.TODO(), options)
                },
                WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
                    options.FieldSelector = istioSecretSelector
                    return core.Secrets(namespace).Watch(context.TODO(), options)
                },
            }
        })
        c.scrtStore, c.scrtController =
            cache.NewInformer(scrtLW, &v1.Secret{}, secretResyncPeriod, cache.ResourceEventHandlerFuncs{
                DeleteFunc: c.scrtDeleted,
                UpdateFunc: c.scrtUpdated,
            })
    }

    return c, nil
}

当证书被删除时

scrtDeleted

func (wc *WebhookController) scrtDeleted(obj interface{}) {
    log.Debugf("enter WebhookController.scrtDeleted()")
    scrt, ok := obj.(*v1.Secret)
    if !ok {
        log.Warnf("failed to convert to secret object: %v", obj)
        return
    }

    scrtName := scrt.Name
    if wc.isWebhookSecret(scrtName, scrt.GetNamespace()) {
        log.Infof("re-create deleted Istio secret %s in namespace %s", scrtName, scrt.GetNamespace())
        dnsName, found := wc.getDNSName(scrtName)
        if !found {
            log.Errorf("failed to find the DNS name of the secret: %v", scrtName)
            return
        }
        // 重新生成证书
        err := wc.upsertSecret(scrtName, dnsName, scrt.GetNamespace())
        if err != nil {
            log.Errorf("re-create deleted Istio secret %s in namespace %s failed: %v",
                scrtName, scrt.GetNamespace(), err)
        }
    }
}

当证书被更新时

scrtUpdated

// scrtUpdated()在收到更新事件进行回调. 用于证书轮转

func (wc *WebhookController) scrtUpdated(oldObj, newObj interface{}) {

    ......

    //解析证书,无法解析则更新
    certBytes := scrt.Data[ca.CertChainID]
    _, err := util.ParsePemEncodedCertificate(certBytes)
    if err != nil {
        log.Warnf("failed to parse certificates in secret %s/%s (error: %v), refreshing the secret.",
            namespace, name, err)
        //更新证书
        if err = wc.refreshSecret(scrt); err != nil {
            log.Errora(err)
        }

        return
    }

    _, waitErr := wc.certUtil.GetWaitTime(certBytes, time.Now(), wc.minGracePeriod)

    //当 证书过期或者证书CA非K8S CA生成则重新签发
    caCert, err := wc.getCACert()
    if err != nil {
        log.Errorf("failed to get CA certificate: %v", err)
        return
    }
    if waitErr != nil || !bytes.Equal(caCert, scrt.Data[ca.RootCertID]) {
        log.Infof("refreshing secret %s/%s, either the leaf certificate is about to expire "+
            "or the root certificate is outdated", namespace, name)
        //更新证书
        if err = wc.refreshSecret(scrt); err != nil {
            log.Errorf("failed to update secret %s/%s (error: %s)", namespace, name, err)
        }
    }
}

upsertSecret/refreshSecret分别将调用GenKeyCertK8sCA生成证书,GenKeyCertK8sCA又将调用SignCSRK8s发出请求

SignCSRK8s 1. 发起CSR请求 2. Approve CSR 3. 读取certificate 4.删除依赖

Last updated