测试环境中 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。

对于 kubevirt 的 ca,先从installystrategy中获得名为kubevirt-ca的secret
结构体,根据caDuration
和caRenewBefore
以及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 中TargetKubeVirtVersion
、TargetKubeVirtRegistry
、TargetDeploymentID
放入 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)
}
|
首先根据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
}
|
对于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。
首先选择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)
}
|