forked from grafana/loki
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
operator: Add support for built-in-cert-rotation for all internal lok…
…istack encryption (grafana#7064)
- Loading branch information
1 parent
a447bee
commit 27b8d8d
Showing
76 changed files
with
5,754 additions
and
1,724 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -984,6 +984,7 @@ spec: | |
- endpoints | ||
- nodes | ||
- pods | ||
- secrets | ||
- serviceaccounts | ||
- services | ||
verbs: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ rules: | |
- endpoints | ||
- nodes | ||
- pods | ||
- secrets | ||
- serviceaccounts | ||
- services | ||
verbs: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package controllers | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"time" | ||
|
||
"github.com/go-logr/logr" | ||
configv1 "github.com/grafana/loki/operator/apis/config/v1" | ||
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" | ||
"github.com/grafana/loki/operator/controllers/loki/internal/lokistack" | ||
"github.com/grafana/loki/operator/controllers/loki/internal/management/state" | ||
"github.com/grafana/loki/operator/internal/certrotation" | ||
"github.com/grafana/loki/operator/internal/external/k8s" | ||
"github.com/grafana/loki/operator/internal/handlers" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
ctrl "sigs.k8s.io/controller-runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
) | ||
|
||
// CertRotationReconciler reconciles the `loki.grafana.com/certRotationRequiredAt` annotation on | ||
// any LokiStack object associated with any of the owned signer/client/serving certificates secrets | ||
// and CA bundle configmap. | ||
type CertRotationReconciler struct { | ||
client.Client | ||
Log logr.Logger | ||
Scheme *runtime.Scheme | ||
FeatureGates configv1.FeatureGates | ||
} | ||
|
||
// Reconcile is part of the main kubernetes reconciliation loop which aims to | ||
// move the current state of the cluster closer to the desired state. | ||
// Compare the state specified by the LokiStack object against the actual cluster state, | ||
// and then perform operations to make the cluster state reflect the state specified by | ||
// the user. | ||
// | ||
// For more details, check Reconcile and its Result here: | ||
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile | ||
func (r *CertRotationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { | ||
managed, err := state.IsManaged(ctx, req, r.Client) | ||
if err != nil { | ||
return ctrl.Result{ | ||
Requeue: true, | ||
}, err | ||
} | ||
if !managed { | ||
r.Log.Info("Skipping reconciliation for unmanaged LokiStack resource", "name", req.String()) | ||
// Stop requeueing for unmanaged LokiStack custom resources | ||
return ctrl.Result{}, nil | ||
} | ||
|
||
rt, err := certrotation.ParseRotation(r.FeatureGates.BuiltInCertManagement) | ||
if err != nil { | ||
return ctrl.Result{Requeue: false}, err | ||
} | ||
|
||
checkExpiryAfter := expiryRetryAfter(rt.TargetCertRefresh) | ||
r.Log.Info("Checking if LokiStack certificates expired", "name", req.String(), "interval", checkExpiryAfter.String()) | ||
|
||
var expired *certrotation.CertExpiredError | ||
|
||
err = handlers.CheckCertExpiry(ctx, r.Log, req, r.Client, r.FeatureGates) | ||
switch { | ||
case errors.As(err, &expired): | ||
r.Log.Info("Certificate expired", "msg", expired.Error()) | ||
case err != nil: | ||
return ctrl.Result{ | ||
Requeue: true, | ||
}, err | ||
default: | ||
r.Log.Info("Skipping cert rotation, all LokiStack certificates still valid", "name", req.String()) | ||
return ctrl.Result{ | ||
RequeueAfter: checkExpiryAfter, | ||
}, nil | ||
} | ||
|
||
r.Log.Error(err, "LokiStack certificates expired", "name", req.String()) | ||
err = lokistack.AnnotateForRequiredCertRotation(ctx, r.Client, req.Name, req.Namespace) | ||
if err != nil { | ||
r.Log.Error(err, "failed to annotate required cert rotation", "name", req.String()) | ||
return ctrl.Result{ | ||
Requeue: true, | ||
}, err | ||
} | ||
|
||
return ctrl.Result{ | ||
RequeueAfter: checkExpiryAfter, | ||
}, nil | ||
} | ||
|
||
// SetupWithManager sets up the controller with the Manager. | ||
func (r *CertRotationReconciler) SetupWithManager(mgr ctrl.Manager) error { | ||
b := ctrl.NewControllerManagedBy(mgr) | ||
return r.buildController(k8s.NewCtrlBuilder(b)) | ||
} | ||
|
||
func (r *CertRotationReconciler) buildController(bld k8s.Builder) error { | ||
return bld. | ||
For(&lokiv1.LokiStack{}). | ||
Owns(&corev1.Secret{}). | ||
Complete(r) | ||
} | ||
|
||
func expiryRetryAfter(certRefresh time.Duration) time.Duration { | ||
day := 24 * time.Hour | ||
if certRefresh > day { | ||
return 12 * time.Hour | ||
} | ||
|
||
return certRefresh / 4 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package controllers | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" | ||
"github.com/grafana/loki/operator/internal/external/k8s/k8sfakes" | ||
"github.com/stretchr/testify/require" | ||
corev1 "k8s.io/api/core/v1" | ||
) | ||
|
||
func TestCertRotationController_RegistersCustomResource_WithDefaultPredicates(t *testing.T) { | ||
b := &k8sfakes.FakeBuilder{} | ||
k := &k8sfakes.FakeClient{} | ||
c := &CertRotationReconciler{Client: k, Scheme: scheme} | ||
|
||
b.ForReturns(b) | ||
b.OwnsReturns(b) | ||
|
||
err := c.buildController(b) | ||
require.NoError(t, err) | ||
|
||
// Require only one For-Call for the custom resource | ||
require.Equal(t, 1, b.ForCallCount()) | ||
|
||
// Require For-call with LokiStack resource | ||
obj, _ := b.ForArgsForCall(0) | ||
require.Equal(t, &lokiv1.LokiStack{}, obj) | ||
} | ||
|
||
func TestCertRotationController_RegisterOwnedResources_WithDefaultPredicates(t *testing.T) { | ||
b := &k8sfakes.FakeBuilder{} | ||
k := &k8sfakes.FakeClient{} | ||
c := &CertRotationReconciler{Client: k, Scheme: scheme} | ||
|
||
b.ForReturns(b) | ||
b.OwnsReturns(b) | ||
|
||
err := c.buildController(b) | ||
require.NoError(t, err) | ||
|
||
require.Equal(t, 1, b.OwnsCallCount()) | ||
|
||
obj, _ := b.OwnsArgsForCall(0) | ||
require.Equal(t, &corev1.Secret{}, obj) | ||
} | ||
|
||
func TestCertRotationController_ExpiryRetryAfter(t *testing.T) { | ||
tt := []struct { | ||
desc string | ||
refresh time.Duration | ||
wantDuration time.Duration | ||
wantError bool | ||
}{ | ||
{ | ||
desc: "multi-day refresh durarion", | ||
refresh: 120 * time.Hour, | ||
wantDuration: 12 * time.Hour, | ||
}, | ||
{ | ||
desc: "less than a day refresh duration", | ||
refresh: 10 * time.Hour, | ||
wantDuration: 2*time.Hour + 30*time.Minute, | ||
}, | ||
} | ||
for _, tc := range tt { | ||
tc := tc | ||
t.Run(tc.desc, func(t *testing.T) { | ||
t.Parallel() | ||
require.Equal(t, tc.wantDuration, expiryRetryAfter(tc.refresh)) | ||
}) | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
operator/controllers/loki/internal/lokistack/certrotation_discovery.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package lokistack | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/ViaQ/logerr/v2/kverrors" | ||
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" | ||
"github.com/grafana/loki/operator/internal/external/k8s" | ||
apierrors "k8s.io/apimachinery/pkg/api/errors" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
) | ||
|
||
const certRotationRequiredAtKey = "loki.grafana.com/certRotationRequiredAt" | ||
|
||
// AnnotateForRequiredCertRotation adds/updates the `loki.grafana.com/certRotationRequiredAt` annotation | ||
// to the named Lokistack if any of the managed client/serving/ca certificates expired. If no LokiStack | ||
// is found, then skip reconciliation. | ||
func AnnotateForRequiredCertRotation(ctx context.Context, k k8s.Client, name, namespace string) error { | ||
var s lokiv1.LokiStack | ||
key := client.ObjectKey{Name: name, Namespace: namespace} | ||
|
||
if err := k.Get(ctx, key, &s); err != nil { | ||
if apierrors.IsNotFound(err) { | ||
// Do nothing | ||
return nil | ||
} | ||
|
||
return kverrors.Wrap(err, "failed to get lokistack", "key", key) | ||
} | ||
|
||
ss := s.DeepCopy() | ||
if ss.Annotations == nil { | ||
ss.Annotations = make(map[string]string) | ||
} | ||
|
||
ss.Annotations[certRotationRequiredAtKey] = time.Now().UTC().Format(time.RFC3339) | ||
|
||
if err := k.Update(ctx, ss); err != nil { | ||
return kverrors.Wrap(err, fmt.Sprintf("failed to update lokistack `%s` annotation", certRotationRequiredAtKey), "key", key) | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.