github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/initializer/ca/hsmdaemon.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 initializer
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"path/filepath"
    25  	"strings"
    26  
    27  	"github.com/pkg/errors"
    28  
    29  	current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1"
    30  	v1 "github.com/IBM-Blockchain/fabric-operator/pkg/apis/ca/v1"
    31  	caconfig "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/ca/config"
    32  	"github.com/IBM-Blockchain/fabric-operator/pkg/initializer/common/config"
    33  	controller "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient"
    34  	jobv1 "github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources/job"
    35  	"github.com/IBM-Blockchain/fabric-operator/pkg/util"
    36  	"github.com/IBM-Blockchain/fabric-operator/pkg/util/image"
    37  
    38  	batchv1 "k8s.io/api/batch/v1"
    39  	corev1 "k8s.io/api/core/v1"
    40  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    41  	"k8s.io/apimachinery/pkg/runtime"
    42  )
    43  
    44  // HSMDaemon implements the ability to initialize HSM Daemon based CA
    45  type HSMDaemon struct {
    46  	Config   *config.HSMConfig
    47  	Scheme   *runtime.Scheme
    48  	Timeouts HSMInitJobTimeouts
    49  	Client   controller.Client
    50  }
    51  
    52  // Create creates the crypto and config materical to initialize an HSM based CA
    53  func (h *HSMDaemon) Create(instance *current.IBPCA, overrides *v1.ServerConfig, ca IBPCA) (*Response, error) {
    54  	log.Info(fmt.Sprintf("Creating job to initialize ca '%s'", instance.GetName()))
    55  
    56  	if err := ca.OverrideServerConfig(overrides); err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	if err := createCACryptoSecret(h.Client, h.Scheme, instance, ca); err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	if err := createCAConfigMap(h.Client, h.Scheme, instance, h.Config.Library.FilePath, ca); err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	dbConfig, err := getDBConfig(instance, ca.GetType())
    69  	if err != nil {
    70  		return nil, errors.Wrapf(err, "failed get DB config for CA '%s'", instance.GetName())
    71  	}
    72  
    73  	job := h.initHSMCAJob(instance, dbConfig, ca.GetType())
    74  	if err := h.Client.Create(context.TODO(), job.Job, controller.CreateOption{
    75  		Owner:  instance,
    76  		Scheme: h.Scheme,
    77  	}); err != nil {
    78  		return nil, errors.Wrap(err, "failed to create HSM ca initialization job")
    79  	}
    80  	log.Info(fmt.Sprintf("Job '%s' created", job.GetName()))
    81  
    82  	if err := job.WaitUntilActive(h.Client); err != nil {
    83  		return nil, err
    84  	}
    85  	log.Info(fmt.Sprintf("Job '%s' active", job.GetName()))
    86  
    87  	if err := job.WaitUntilContainerFinished(h.Client, CertGen); err != nil {
    88  		return nil, err
    89  	}
    90  	log.Info(fmt.Sprintf("Job '%s' finished", job.GetName()))
    91  
    92  	status, err := job.ContainerStatus(h.Client, CertGen)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	switch status {
    98  	case jobv1.FAILED:
    99  		return nil, fmt.Errorf("Job '%s' finished unsuccessfully, not cleaning up pods to allow for error evaluation", job.GetName())
   100  	case jobv1.COMPLETED:
   101  		// For posterity, job is only deleted if successful, not deleting on failure allows logs to be
   102  		// examined.
   103  		if err := job.Delete(h.Client); err != nil {
   104  			return nil, err
   105  		}
   106  	}
   107  
   108  	if ca.GetType().Is(caconfig.EnrollmentCA) {
   109  		if err := updateCAConfigMap(h.Client, h.Scheme, instance, ca); err != nil {
   110  			return nil, errors.Wrapf(err, "failed to update CA configmap for CA %s", instance.GetName())
   111  		}
   112  	}
   113  
   114  	return nil, nil
   115  }
   116  
   117  const (
   118  	// HSMClient is the name of container that contain the HSM client library
   119  	HSMClient = "hsm-client"
   120  	// CertGen is the name of container that runs the command to generate the certificate for the CA
   121  	CertGen = "certgen"
   122  )
   123  
   124  func (h *HSMDaemon) initHSMCAJob(instance *current.IBPCA, dbConfig *v1.CAConfigDB, caType caconfig.Type) *jobv1.Job {
   125  	var typ string
   126  
   127  	switch caType {
   128  	case caconfig.EnrollmentCA:
   129  		typ = "ca"
   130  	case caconfig.TLSCA:
   131  		typ = "tlsca"
   132  	}
   133  
   134  	cryptoMountPath := fmt.Sprintf("/crypto/%s", typ)
   135  	homeDir := fmt.Sprintf("/tmp/data/%s/%s", instance.GetName(), typ)
   136  	secretName := fmt.Sprintf("%s-%s-crypto", instance.GetName(), typ)
   137  
   138  	jobName := fmt.Sprintf("%s-%s-init", instance.GetName(), typ)
   139  
   140  	hsmLibraryPath := h.Config.Library.FilePath
   141  	hsmLibraryName := filepath.Base(hsmLibraryPath)
   142  
   143  	t := true
   144  	user := int64(1000)
   145  	root := int64(0)
   146  	backoffLimit := int32(0)
   147  	mountPath := "/shared"
   148  	pvcVolumeName := "fabric-ca"
   149  
   150  	batchJob := &batchv1.Job{
   151  		ObjectMeta: metav1.ObjectMeta{
   152  			Name:      jobName,
   153  			Namespace: instance.GetNamespace(),
   154  			Labels: map[string]string{
   155  				"name":  jobName,
   156  				"owner": instance.GetName(),
   157  			},
   158  		},
   159  		Spec: batchv1.JobSpec{
   160  			BackoffLimit: &backoffLimit,
   161  			Template: corev1.PodTemplateSpec{
   162  				Spec: corev1.PodSpec{
   163  					ServiceAccountName: instance.GetName(),
   164  					ImagePullSecrets:   util.AppendImagePullSecretIfMissing(instance.GetPullSecrets(), h.Config.BuildPullSecret()),
   165  					RestartPolicy:      corev1.RestartPolicyNever,
   166  					InitContainers: []corev1.Container{
   167  						{
   168  							Name:            HSMClient,
   169  							Image:           h.Config.Library.Image,
   170  							ImagePullPolicy: corev1.PullAlways,
   171  							Command: []string{
   172  								"sh",
   173  								"-c",
   174  								fmt.Sprintf("mkdir -p %s/hsm && dst=\"%s/hsm/%s\" && echo \"Copying %s to ${dst}\" && mkdir -p $(dirname $dst) && cp -r %s $dst", mountPath, mountPath, hsmLibraryName, hsmLibraryPath, hsmLibraryPath),
   175  							},
   176  							SecurityContext: &corev1.SecurityContext{
   177  								RunAsUser:    &user,
   178  								RunAsNonRoot: &t,
   179  							},
   180  							VolumeMounts: []corev1.VolumeMount{
   181  								{
   182  									Name:      "shared",
   183  									MountPath: mountPath,
   184  								},
   185  							},
   186  							Resources: instance.GetResource("init"),
   187  						},
   188  					},
   189  					Containers: []corev1.Container{
   190  						{
   191  							Name: CertGen,
   192  							Image: image.Format(
   193  								instance.Spec.Images.EnrollerImage,
   194  								instance.Spec.Images.EnrollerTag,
   195  							),
   196  							ImagePullPolicy: corev1.PullAlways,
   197  							SecurityContext: &corev1.SecurityContext{
   198  								RunAsUser:                &root,
   199  								Privileged:               &t,
   200  								AllowPrivilegeEscalation: &t,
   201  							},
   202  							Command: []string{
   203  								"sh",
   204  								"-c",
   205  							},
   206  							Args: []string{
   207  								fmt.Sprintf(config.DAEMON_CHECK_CMD+" && /usr/local/bin/enroller ca %s %s %s %s %s %s", instance.GetName(), instance.GetNamespace(), homeDir, cryptoMountPath, secretName, caType),
   208  							},
   209  							Env:       h.Config.GetEnvs(),
   210  							Resources: instance.GetResource(current.ENROLLER),
   211  							VolumeMounts: []corev1.VolumeMount{
   212  								{
   213  									Name:      "shared",
   214  									MountPath: "/hsm/lib",
   215  									SubPath:   "hsm",
   216  								},
   217  								{
   218  									Name:      "shared",
   219  									MountPath: "/shared",
   220  								},
   221  								{
   222  									Name:      "caconfig",
   223  									MountPath: fmt.Sprintf("/tmp/data/%s/%s/fabric-ca-server-config.yaml", instance.GetName(), typ),
   224  									SubPath:   "fabric-ca-server-config.yaml",
   225  								},
   226  							},
   227  						},
   228  					},
   229  					Volumes: []corev1.Volume{
   230  						{
   231  							Name: "shared",
   232  							VolumeSource: corev1.VolumeSource{
   233  								EmptyDir: &corev1.EmptyDirVolumeSource{
   234  									Medium: corev1.StorageMediumMemory,
   235  								},
   236  							},
   237  						},
   238  						{
   239  							Name: "caconfig",
   240  							VolumeSource: corev1.VolumeSource{
   241  								ConfigMap: &corev1.ConfigMapVolumeSource{
   242  									LocalObjectReference: corev1.LocalObjectReference{
   243  										Name: fmt.Sprintf("%s-%s-config", instance.GetName(), typ),
   244  									},
   245  								},
   246  							},
   247  						},
   248  						{
   249  							Name: pvcVolumeName,
   250  							VolumeSource: corev1.VolumeSource{
   251  								PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
   252  									ClaimName: instance.PVCName(),
   253  								},
   254  							},
   255  						},
   256  					},
   257  				},
   258  			},
   259  		},
   260  	}
   261  	job := jobv1.New(batchJob, &jobv1.Timeouts{
   262  		WaitUntilActive:   h.Timeouts.JobStart.Get(),
   263  		WaitUntilFinished: h.Timeouts.JobCompletion.Get(),
   264  	})
   265  
   266  	job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, h.Config.GetVolumes()...)
   267  	job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts, h.Config.GetVolumeMounts()...)
   268  
   269  	if dbConfig != nil {
   270  		// If using postgres with TLS enabled need to mount trusted root TLS certificate for database server
   271  		if strings.ToLower(dbConfig.Type) == "postgres" {
   272  			if dbConfig.TLS.IsEnabled() {
   273  				job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts,
   274  					corev1.VolumeMount{
   275  						Name:      "cacrypto",
   276  						MountPath: fmt.Sprintf("/crypto/%s/db-certfile0.pem", typ),
   277  						SubPath:   "db-certfile0.pem",
   278  					})
   279  
   280  				job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes,
   281  					corev1.Volume{
   282  						Name: "cacrypto",
   283  						VolumeSource: corev1.VolumeSource{
   284  							Secret: &corev1.SecretVolumeSource{
   285  								SecretName: fmt.Sprintf("%s-%s-crypto", instance.GetName(), typ),
   286  								Items: []corev1.KeyToPath{
   287  									corev1.KeyToPath{
   288  										Key:  "db-certfile0.pem",
   289  										Path: "db-certfile0.pem",
   290  									},
   291  								},
   292  							},
   293  						},
   294  					},
   295  				)
   296  			}
   297  		}
   298  	}
   299  
   300  	// If daemon settings are configured in HSM config, create a sidecar that is running the daemon image
   301  	if h.Config.Daemon != nil {
   302  		// Certain token information requires to be stored in persistent store, the administrator
   303  		// responsible for configuring HSM sets the HSM config to point to the path where the PVC
   304  		// needs to be mounted.
   305  		var pvcMount *corev1.VolumeMount
   306  		for _, vm := range h.Config.MountPaths {
   307  			if vm.UsePVC {
   308  				pvcMount = &corev1.VolumeMount{
   309  					Name:      pvcVolumeName,
   310  					MountPath: vm.MountPath,
   311  				}
   312  			}
   313  		}
   314  
   315  		// Add daemon container to the deployment
   316  		config.AddDaemonContainer(h.Config, job, instance.GetResource(current.HSMDAEMON), pvcMount)
   317  
   318  		// If a pvc mount has been configured in HSM config, set the volume mount on the CertGen container
   319  		if pvcMount != nil {
   320  			job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts, *pvcMount)
   321  		}
   322  	}
   323  
   324  	return job
   325  }