Skip to content

Commit

Permalink
operator: React to changes in ConfigMap used for storage CA (grafana#…
Browse files Browse the repository at this point in the history
  • Loading branch information
xperimental authored and periklis committed Jan 18, 2024
1 parent 326b683 commit f762b71
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 28 deletions.
4 changes: 4 additions & 0 deletions operator/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Main

## Release 5.6.16

- [11624](https://github.com/grafana/loki/pull/11624) **xperimental**: React to changes in ConfigMap used for storage CA

## Release 5.6.15

- [11448](https://github.com/grafana/loki/pull/11448) **periklis**: Update Loki operand to v2.9.3
Expand Down
43 changes: 36 additions & 7 deletions operator/controllers/loki/lokistack_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,7 @@ var (
})
createUpdateOrDeletePred = builder.WithPredicates(predicate.Funcs{
UpdateFunc: func(e event.UpdateEvent) bool {
if e.ObjectOld.GetGeneration() == 0 && len(e.ObjectOld.GetAnnotations()) == 0 {
return e.ObjectOld.GetResourceVersion() != e.ObjectNew.GetResourceVersion()
}

return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() ||
cmp.Diff(e.ObjectOld.GetAnnotations(), e.ObjectNew.GetAnnotations()) != ""
return e.ObjectOld.GetResourceVersion() != e.ObjectNew.GetResourceVersion()
},
CreateFunc: func(e event.CreateEvent) bool { return true },
DeleteFunc: func(e event.DeleteEvent) bool { return true },
Expand Down Expand Up @@ -203,7 +198,8 @@ func (r *LokiStackReconciler) buildController(bld k8s.Builder) error {
Owns(&rbacv1.ClusterRoleBinding{}, updateOrDeleteOnlyPred).
Owns(&rbacv1.Role{}, updateOrDeleteOnlyPred).
Owns(&rbacv1.RoleBinding{}, updateOrDeleteOnlyPred).
Watches(&source.Kind{Type: &corev1.Secret{}}, r.enqueueForStorageSecret(), createUpdateOrDeletePred)
Watches(&source.Kind{Type: &corev1.Secret{}}, r.enqueueForStorageSecret(), createUpdateOrDeletePred).
Watches(&source.Kind{Type: &corev1.ConfigMap{}}, r.enqueueForStorageCA(), createUpdateOrDeletePred)

if r.FeatureGates.LokiStackAlerts {
bld = bld.Owns(&monitoringv1.PrometheusRule{}, updateOrDeleteOnlyPred)
Expand Down Expand Up @@ -290,3 +286,36 @@ func statusDifferent(e event.UpdateEvent) bool {
return false
}
}

func (r *LokiStackReconciler) enqueueForStorageCA() handler.EventHandler {
ctx := context.TODO()
return handler.EnqueueRequestsFromMapFunc(func(obj client.Object) []reconcile.Request {
lokiStacks := &lokiv1.LokiStackList{}
if err := r.Client.List(ctx, lokiStacks, client.InNamespace(obj.GetNamespace())); err != nil {
r.Log.Error(err, "Error listing LokiStack resources for storage CA update")
return nil
}

var requests []reconcile.Request
for _, stack := range lokiStacks.Items {
if stack.Spec.Storage.TLS == nil {
continue
}

storageTLS := stack.Spec.Storage.TLS
if obj.GetName() != storageTLS.CA {
continue
}

requests = append(requests, reconcile.Request{
NamespacedName: types.NamespacedName{
Namespace: stack.Namespace,
Name: stack.Name,
},
})
r.Log.Info("Enqueued request for LokiStack because of Storage CA resource change", "LokiStack", stack.Name, "ConfigMap", obj.GetName())
}

return requests
})
}
17 changes: 12 additions & 5 deletions operator/controllers/loki/lokistack_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ func TestLokiStackController_RegisterWatchedResources(t *testing.T) {
table := []test{
{
src: &source.Kind{Type: &openshiftconfigv1.APIServer{}},
index: 1,
watchesCallsCount: 2,
index: 2,
watchesCallsCount: 3,
featureGates: configv1.FeatureGates{
OpenShift: configv1.OpenShiftFeatureGates{
ClusterTLSPolicy: true,
Expand All @@ -215,8 +215,8 @@ func TestLokiStackController_RegisterWatchedResources(t *testing.T) {
},
{
src: &source.Kind{Type: &openshiftconfigv1.Proxy{}},
index: 1,
watchesCallsCount: 2,
index: 2,
watchesCallsCount: 3,
featureGates: configv1.FeatureGates{
OpenShift: configv1.OpenShiftFeatureGates{
ClusterProxy: true,
Expand All @@ -227,7 +227,14 @@ func TestLokiStackController_RegisterWatchedResources(t *testing.T) {
{
src: &source.Kind{Type: &corev1.Secret{}},
index: 0,
watchesCallsCount: 1,
watchesCallsCount: 2,
featureGates: configv1.FeatureGates{},
pred: createUpdateOrDeletePred,
},
{
src: &source.Kind{Type: &corev1.ConfigMap{}},
index: 1,
watchesCallsCount: 2,
featureGates: configv1.FeatureGates{},
pred: createUpdateOrDeletePred,
},
Expand Down
39 changes: 34 additions & 5 deletions operator/internal/handlers/internal/storage/ca_configmap.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,38 @@
package storage

import corev1 "k8s.io/api/core/v1"
import (
"crypto/sha1"
"fmt"

// IsValidCAConfigMap checks if the given CA configMap has an
// non-empty entry for the key
func IsValidCAConfigMap(cm *corev1.ConfigMap, key string) bool {
return cm.Data[key] != ""
corev1 "k8s.io/api/core/v1"
)

type caKeyError string

func (e caKeyError) Error() string {
return fmt.Sprintf("key not present or data empty: %s", string(e))
}

// CheckCAConfigMap checks if the given CA configMap has an non-empty entry for the key used as CA certificate.
// If the key is present it will return a hash of the current key name and contents.
func CheckCAConfigMap(cm *corev1.ConfigMap, key string) (string, error) {
data := cm.Data[key]
if data == "" {
return "", caKeyError(key)
}

h := sha1.New()
if _, err := h.Write([]byte(key)); err != nil {
return "", err
}

if _, err := h.Write(hashSeparator); err != nil {
return "", err
}

if _, err := h.Write([]byte(data)); err != nil {
return "", err
}

return fmt.Sprintf("%x", h.Sum(nil)), nil
}
26 changes: 18 additions & 8 deletions operator/internal/handlers/internal/storage/ca_configmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import (

func TestIsValidConfigMap(t *testing.T) {
type test struct {
name string
cm *corev1.ConfigMap
valid bool
name string
cm *corev1.ConfigMap
wantHash string
wantErrorMsg string
}
table := []test{
{
Expand All @@ -23,11 +24,13 @@ func TestIsValidConfigMap(t *testing.T) {
"service-ca.crt": "has-some-data",
},
},
valid: true,
wantHash: "de6ae206d4920549d21c24ad9721e87a9b1ec7dc",
wantErrorMsg: "",
},
{
name: "missing `service-ca.crt` key",
cm: &corev1.ConfigMap{},
name: "missing `service-ca.crt` key",
cm: &corev1.ConfigMap{},
wantErrorMsg: "key not present or data empty: service-ca.crt",
},
{
name: "missing CA content",
Expand All @@ -36,15 +39,22 @@ func TestIsValidConfigMap(t *testing.T) {
"service-ca.crt": "",
},
},
wantErrorMsg: "key not present or data empty: service-ca.crt",
},
}
for _, tst := range table {
tst := tst
t.Run(tst.name, func(t *testing.T) {
t.Parallel()

ok := storage.IsValidCAConfigMap(tst.cm, "service-ca.crt")
require.Equal(t, tst.valid, ok)
hash, err := storage.CheckCAConfigMap(tst.cm, "service-ca.crt")

require.Equal(t, tst.wantHash, hash)
if tst.wantErrorMsg == "" {
require.NoError(t, err)
} else {
require.EqualError(t, err, tst.wantErrorMsg)
}
})
}
}
7 changes: 5 additions & 2 deletions operator/internal/handlers/lokistack_create_or_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,17 @@ func CreateOrUpdateLokiStack(
caKey = tlsConfig.CAKey
}

if !storage.IsValidCAConfigMap(&cm, caKey) {
var caHash string
caHash, err = storage.CheckCAConfigMap(&cm, caKey)
if err != nil {
return &status.DegradedError{
Message: "Invalid object storage CA configmap contents: missing key or no contents",
Message: fmt.Sprintf("Invalid object storage CA configmap contents: %s", err),
Reason: lokiv1.ReasonInvalidObjectStorageCAConfigMap,
Requeue: false,
}
}

objStore.SecretSHA1 = fmt.Sprintf("%s;%s", objStore.SecretSHA1, caHash)
objStore.TLS = &storageoptions.TLSConfig{CA: cm.Name, Key: caKey}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -965,7 +965,7 @@ func TestCreateOrUpdateLokiStack_WhenInvalidCAConfigMap_SetDegraded(t *testing.T
}

degradedErr := &status.DegradedError{
Message: "Invalid object storage CA configmap contents: missing key or no contents",
Message: "Invalid object storage CA configmap contents: key not present or data empty: service-ca.crt",
Reason: lokiv1.ReasonInvalidObjectStorageCAConfigMap,
Requeue: false,
}
Expand Down

0 comments on commit f762b71

Please sign in to comment.