Kubevirt 证书实现分析

测试环境中 kubevirt、cdi 均出现过证书过期的问题,于是读了一下相关代码,得出了相关的证书会在过期之前自动更新:),因此理论上不会出现过期问题。相关分析如下,如果各位看官有什么思路非常感谢分享。

ps:关于operator的相关逻辑可以参考“operator ''以及"installstrategy"相关内容本篇仅针对证书相关逻辑。

相关代码包括

KubeVirtController.execute --> c.syncInstallation(kvCopy)-->reconciler.Sync(c.queue)-->createOrUpdateComponentsWithCertificates(queue)

总体流程:首先针对ca以及cert生成Duration、RenewBefore (= 0.2*Duration)等Duration变量。

其次通过各种CreateOrUpdate方法更新secret、cm、service等obj。

CreateOrUpdate方法

对于 kubevirt 的 ca,先从installystrategy中获得名为kubevirt-ca的secret结构体,根据caDurationcaRenewBefore以及secret调用createOrUpdateCertificateSecret方法。

1
2
3
4
//createOrUpdateComponentsWithCertificates
caDuration := GetCADuration(r.kv.Spec.CertificateRotationStrategy.SelfSigned)
caRenewBefore := GetCARenewBefore(r.kv.Spec.CertificateRotationStrategy.SelfSigned)
caCert, err := r.createOrUpdateCACertificateSecret(queue, components.KubeVirtCASecretName, caDuration, caRenewBefore)
1
2
3
4
5
//createOrUpdateCACertificateSecret
	rotateCertificate := false
	if exists {
		rotateCertificate = certificationNeedsRotation(cachedSecret, duration, ca, renewBefore, caRenewBefore)
	}

将 kubevirt 中TargetKubeVirtVersionTargetKubeVirtRegistryTargetDeploymentID放入 secret 的 label 中。然后根据结构体,在 store 和 etcd 中查看该 secret 是否已经在集群中创建。

若存在则调用certificationNeedsRotation方法,判断是否需要更新。

若不存在secret或者secret需要更更新则调用PopulateSecretWithCertificate方法,生成新的secret的相关项。

若不需要更新且secret存在则将secret.data项置为 cachedSecret.data,因为secret是从installstrategy中获取的。

接着根据secretLoadCertificates,返回一个*tls.Certificate。将cert也作为 tls.Certificate的leaf。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func LoadCertificates(secret *k8sv1.Secret) (serverCrt *tls.Certificate, err error) {

	if err := ValidateSecret(secret); err != nil {
		return nil, err
	}

	crt, err := tls.X509KeyPair(secret.Data[bootstrap.CertBytesValue], secret.Data[bootstrap.KeyBytesValue])
	if err != nil {
		return nil, fmt.Errorf("failed to load certificate: %v\n", err)
	}
	leaf, err := cert.ParseCertsPEM(secret.Data[bootstrap.CertBytesValue])
	if err != nil {
		return nil, fmt.Errorf("failed to load leaf certificate: %v\n", err)
	}
	crt.Leaf = leaf[0]
	return &crt, nil
}

然后计算出下次deadline的时间,在deadline之后将kubevirt加入处理队列。

1
2
3
//createOrUpdateCertificateSecret
wakeupDeadline := components.NextRotationDeadline(crt, ca, renewBefore, caRenewBefore).Sub(time.Now())
	queue.AddAfter(r.kvKey, wakeupDeadline)

如果 secret 不存在发送创建 secret 的请求。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
	if !exists {
		r.expectations.Secrets.RaiseExpectations(r.kvKey, 1, 0)
		_, err := r.clientset.CoreV1().Secrets(secret.Namespace).Create(context.Background(), secret, metav1.CreateOptions{})
		if err != nil {
			r.expectations.Secrets.LowerExpectations(r.kvKey, 1, 0)
			return nil, fmt.Errorf("unable to create secret %+v: %v", secret, err)
		}

		return crt, nil
	}

