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 }