Skip to content
16 changes: 16 additions & 0 deletions .chloggen/fix_monolithic_metrics.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: bug_fix

# The name of the component, or a single word describing the area of concern, (e.g. tempostack, tempomonolithic, github action)
component: tempomonolithic

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Scrape tempo metrics for monolithic.

# One or more tracking issues related to the change
issues: [1275]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:
9 changes: 9 additions & 0 deletions cmd/start/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ func start(c *cobra.Command, args []string) {
setupLog.Error(err, "unable to create controller", "controller", "certrotation")
os.Exit(1)
}

if err = (&controllers.CertRotationMonolithicReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
FeatureGates: ctrlConfig.Gates,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "certrotationmonolithic")
os.Exit(1)
}
}

if err = (&controllers.TempoStackReconciler{
Expand Down
7 changes: 4 additions & 3 deletions internal/certrotation/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var defaultUserInfo = &user.DefaultInfo{Name: "system:tempostacks", Groups: []st

// BuildAll builds all secrets and configmaps containing
// CA certificates, CA bundles and client certificates for
// a TempoStack.
// a Tempo CR.
func BuildAll(opts Options) ([]client.Object, error) {
res := make([]client.Object, 0)

Expand All @@ -40,7 +40,7 @@ func BuildAll(opts Options) ([]client.Object, error) {
}

// ApplyDefaultSettings merges the default options with the ones we give.
func ApplyDefaultSettings(opts *Options, cfg configv1alpha1.BuiltInCertManagement) error {
func ApplyDefaultSettings(opts *Options, cfg configv1alpha1.BuiltInCertManagement, components map[string]string) error {
rotation, err := ParseRotation(cfg)
if err != nil {
return err
Expand All @@ -55,11 +55,12 @@ func ApplyDefaultSettings(opts *Options, cfg configv1alpha1.BuiltInCertManagemen
if opts.Certificates == nil {
opts.Certificates = make(map[string]SelfSignedCertKey)
}
for service, name := range ComponentCertSecretNames(opts.StackName) {
for service, name := range components {
r := certificateRotation{
Clock: clock,
UserInfo: defaultUserInfo,
Hostnames: []string{
"localhost",
fmt.Sprintf("%s.%s.svc.cluster.local", service, opts.StackNamespace),
fmt.Sprintf("%s.%s.svc", service, opts.StackNamespace),
},
Expand Down
12 changes: 7 additions & 5 deletions internal/certrotation/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func TestBuildAll(t *testing.T) {
StackName: "dev",
StackNamespace: "ns",
}
err := ApplyDefaultSettings(&opts, cfg)
err := ApplyDefaultSettings(&opts, cfg, TempoStackComponentCertSecretNames(opts.StackName))
require.NoError(t, err)

objs, err := BuildAll(opts)
Expand Down Expand Up @@ -69,17 +69,18 @@ func TestApplyDefaultSettings_EmptySecrets(t *testing.T) {
StackNamespace: "ns",
}

err := ApplyDefaultSettings(&opts, cfg)
err := ApplyDefaultSettings(&opts, cfg, TempoStackComponentCertSecretNames(opts.StackName))
require.NoError(t, err)

cs := ComponentCertSecretNames(opts.StackName)
cs := TempoStackComponentCertSecretNames(opts.StackName)

for service, name := range cs {
cert, ok := opts.Certificates[name]
require.True(t, ok)
require.NotEmpty(t, cert.Rotation)

hostnames := []string{
"localhost",
fmt.Sprintf("%s.%s.svc.cluster.local", service, opts.StackNamespace),
fmt.Sprintf("%s.%s.svc", service, opts.StackNamespace),
}
Expand Down Expand Up @@ -114,7 +115,7 @@ func TestApplyDefaultSettings_ExistingSecrets(t *testing.T) {
Certificates: ComponentCertificates{},
}

cs := ComponentCertSecretNames(opts.StackName)
cs := TempoStackComponentCertSecretNames(opts.StackName)

for _, name := range cs {
opts.Certificates[name] = SelfSignedCertKey{
Expand All @@ -131,7 +132,7 @@ func TestApplyDefaultSettings_ExistingSecrets(t *testing.T) {
}
}

err := ApplyDefaultSettings(&opts, cfg)
err := ApplyDefaultSettings(&opts, cfg, TempoStackComponentCertSecretNames(opts.StackName))
require.NoError(t, err)

for service, name := range cs {
Expand All @@ -140,6 +141,7 @@ func TestApplyDefaultSettings_ExistingSecrets(t *testing.T) {
require.NotEmpty(t, cert.Rotation)

hostnames := []string{
"localhost",
fmt.Sprintf("%s.%s.svc.cluster.local", service, opts.StackNamespace),
fmt.Sprintf("%s.%s.svc", service, opts.StackNamespace),
}
Expand Down
34 changes: 32 additions & 2 deletions internal/certrotation/handlers/certrotation_discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import (

const certRotationRequiredAtKey = "tempo.grafana.com/certRotationRequiredAt"

// AnnotateForRequiredCertRotation adds/updates the `tempo.grafana.com/certRotationRequiredAt` annotation
// AnnotateTemnpoStackForRequiredCertRotation adds/updates the `tempo.grafana.com/certRotationRequiredAt` annotation
// to the named TempoStack if any of the managed client/serving/ca certificates expired. If no TempoStack
// is found, then skip reconciliation.
func AnnotateForRequiredCertRotation(ctx context.Context, k client.Client, name, namespace string) error {
func AnnotateTempoStackForRequiredCertRotation(ctx context.Context, k client.Client, name, namespace string) error {
var s v1alpha1.TempoStack
key := client.ObjectKey{Name: name, Namespace: namespace}

Expand All @@ -43,3 +43,33 @@ func AnnotateForRequiredCertRotation(ctx context.Context, k client.Client, name,

return nil
}

// AnnotateMonolithicForRequiredCertRotation adds/updates the `tempo.grafana.com/certRotationRequiredAt` annotation
// to the named TempoStack if any of the managed client/serving/ca certificates expired. If no TempoStack
// is found, then skip reconciliation.
func AnnotateMonolithicForRequiredCertRotation(ctx context.Context, k client.Client, name, namespace string) error {
var s v1alpha1.TempoMonolithic
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 tempo TempoStack", "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 tempo TempoStack `%s` annotation", certRotationRequiredAtKey), "key", key)
}

return nil
}
21 changes: 5 additions & 16 deletions internal/certrotation/handlers/check_cert_expiry.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,26 @@ import (

"github.com/ViaQ/logerr/v2/kverrors"
"github.com/go-logr/logr"
apierrors "k8s.io/apimachinery/pkg/api/errors"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

configv1alpha1 "github.com/grafana/tempo-operator/api/config/v1alpha1"
v1alpha1 "github.com/grafana/tempo-operator/api/tempo/v1alpha1"
"github.com/grafana/tempo-operator/internal/certrotation"
)

// CheckCertExpiry handles the case if the TempoStack managed signing CA, client and/or serving
// certificates expired. Returns true if any of those expired and an error representing the reason
// of expiry.
func CheckCertExpiry(ctx context.Context, log logr.Logger, req ctrl.Request, k client.Client, fg configv1alpha1.FeatureGates) error {
ll := log.WithValues("tempostacks", req.String(), "event", "checkCertExpiry")

var stack v1alpha1.TempoStack
if err := k.Get(ctx, req.NamespacedName, &stack); err != nil {
if apierrors.IsNotFound(err) {
// maybe the user deleted it before we could react? Either way this isn't an issue
ll.Error(err, "could not find the requested tempo tempostacks", "name", req.String())
return nil
}
return kverrors.Wrap(err, "failed to lookup tempostacks", "name", req.String())
}
func CheckCertExpiry(controllerName string, ctx context.Context, log logr.Logger, req ctrl.Request, k client.Client,
fg configv1alpha1.FeatureGates, components map[string]string) error {
ll := log.WithValues(controllerName, req.String(), "event", "checkCertExpiry")

opts, err := GetOptions(ctx, k, req)
opts, err := GetOptions(ctx, k, req, components)
if err != nil {
return kverrors.Wrap(err, "failed to lookup certificates secrets", "name", req.String())
}

if optErr := certrotation.ApplyDefaultSettings(&opts, fg.BuiltInCertManagement); optErr != nil {
if optErr := certrotation.ApplyDefaultSettings(&opts, fg.BuiltInCertManagement, components); optErr != nil {
ll.Error(optErr, "failed to conform options to build settings")
return optErr
}
Expand Down
7 changes: 3 additions & 4 deletions internal/certrotation/handlers/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (

// GetOptions return a certrotation options struct filled with all found client and serving certificate secrets if any found.
// Return an error only if either the k8s client returns any other error except IsNotFound or if merging options fails.
func GetOptions(ctx context.Context, k client.Client, req ctrl.Request) (certrotation.Options, error) {
func GetOptions(ctx context.Context, k client.Client, req ctrl.Request, cs map[string]string) (certrotation.Options, error) {
name := certrotation.SigningCASecretName(req.Name)
ca, err := getSecret(ctx, k, name, req.Namespace)
if err != nil {
Expand All @@ -31,7 +31,7 @@ func GetOptions(ctx context.Context, k client.Client, req ctrl.Request) (certrot
}
}

certs, err := getCertificateOptions(ctx, k, req)
certs, err := getCertificateOptions(ctx, k, req, cs)
if err != nil {
return certrotation.Options{}, err
}
Expand All @@ -47,8 +47,7 @@ func GetOptions(ctx context.Context, k client.Client, req ctrl.Request) (certrot
}, nil
}

func getCertificateOptions(ctx context.Context, k client.Client, req ctrl.Request) (certrotation.ComponentCertificates, error) {
cs := certrotation.ComponentCertSecretNames(req.Name)
func getCertificateOptions(ctx context.Context, k client.Client, req ctrl.Request, cs map[string]string) (certrotation.ComponentCertificates, error) {
certs := make(certrotation.ComponentCertificates, len(cs))

for _, name := range cs {
Expand Down
6 changes: 3 additions & 3 deletions internal/certrotation/handlers/rotate_certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
// including the signing CA and a ca bundle or else returns an error. It returns only a degrade-condition-worthy
// error if building the manifests fails for any reason.
func CreateOrRotateCertificates(ctx context.Context, log logr.Logger,
req ctrl.Request, k client.Client, s *runtime.Scheme, fg configv1alpha1.FeatureGates) error {
req ctrl.Request, k client.Client, s *runtime.Scheme, fg configv1alpha1.FeatureGates, cs map[string]string) error {
ll := log.WithValues("tempostacks", req.String(), "event", "createOrRotateCerts")
var stack v1alpha1.TempoStack
if err := k.Get(ctx, req.NamespacedName, &stack); err != nil {
Expand All @@ -35,12 +35,12 @@ func CreateOrRotateCertificates(ctx context.Context, log logr.Logger,
return kverrors.Wrap(err, "failed to lookup tempostacks", "name", req.String())
}

opts, err := GetOptions(ctx, k, req)
opts, err := GetOptions(ctx, k, req, cs)
if err != nil {
return kverrors.Wrap(err, "failed to lookup certificates secrets", "name", req.String())
}

if optErr := certrotation.ApplyDefaultSettings(&opts, fg.BuiltInCertManagement); optErr != nil {
if optErr := certrotation.ApplyDefaultSettings(&opts, fg.BuiltInCertManagement, certrotation.TempoStackComponentCertSecretNames(opts.StackName)); optErr != nil {
ll.Error(optErr, "failed to conform options to build settings")
return kverrors.Wrap(err, "failed to conform options to build settings", "name", req.String())
}
Expand Down
2 changes: 1 addition & 1 deletion internal/certrotation/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func CertificatesExpired(opts Options) error {
return &CertExpiredError{Message: "certificates expired", Reasons: reasons}
}

// buildTargetCertKeyPairSecrets returns a slice of all rotated client and serving tempostacks certificates.
// buildTargetCertKeyPairSecrets returns a slice of all rotated client and serving tempo certificates.
func buildTargetCertKeyPairSecrets(opts Options) ([]client.Object, error) {
var (
res = make([]client.Object, 0)
Expand Down
8 changes: 4 additions & 4 deletions internal/certrotation/target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ func TestCertificatesExpired(t *testing.T) {
},
RawCACerts: rawCA.Config.Certs,
}
err = ApplyDefaultSettings(&opts, cfg)
err = ApplyDefaultSettings(&opts, cfg, TempoStackComponentCertSecretNames(opts.StackName))
require.NoError(t, err)

for _, name := range ComponentCertSecretNames(stackName) {
for _, name := range TempoStackComponentCertSecretNames(stackName) {
cert := opts.Certificates[name]
cert.Secret = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -106,7 +106,7 @@ func TestBuildTargetCertKeyPairSecrets_Create(t *testing.T) {
RawCACerts: rawCA.Config.Certs,
}

err := ApplyDefaultSettings(&opts, cfg)
err := ApplyDefaultSettings(&opts, cfg, TempoStackComponentCertSecretNames(opts.StackName))
require.NoError(t, err)

objs, err := buildTargetCertKeyPairSecrets(opts)
Expand Down Expand Up @@ -154,7 +154,7 @@ func TestBuildTargetCertKeyPairSecrets_Rotate(t *testing.T) {
},
},
}
err := ApplyDefaultSettings(&opts, cfg)
err := ApplyDefaultSettings(&opts, cfg, TempoStackComponentCertSecretNames(opts.StackName))
require.NoError(t, err)

objs, err := buildTargetCertKeyPairSecrets(opts)
Expand Down
11 changes: 9 additions & 2 deletions internal/certrotation/var.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ func CABundleName(stackName string) string {
return fmt.Sprintf("tempo-%s-ca-bundle", stackName)
}

// ComponentCertSecretNames returns a map, with the key as the service name, and the value the secret name.
func ComponentCertSecretNames(stackName string) map[string]string {
// TempoStackComponentCertSecretNames returns a map, with the key as the service name, and the value the secret name.
func TempoStackComponentCertSecretNames(stackName string) map[string]string {
return map[string]string{
naming.Name(manifestutils.DistributorComponentName, stackName): naming.TLSSecretName(manifestutils.DistributorComponentName, stackName),
naming.Name(manifestutils.IngesterComponentName, stackName): naming.TLSSecretName(manifestutils.IngesterComponentName, stackName),
Expand All @@ -44,3 +44,10 @@ func ComponentCertSecretNames(stackName string) map[string]string {
naming.Name(manifestutils.GatewayComponentName, stackName): naming.TLSSecretName(manifestutils.GatewayComponentName, stackName),
}
}

// MonolithicComponentCertSecretNames returns a map of component names to their respective TLS secret names based on input name.
func MonolithicComponentCertSecretNames(name string) map[string]string {
return map[string]string{
naming.Name(manifestutils.TempoMonolithComponentName, name): naming.TLSSecretName(manifestutils.TempoMonolithComponentName, name),
}
}
5 changes: 3 additions & 2 deletions internal/controller/tempo/certrotation_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@

var expired *certrotation.CertExpiredError

err = handlers.CheckCertExpiry(ctx, log, req, r.Client, r.FeatureGates)
err = handlers.CheckCertExpiry("tempostack", ctx, log, req, r.Client, r.FeatureGates,
certrotation.TempoStackComponentCertSecretNames(req.Name))
switch {
case errors.As(err, &expired):
log.Info("Certificate expired", "msg", expired.Error())
Expand All @@ -74,7 +75,7 @@
}

log.Error(err, "TempoStack certificates expired", "name", req.String())
err = handlers.AnnotateForRequiredCertRotation(ctx, r.Client, req.Name, req.Namespace)
err = handlers.AnnotateTemnpoStackForRequiredCertRotation(ctx, r.Client, req.Name, req.Namespace)

Check failure on line 78 in internal/controller/tempo/certrotation_controller.go

View workflow job for this annotation

GitHub Actions / End-to-end tests max

undefined: handlers.AnnotateTemnpoStackForRequiredCertRotation

Check failure on line 78 in internal/controller/tempo/certrotation_controller.go

View workflow job for this annotation

GitHub Actions / Unit tests

undefined: handlers.AnnotateTemnpoStackForRequiredCertRotation
if err != nil {
log.Error(err, "failed to annotate required cert rotation", "name", req.String())
return ctrl.Result{}, err
Expand Down
Loading
Loading