diff --git a/api/bases/manila.openstack.org_manilaapis.yaml b/api/bases/manila.openstack.org_manilaapis.yaml index facebc88..ef7c61c0 100644 --- a/api/bases/manila.openstack.org_manilaapis.yaml +++ b/api/bases/manila.openstack.org_manilaapis.yaml @@ -931,6 +931,24 @@ spec: serviceUser: default: manila type: string + tls: + properties: + api: + properties: + internal: + properties: + secretName: + type: string + type: object + public: + properties: + secretName: + type: string + type: object + type: object + caBundleSecretName: + type: string + type: object transportURLSecret: type: string required: diff --git a/api/bases/manila.openstack.org_manilas.yaml b/api/bases/manila.openstack.org_manilas.yaml index 230287f0..677b4ba1 100644 --- a/api/bases/manila.openstack.org_manilas.yaml +++ b/api/bases/manila.openstack.org_manilas.yaml @@ -931,6 +931,24 @@ spec: x-kubernetes-int-or-string: true type: object type: object + tls: + properties: + api: + properties: + internal: + properties: + secretName: + type: string + type: object + public: + properties: + secretName: + type: string + type: object + type: object + caBundleSecretName: + type: string + type: object required: - containerImage type: object diff --git a/api/bases/manila.openstack.org_manilaschedulers.yaml b/api/bases/manila.openstack.org_manilaschedulers.yaml index dacaf67d..5c279442 100644 --- a/api/bases/manila.openstack.org_manilaschedulers.yaml +++ b/api/bases/manila.openstack.org_manilaschedulers.yaml @@ -880,6 +880,11 @@ spec: serviceUser: default: manila type: string + tls: + properties: + caBundleSecretName: + type: string + type: object transportURLSecret: type: string required: diff --git a/api/bases/manila.openstack.org_manilashares.yaml b/api/bases/manila.openstack.org_manilashares.yaml index 697247db..51b6f953 100644 --- a/api/bases/manila.openstack.org_manilashares.yaml +++ b/api/bases/manila.openstack.org_manilashares.yaml @@ -880,6 +880,11 @@ spec: serviceUser: default: manila type: string + tls: + properties: + caBundleSecretName: + type: string + type: object transportURLSecret: type: string required: diff --git a/api/v1beta1/manilaapi_types.go b/api/v1beta1/manilaapi_types.go index efb82126..b766fa78 100644 --- a/api/v1beta1/manilaapi_types.go +++ b/api/v1beta1/manilaapi_types.go @@ -19,6 +19,7 @@ package v1beta1 import ( condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/service" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -38,6 +39,11 @@ type ManilaAPITemplate struct { // +kubebuilder:validation:Optional // Override, provides the ability to override the generated manifest of several child resources. Override APIOverrideSpec `json:"override,omitempty"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS tls.API `json:"tls,omitempty"` } // APIOverrideSpec to override the generated manifest of several child resources. diff --git a/api/v1beta1/manilascheduler_types.go b/api/v1beta1/manilascheduler_types.go index 6e61a9d1..91ed3c9d 100644 --- a/api/v1beta1/manilascheduler_types.go +++ b/api/v1beta1/manilascheduler_types.go @@ -18,6 +18,7 @@ package v1beta1 import ( condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -61,6 +62,11 @@ type ManilaSchedulerSpec struct { // +kubebuilder:validation:Required // ServiceAccount - service account name used internally to provide the default SA name ServiceAccount string `json:"serviceAccount"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS tls.Ca `json:"tls,omitempty"` } // ManilaSchedulerStatus defines the observed state of ManilaScheduler diff --git a/api/v1beta1/manilashare_types.go b/api/v1beta1/manilashare_types.go index b46fedb6..a5478566 100644 --- a/api/v1beta1/manilashare_types.go +++ b/api/v1beta1/manilashare_types.go @@ -18,6 +18,7 @@ package v1beta1 import ( condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -61,6 +62,11 @@ type ManilaShareSpec struct { // +kubebuilder:validation:Required // ServiceAccount - service account name used internally to provide the default SA name ServiceAccount string `json:"serviceAccount"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS tls.Ca `json:"tls,omitempty"` } // ManilaShareStatus defines the observed state of ManilaShare diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 1ea2a8a8..55d97ce5 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -229,6 +229,7 @@ func (in *ManilaAPITemplate) DeepCopyInto(out *ManilaAPITemplate) { **out = **in } in.Override.DeepCopyInto(&out.Override) + in.TLS.DeepCopyInto(&out.TLS) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManilaAPITemplate. @@ -396,6 +397,7 @@ func (in *ManilaSchedulerSpec) DeepCopyInto(out *ManilaSchedulerSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + out.TLS = in.TLS } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManilaSchedulerSpec. @@ -593,6 +595,7 @@ func (in *ManilaShareSpec) DeepCopyInto(out *ManilaShareSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + out.TLS = in.TLS } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManilaShareSpec. diff --git a/config/crd/bases/manila.openstack.org_manilaapis.yaml b/config/crd/bases/manila.openstack.org_manilaapis.yaml index facebc88..ef7c61c0 100644 --- a/config/crd/bases/manila.openstack.org_manilaapis.yaml +++ b/config/crd/bases/manila.openstack.org_manilaapis.yaml @@ -931,6 +931,24 @@ spec: serviceUser: default: manila type: string + tls: + properties: + api: + properties: + internal: + properties: + secretName: + type: string + type: object + public: + properties: + secretName: + type: string + type: object + type: object + caBundleSecretName: + type: string + type: object transportURLSecret: type: string required: diff --git a/config/crd/bases/manila.openstack.org_manilas.yaml b/config/crd/bases/manila.openstack.org_manilas.yaml index 230287f0..677b4ba1 100644 --- a/config/crd/bases/manila.openstack.org_manilas.yaml +++ b/config/crd/bases/manila.openstack.org_manilas.yaml @@ -931,6 +931,24 @@ spec: x-kubernetes-int-or-string: true type: object type: object + tls: + properties: + api: + properties: + internal: + properties: + secretName: + type: string + type: object + public: + properties: + secretName: + type: string + type: object + type: object + caBundleSecretName: + type: string + type: object required: - containerImage type: object diff --git a/config/crd/bases/manila.openstack.org_manilaschedulers.yaml b/config/crd/bases/manila.openstack.org_manilaschedulers.yaml index dacaf67d..5c279442 100644 --- a/config/crd/bases/manila.openstack.org_manilaschedulers.yaml +++ b/config/crd/bases/manila.openstack.org_manilaschedulers.yaml @@ -880,6 +880,11 @@ spec: serviceUser: default: manila type: string + tls: + properties: + caBundleSecretName: + type: string + type: object transportURLSecret: type: string required: diff --git a/config/crd/bases/manila.openstack.org_manilashares.yaml b/config/crd/bases/manila.openstack.org_manilashares.yaml index 697247db..51b6f953 100644 --- a/config/crd/bases/manila.openstack.org_manilashares.yaml +++ b/config/crd/bases/manila.openstack.org_manilashares.yaml @@ -880,6 +880,11 @@ spec: serviceUser: default: manila type: string + tls: + properties: + caBundleSecretName: + type: string + type: object transportURLSecret: type: string required: diff --git a/config/samples/manila_v1beta1_manila_tls.yaml b/config/samples/manila_v1beta1_manila_tls.yaml new file mode 100644 index 00000000..e12d554e --- /dev/null +++ b/config/samples/manila_v1beta1_manila_tls.yaml @@ -0,0 +1,25 @@ +apiVersion: manila.openstack.org/v1beta1 +kind: Manila +metadata: + name: manila + namespace: openstack +spec: + serviceUser: manila + customServiceConfig: | + [DEFAULT] + debug = true + databaseInstance: openstack + secret: osp-secret + databaseUser: manila + rabbitMqClusterName: rabbitmq + manilaAPI: + tls: + api: + internal: + secretName: cert-manila-internal-svc + public: + secretName: cert-manila-public-svc + caBundleSecretName: combined-ca-bundle + manilaScheduler: {} + manilaShares: + share1: {} diff --git a/config/samples/manila_v1beta1_manilaapi.yaml b/config/samples/manila_v1beta1_manilaapi.yaml index 6f3be559..5ec01628 100644 --- a/config/samples/manila_v1beta1_manilaapi.yaml +++ b/config/samples/manila_v1beta1_manilaapi.yaml @@ -4,3 +4,13 @@ metadata: name: manilaapi-sample spec: # TODO(user): Add fields here + #tls: + # api: + # # secret holding tls.crt and tls.key for the APIs internal k8s service + # internal: + # secretName: cert-internal-svc + # # secret holding tls.crt and tls.key for the APIs public k8s service + # public: + # secretName: cert-public-svc + # # secret holding the tls-ca-bundle.pem to be used as a deploymend env CA bundle + # caBundleSecretName: combined-ca-bundle diff --git a/controllers/manila_controller.go b/controllers/manila_controller.go index 6055a36a..34d54ab4 100644 --- a/controllers/manila_controller.go +++ b/controllers/manila_controller.go @@ -37,6 +37,7 @@ import ( nad "github.com/openstack-k8s-operators/lib-common/modules/common/networkattachment" common_rbac "github.com/openstack-k8s-operators/lib-common/modules/common/rbac" "github.com/openstack-k8s-operators/lib-common/modules/common/secret" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/openstack-k8s-operators/lib-common/modules/common/util" manilav1beta1 "github.com/openstack-k8s-operators/manila-operator/api/v1beta1" "github.com/openstack-k8s-operators/manila-operator/pkg/manila" @@ -209,6 +210,27 @@ func (r *ManilaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res return r.reconcileNormal(ctx, instance, helper) } +// fields to index to reconcile when change +const ( + passwordSecretField = ".spec.secret" + caBundleSecretNameField = ".spec.tls.caBundleSecretName" + tlsAPIInternalField = ".spec.tls.api.internal.secretName" + tlsAPIPublicField = ".spec.tls.api.public.secretName" +) + +var ( + commonWatchFields = []string{ + passwordSecretField, + caBundleSecretNameField, + } + manilaAPIWatchFields = []string{ + passwordSecretField, + caBundleSecretNameField, + tlsAPIInternalField, + tlsAPIPublicField, + } +) + // SetupWithManager sets up the controller with the Manager. func (r *ManilaReconciler) SetupWithManager(mgr ctrl.Manager) error { // transportURLSecretFn - Watch for changes made to the secret associated with the RabbitMQ @@ -872,6 +894,21 @@ func (r *ManilaReconciler) generateServiceConfig( "MemcachedServersWithInet": strings.Join(memcached.Status.ServerListWithInet, ","), } + // create httpd vhost template parameters + httpdVhostConfig := map[string]interface{}{} + for _, endpt := range []service.Endpoint{service.EndpointInternal, service.EndpointPublic} { + endptConfig := map[string]interface{}{} + endptConfig["ServerName"] = fmt.Sprintf("manila-%s.%s.svc", endpt.String(), instance.Namespace) + endptConfig["TLS"] = false // default TLS to false, and set it bellow to true if enabled + if instance.Spec.ManilaAPI.TLS.API.Enabled(endpt) { + endptConfig["TLS"] = true + endptConfig["SSLCertificateFile"] = fmt.Sprintf("/etc/pki/tls/certs/%s.crt", endpt.String()) + endptConfig["SSLCertificateKeyFile"] = fmt.Sprintf("/etc/pki/tls/private/%s.key", endpt.String()) + } + httpdVhostConfig[endpt.String()] = endptConfig + } + templateParameters["VHosts"] = httpdVhostConfig + configTemplates := []util.Template{ // ScriptsConfigMap { @@ -971,6 +1008,7 @@ func (r *ManilaReconciler) schedulerDeploymentCreateOrUpdate(ctx context.Context DatabaseHostname: instance.Status.DatabaseHostname, TransportURLSecret: instance.Status.TransportURLSecret, ServiceAccount: instance.RbacResourceName(), + TLS: instance.Spec.ManilaAPI.TLS.Ca, } op, err := controllerutil.CreateOrUpdate(ctx, r.Client, deployment, func() error { @@ -1007,6 +1045,7 @@ func (r *ManilaReconciler) shareDeploymentCreateOrUpdate(ctx context.Context, in DatabaseHostname: instance.Status.DatabaseHostname, TransportURLSecret: instance.Status.TransportURLSecret, ServiceAccount: instance.RbacResourceName(), + TLS: instance.Spec.ManilaAPI.TLS.Ca, } op, err := controllerutil.CreateOrUpdate(ctx, r.Client, deployment, func() error { diff --git a/controllers/manilaapi_controller.go b/controllers/manilaapi_controller.go index 8f637060..de09ff58 100644 --- a/controllers/manilaapi_controller.go +++ b/controllers/manilaapi_controller.go @@ -21,11 +21,16 @@ import ( "fmt" "time" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" "github.com/go-logr/logr" @@ -40,6 +45,7 @@ import ( "github.com/openstack-k8s-operators/lib-common/modules/common/secret" "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/openstack-k8s-operators/lib-common/modules/common/statefulset" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" "github.com/openstack-k8s-operators/lib-common/modules/common/util" manilav1beta1 "github.com/openstack-k8s-operators/manila-operator/api/v1beta1" "github.com/openstack-k8s-operators/manila-operator/pkg/manila" @@ -178,6 +184,7 @@ func (r *ManilaAPIReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( condition.UnknownCondition(condition.KeystoneServiceReadyCondition, condition.InitReason, ""), condition.UnknownCondition(condition.KeystoneEndpointReadyCondition, condition.InitReason, ""), condition.UnknownCondition(condition.NetworkAttachmentsReadyCondition, condition.InitReason, condition.NetworkAttachmentsReadyInitMessage), + condition.UnknownCondition(condition.TLSInputReadyCondition, condition.InitReason, condition.InputReadyInitMessage), ) instance.Status.Conditions.Init(&cl) @@ -256,6 +263,54 @@ func (r *ManilaAPIReconciler) SetupWithManager(mgr ctrl.Manager) error { return nil } + // index passwordSecretField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &manilav1beta1.ManilaAPI{}, passwordSecretField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*manilav1beta1.ManilaAPI) + if cr.Spec.Secret == "" { + return nil + } + return []string{cr.Spec.Secret} + }); err != nil { + return err + } + + // index caBundleSecretNameField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &manilav1beta1.ManilaAPI{}, caBundleSecretNameField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*manilav1beta1.ManilaAPI) + if cr.Spec.TLS.CaBundleSecretName == "" { + return nil + } + return []string{cr.Spec.TLS.CaBundleSecretName} + }); err != nil { + return err + } + + // index tlsAPIInternalField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &manilav1beta1.ManilaAPI{}, tlsAPIInternalField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*manilav1beta1.ManilaAPI) + if cr.Spec.TLS.API.Internal.SecretName == nil { + return nil + } + return []string{*cr.Spec.TLS.API.Internal.SecretName} + }); err != nil { + return err + } + + // index tlsAPIPublicField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &manilav1beta1.ManilaAPI{}, tlsAPIPublicField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*manilav1beta1.ManilaAPI) + if cr.Spec.TLS.API.Public.SecretName == nil { + return nil + } + return []string{*cr.Spec.TLS.API.Public.SecretName} + }); err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). For(&manilav1beta1.ManilaAPI{}). Owns(&keystonev1.KeystoneService{}). @@ -266,9 +321,47 @@ func (r *ManilaAPIReconciler) SetupWithManager(mgr ctrl.Manager) error { // watch the secrets we don't own Watches(&source.Kind{Type: &corev1.Secret{}}, handler.EnqueueRequestsFromMapFunc(secretFn)). + Watches( + &source.Kind{Type: &corev1.Secret{}}, + handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). Complete(r) } +func (r *ManilaAPIReconciler) findObjectsForSrc(src client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + l := log.FromContext(context.Background()).WithName("Controllers").WithName("ManilaAPI") + + for _, field := range manilaAPIWatchFields { + crList := &manilav1beta1.ManilaAPIList{} + listOps := &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(field, src.GetName()), + Namespace: src.GetNamespace(), + } + err := r.List(context.TODO(), crList, listOps) + if err != nil { + return []reconcile.Request{} + } + + for _, item := range crList.Items { + l.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace())) + + requests = append(requests, + reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + }, + ) + } + } + + return requests +} + func (r *ManilaAPIReconciler) reconcileDelete(ctx context.Context, instance *manilav1beta1.ManilaAPI, helper *helper.Helper) (ctrl.Result, error) { r.Log.Info(fmt.Sprintf("Reconciling Service '%s' delete", instance.Name)) @@ -422,7 +515,12 @@ func (r *ManilaAPIReconciler) reconcileInit( } // create service - end - // TODO: TLS, pass in https as protocol + // if TLS is enabled + if instance.Spec.TLS.API.Enabled(endpointType) { + // set endpoint protocol to https + data.Protocol = ptr.To(service.ProtocolHTTPS) + } + apiEndpointsV2[string(endpointType)], err = svc.GetAPIEndpoint( svcOverride.EndpointURL, data.Protocol, data.Path) if err != nil { @@ -540,9 +638,6 @@ func (r *ManilaAPIReconciler) reconcileNormal(ctx context.Context, instance *man fmt.Sprintf("%s-config-data", parentManilaName), //ConfigSecret } - // - // Create Secrets required as input for the Service and calculate an overall hash of hashes - // for _, parentSecret := range parentSecrets { ctrlResult, err = r.getSecret(ctx, helper, instance, parentSecret, &configVars) if err != nil { @@ -551,15 +646,66 @@ func (r *ManilaAPIReconciler) reconcileNormal(ctx context.Context, instance *man } instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) + // + // TLS input validation + // + // Validate the CA cert secret if provided + if instance.Spec.TLS.CaBundleSecretName != "" { + hash, ctrlResult, err := tls.ValidateCACertSecret( + ctx, + helper.GetClient(), + types.NamespacedName{ + Name: instance.Spec.TLS.CaBundleSecretName, + Namespace: instance.Namespace, + }, + ) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TLSInputErrorMessage, + err.Error())) + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + if hash != "" { + configVars[tls.CABundleKey] = env.SetValue(hash) + } + + // Validate API service certs secrets + certsHash, ctrlResult, err := instance.Spec.TLS.API.ValidateCertSecrets(ctx, helper, instance.Namespace) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TLSInputErrorMessage, + err.Error())) + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + configVars[tls.TLSHashName] = env.SetValue(certsHash) + } + + // all cert input checks out so report InputReady + instance.Status.Conditions.MarkTrue(condition.TLSInputReadyCondition, condition.InputReadyMessage) + + // + // Create secrets required as input for the Service and calculate an overall hash of hashes + // serviceLabels := map[string]string{ common.AppSelector: manila.ServiceName, common.ComponentSelector: manilaapi.Component, } - - // - // create Secrets for manila-api service + // create hash over all the different input resources to identify if any those changed + // and a restart/recreate is required. // - err = r.generateServiceConfig(ctx, helper, instance, &configVars, serviceLabels) + inputHash, hashChanged, err := r.createHashOfInputHashes(ctx, instance, configVars) if err != nil { instance.Status.Conditions.Set(condition.FalseCondition( condition.ServiceConfigReadyCondition, @@ -568,13 +714,16 @@ func (r *ManilaAPIReconciler) reconcileNormal(ctx context.Context, instance *man condition.ServiceConfigReadyErrorMessage, err.Error())) return ctrl.Result{}, err + } else if hashChanged { + // Hash changed and instance status should be updated (which will be done by main defer func), + // so we need to return and reconcile again + return ctrl.Result{}, nil } // - // create hash over all the different input resources to identify if any those changed - // and a restart/recreate is required. + // create custom config for this manila service // - inputHash, hashChanged, err := r.createHashOfInputHashes(ctx, instance, configVars) + err = r.generateServiceConfig(ctx, helper, instance, &configVars, serviceLabels) if err != nil { instance.Status.Conditions.Set(condition.FalseCondition( condition.ServiceConfigReadyCondition, diff --git a/controllers/manilascheduler_controller.go b/controllers/manilascheduler_controller.go index 18108536..7e62d076 100644 --- a/controllers/manilascheduler_controller.go +++ b/controllers/manilascheduler_controller.go @@ -24,13 +24,17 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" @@ -43,6 +47,7 @@ import ( nad "github.com/openstack-k8s-operators/lib-common/modules/common/networkattachment" "github.com/openstack-k8s-operators/lib-common/modules/common/secret" "github.com/openstack-k8s-operators/lib-common/modules/common/statefulset" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" "github.com/openstack-k8s-operators/lib-common/modules/common/util" manilav1beta1 "github.com/openstack-k8s-operators/manila-operator/api/v1beta1" "github.com/openstack-k8s-operators/manila-operator/pkg/manila" @@ -151,6 +156,7 @@ func (r *ManilaSchedulerReconciler) Reconcile(ctx context.Context, req ctrl.Requ condition.UnknownCondition(condition.ServiceConfigReadyCondition, condition.InitReason, condition.ServiceConfigReadyInitMessage), condition.UnknownCondition(condition.DeploymentReadyCondition, condition.InitReason, condition.DeploymentReadyInitMessage), condition.UnknownCondition(condition.NetworkAttachmentsReadyCondition, condition.InitReason, condition.NetworkAttachmentsReadyInitMessage), + condition.UnknownCondition(condition.TLSInputReadyCondition, condition.InitReason, condition.InputReadyInitMessage), ) instance.Status.Conditions.Init(&cl) @@ -227,6 +233,30 @@ func (r *ManilaSchedulerReconciler) SetupWithManager(mgr ctrl.Manager) error { return nil } + // index passwordSecretField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &manilav1beta1.ManilaScheduler{}, passwordSecretField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*manilav1beta1.ManilaScheduler) + if cr.Spec.Secret == "" { + return nil + } + return []string{cr.Spec.Secret} + }); err != nil { + return err + } + + // index caBundleSecretNameField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &manilav1beta1.ManilaScheduler{}, caBundleSecretNameField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*manilav1beta1.ManilaScheduler) + if cr.Spec.TLS.CaBundleSecretName == "" { + return nil + } + return []string{cr.Spec.TLS.CaBundleSecretName} + }); err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). For(&manilav1beta1.ManilaScheduler{}). Owns(&appsv1.StatefulSet{}). @@ -234,9 +264,47 @@ func (r *ManilaSchedulerReconciler) SetupWithManager(mgr ctrl.Manager) error { // watch the secrets we don't own Watches(&source.Kind{Type: &corev1.Secret{}}, handler.EnqueueRequestsFromMapFunc(secretFn)). + Watches( + &source.Kind{Type: &corev1.Secret{}}, + handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). Complete(r) } +func (r *ManilaSchedulerReconciler) findObjectsForSrc(src client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + l := log.FromContext(context.Background()).WithName("Controllers").WithName("ManilaScheduler") + + for _, field := range commonWatchFields { + crList := &manilav1beta1.ManilaSchedulerList{} + listOps := &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(field, src.GetName()), + Namespace: src.GetNamespace(), + } + err := r.List(context.TODO(), crList, listOps) + if err != nil { + return []reconcile.Request{} + } + + for _, item := range crList.Items { + l.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace())) + + requests = append(requests, + reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + }, + ) + } + } + + return requests +} + func (r *ManilaSchedulerReconciler) reconcileDelete(ctx context.Context, instance *manilav1beta1.ManilaScheduler, helper *helper.Helper) (ctrl.Result, error) { r.Log.Info(fmt.Sprintf("Reconciling Service '%s' delete", instance.Name)) @@ -304,6 +372,38 @@ func (r *ManilaSchedulerReconciler) reconcileNormal(ctx context.Context, instanc } instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) + // + // TLS input validation + // + // Validate the CA cert secret if provided + if instance.Spec.TLS.CaBundleSecretName != "" { + hash, ctrlResult, err := tls.ValidateCACertSecret( + ctx, + helper.GetClient(), + types.NamespacedName{ + Name: instance.Spec.TLS.CaBundleSecretName, + Namespace: instance.Namespace, + }, + ) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TLSInputErrorMessage, + err.Error())) + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + if hash != "" { + configVars[tls.CABundleKey] = env.SetValue(hash) + } + } + // all cert input checks out so report InputReady + instance.Status.Conditions.MarkTrue(condition.TLSInputReadyCondition, condition.InputReadyMessage) + // // Create Secrets required as input for the Service and calculate an overall hash of hashes // diff --git a/controllers/manilashare_controller.go b/controllers/manilashare_controller.go index 734b7f8f..5e87f73e 100644 --- a/controllers/manilashare_controller.go +++ b/controllers/manilashare_controller.go @@ -24,13 +24,17 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" @@ -43,6 +47,7 @@ import ( nad "github.com/openstack-k8s-operators/lib-common/modules/common/networkattachment" "github.com/openstack-k8s-operators/lib-common/modules/common/secret" "github.com/openstack-k8s-operators/lib-common/modules/common/statefulset" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" "github.com/openstack-k8s-operators/lib-common/modules/common/util" manilav1beta1 "github.com/openstack-k8s-operators/manila-operator/api/v1beta1" "github.com/openstack-k8s-operators/manila-operator/pkg/manila" @@ -152,6 +157,7 @@ func (r *ManilaShareReconciler) Reconcile(ctx context.Context, req ctrl.Request) condition.UnknownCondition(condition.ServiceConfigReadyCondition, condition.InitReason, condition.ServiceConfigReadyInitMessage), condition.UnknownCondition(condition.DeploymentReadyCondition, condition.InitReason, condition.DeploymentReadyInitMessage), condition.UnknownCondition(condition.NetworkAttachmentsReadyCondition, condition.InitReason, condition.NetworkAttachmentsReadyInitMessage), + condition.UnknownCondition(condition.TLSInputReadyCondition, condition.InitReason, condition.InputReadyInitMessage), ) instance.Status.Conditions.Init(&cl) @@ -225,6 +231,30 @@ func (r *ManilaShareReconciler) SetupWithManager(mgr ctrl.Manager) error { return nil } + // index passwordSecretField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &manilav1beta1.ManilaShare{}, passwordSecretField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*manilav1beta1.ManilaShare) + if cr.Spec.Secret == "" { + return nil + } + return []string{cr.Spec.Secret} + }); err != nil { + return err + } + + // index caBundleSecretNameField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &manilav1beta1.ManilaShare{}, caBundleSecretNameField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*manilav1beta1.ManilaShare) + if cr.Spec.TLS.CaBundleSecretName == "" { + return nil + } + return []string{cr.Spec.TLS.CaBundleSecretName} + }); err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). For(&manilav1beta1.ManilaShare{}). Owns(&appsv1.StatefulSet{}). @@ -232,9 +262,47 @@ func (r *ManilaShareReconciler) SetupWithManager(mgr ctrl.Manager) error { // watch the secrets we don't own Watches(&source.Kind{Type: &corev1.Secret{}}, handler.EnqueueRequestsFromMapFunc(secretFn)). + Watches( + &source.Kind{Type: &corev1.Secret{}}, + handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). Complete(r) } +func (r *ManilaShareReconciler) findObjectsForSrc(src client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + l := log.FromContext(context.Background()).WithName("Controllers").WithName("CinderBackup") + + for _, field := range commonWatchFields { + crList := &manilav1beta1.ManilaShareList{} + listOps := &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(field, src.GetName()), + Namespace: src.GetNamespace(), + } + err := r.List(context.TODO(), crList, listOps) + if err != nil { + return []reconcile.Request{} + } + + for _, item := range crList.Items { + l.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace())) + + requests = append(requests, + reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + }, + ) + } + } + + return requests +} + func (r *ManilaShareReconciler) reconcileDelete(ctx context.Context, instance *manilav1beta1.ManilaShare, helper *helper.Helper) (ctrl.Result, error) { r.Log.Info(fmt.Sprintf("Reconciling Service '%s' delete", instance.Name)) @@ -303,6 +371,38 @@ func (r *ManilaShareReconciler) reconcileNormal(ctx context.Context, instance *m } instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) + // + // TLS input validation + // + // Validate the CA cert secret if provided + if instance.Spec.TLS.CaBundleSecretName != "" { + hash, ctrlResult, err := tls.ValidateCACertSecret( + ctx, + helper.GetClient(), + types.NamespacedName{ + Name: instance.Spec.TLS.CaBundleSecretName, + Namespace: instance.Namespace, + }, + ) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TLSInputErrorMessage, + err.Error())) + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + if hash != "" { + configVars[tls.CABundleKey] = env.SetValue(hash) + } + } + // all cert input checks out so report InputReady + instance.Status.Conditions.MarkTrue(condition.TLSInputReadyCondition, condition.InputReadyMessage) + serviceLabels := map[string]string{ common.AppSelector: manila.ServiceName, common.ComponentSelector: manilashare.Component,