From 6c33c58909f1d5e1214541e0a83ea00ad665b2fb Mon Sep 17 00:00:00 2001 From: Jaromir Wysoglad Date: Thu, 30 May 2024 03:02:32 -0400 Subject: [PATCH] feat: watch Querier TLS resources --- .../monitoring/thanos-querier/components.go | 22 +++- .../monitoring/thanos-querier/controller.go | 121 +++++++++++++++++- 2 files changed, 139 insertions(+), 4 deletions(-) diff --git a/pkg/controllers/monitoring/thanos-querier/components.go b/pkg/controllers/monitoring/thanos-querier/components.go index 6781f1358..160684a78 100644 --- a/pkg/controllers/monitoring/thanos-querier/components.go +++ b/pkg/controllers/monitoring/thanos-querier/components.go @@ -13,11 +13,16 @@ import ( "github.com/rhobs/observability-operator/pkg/reconciler" ) -func thanosComponentReconcilers(thanos *msoapi.ThanosQuerier, sidecarUrls []string, thanosCfg ThanosConfiguration) []reconciler.Reconciler { +func thanosComponentReconcilers( + thanos *msoapi.ThanosQuerier, + sidecarUrls []string, + thanosCfg ThanosConfiguration, + tlsHashes map[string]string, +) []reconciler.Reconciler { name := "thanos-querier-" + thanos.Name return []reconciler.Reconciler{ reconciler.NewUpdater(newServiceAccount(name, thanos.Namespace), thanos), - reconciler.NewUpdater(newThanosQuerierDeployment(name, thanos, sidecarUrls, thanosCfg), thanos), + reconciler.NewUpdater(newThanosQuerierDeployment(name, thanos, sidecarUrls, thanosCfg, tlsHashes), thanos), reconciler.NewUpdater(newService(name, thanos.Namespace), thanos), reconciler.NewUpdater(newServiceMonitor(name, thanos.Namespace, thanos), thanos), reconciler.NewOptionalUpdater(newHttpConfConfigMap(name, thanos), thanos, thanos.Spec.WebTLSConfig != nil), @@ -47,7 +52,13 @@ tls_server_config: return httpConf } -func newThanosQuerierDeployment(name string, spec *msoapi.ThanosQuerier, sidecarUrls []string, thanosCfg ThanosConfiguration) *appsv1.Deployment { +func newThanosQuerierDeployment( + name string, + spec *msoapi.ThanosQuerier, + sidecarUrls []string, + thanosCfg ThanosConfiguration, + tlsHashes map[string]string, +) *appsv1.Deployment { httpConfCMName := fmt.Sprintf("%s-http-conf", name) args := []string{ @@ -178,6 +189,11 @@ func newThanosQuerierDeployment(name string, spec *msoapi.ThanosQuerier, sidecar ReadOnly: true, }, }...) + tlsAnnotations := map[string]string{} + for name, hash := range tlsHashes { + tlsAnnotations[fmt.Sprintf("monitoring.openshift.io/%s-hash", name)] = hash + } + thanos.ObjectMeta.Annotations = tlsAnnotations } return thanos diff --git a/pkg/controllers/monitoring/thanos-querier/controller.go b/pkg/controllers/monitoring/thanos-querier/controller.go index 57e94ab39..b9285fe72 100644 --- a/pkg/controllers/monitoring/thanos-querier/controller.go +++ b/pkg/controllers/monitoring/thanos-querier/controller.go @@ -14,6 +14,7 @@ package thanos_querier import ( "context" + "crypto/sha256" "fmt" "time" @@ -22,9 +23,11 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/rand" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -51,6 +54,12 @@ type Options struct { Thanos ThanosConfiguration } +const ( + thanosTLSPrivateKeySecretNameField = ".spec.webTLSConfig.privateKey.name" + thanosTLSCertificateSecretNameField = ".spec.webTLSConfig.certificate.name" + thanosTLSCertificateAuthoritySecretNameField = ".spec.webTLSConfig.certificateAuthority.name" +) + // RBAC for watching monitoring stacks //+kubebuilder:rbac:groups=monitoring.rhobs,resources=monitoringstacks,verbs=list;watch @@ -64,6 +73,7 @@ type Options struct { // RBAC for managing core resources //+kubebuilder:rbac:groups=core,resources=services;serviceaccounts;configmaps,verbs=list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=core,resources=secrets,verbs=list;watch // RBAC for managing Prometheus Operator CRs //+kubebuilder:rbac:groups=monitoring.rhobs,resources=servicemonitors,verbs=list;watch;create;update;patch;delete @@ -79,17 +89,56 @@ func RegisterWithManager(mgr ctrl.Manager, opts Options) error { thanos: opts.Thanos, } + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &msoapi.ThanosQuerier{}, thanosTLSPrivateKeySecretNameField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*msoapi.ThanosQuerier) + if cr.Spec.WebTLSConfig == nil { + return nil + } + return []string{cr.Spec.WebTLSConfig.PrivateKey.Name} + }); err != nil { + return err + } + + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &msoapi.ThanosQuerier{}, thanosTLSCertificateSecretNameField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*msoapi.ThanosQuerier) + if cr.Spec.WebTLSConfig == nil { + return nil + } + return []string{cr.Spec.WebTLSConfig.Certificate.Name} + }); err != nil { + return err + } + + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &msoapi.ThanosQuerier{}, thanosTLSCertificateAuthoritySecretNameField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*msoapi.ThanosQuerier) + if cr.Spec.WebTLSConfig == nil { + return nil + } + return []string{cr.Spec.WebTLSConfig.CertificateAuthority.Name} + }); err != nil { + return err + } + p := predicate.GenerationChangedPredicate{} return ctrl.NewControllerManagedBy(mgr). For(&msoapi.ThanosQuerier{}). Owns(&appsv1.Deployment{}).WithEventFilter(p). Owns(&corev1.ServiceAccount{}).WithEventFilter(p). Owns(&corev1.Service{}).WithEventFilter(p). + Owns(&corev1.ConfigMap{}).WithEventFilter(p). Watches( &msoapi.MonitoringStack{}, handler.EnqueueRequestsFromMapFunc(rm.findQueriersForMonitoringStack), builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), ). + Watches( + &corev1.Secret{}, + handler.EnqueueRequestsFromMapFunc(rm.findQueriersForTLSSecrets), + builder.WithPredicates(predicate.GenerationChangedPredicate{}), + ). Complete(rm) } @@ -113,7 +162,23 @@ func (rm resourceManager) Reconcile(ctx context.Context, req ctrl.Request) (ctrl return ctrl.Result{RequeueAfter: 10 * time.Second}, err } - reconcilers := thanosComponentReconcilers(querier, sidecarServices, rm.thanos) + tlsHashes := map[string]string{} + if querier.Spec.WebTLSConfig != nil { + secretSelectors := []msoapi.SecretKeySelector{ + querier.Spec.WebTLSConfig.CertificateAuthority, + querier.Spec.WebTLSConfig.Certificate, + querier.Spec.WebTLSConfig.PrivateKey, + } + for _, secretSelector := range secretSelectors { + hash, err := rm.hashOfTLSSecret(secretSelector, querier.Namespace) + if err != nil { + return ctrl.Result{}, err + } + tlsHashes[fmt.Sprintf("%s-%s", secretSelector.Name, secretSelector.Key)] = hash + } + } + + reconcilers := thanosComponentReconcilers(querier, sidecarServices, rm.thanos, tlsHashes) for _, reconciler := range reconcilers { err := reconciler.Reconcile(ctx, rm, rm.scheme) // handle creation / updation errors that can happen due to a stale cache by @@ -156,6 +221,20 @@ func (rm resourceManager) findSidecarServices(ctx context.Context, tQuerier *mso return sidecarUrls, nil } +func (rm resourceManager) hashOfTLSSecret(selector msoapi.SecretKeySelector, namespace string) (string, error) { + var secret corev1.Secret + err := rm.Get(context.Background(), types.NamespacedName{ + Name: selector.Name, + Namespace: namespace, + }, &secret) + if err != nil { + return "", fmt.Errorf("Couldn't get TLS secret %s: %s", selector.Name, err) + } + + hash := sha256.Sum256(secret.Data[selector.Key]) + return rand.SafeEncodeString(fmt.Sprint(hash)), nil +} + // Given a Service object, return a url to use as value for --store/--endpoint. func getEndpointUrl(serviceName string, namespace string) string { return fmt.Sprintf("dnssrv+_grpc._tcp.%s.%s.svc.cluster.local", serviceName, namespace) @@ -191,3 +270,43 @@ func (rm resourceManager) findQueriersForMonitoringStack(ctx context.Context, ms } return requests } + +// Find all ThanosQueriers, whose TLS secrets fit the given Secret and +// return a list of reconcile requests, one for each ThanosQuerier. +func (rm resourceManager) findQueriersForTLSSecrets(ctx context.Context, src client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + logger := rm.logger.WithValues("Secret", src.GetNamespace()+"/"+src.GetName()) + logger.Info("watched Secret changed, checking for matching querier") + + thanosWatchFields := []string{ + thanosTLSCertificateAuthoritySecretNameField, + thanosTLSCertificateSecretNameField, + thanosTLSPrivateKeySecretNameField, + } + + for _, field := range thanosWatchFields { + crList := &msoapi.ThanosQuerierList{} + listOps := &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(field, src.GetName()), + Namespace: src.GetNamespace(), + } + err := rm.Client.List(ctx, crList, listOps) + if err != nil { + logger.Error(err, "Failed to list Thanosqueriers") + return []reconcile.Request{} + } + + for _, item := range crList.Items { + logger.Info("Found querier, scheduling sync") + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + }) + } + } + + return requests +}