istio.io/istio@v0.0.0-20240520182934-d79c90f27776/security/pkg/pki/ca/selfsignedcarootcertrotator.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package ca 16 17 import ( 18 "bytes" 19 "fmt" 20 "math/rand" 21 "time" 22 23 v1 "k8s.io/api/core/v1" 24 corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 25 26 "istio.io/istio/pkg/log" 27 "istio.io/istio/security/pkg/k8s/controller" 28 "istio.io/istio/security/pkg/pki/util" 29 certutil "istio.io/istio/security/pkg/util" 30 ) 31 32 var rootCertRotatorLog = log.RegisterScope("rootcertrotator", "Self-signed CA root cert rotator log") 33 34 type SelfSignedCARootCertRotatorConfig struct { 35 certInspector certutil.CertUtil 36 caStorageNamespace string 37 org string 38 rootCertFile string 39 secretName string 40 client corev1.CoreV1Interface 41 CheckInterval time.Duration 42 caCertTTL time.Duration 43 retryInterval time.Duration 44 retryMax time.Duration 45 dualUse bool 46 enableJitter bool 47 } 48 49 // SelfSignedCARootCertRotator automatically checks self-signed signing root 50 // certificate and rotates root certificate if it is going to expire. 51 type SelfSignedCARootCertRotator struct { 52 caSecretController *controller.CaSecretController 53 config *SelfSignedCARootCertRotatorConfig 54 backOffTime time.Duration 55 ca *IstioCA 56 onRootCertUpdate func() error 57 } 58 59 // NewSelfSignedCARootCertRotator returns a new root cert rotator instance that 60 // rotates self-signed root cert periodically. 61 // nolint: gosec 62 // Not security sensitive code 63 func NewSelfSignedCARootCertRotator(config *SelfSignedCARootCertRotatorConfig, 64 ca *IstioCA, 65 onRootCertUpdate func() error, 66 ) *SelfSignedCARootCertRotator { 67 rotator := &SelfSignedCARootCertRotator{ 68 caSecretController: controller.NewCaSecretController(config.client), 69 config: config, 70 ca: ca, 71 onRootCertUpdate: onRootCertUpdate, 72 } 73 if config.enableJitter { 74 // Select a back off time in seconds, which is in the range of [0, rotator.config.CheckInterval). 75 randSource := rand.NewSource(time.Now().UnixNano()) 76 randBackOff := rand.New(randSource) 77 backOffSeconds := int(time.Duration(randBackOff.Int63n(int64(rotator.config.CheckInterval))).Seconds()) 78 rotator.backOffTime = time.Duration(backOffSeconds) * time.Second 79 rootCertRotatorLog.Infof("Set up back off time %s to start rotator.", rotator.backOffTime.String()) 80 } else { 81 rotator.backOffTime = time.Duration(0) 82 } 83 return rotator 84 } 85 86 // Run refreshes root certs and updates config map accordingly. 87 func (rotator *SelfSignedCARootCertRotator) Run(stopCh chan struct{}) { 88 if rotator.config.enableJitter { 89 rootCertRotatorLog.Infof("Jitter is enabled, wait %s before "+ 90 "starting root cert rotator.", rotator.backOffTime.String()) 91 select { 92 case <-time.After(rotator.backOffTime): 93 rootCertRotatorLog.Infof("Jitter complete, start rotator.") 94 case <-stopCh: 95 rootCertRotatorLog.Info("Received stop signal, so stop the root cert rotator.") 96 return 97 } 98 } 99 ticker := time.NewTicker(rotator.config.CheckInterval) 100 for { 101 select { 102 case <-ticker.C: 103 rootCertRotatorLog.Info("Check and rotate root cert.") 104 rotator.checkAndRotateRootCert() 105 case _, ok := <-stopCh: 106 if !ok { 107 rootCertRotatorLog.Info("Received stop signal, so stop the root cert rotator.") 108 if ticker != nil { 109 ticker.Stop() 110 } 111 return 112 } 113 } 114 } 115 } 116 117 // checkAndRotateRootCert decides whether root cert should be refreshed, and rotates 118 // root cert for self-signed Citadel. 119 func (rotator *SelfSignedCARootCertRotator) checkAndRotateRootCert() { 120 caSecret, scrtErr := rotator.caSecretController.LoadCASecretWithRetry(rotator.config.secretName, 121 rotator.config.caStorageNamespace, rotator.config.retryInterval, rotator.config.retryMax) 122 123 if scrtErr != nil { 124 rootCertRotatorLog.Errorf("Fail to load CA secret %s:%s (error: %s), skip cert rotation job", 125 rotator.config.caStorageNamespace, rotator.config.secretName, scrtErr.Error()) 126 } else { 127 rotator.checkAndRotateRootCertForSigningCertCitadel(caSecret) 128 } 129 } 130 131 // checkAndRotateRootCertForSigningCertCitadel checks root cert secret and rotates 132 // root cert if the current one is about to expire. The rotation uses existing 133 // root private key to generate a new root cert, and updates root cert secret. 134 func (rotator *SelfSignedCARootCertRotator) checkAndRotateRootCertForSigningCertCitadel( 135 caSecret *v1.Secret, 136 ) { 137 if caSecret == nil { 138 rootCertRotatorLog.Errorf("root cert secret %s is nil, skip cert rotation job", 139 rotator.config.secretName) 140 return 141 } 142 // Check root certificate expiration time in CA secret 143 waitTime, err := rotator.config.certInspector.GetWaitTime(caSecret.Data[CACertFile], time.Now()) 144 if err == nil && waitTime > 0 { 145 rootCertRotatorLog.Info("Root cert is not about to expire, skipping root cert rotation.") 146 caCertInMem, _, _, _ := rotator.ca.GetCAKeyCertBundle().GetAllPem() 147 // If CA certificate is different from the CA certificate in local key 148 // cert bundle, it implies that other Citadels have updated istio-ca-secret or cacerts. 149 // Reload root certificate into key cert bundle. 150 if !bytes.Equal(caCertInMem, caSecret.Data[CACertFile]) { 151 rootCertRotatorLog.Warnf("CA cert in KeyCertBundle does not match CA cert in "+ 152 "%s. Start to reload root cert into KeyCertBundle", rotator.config.secretName) 153 rootCerts, err := util.AppendRootCerts(caSecret.Data[CACertFile], rotator.config.rootCertFile) 154 if err != nil { 155 rootCertRotatorLog.Errorf("failed to append root certificates from file: %s", err.Error()) 156 return 157 } 158 159 if err := rotator.ca.GetCAKeyCertBundle().VerifyAndSetAll(caSecret.Data[CACertFile], 160 caSecret.Data[CAPrivateKeyFile], nil, rootCerts); err != nil { 161 rootCertRotatorLog.Errorf("failed to reload root cert into KeyCertBundle (%v)", err) 162 } else { 163 rootCertRotatorLog.Info("Successfully reloaded root cert into KeyCertBundle.") 164 } 165 if rotator.onRootCertUpdate != nil { 166 _ = rotator.onRootCertUpdate() 167 } 168 } 169 return 170 } 171 172 rootCertRotatorLog.Infof("Refresh root certificate, root cert is about to expire: %s", err.Error()) 173 174 oldCertOptions, err := util.GetCertOptionsFromExistingCert(caSecret.Data[CACertFile]) 175 if err != nil { 176 rootCertRotatorLog.Warnf("Failed to generate cert options from existing root certificate (%v), "+ 177 "new root certificate may not match old root certificate", err) 178 } 179 options := util.CertOptions{ 180 TTL: rotator.config.caCertTTL, 181 SignerPrivPem: caSecret.Data[CAPrivateKeyFile], 182 Org: rotator.config.org, 183 IsCA: true, 184 IsSelfSigned: true, 185 RSAKeySize: rotator.ca.caRSAKeySize, 186 IsDualUse: rotator.config.dualUse, 187 } 188 // options should be consistent with the one used in NewSelfSignedIstioCAOptions(). 189 // This is to make sure when rotate the root cert, we don't make unnecessary changes 190 // to the certificate or add extra fields to the certificate. 191 options = util.MergeCertOptions(options, oldCertOptions) 192 pemCert, pemKey, ckErr := util.GenRootCertFromExistingKey(options) 193 if ckErr != nil { 194 rootCertRotatorLog.Errorf("unable to generate CA cert and key for self-signed CA: %s", ckErr.Error()) 195 return 196 } 197 198 pemRootCerts, err := util.AppendRootCerts(pemCert, rotator.config.rootCertFile) 199 if err != nil { 200 rootCertRotatorLog.Errorf("failed to append root certificates: %s", err.Error()) 201 return 202 } 203 204 oldCaCert := caSecret.Data[CACertFile] 205 oldCaPrivateKey := caSecret.Data[CAPrivateKeyFile] 206 oldRootCerts := rotator.ca.GetCAKeyCertBundle().GetRootCertPem() 207 if rollback, err := rotator.updateRootCertificate(caSecret, true, pemCert, pemKey, pemRootCerts); err != nil { 208 if !rollback { 209 rootCertRotatorLog.Errorf("Failed to roll forward root certificate (error: %s). "+ 210 "Abort new root certificate", err.Error()) 211 return 212 } 213 // caSecret is out-of-date. Need to load the latest istio-ca-secret to roll back root certificate. 214 _, err = rotator.updateRootCertificate(nil, false, oldCaCert, oldCaPrivateKey, oldRootCerts) 215 if err != nil { 216 rootCertRotatorLog.Errorf("Failed to roll backward root certificate (error: %s).", err.Error()) 217 } 218 return 219 } 220 rootCertRotatorLog.Info("Root certificate rotation is completed successfully.") 221 } 222 223 // updateRootCertificate updates root certificate in istio-ca-secret, keycertbundle and configmap. It takes a scrt 224 // object, cert, and key, and a flag rollForward indicating whether this update is to roll forward root certificate or 225 // to roll backward. 226 // updateRootCertificate returns error when any step is failed, and a flag indicating whether a rollback is required. 227 // Only when rollForward is true and failure happens, the returned rollback flag is true. 228 func (rotator *SelfSignedCARootCertRotator) updateRootCertificate(caSecret *v1.Secret, rollForward bool, cert, key, rootCert []byte) (bool, error) { 229 var err error 230 if caSecret == nil { 231 caSecret, err = rotator.caSecretController.LoadCASecretWithRetry(rotator.config.secretName, 232 rotator.config.caStorageNamespace, rotator.config.retryInterval, rotator.config.retryMax) 233 if err != nil { 234 return false, fmt.Errorf("failed to load CA secret %s:%s (error: %s)", rotator.config.caStorageNamespace, rotator.config.secretName, 235 err.Error()) 236 } 237 } 238 caSecret.Data[CACertFile] = cert 239 caSecret.Data[CAPrivateKeyFile] = key 240 if err = rotator.caSecretController.UpdateCASecretWithRetry(caSecret, rotator.config.retryInterval, rotator.config.retryMax); err != nil { 241 return false, fmt.Errorf("failed to update CA secret (error: %s)", err.Error()) 242 } 243 rootCertRotatorLog.Infof("Root certificate is written into CA secret: %v", string(cert)) 244 if err := rotator.ca.GetCAKeyCertBundle().VerifyAndSetAll(cert, key, nil, rootCert); err != nil { 245 if rollForward { 246 // Rolling forward root certificate fails at keycertbundle update, notify caller to rollback. 247 return true, fmt.Errorf("failed to update CA KeyCertBundle (error: %s)", err.Error()) 248 } 249 return false, fmt.Errorf("failed to update CA KeyCertBundle (error: %s)", err.Error()) 250 } 251 rootCertRotatorLog.Infof("Root certificate is updated in CA KeyCertBundle: %v", string(cert)) 252 if rotator.onRootCertUpdate != nil { 253 _ = rotator.onRootCertUpdate() 254 } 255 return false, nil 256 }