github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/offering/base/ca/ca.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 baseca 20 21 import ( 22 "context" 23 "crypto/ecdsa" 24 "crypto/elliptic" 25 crand "crypto/rand" 26 "crypto/x509" 27 "crypto/x509/pkix" 28 "encoding/base64" 29 "encoding/json" 30 "encoding/pem" 31 "fmt" 32 "math/big" 33 "net" 34 "os" 35 "strings" 36 "time" 37 38 current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" 39 config "github.com/IBM-Blockchain/fabric-operator/operatorconfig" 40 cav1 "github.com/IBM-Blockchain/fabric-operator/pkg/apis/ca/v1" 41 "github.com/IBM-Blockchain/fabric-operator/pkg/certificate" 42 initializer "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/ca" 43 commonconfig "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/common/config" 44 controllerclient "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient" 45 "github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources" 46 resourcemanager "github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources/manager" 47 "github.com/IBM-Blockchain/fabric-operator/pkg/offering/common" 48 "github.com/IBM-Blockchain/fabric-operator/pkg/offering/common/reconcilechecks" 49 "github.com/IBM-Blockchain/fabric-operator/pkg/operatorerrors" 50 "github.com/IBM-Blockchain/fabric-operator/pkg/restart" 51 "github.com/IBM-Blockchain/fabric-operator/pkg/util" 52 "github.com/IBM-Blockchain/fabric-operator/pkg/util/pointer" 53 "github.com/IBM-Blockchain/fabric-operator/version" 54 "github.com/pkg/errors" 55 k8sclient "sigs.k8s.io/controller-runtime/pkg/client" 56 "sigs.k8s.io/yaml" 57 58 appsv1 "k8s.io/api/apps/v1" 59 corev1 "k8s.io/api/core/v1" 60 rbacv1 "k8s.io/api/rbac/v1" 61 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 62 "k8s.io/apimachinery/pkg/runtime" 63 "k8s.io/apimachinery/pkg/types" 64 65 logf "sigs.k8s.io/controller-runtime/pkg/log" 66 "sigs.k8s.io/controller-runtime/pkg/reconcile" 67 ) 68 69 var log = logf.Log.WithName("base_ca") 70 71 const ( 72 DaysToSecondsConversion = int64(24 * 60 * 60) 73 ) 74 75 type Override interface { 76 Deployment(v1.Object, *appsv1.Deployment, resources.Action) error 77 Service(v1.Object, *corev1.Service, resources.Action) error 78 PVC(v1.Object, *corev1.PersistentVolumeClaim, resources.Action) error 79 Role(v1.Object, *rbacv1.Role, resources.Action) error 80 RoleBinding(v1.Object, *rbacv1.RoleBinding, resources.Action) error 81 ServiceAccount(v1.Object, *corev1.ServiceAccount, resources.Action) error 82 IsPostgres(instance *current.IBPCA) bool 83 } 84 85 //go:generate counterfeiter -o mocks/update.go -fake-name Update . Update 86 87 type Update interface { 88 SpecUpdated() bool 89 CAOverridesUpdated() bool 90 TLSCAOverridesUpdated() bool 91 ConfigOverridesUpdated() bool 92 RestartNeeded() bool 93 CACryptoUpdated() bool 94 CACryptoCreated() bool 95 RenewTLSCert() bool 96 FabricVersionUpdated() bool 97 ImagesUpdated() bool 98 CATagUpdated() bool 99 } 100 101 //go:generate counterfeiter -o mocks/restart_manager.go -fake-name RestartManager . RestartManager 102 103 type RestartManager interface { 104 ForConfigOverride(instance v1.Object) error 105 TriggerIfNeeded(instance restart.Instance) error 106 ForTLSReenroll(instance v1.Object) error 107 ForRestartAction(instance v1.Object) error 108 } 109 110 type IBPCA interface { 111 Initialize(instance *current.IBPCA, update Update) error 112 PreReconcileChecks(instance *current.IBPCA, update Update) (bool, error) 113 ReconcileManagers(instance *current.IBPCA, update Update) error 114 Reconcile(instance *current.IBPCA, update Update) (common.Result, error) 115 } 116 117 //go:generate counterfeiter -o mocks/initialize.go -fake-name InitializeIBPCA . InitializeIBPCA 118 119 type InitializeIBPCA interface { 120 HandleEnrollmentCAInit(instance *current.IBPCA, update Update) (*initializer.Response, error) 121 HandleConfigResources(name string, instance *current.IBPCA, resp *initializer.Response, update Update) error 122 HandleTLSCAInit(instance *current.IBPCA, update Update) (*initializer.Response, error) 123 SyncDBConfig(*current.IBPCA) (*current.IBPCA, error) 124 CreateOrUpdateConfigMap(instance *current.IBPCA, data map[string][]byte, name string) error 125 ReadConfigMap(instance *current.IBPCA, name string) (*corev1.ConfigMap, error) 126 } 127 128 //go:generate counterfeiter -o mocks/certificate_manager.go -fake-name CertificateManager . CertificateManager 129 130 type CertificateManager interface { 131 GetDurationToNextRenewalForCert(string, []byte, v1.Object, int64) (time.Duration, error) 132 GetSecret(string, string) (*corev1.Secret, error) 133 Expires([]byte, int64) (bool, time.Time, error) 134 UpdateSecret(v1.Object, string, map[string][]byte) error 135 } 136 137 var _ IBPCA = &CA{} 138 139 type CA struct { 140 Client controllerclient.Client 141 Scheme *runtime.Scheme 142 Config *config.Config 143 144 DeploymentManager resources.Manager 145 ServiceManager resources.Manager 146 PVCManager resources.Manager 147 RoleManager resources.Manager 148 RoleBindingManager resources.Manager 149 ServiceAccountManager resources.Manager 150 151 Override Override 152 Initializer InitializeIBPCA 153 154 CertificateManager CertificateManager 155 RenewCertTimers map[string]*time.Timer 156 157 Restart RestartManager 158 } 159 160 func New(client controllerclient.Client, scheme *runtime.Scheme, config *config.Config, o Override) *CA { 161 ca := &CA{ 162 Client: client, 163 Scheme: scheme, 164 Config: config, 165 Override: o, 166 } 167 ca.CreateManagers() 168 ca.Initializer = NewInitializer(config.CAInitConfig, scheme, client, ca.GetLabels, config.Operator.CA.Timeouts.HSMInitJob) 169 ca.Restart = restart.New(client, config.Operator.Restart.WaitTime.Get(), config.Operator.Restart.Timeout.Get()) 170 ca.CertificateManager = &certificate.CertificateManager{ 171 Client: client, 172 Scheme: scheme, 173 } 174 ca.RenewCertTimers = make(map[string]*time.Timer) 175 176 return ca 177 } 178 179 func (ca *CA) CreateManagers() { 180 override := ca.Override 181 resourceManager := resourcemanager.New(ca.Client, ca.Scheme) 182 ca.DeploymentManager = resourceManager.CreateDeploymentManager("", override.Deployment, ca.GetLabels, ca.Config.CAInitConfig.DeploymentFile) 183 ca.ServiceManager = resourceManager.CreateServiceManager("", override.Service, ca.GetLabels, ca.Config.CAInitConfig.ServiceFile) 184 ca.PVCManager = resourceManager.CreatePVCManager("", override.PVC, ca.GetLabels, ca.Config.CAInitConfig.PVCFile) 185 ca.RoleManager = resourceManager.CreateRoleManager("", override.Role, ca.GetLabels, ca.Config.CAInitConfig.RoleFile) 186 ca.RoleBindingManager = resourceManager.CreateRoleBindingManager("", override.RoleBinding, ca.GetLabels, ca.Config.CAInitConfig.RoleBindingFile) 187 ca.ServiceAccountManager = resourceManager.CreateServiceAccountManager("", override.ServiceAccount, ca.GetLabels, ca.Config.CAInitConfig.ServiceAccountFile) 188 } 189 190 func (ca *CA) Reconcile(instance *current.IBPCA, update Update) (common.Result, error) { 191 var err error 192 193 versionSet, err := ca.SetVersion(instance) 194 if err != nil { 195 return common.Result{}, errors.Wrap(err, fmt.Sprintf("failed updating CR '%s' to version '%s'", instance.Name, version.Operator)) 196 } 197 if versionSet { 198 log.Info("Instance version updated, requeuing request...") 199 return common.Result{ 200 Result: reconcile.Result{ 201 Requeue: true, 202 }, 203 }, nil 204 } 205 206 instanceUpdated, err := ca.PreReconcileChecks(instance, update) 207 if err != nil { 208 return common.Result{}, errors.Wrap(err, "failed pre reconcile checks") 209 } 210 211 if instanceUpdated { 212 log.Info("Updating instance after pre reconcile checks") 213 err := ca.Client.Patch(context.TODO(), instance, nil, controllerclient.PatchOption{ 214 Resilient: &controllerclient.ResilientPatch{ 215 Retry: 3, 216 Into: ¤t.IBPCA{}, 217 Strategy: k8sclient.MergeFrom, 218 }, 219 }) 220 if err != nil { 221 return common.Result{}, errors.Wrap(err, "failed to update instance") 222 } 223 224 log.Info("Instance updated, requeuing request...") 225 return common.Result{ 226 Result: reconcile.Result{ 227 Requeue: true, 228 }, 229 }, nil 230 } 231 232 err = ca.AddTLSCryptoIfMissing(instance, ca.GetEndpointsDNS(instance)) 233 if err != nil { 234 return common.Result{}, errors.Wrap(err, "failed to generate tls crypto") 235 } 236 237 err = ca.Initialize(instance, update) 238 if err != nil { 239 return common.Result{}, operatorerrors.Wrap(err, operatorerrors.CAInitilizationFailed, "failed to initialize ca") 240 } 241 242 err = ca.ReconcileManagers(instance, update) 243 if err != nil { 244 return common.Result{}, errors.Wrap(err, "failed to reconcile managers") 245 } 246 247 err = ca.UpdateConnectionProfile(instance) 248 if err != nil { 249 return common.Result{}, errors.Wrap(err, "failed to create connection profile") 250 } 251 252 err = ca.CheckStates(instance) 253 if err != nil { 254 return common.Result{}, errors.Wrap(err, "failed to check and restore state") 255 } 256 257 if update.CACryptoUpdated() { 258 log.Info("TLS crypto updated, triggering restart") 259 err = ca.Restart.ForTLSReenroll(instance) 260 if err != nil { 261 return common.Result{}, errors.Wrap(err, "failed to update restart config") 262 } 263 } 264 265 err = ca.HandleActions(instance, update) 266 if err != nil { 267 return common.Result{}, err 268 } 269 270 err = ca.HandleRestart(instance, update) 271 if err != nil { 272 return common.Result{}, err 273 } 274 275 return common.Result{}, nil 276 } 277 278 // PreReconcileChecks validate CR request before starting reconcile flow 279 func (ca *CA) PreReconcileChecks(instance *current.IBPCA, update Update) (bool, error) { 280 var err error 281 282 imagesUpdated, err := reconcilechecks.FabricVersionHelper(instance, ca.Config.Operator.Versions, update) 283 if err != nil { 284 return false, errors.Wrap(err, "failed to during version and image checks") 285 } 286 287 var maxNameLength *int 288 if instance.Spec.ConfigOverride != nil { 289 maxNameLength = instance.Spec.ConfigOverride.MaxNameLength 290 } 291 err = util.ValidationChecks(instance.TypeMeta, instance.ObjectMeta, "IBPCA", maxNameLength) 292 if err != nil { 293 return false, err 294 } 295 296 if instance.Spec.HSMSet() { 297 err = util.ValidateHSMProxyURL(instance.Spec.HSM.PKCS11Endpoint) 298 if err != nil { 299 return false, errors.Wrapf(err, "invalid HSM endpoint for ca instance '%s'", instance.GetName()) 300 } 301 } 302 303 if !instance.Spec.DomainSet() { 304 return false, fmt.Errorf("domain not set for ca instance '%s'", instance.GetName()) 305 } 306 307 zoneUpdated, err := ca.SelectZone(instance) 308 if err != nil { 309 return false, err 310 } 311 312 regionUpdated, err := ca.SelectRegion(instance) 313 if err != nil { 314 return false, err 315 } 316 317 hsmImageUpdated := ca.ReconcileHSMImages(instance) 318 319 var replicasUpdated bool 320 if instance.Spec.Replicas == nil { 321 replicas := int32(1) 322 instance.Spec.Replicas = &replicas 323 replicasUpdated = true 324 } 325 326 updated := zoneUpdated || regionUpdated || hsmImageUpdated || replicasUpdated || imagesUpdated 327 328 if updated { 329 log.Info(fmt.Sprintf("zoneUpdated %t, regionUpdated %t, hsmImageUpdated %t, replicasUpdated %t, imagesUpdated %t", 330 zoneUpdated, regionUpdated, hsmImageUpdated, replicasUpdated, imagesUpdated)) 331 } 332 333 return updated, nil 334 } 335 336 func (ca *CA) SetVersion(instance *current.IBPCA) (bool, error) { 337 if instance.Status.Version == "" || !version.String(instance.Status.Version).Equal(version.Operator) { 338 log.Info("Version of Operator: ", "version", version.Operator) 339 log.Info("Version of CR: ", "version", instance.Status.Version) 340 log.Info(fmt.Sprintf("Setting '%s' to version '%s'", instance.Name, version.Operator)) 341 342 instance.Status.Version = version.Operator 343 err := ca.Client.PatchStatus(context.TODO(), instance, nil, controllerclient.PatchOption{ 344 Resilient: &controllerclient.ResilientPatch{ 345 Retry: 3, 346 Into: ¤t.IBPCA{}, 347 Strategy: k8sclient.MergeFrom, 348 }, 349 }) 350 if err != nil { 351 return false, err 352 } 353 return true, nil 354 } 355 return false, nil 356 } 357 358 func (ca *CA) Initialize(instance *current.IBPCA, update Update) error { 359 var err error 360 361 // TODO: Add checks to determine if initialization is neeeded. Split this method into 362 // two, one should handle initialization during the create event of a CR and the other 363 // should update events 364 365 // Service account is required by job 366 err = ca.ReconcileRBAC(instance) 367 if err != nil { 368 return err 369 } 370 371 if instance.IsHSMEnabled() { 372 // If HSM config not found, HSM proxy is being used 373 if instance.UsingHSMProxy() { 374 err = os.Setenv("PKCS11_PROXY_SOCKET", instance.Spec.HSM.PKCS11Endpoint) 375 if err != nil { 376 return err 377 } 378 } else { 379 hsmConfig, err := commonconfig.ReadHSMConfig(ca.Client, instance) 380 if err != nil { 381 return errors.New("using non-proxy HSM, but no HSM config defined as config map 'ibp-hsm-config'") 382 } 383 384 if hsmConfig.Daemon != nil { 385 log.Info("Using daemon based HSM, creating pvc...") 386 ca.PVCManager.SetCustomName(instance.Spec.CustomNames.PVC.CA) 387 err = ca.PVCManager.Reconcile(instance, update.SpecUpdated()) 388 if err != nil { 389 return errors.Wrap(err, "failed PVC reconciliation") 390 } 391 } 392 } 393 } 394 395 instance, err = ca.Initializer.SyncDBConfig(instance) 396 if err != nil { 397 return err 398 } 399 400 eresp, err := ca.Initializer.HandleEnrollmentCAInit(instance, update) 401 if err != nil { 402 return err 403 } 404 405 if eresp != nil { 406 err = ca.Initializer.HandleConfigResources(fmt.Sprintf("%s-ca", instance.GetName()), instance, eresp, update) 407 if err != nil { 408 return err 409 } 410 } 411 412 tresp, err := ca.Initializer.HandleTLSCAInit(instance, update) 413 if err != nil { 414 return err 415 } 416 417 if tresp != nil { 418 err = ca.Initializer.HandleConfigResources(fmt.Sprintf("%s-tlsca", instance.GetName()), instance, tresp, update) 419 if err != nil { 420 return err 421 } 422 } 423 424 // If deployment exists, and configoverride update detected need to restart pod(s) to pick up 425 // the latest configuration from configmap and secret 426 if ca.DeploymentManager.Exists(instance) && update.ConfigOverridesUpdated() { 427 // Request deployment restart for config override update 428 if err := ca.Restart.ForConfigOverride(instance); err != nil { 429 return err 430 } 431 } 432 433 return nil 434 } 435 436 func (ca *CA) SelectZone(instance *current.IBPCA) (bool, error) { 437 if instance.Spec.Zone == "select" { 438 zone := util.GetZone(ca.Client) 439 instance.Spec.Zone = zone 440 return true, nil 441 } 442 if instance.Spec.Zone != "" { 443 err := util.ValidateZone(ca.Client, instance.Spec.Zone) 444 if err != nil { 445 return false, err 446 } 447 } 448 return false, nil 449 } 450 451 func (ca *CA) SelectRegion(instance *current.IBPCA) (bool, error) { 452 if instance.Spec.Region == "select" { 453 region := util.GetRegion(ca.Client) 454 instance.Spec.Region = region 455 return true, nil 456 } 457 if instance.Spec.Region != "" { 458 err := util.ValidateRegion(ca.Client, instance.Spec.Region) 459 if err != nil { 460 return false, err 461 } 462 } 463 return false, nil 464 } 465 466 func (ca *CA) ReconcileManagers(instance *current.IBPCA, updated Update) error { 467 var err error 468 469 update := updated.SpecUpdated() 470 471 if !ca.Override.IsPostgres(instance) { 472 log.Info("Using sqlite database, creating pvc...") 473 ca.PVCManager.SetCustomName(instance.Spec.CustomNames.PVC.CA) 474 err = ca.PVCManager.Reconcile(instance, update) 475 if err != nil { 476 return errors.Wrap(err, "failed PVC reconciliation") 477 } 478 } 479 480 err = ca.ServiceManager.Reconcile(instance, update) 481 if err != nil { 482 return errors.Wrap(err, "failed Service reconciliation") 483 } 484 485 err = ca.ReconcileRBAC(instance) 486 if err != nil { 487 return errors.Wrap(err, "failed RBAC reconciliation") 488 } 489 490 err = ca.DeploymentManager.Reconcile(instance, update) 491 if err != nil { 492 return errors.Wrap(err, "failed Deployment reconciliation") 493 } 494 495 // TODO: Can this be removed? 496 err = ca.createSecret(instance, "-ca") 497 if err != nil { 498 return errors.Wrap(err, "failed CA Secret reconciliation") 499 } 500 501 // TODO: Can this be removed? 502 err = ca.createSecret(instance, "-tlsca") 503 if err != nil { 504 return errors.Wrap(err, "failed TLS Secret reconciliation") 505 } 506 507 return nil 508 } 509 510 func (ca *CA) ReconcileRBAC(instance *current.IBPCA) error { 511 var err error 512 513 err = ca.RoleManager.Reconcile(instance, false) 514 if err != nil { 515 return err 516 } 517 518 err = ca.RoleBindingManager.Reconcile(instance, false) 519 if err != nil { 520 return err 521 } 522 523 err = ca.ServiceAccountManager.Reconcile(instance, false) 524 if err != nil { 525 return err 526 } 527 528 return nil 529 } 530 531 func (ca *CA) UpdateConnectionProfile(instance *current.IBPCA) error { 532 var err error 533 534 endpoints := ca.GetEndpoints(instance) 535 536 cacrypto, err := common.GetCACryptoEncoded(ca.Client, instance) 537 if err != nil { 538 return err 539 } 540 tlscacrypto, err := common.GetTLSCACryptoEncoded(ca.Client, instance) 541 if err != nil { 542 return err 543 } 544 545 err = ca.UpdateConnectionProfileConfigmap(instance, *endpoints, cacrypto.TLSCert, cacrypto.Cert, tlscacrypto.Cert) 546 if err != nil { 547 return err 548 } 549 550 return nil 551 } 552 553 func (ca *CA) UpdateConnectionProfileConfigmap(instance *current.IBPCA, endpoints current.CAEndpoints, tlscert, cacert, tlscacert string) error { 554 var err error 555 556 name := instance.Name + "-connection-profile" 557 nn := types.NamespacedName{ 558 Name: name, 559 Namespace: instance.GetNamespace(), 560 } 561 562 log.Info(fmt.Sprintf("Create connection profle configmap called for %s", instance.Name)) 563 connectionProfile := ¤t.CAConnectionProfile{ 564 Endpoints: endpoints, 565 TLS: ¤t.ConnectionProfileTLS{ 566 Cert: tlscert, 567 }, 568 CA: ¤t.MSP{ 569 SignCerts: cacert, 570 }, 571 TLSCA: ¤t.MSP{ 572 SignCerts: tlscacert, 573 }, 574 } 575 576 bytes, err := json.Marshal(connectionProfile) 577 if err != nil { 578 return errors.Wrap(err, "failed to marshal connectionprofile") 579 } 580 581 cm := &corev1.ConfigMap{ 582 ObjectMeta: v1.ObjectMeta{ 583 Name: name, 584 Namespace: instance.GetNamespace(), 585 Labels: ca.GetLabels(instance), 586 }, 587 BinaryData: map[string][]byte{"profile.json": bytes}, 588 } 589 590 err = ca.Client.Get(context.TODO(), nn, &corev1.ConfigMap{}) 591 if err == nil { 592 err = ca.Client.Update(context.TODO(), cm, controllerclient.UpdateOption{Owner: instance, Scheme: ca.Scheme}) 593 if err != nil { 594 return errors.Wrap(err, "failed to update connection profile configmap") 595 } 596 } else { 597 err = ca.Client.Create(context.TODO(), cm, controllerclient.CreateOption{Owner: instance, Scheme: ca.Scheme}) 598 if err != nil { 599 return errors.Wrap(err, "failed to create connection profile configmap") 600 } 601 } 602 603 return nil 604 } 605 606 func (ca *CA) GetEndpoints(instance *current.IBPCA) *current.CAEndpoints { 607 endpoints := ¤t.CAEndpoints{ 608 API: "https://" + instance.Namespace + "-" + instance.Name + "-ca." + instance.Spec.Domain + ":443", 609 Operations: "https://" + instance.Namespace + "-" + instance.Name + "-operations." + instance.Spec.Domain + ":443", 610 } 611 return endpoints 612 } 613 614 func (ca *CA) CheckStates(instance *current.IBPCA) error { 615 // Check state if deployment exists, make sure that deployment matches what is expected 616 // base on IBPCA spec 617 if ca.DeploymentManager.Exists(instance) { 618 err := ca.DeploymentManager.CheckState(instance) 619 if err != nil { 620 log.Error(err, "unexpected state") 621 err = ca.DeploymentManager.RestoreState(instance) 622 if err != nil { 623 return err 624 } 625 } 626 } 627 628 return nil 629 } 630 631 func (ca *CA) GetLabels(instance v1.Object) map[string]string { 632 return instance.GetLabels() 633 } 634 635 // TODO: Can this be removed? 636 func (ca *CA) createSecret(instance *current.IBPCA, suffix string) error { 637 secretCA := &corev1.Secret{} 638 secretCA.Name = instance.Name + suffix 639 secretCA.Namespace = instance.Namespace 640 secretCA.Labels = ca.GetLabels(instance) 641 642 secretCA.Data = map[string][]byte{} 643 secretCA.Data["_shared_creation"] = []byte("-----BEGIN") 644 645 err := ca.Client.Create(context.TODO(), secretCA, controllerclient.CreateOption{ 646 Owner: instance, 647 Scheme: ca.Scheme, 648 }) 649 if err != nil { 650 return err 651 } 652 653 return nil 654 } 655 656 func (ca *CA) CreateCACryptoSecret(instance *current.IBPCA, caCrypto map[string][]byte) error { 657 // Create CA secret with crypto 658 secret := &corev1.Secret{ 659 Data: caCrypto, 660 Type: corev1.SecretTypeOpaque, 661 } 662 secret.Name = instance.Name + "-ca-crypto" 663 secret.Namespace = instance.Namespace 664 secret.Labels = ca.GetLabels(instance) 665 666 err := ca.Client.Create(context.TODO(), secret, controllerclient.CreateOption{ 667 Owner: instance, 668 Scheme: ca.Scheme, 669 }) 670 if err != nil { 671 return errors.Wrap(err, "failed to create CA crypto secret") 672 } 673 674 return nil 675 } 676 677 func (ca *CA) CreateTLSCACryptoSecret(instance *current.IBPCA, tlscaCrypto map[string][]byte) error { 678 // Create TLSCA secret with crypto 679 secret := &corev1.Secret{ 680 Data: tlscaCrypto, 681 Type: corev1.SecretTypeOpaque, 682 } 683 secret.Name = instance.Name + "-tlsca-crypto" 684 secret.Namespace = instance.Namespace 685 secret.Labels = ca.GetLabels(instance) 686 687 err := ca.Client.Create(context.TODO(), secret, controllerclient.CreateOption{ 688 Owner: instance, 689 Scheme: ca.Scheme, 690 }) 691 if err != nil { 692 return errors.Wrap(err, "failed to create TLS CA crypto secret") 693 } 694 695 return nil 696 } 697 698 func (ca *CA) AddTLSCryptoIfMissing(instance *current.IBPCA, endpoints *current.CAEndpoints) error { 699 var err error 700 caOverrides := &cav1.ServerConfig{} 701 702 genTLSCrypto := func() error { 703 tlskey, tlscert, err := ca.GenTLSCrypto(instance, endpoints) 704 if err != nil { 705 return err 706 } 707 708 base64cert := base64.StdEncoding.EncodeToString(tlscert) 709 base64key := base64.StdEncoding.EncodeToString(tlskey) 710 711 caOverrides.TLS = cav1.ServerTLSConfig{ 712 Enabled: pointer.True(), 713 CertFile: base64cert, 714 KeyFile: base64key, 715 } 716 717 caBytes, err := json.Marshal(caOverrides) 718 if err != nil { 719 return err 720 } 721 722 instance.Spec.ConfigOverride.CA = &runtime.RawExtension{Raw: caBytes} 723 return nil 724 } 725 726 // check for cert 727 err = ca.CheckForTLSSecret(instance) 728 if err != nil { 729 log.Info(fmt.Sprintf("No TLS crypto configurated for CA '%s', generating TLS crypto...", instance.GetName())) 730 // that means secret is not found on cluster 731 if instance.Spec.ConfigOverride == nil { 732 instance.Spec.ConfigOverride = ¤t.ConfigOverride{} 733 err := genTLSCrypto() 734 if err != nil { 735 return err 736 } 737 738 return nil 739 } 740 741 if instance.Spec.ConfigOverride.CA == nil { 742 err := genTLSCrypto() 743 if err != nil { 744 return err 745 } 746 747 return nil 748 } 749 750 if instance.Spec.ConfigOverride != nil && instance.Spec.ConfigOverride.CA != nil { 751 err = json.Unmarshal(instance.Spec.ConfigOverride.CA.Raw, caOverrides) 752 if err != nil { 753 return err 754 } 755 756 if caOverrides.TLS.CertFile == "" { 757 err := genTLSCrypto() 758 if err != nil { 759 return err 760 } 761 } 762 763 return nil 764 } 765 } 766 767 return nil 768 } 769 770 func (ca *CA) GenTLSCrypto(instance *current.IBPCA, endpoints *current.CAEndpoints) ([]byte, []byte, error) { 771 priv, err := ecdsa.GenerateKey(elliptic.P256(), crand.Reader) 772 if err != nil { 773 return nil, nil, errors.Wrap(err, "failed to generate key") 774 } 775 776 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 777 serialNumber, err := crand.Int(crand.Reader, serialNumberLimit) 778 if err != nil { 779 return nil, nil, errors.Wrap(err, "failed to generate serial number") 780 } 781 782 notBefore := time.Now() 783 notAfter := notBefore.Add(time.Hour * 87600) // valid for 10 years 784 785 template := x509.Certificate{ 786 SerialNumber: serialNumber, 787 Issuer: pkix.Name{ 788 Country: []string{"US"}, 789 Province: []string{"North Carolina"}, 790 Locality: []string{"Durham"}, 791 Organization: []string{"IBM"}, 792 OrganizationalUnit: []string{"Blockchain"}, 793 CommonName: endpoints.API, 794 }, 795 Subject: pkix.Name{ 796 Country: []string{"US"}, 797 Province: []string{"North Carolina"}, 798 Locality: []string{"Durham"}, 799 Organization: []string{"IBM"}, 800 OrganizationalUnit: []string{"Blockchain"}, 801 CommonName: endpoints.API, 802 }, 803 804 NotBefore: notBefore, 805 NotAfter: notAfter, 806 } 807 808 ip := net.ParseIP(endpoints.API) 809 if ip == nil { 810 template.DNSNames = append(template.DNSNames, endpoints.API) 811 } else { 812 template.IPAddresses = append(template.IPAddresses, ip) 813 } 814 ip = net.ParseIP(endpoints.Operations) 815 if ip == nil { 816 template.DNSNames = append(template.DNSNames, endpoints.Operations) 817 } else { 818 template.IPAddresses = append(template.IPAddresses, ip) 819 } 820 821 derBytes, err := x509.CreateCertificate(crand.Reader, &template, &template, &priv.PublicKey, priv) 822 if err != nil { 823 return nil, nil, errors.Wrap(err, "failed to create certificate") 824 } 825 826 keyBytes, err := x509.MarshalECPrivateKey(priv) 827 if err != nil { 828 return nil, nil, errors.Wrap(err, "failed to marshal key") 829 } 830 831 certPEM := &pem.Block{Type: "CERTIFICATE", Bytes: derBytes} 832 keyPEM := &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes} 833 834 certBytes := pem.EncodeToMemory(certPEM) 835 keyBytes = pem.EncodeToMemory(keyPEM) 836 837 return keyBytes, certBytes, nil 838 } 839 840 func (ca *CA) CheckForTLSSecret(instance *current.IBPCA) error { 841 secret := &corev1.Secret{} 842 err := ca.Client.Get(context.TODO(), types.NamespacedName{ 843 Name: fmt.Sprintf("%s-tlsca-crypto", instance.Name), 844 Namespace: instance.Namespace}, secret) 845 return err 846 } 847 848 func (ca *CA) CheckCertificates(instance *current.IBPCA) (*current.CRStatus, error) { 849 secret, err := ca.CertificateManager.GetSecret( 850 fmt.Sprintf("%s-ca-crypto", instance.GetName()), 851 instance.GetNamespace(), 852 ) 853 854 numSecondsBeforeExpire := instance.GetNumSecondsWarningPeriod() 855 expiring, expireDate, err := ca.CertificateManager.Expires(secret.Data["tls-cert.pem"], numSecondsBeforeExpire) 856 if err != nil { 857 return nil, err 858 } 859 860 var message string 861 statusType := current.Deployed 862 863 if expiring { 864 statusType = current.Warning 865 // Check if tls cert's expiration date has already passed 866 if expireDate.Before(time.Now()) { 867 statusType = current.Error 868 message += fmt.Sprintf("TLS cert for '%s' has expired", instance.GetName()) 869 } else { 870 message += fmt.Sprintf("TLS cert for '%s' expires on %s", instance.GetName(), expireDate.String()) 871 } 872 } 873 874 crStatus := ¤t.CRStatus{ 875 Type: statusType, 876 Message: message, 877 } 878 879 switch statusType { 880 case current.Deployed: 881 crStatus.Reason = "allPodsDeployed" 882 default: 883 crStatus.Reason = "certRenewalRequired" 884 } 885 886 return crStatus, nil 887 } 888 889 func (ca *CA) RenewCert(instance *current.IBPCA, endpoints *current.CAEndpoints) error { 890 log.Info(fmt.Sprintf("Renewing TLS certificate for CA '%s'", instance.GetName())) 891 892 tlskey, tlscert, err := ca.GenTLSCrypto(instance, endpoints) 893 if err != nil { 894 return err 895 } 896 897 name := fmt.Sprintf("%s-ca-crypto", instance.GetName()) 898 secret, err := ca.CertificateManager.GetSecret( 899 name, 900 instance.GetNamespace(), 901 ) 902 903 secret.Data["tls-cert.pem"] = tlscert 904 secret.Data["tls-key.pem"] = tlskey 905 secret.Data["operations-cert.pem"] = tlscert 906 secret.Data["operations-key.pem"] = tlskey 907 908 if err := ca.CertificateManager.UpdateSecret(instance, name, secret.Data); err != nil { 909 return err 910 } 911 912 return nil 913 } 914 915 func (ca *CA) GetEndpointsDNS(instance *current.IBPCA) *current.CAEndpoints { 916 return ¤t.CAEndpoints{ 917 API: fmt.Sprintf("%s-%s-ca.%s", instance.Namespace, instance.Name, instance.Spec.Domain), 918 Operations: fmt.Sprintf("%s-%s-operations.%s", instance.Namespace, instance.Name, instance.Spec.Domain), 919 } 920 } 921 922 func (ca *CA) ReconcileHSMImages(instance *current.IBPCA) bool { 923 hsmConfig, err := commonconfig.ReadHSMConfig(ca.Client, instance) 924 if err != nil { 925 return false 926 } 927 928 if hsmConfig.Library.AutoUpdateDisabled { 929 return false 930 } 931 932 updated := false 933 if hsmConfig.Library.Image != "" { 934 hsmImage := hsmConfig.Library.Image 935 lastIndex := strings.LastIndex(hsmImage, ":") 936 image := hsmImage[:lastIndex] 937 tag := hsmImage[lastIndex+1:] 938 939 if instance.Spec.Images.HSMImage != image { 940 instance.Spec.Images.HSMImage = image 941 updated = true 942 } 943 944 if instance.Spec.Images.HSMTag != tag { 945 instance.Spec.Images.HSMTag = tag 946 updated = true 947 } 948 } 949 950 return updated 951 } 952 953 func (ca *CA) HandleActions(instance *current.IBPCA, update Update) error { 954 orig := instance.DeepCopy() 955 956 if update.RenewTLSCert() { 957 if err := common.BackupCACrypto(ca.Client, ca.Scheme, instance, ca.GetLabels(instance)); err != nil { 958 return errors.Wrap(err, "failed to backup crypto before renewing cert") 959 } 960 961 if err := ca.RenewCert(instance, ca.GetEndpointsDNS(instance)); err != nil { 962 log.Error(err, "Resetting action flag on failure") 963 instance.ResetTLSRenew() 964 return err 965 } 966 instance.ResetTLSRenew() 967 } 968 969 if update.RestartNeeded() { 970 if err := ca.RestartAction(instance); err != nil { 971 log.Error(err, "Resetting action flag on failure") 972 instance.ResetRestart() 973 return err 974 } 975 instance.ResetRestart() 976 } 977 978 if err := ca.Client.Patch(context.TODO(), instance, k8sclient.MergeFrom(orig)); err != nil { 979 return errors.Wrap(err, "failed to reset action flags") 980 } 981 982 return nil 983 } 984 985 func (ca *CA) RestartAction(instance *current.IBPCA) error { 986 log.Info("Restart triggered via action parameter") 987 if err := ca.Restart.ForRestartAction(instance); err != nil { 988 return errors.Wrap(err, "failed to restart ca pods") 989 } 990 return nil 991 } 992 993 func (ca *CA) HandleRestart(instance *current.IBPCA, update Update) error { 994 // If restart is disabled for components, can return immediately 995 if ca.Config.Operator.Restart.Disable.Components { 996 return nil 997 } 998 999 err := ca.Restart.TriggerIfNeeded(instance) 1000 if err != nil { 1001 return errors.Wrap(err, "failed to restart deployment") 1002 } 1003 1004 return nil 1005 } 1006 1007 func (ca *CA) ReconcileFabricCAMigration(instance *current.IBPCA) error { 1008 cmname := fmt.Sprintf("%s-ca-config", instance.GetName()) 1009 cm, err := ca.Initializer.ReadConfigMap(instance, cmname) 1010 if err != nil { 1011 return err 1012 } 1013 1014 log.Info(fmt.Sprintf("Migrating config map '%s'", cmname)) 1015 1016 serverConfig := &cav1.ServerConfig{} 1017 err = yaml.Unmarshal(cm.BinaryData["fabric-ca-server-config.yaml"], serverConfig) 1018 if err != nil { 1019 return err 1020 } 1021 1022 if serverConfig.CA.ReenrollIgnoreCertExpiry == pointer.True() { 1023 // if it is already updated no need to update configmap 1024 return nil 1025 } else { 1026 serverConfig.CA.ReenrollIgnoreCertExpiry = pointer.True() 1027 } 1028 1029 caConfigBytes, err := yaml.Marshal(serverConfig) 1030 if err != nil { 1031 return err 1032 } 1033 1034 log.Info(fmt.Sprintf("Updating config map '%s'", cmname)) 1035 1036 cm.BinaryData["fabric-ca-server-config.yaml"] = caConfigBytes 1037 1038 err = ca.Initializer.CreateOrUpdateConfigMap(instance, cm.BinaryData, cmname) 1039 if err != nil { 1040 return err 1041 } 1042 return nil 1043 }