查看当前状态的secret 是否与stroe中的一致,若一致则不需要更新,直接return,否则将store中的字段更新,并使用patch更新secret。

1
2
3
4
5
6
7
8
9
	ops, err := createSecretPatch(secret)
	if err != nil {
		return nil, err
	}

	_, err = r.clientset.CoreV1().Secrets(secret.Namespace).Patch(context.Background(), secret.Name, types.JSONPatchType, generatePatchBytes(ops), metav1.PatchOptions{})
	if err != nil {
		return nil, fmt.Errorf("unable to patch secret %+v: %v", secret, err)
	}

certificationNeedsRotation方法

首先根据secret中内容LoadCertificates,若 secret.Annotations 的"kubevirt.io/duration"字段不等于方法中的duration参数则需要更新;

接着获取下次更新的 deadline ,若 deadline 早于当时时间则返回true,需要更新。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20

func certificationNeedsRotation(secret *corev1.Secret, duration *metav1.Duration, ca *tls.Certificate, renewBefore *metav1.Duration, caRenewBefore *metav1.Duration) bool {
	crt, err := components.LoadCertificates(secret)
	if err != nil {
		log.DefaultLogger().Reason(err).Infof("Failed to load certificate from secret %s, will rotate it.", secret.Name)
		return true
	}

	if secret.Annotations["kubevirt.io/duration"] != duration.String() {
		return true
	}

	rotationTime := components.NextRotationDeadline(crt, ca, renewBefore, caRenewBefore)
	// We update the certificate if it has passed its renewal timeout
	if rotationTime.Before(time.Now()) {
		return true
	}

	return false
}

NextRotationDeadline方法

对于kubevirt ca ca和 caRenewBefore 为nil,因此 certNotAfter cert.Leaf.NotAfter 即 ca cert 本身的 NotAfter值,deadline 为 certNotAfter - renewBefore 。

若ca 不为空,则首先验证ca是否是 cert 的根证书,并且根据 caNotAfter 是否早于 cert 的 NotAfter,若遭遇则deadline 还是等于 caNotAfter,若 caNotAfter 晚于 cert 的 NotAfter 则还是以 cert 的 NotAfter 为 deadline。

PopulateSecretWithCertificate方法

首先选择secret的种类,对于ca,根据duration生成私钥与x509证书。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21

func NewCA(name string, duration time.Duration) (*KeyPair, error) {
	key, err := certutil.NewPrivateKey()
	if err != nil {
		return nil, fmt.Errorf("unable to create a private key for a new CA: %v", err)
	}

	signerName := fmt.Sprintf("%s@%d", name, time.Now().Unix())
	config := certutil.Config{
		CommonName: signerName,
	}

	cert, err := certutil.NewSelfSignedCACert(config, key, duration)
	if err != nil {
		return nil, fmt.Errorf("unable to create a self-signed certificate for a new CA: %v", err)
	}
	return &KeyPair{
		Key:  key,
		Cert: cert,
	}, nil
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

// NewSelfSignedCACertWithAltNames creates a CA certificate that allows alternative names
func NewSelfSignedCACertWithAltNames(cfg Config, key crypto.Signer, duration time.Duration, altNames ...string) (*x509.Certificate, error) {
	now := time.Now()
	tmpl := x509.Certificate{
		SerialNumber: new(big.Int).SetInt64(randomSerialNumber()),
		Subject: pkix.Name{
			CommonName:   cfg.CommonName,
			Organization: cfg.Organization,
		},
		NotBefore:             now.UTC(),
		NotAfter:              now.Add(duration).UTC(),
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		BasicConstraintsValid: true,
		IsCA:                  true,
		DNSNames:              altNames,
	}

	certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &tmpl, &tmpl, key.Public(), key)
	if err != nil {
		return nil, err
	}
	return x509.ParseCertificate(certDERBytes)
}
updatedupdated2023-03-012023-03-01