Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

operator: React to changes in ConfigMap used for storage CA #11624

Merged
merged 8 commits into from Jan 11, 2024
1 change: 1 addition & 0 deletions operator/CHANGELOG.md
@@ -1,5 +1,6 @@
## Main

- [11624](https://github.com/grafana/loki/pull/11624) **xperimental**: React to changes in ConfigMap used for storage CA
- [11481](https://github.com/grafana/loki/pull/11481) **JoaoBraveCoding**: Adds AWS STS support
- [11533](https://github.com/grafana/loki/pull/11533) **periklis**: Add serviceaccount per LokiStack resource
- [11158](https://github.com/grafana/loki/pull/11158) **btaani**: operator: Add warning for old schema configuration
Expand Down
42 changes: 35 additions & 7 deletions operator/controllers/loki/lokistack_controller.go
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 @@ -207,7 +202,8 @@ func (r *LokiStackReconciler) buildController(bld k8s.Builder) error {
Owns(&rbacv1.Role{}, updateOrDeleteOnlyPred).
Owns(&rbacv1.RoleBinding{}, updateOrDeleteOnlyPred).
Watches(&corev1.Service{}, r.enqueueForAlertManagerServices(), createUpdateOrDeletePred).
Watches(&corev1.Secret{}, r.enqueueForStorageSecret(), createUpdateOrDeletePred)
Watches(&corev1.Secret{}, r.enqueueForStorageSecret(), createUpdateOrDeletePred).
Watches(&corev1.ConfigMap{}, r.enqueueForStorageCA(), createUpdateOrDeletePred)

if r.FeatureGates.LokiStackAlerts {
bld = bld.Owns(&monitoringv1.PrometheusRule{}, updateOrDeleteOnlyPred)
Expand Down Expand Up @@ -324,3 +320,35 @@ func (r *LokiStackReconciler) enqueueForStorageSecret() handler.EventHandler {
return requests
})
}

func (r *LokiStackReconciler) enqueueForStorageCA() handler.EventHandler {
return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, 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
})
}
19 changes: 13 additions & 6 deletions operator/controllers/loki/lokistack_controller_test.go
Expand Up @@ -203,8 +203,8 @@ func TestLokiStackController_RegisterWatchedResources(t *testing.T) {
table := []test{
{
src: &openshiftconfigv1.APIServer{},
index: 2,
watchesCallsCount: 3,
index: 3,
watchesCallsCount: 4,
featureGates: configv1.FeatureGates{
OpenShift: configv1.OpenShiftFeatureGates{
ClusterTLSPolicy: true,
Expand All @@ -214,8 +214,8 @@ func TestLokiStackController_RegisterWatchedResources(t *testing.T) {
},
{
src: &openshiftconfigv1.Proxy{},
index: 2,
watchesCallsCount: 3,
index: 3,
watchesCallsCount: 4,
featureGates: configv1.FeatureGates{
OpenShift: configv1.OpenShiftFeatureGates{
ClusterProxy: true,
Expand All @@ -226,14 +226,21 @@ func TestLokiStackController_RegisterWatchedResources(t *testing.T) {
{
src: &corev1.Service{},
index: 0,
watchesCallsCount: 2,
watchesCallsCount: 3,
featureGates: configv1.FeatureGates{},
pred: createUpdateOrDeletePred,
},
{
src: &corev1.Secret{},
index: 1,
watchesCallsCount: 2,
watchesCallsCount: 3,
featureGates: configv1.FeatureGates{},
pred: createUpdateOrDeletePred,
},
{
src: &corev1.ConfigMap{},
index: 2,
watchesCallsCount: 3,
featureGates: configv1.FeatureGates{},
pred: createUpdateOrDeletePred,
},
Expand Down
39 changes: 34 additions & 5 deletions operator/internal/handlers/internal/storage/ca_configmap.go
@@ -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
}
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
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
Expand Up @@ -997,7 +997,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