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  }