github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/certificate/certificate.go (about) 1 /* 2 * Copyright contributors to the Hyperledger Fabric Operator project 3 * 4 * SPDX-License-Identifier: Apache-2.0 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package certificate 20 21 import ( 22 "context" 23 "fmt" 24 "path/filepath" 25 "time" 26 27 current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" 28 commonapi "github.com/IBM-Blockchain/fabric-operator/pkg/apis/common" 29 "github.com/IBM-Blockchain/fabric-operator/pkg/certificate/reenroller" 30 "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/common" 31 "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/common/config" 32 k8sclient "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient" 33 "github.com/IBM-Blockchain/fabric-operator/pkg/util" 34 "github.com/pkg/errors" 35 corev1 "k8s.io/api/core/v1" 36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 37 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 38 "k8s.io/apimachinery/pkg/runtime" 39 "k8s.io/apimachinery/pkg/types" 40 logf "sigs.k8s.io/controller-runtime/pkg/log" 41 ) 42 43 var log = logf.Log.WithName("certificate_manager") 44 45 //go:generate counterfeiter -o mocks/reenroller.go -fake-name Reenroller . Reenroller 46 type Reenroller interface { 47 Reenroll() (*config.Response, error) 48 } 49 50 type CertificateManager struct { 51 Client k8sclient.Client 52 Scheme *runtime.Scheme 53 } 54 55 func New(client k8sclient.Client, scheme *runtime.Scheme) *CertificateManager { 56 return &CertificateManager{ 57 Client: client, 58 Scheme: scheme, 59 } 60 } 61 62 func (c *CertificateManager) GetExpireDate(pemBytes []byte) (time.Time, error) { 63 cert, err := util.GetCertificateFromPEMBytes(pemBytes) 64 if err != nil { 65 return time.Time{}, errors.New("failed to get certificate from bytes") 66 } 67 68 return cert.NotAfter, nil 69 } 70 71 func (c *CertificateManager) GetDurationToNextRenewal(certType common.SecretType, instance v1.Object, numSecondsBeforeExpire int64) (time.Duration, error) { 72 certName := fmt.Sprintf("%s-%s-signcert", certType, instance.GetName()) 73 cert, err := c.GetSignCert(certName, instance.GetNamespace()) 74 if err != nil { 75 return time.Duration(0), err 76 } 77 78 return c.GetDurationToNextRenewalForCert(certName, cert, instance, numSecondsBeforeExpire) 79 } 80 81 func (c *CertificateManager) GetDurationToNextRenewalForCert(certName string, cert []byte, instance v1.Object, numSecondsBeforeExpire int64) (time.Duration, error) { 82 expireDate, err := c.GetExpireDate(cert) 83 if err != nil { 84 return time.Duration(0), err 85 } 86 87 if expireDate.IsZero() { 88 return time.Duration(0), errors.New("failed to get non-zero expiration date from certificate") 89 } 90 if expireDate.Before(time.Now()) { 91 return time.Duration(0), fmt.Errorf("%s has expired", certName) 92 } 93 94 renewDate := expireDate.Add(-time.Duration(numSecondsBeforeExpire) * time.Second) // Subtract num seconds from expire date 95 duration := renewDate.Sub(time.Now()) // Get duration between now and the renew date (negative duration means renew date < time.Now()) 96 if duration < 0 { 97 return time.Duration(0), nil 98 } 99 return duration, nil 100 } 101 102 func (c *CertificateManager) CertificateExpiring(certType common.SecretType, instance v1.Object, numSecondsBeforeExpire int64) (expiring bool, expireDate time.Time, err error) { 103 certName := fmt.Sprintf("%s-%s-signcert", certType, instance.GetName()) 104 cert, err := c.GetSignCert(certName, instance.GetNamespace()) 105 if err != nil { 106 return false, time.Time{}, err 107 } 108 109 return c.Expires(cert, numSecondsBeforeExpire) 110 } 111 112 func (c *CertificateManager) Expires(cert []byte, numSecondsBeforeExpire int64) (expiring bool, expireDate time.Time, err error) { 113 expireDate, err = c.GetExpireDate(cert) 114 if err != nil { 115 return false, time.Time{}, err 116 } 117 118 // Checks if the duration between time.Now() and the expiration date is less than or equal to the numSecondsBeforeExpire 119 if expireDate.Sub(time.Now()) <= time.Duration(numSecondsBeforeExpire)*time.Second { 120 return true, expireDate, nil 121 } 122 123 return false, time.Time{}, nil 124 } 125 126 func (c *CertificateManager) CheckCertificatesForExpire(instance v1.Object, numSecondsBeforeExpire int64) (statusType current.IBPCRStatusType, message string, err error) { 127 tlsExpiring, tlsExpireDate, err := c.CertificateExpiring(common.TLS, instance, numSecondsBeforeExpire) 128 if err != nil { 129 err = errors.Wrap(err, "failed to get tls signcert expiry info") 130 return 131 } 132 133 ecertExpiring, ecertExpireDate, err := c.CertificateExpiring(common.ECERT, instance, numSecondsBeforeExpire) 134 if err != nil { 135 err = errors.Wrap(err, "failed to get ecert signcert expiry info") 136 return 137 } 138 139 // If not certificate are expring, no further action is required 140 if !tlsExpiring && !ecertExpiring { 141 return current.Deployed, "", nil 142 } 143 144 statusType = current.Warning 145 146 if tlsExpiring { 147 // Check if tls cert's expiration date has already passed 148 if tlsExpireDate.Before(time.Now()) { 149 statusType = current.Error 150 message += fmt.Sprintf("tls-%s-signcert has expired", instance.GetName()) 151 } else { 152 message += fmt.Sprintf("tls-%s-signcert expires on %s", instance.GetName(), tlsExpireDate.String()) 153 } 154 } 155 156 if message != "" { 157 message += ", " 158 } 159 160 if ecertExpiring { 161 // Check if ecert's expiration date has already passed 162 if ecertExpireDate.Before(time.Now()) { 163 statusType = current.Error 164 message += fmt.Sprintf("ecert-%s-signcert has expired", instance.GetName()) 165 } else { 166 message += fmt.Sprintf("ecert-%s-signcert expires on %s", instance.GetName(), ecertExpireDate.String()) 167 } 168 } 169 170 return statusType, message, nil 171 } 172 173 func (c *CertificateManager) GetReenroller(certType common.SecretType, spec *current.EnrollmentSpec, bccsp *commonapi.BCCSP, storagePath string, certPemBytes, keyPemBytes []byte, hsmEnabled bool, newKey bool) (Reenroller, error) { 174 storagePath = filepath.Join(storagePath, "reenroller", string(certType)) 175 176 var cfg *current.Enrollment 177 if certType == common.TLS { 178 cfg = spec.TLS 179 } else { 180 cfg = spec.Component 181 } 182 183 certReenroller, err := reenroller.New(cfg, storagePath, bccsp, "", newKey) 184 if err != nil { 185 return nil, errors.Wrap(err, "failed to initialize reenroller") 186 } 187 188 err = certReenroller.InitClient() 189 if err != nil { 190 return nil, errors.Wrap(err, "failed to initialize CA client for reenroller") 191 } 192 193 err = certReenroller.LoadIdentity(certPemBytes, keyPemBytes, hsmEnabled) 194 if err != nil { 195 return nil, errors.Wrap(err, "failed to load Identity for reenroller") 196 } 197 198 return certReenroller, nil 199 } 200 201 func (c *CertificateManager) ReenrollCert(certType common.SecretType, reenroller Reenroller, instance v1.Object, hsmEnabled bool) error { 202 if reenroller == nil { 203 return errors.New("reenroller not passed") 204 } 205 206 resp, err := reenroller.Reenroll() 207 if err != nil { 208 return errors.Wrapf(err, "failed to renew %s certificate for instance '%s'", certType, instance.GetName()) 209 } 210 211 err = c.UpdateSignCert(fmt.Sprintf("%s-%s-signcert", certType, instance.GetName()), resp.SignCert, instance) 212 if err != nil { 213 return errors.Wrapf(err, "failed to update signcert secret for instance '%s'", instance.GetName()) 214 } 215 216 if !hsmEnabled { 217 err = c.UpdateKey(fmt.Sprintf("%s-%s-keystore", certType, instance.GetName()), resp.Keystore, instance) 218 if err != nil { 219 return errors.Wrapf(err, "failed to update keystore secret for instance '%s'", instance.GetName()) 220 } 221 } 222 223 return nil 224 } 225 226 type Instance interface { 227 v1.Object 228 UsingHSMProxy() bool 229 IsHSMEnabled() bool 230 EnrollerImage() string 231 GetPullSecrets() []corev1.LocalObjectReference 232 GetResource(current.Component) corev1.ResourceRequirements 233 PVCName() string 234 } 235 236 func (c *CertificateManager) RenewCert(certType common.SecretType, instance Instance, spec *current.EnrollmentSpec, bccsp *commonapi.BCCSP, storagePath string, hsmEnabled bool, newKey bool) error { 237 cert, key, err := c.GetSignCertAndKey(certType, instance, hsmEnabled) 238 if err != nil { 239 return err 240 } 241 242 if certType == common.TLS && hsmEnabled && !instance.UsingHSMProxy() { 243 bccsp = nil 244 } 245 246 var certReenroller Reenroller 247 if certType == common.ECERT && hsmEnabled && !instance.UsingHSMProxy() { 248 log.Info(fmt.Sprintf("Certificate manager renewing ecert, non-proxy HSM enabled")) 249 hsmConfig, err := config.ReadHSMConfig(c.Client, instance) 250 if err != nil { 251 return err 252 } 253 254 if hsmConfig.Daemon != nil { 255 certReenroller, err = reenroller.NewHSMDaemonReenroller(spec.Component, storagePath, bccsp, "", hsmConfig, instance, c.Client, c.Scheme, newKey) 256 if err != nil { 257 return err 258 } 259 } else { 260 certReenroller, err = reenroller.NewHSMReenroller(spec.Component, storagePath, bccsp, "", hsmConfig, instance, c.Client, c.Scheme, newKey) 261 if err != nil { 262 return err 263 } 264 } 265 266 err = c.ReenrollCert(certType, certReenroller, instance, hsmEnabled) 267 if err != nil { 268 return err 269 } 270 271 return nil 272 } 273 274 // For TLS certificate, always use software enroller. We don't support HSM for TLS certificates 275 if certType == common.TLS { 276 log.Info("Certificate manager renewing TLS") 277 bccsp = nil 278 279 keySecretName := fmt.Sprintf("%s-%s-keystore", certType, instance.GetName()) 280 key, err = c.GetKey(keySecretName, instance.GetNamespace()) 281 if err != nil { 282 return err 283 } 284 285 certReenroller, err = c.GetReenroller(certType, spec, bccsp, storagePath, cert, key, false, newKey) 286 if err != nil { 287 return err 288 } 289 290 err = c.ReenrollCert(certType, certReenroller, instance, false) 291 if err != nil { 292 return err 293 } 294 295 return nil 296 } 297 298 log.Info(fmt.Sprintf("Certificate manager renewing %s", certType)) 299 certReenroller, err = c.GetReenroller(certType, spec, bccsp, storagePath, cert, key, hsmEnabled, newKey) 300 if err != nil { 301 return err 302 } 303 304 err = c.ReenrollCert(certType, certReenroller, instance, hsmEnabled) 305 if err != nil { 306 return err 307 } 308 309 return nil 310 } 311 312 func (c *CertificateManager) UpdateSignCert(name string, cert []byte, instance v1.Object) error { 313 // Cert might not be returned from reenroll call, for example if the reenroll happens in a job which handles 314 // updating the secret when using HSM (non-proxy) 315 if cert == nil || len(cert) == 0 { 316 return nil 317 } 318 319 data := map[string][]byte{ 320 "cert.pem": cert, 321 } 322 323 err := c.UpdateSecret(instance, name, data) 324 if err != nil { 325 return err 326 } 327 328 return nil 329 } 330 331 func (c *CertificateManager) UpdateKey(name string, key []byte, instance v1.Object) error { 332 // Need to ensure the value passed in for key is valid before updating. 333 // Otherwise, an empty key will end up in the secret overriding a valid key, which 334 // will cause runtime errors on nodes 335 if key == nil || len(key) == 0 { 336 return nil 337 } 338 339 data := map[string][]byte{ 340 "key.pem": key, 341 } 342 343 err := c.UpdateSecret(instance, name, data) 344 if err != nil { 345 return err 346 } 347 348 return nil 349 } 350 351 func (c *CertificateManager) UpdateSecret(instance v1.Object, name string, data map[string][]byte) error { 352 secret := &corev1.Secret{ 353 ObjectMeta: metav1.ObjectMeta{ 354 Name: name, 355 Namespace: instance.GetNamespace(), 356 Labels: instance.GetLabels(), 357 }, 358 Data: data, 359 Type: corev1.SecretTypeOpaque, 360 } 361 362 err := c.Client.Update(context.TODO(), secret, k8sclient.UpdateOption{Owner: instance, Scheme: c.Scheme}) 363 if err != nil { 364 return err 365 } 366 367 return nil 368 } 369 370 func (c *CertificateManager) GetSignCertAndKey(certType common.SecretType, instance v1.Object, hsmEnabled bool) ([]byte, []byte, error) { 371 certSecretName := fmt.Sprintf("%s-%s-signcert", certType, instance.GetName()) 372 keySecretName := fmt.Sprintf("%s-%s-keystore", certType, instance.GetName()) 373 374 cert, err := c.GetSignCert(certSecretName, instance.GetNamespace()) 375 if err != nil { 376 return nil, nil, err 377 } 378 379 key := []byte{} 380 if !hsmEnabled { 381 key, err = c.GetKey(keySecretName, instance.GetNamespace()) 382 if err != nil { 383 return nil, nil, err 384 } 385 } 386 387 return cert, key, nil 388 } 389 390 func (c *CertificateManager) GetSignCert(name, namespace string) ([]byte, error) { 391 secret, err := c.GetSecret(name, namespace) 392 if err != nil { 393 return nil, err 394 } 395 396 if secret.Data == nil || len(secret.Data) == 0 { 397 return nil, errors.New(fmt.Sprintf("%s secret is blank", name)) 398 } 399 400 if secret.Data["cert.pem"] != nil { 401 return secret.Data["cert.pem"], nil 402 } 403 404 return nil, errors.New(fmt.Sprintf("cannot get %s", name)) 405 } 406 407 func (c *CertificateManager) GetKey(name, namespace string) ([]byte, error) { 408 secret, err := c.GetSecret(name, namespace) 409 if err != nil { 410 return nil, err 411 } 412 413 if secret.Data == nil || len(secret.Data) == 0 { 414 return nil, errors.New(fmt.Sprintf("%s secret is blank", name)) 415 } 416 417 if secret.Data["key.pem"] != nil { 418 return secret.Data["key.pem"], nil 419 } 420 421 return nil, errors.New(fmt.Sprintf("cannot get %s", name)) 422 } 423 424 func (c *CertificateManager) GetSecret(name, namespace string) (*corev1.Secret, error) { 425 namespacedName := types.NamespacedName{ 426 Name: name, 427 Namespace: namespace, 428 } 429 430 secret := &corev1.Secret{} 431 err := c.Client.Get(context.TODO(), namespacedName, secret) 432 if err != nil { 433 return nil, err 434 } 435 436 return secret, nil 437 }