mirror of
https://github.com/outbackdingo/kamaji.git
synced 2026-01-27 10:19:29 +00:00
166 lines
4.2 KiB
Go
166 lines
4.2 KiB
Go
// Copyright 2022 Clastix Labs
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package controllers
|
|
|
|
import (
|
|
"context"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
corev1 "k8s.io/api/core/v1"
|
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
clientcmdapiv1 "k8s.io/client-go/tools/clientcmd/api/v1"
|
|
controllerruntime "sigs.k8s.io/controller-runtime"
|
|
"sigs.k8s.io/controller-runtime/pkg/builder"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/event"
|
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
|
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
|
|
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
|
"github.com/clastix/kamaji/internal/constants"
|
|
"github.com/clastix/kamaji/internal/crypto"
|
|
"github.com/clastix/kamaji/internal/utilities"
|
|
)
|
|
|
|
type CertificateLifecycle struct {
|
|
Channel chan event.GenericEvent
|
|
Deadline time.Duration
|
|
|
|
client client.Client
|
|
}
|
|
|
|
func (s *CertificateLifecycle) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
|
|
logger := log.FromContext(ctx)
|
|
|
|
logger.Info("starting CertificateLifecycle handling")
|
|
|
|
secret := corev1.Secret{}
|
|
err := s.client.Get(ctx, request.NamespacedName, &secret)
|
|
if k8serrors.IsNotFound(err) {
|
|
logger.Info("resource have been deleted, skipping")
|
|
|
|
return reconcile.Result{}, nil
|
|
}
|
|
if err != nil {
|
|
logger.Error(err, "cannot retrieve the required resource")
|
|
|
|
return reconcile.Result{}, err
|
|
}
|
|
|
|
checkType, ok := secret.GetLabels()[constants.ControllerLabelResource]
|
|
if !ok {
|
|
logger.Info("missing controller label, shouldn't happen")
|
|
|
|
return reconcile.Result{}, nil
|
|
}
|
|
|
|
var crt *x509.Certificate
|
|
|
|
switch checkType {
|
|
case "x509":
|
|
crt, err = s.extractCertificateFromBareSecret(secret)
|
|
case "kubeconfig":
|
|
crt, err = s.extractCertificateFromKubeconfig(secret)
|
|
default:
|
|
err = fmt.Errorf("unsupported strategy, %s", checkType)
|
|
}
|
|
|
|
if err != nil {
|
|
logger.Error(err, "skipping reconciliation")
|
|
|
|
return reconcile.Result{}, nil
|
|
}
|
|
|
|
deadline := time.Now().Add(s.Deadline)
|
|
|
|
if deadline.After(crt.NotAfter) {
|
|
logger.Info("certificate near expiration, must be rotated")
|
|
|
|
s.Channel <- event.GenericEvent{Object: &kamajiv1alpha1.TenantControlPlane{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: secret.GetOwnerReferences()[0].Name,
|
|
Namespace: secret.Namespace,
|
|
},
|
|
}}
|
|
|
|
logger.Info("certificate rotation triggered")
|
|
|
|
return reconcile.Result{}, nil
|
|
}
|
|
|
|
after := crt.NotAfter.Sub(deadline)
|
|
|
|
logger.Info("certificate is still valid, enqueuing back", "after", after.String())
|
|
|
|
return reconcile.Result{RequeueAfter: after}, nil
|
|
}
|
|
|
|
func (s *CertificateLifecycle) extractCertificateFromBareSecret(secret corev1.Secret) (*x509.Certificate, error) {
|
|
var crt *x509.Certificate
|
|
var err error
|
|
|
|
for _, v := range secret.Data {
|
|
if crt, err = crypto.ParseCertificateBytes(v); err == nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
if crt == nil {
|
|
return nil, fmt.Errorf("none of the provided keys is containing a valid x509 certificate")
|
|
}
|
|
|
|
return crt, nil
|
|
}
|
|
|
|
func (s *CertificateLifecycle) extractCertificateFromKubeconfig(secret corev1.Secret) (*x509.Certificate, error) {
|
|
var kc *clientcmdapiv1.Config
|
|
var err error
|
|
|
|
for k := range secret.Data {
|
|
if kc, err = utilities.DecodeKubeconfig(secret, k); err == nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
if kc == nil {
|
|
return nil, fmt.Errorf("none of the provided keys is containing a valid kubeconfig")
|
|
}
|
|
|
|
crt, err := crypto.ParseCertificateBytes(kc.AuthInfos[0].AuthInfo.ClientCertificateData)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "cannot parse kubeconfig certificate bytes")
|
|
}
|
|
|
|
return crt, nil
|
|
}
|
|
|
|
func (s *CertificateLifecycle) SetupWithManager(mgr controllerruntime.Manager) error {
|
|
s.client = mgr.GetClient()
|
|
|
|
supportedStrategies := sets.New[string]("x509", "kubeconfig")
|
|
|
|
return controllerruntime.NewControllerManagedBy(mgr).
|
|
For(&corev1.Secret{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
|
|
labels := object.GetLabels()
|
|
|
|
if labels == nil {
|
|
return false
|
|
}
|
|
|
|
value, ok := labels[constants.ControllerLabelResource]
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
return supportedStrategies.Has(value)
|
|
}))).
|
|
Complete(s)
|
|
}
|