Skip to content

Commit

Permalink
feat(certmanager): use controller-runtime style controller
Browse files Browse the repository at this point in the history
Signed-off-by: Khaled Emara <[email protected]>
  • Loading branch information
KhaledEmaraDev committed Jan 24, 2025
1 parent 686c3a3 commit c97b014
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 439 deletions.
204 changes: 78 additions & 126 deletions certmanager/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,165 +2,117 @@ package certmanager

import (
"context"
"crypto/tls"
"errors"
"fmt"
"time"

"github.com/go-logr/logr"
tlsMgr "github.com/kyverno/pkg/tls"
"github.com/kyverno/pkg/tls"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
corev1informers "k8s.io/client-go/informers/core/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/util/workqueue"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
)

const (
// Workers is the number of workers for this controller
Workers = 1
ControllerName = "certmanager-controller"
maxRetries = 10
)

type CertManagerController interface {
// Run starts the controller
Run(context.Context, int)

// GetCertificate can be used to fetch secrets in tls.Config
GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error)
type CertController struct {
Renewer tls.CertRenewer
caSecretName string
tlsSecretName string
namespace string
}

type controller struct {
logger logr.Logger

renewer tlsMgr.CertRenewer
func NewController(
certRenewer tls.CertRenewer,
caSecretName string,
tlsSecretName string,
namespace string,
) *CertController {
return &CertController{
Renewer: certRenewer,
caSecretName: caSecretName,
tlsSecretName: tlsSecretName,
namespace: namespace,
}
}

// listers
caLister corev1listers.SecretLister
tlsLister corev1listers.SecretLister
func (r *CertController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)

// queue
queue workqueue.RateLimitingInterface
caEnqueue EnqueueFunc
tlsEnqueue EnqueueFunc
if req.Namespace != r.namespace {
return ctrl.Result{}, nil
}

tlsConfig *tlsMgr.Config
}
if req.Name != r.caSecretName && req.Name != r.tlsSecretName {
return ctrl.Result{}, nil
}

func NewController(
logger logr.Logger,
caInformer corev1informers.SecretInformer,
tlsInformer corev1informers.SecretInformer,
certRenewer tlsMgr.CertRenewer,
config *tlsMgr.Config,
) CertManagerController {
queue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), ControllerName)
c := controller{
logger: logger,
renewer: certRenewer,
caLister: caInformer.Lister(),
tlsLister: tlsInformer.Lister(),
queue: queue,
caEnqueue: AddDefaultEventHandlers(logger, caInformer.Informer(), queue),
tlsEnqueue: AddDefaultEventHandlers(logger, tlsInformer.Informer(), queue),
tlsConfig: config,
logger.V(4).Info("reconciling certificate")
if err := r.renewCertificates(ctx); err != nil {
logger.Error(err, "failed to renew certificates")
return ctrl.Result{}, err
}
return &c

return ctrl.Result{
RequeueAfter: tls.CertRenewalInterval,
}, nil
}

func (c *controller) Run(ctx context.Context, workers int) {
// we need to enqueue our secrets in case they don't exist yet in the cluster
// this way we ensure the reconcile happens (hence renewal/creation)
if err := c.tlsEnqueue(&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: c.tlsConfig.Namespace,
Name: tlsMgr.GenerateTLSPairSecretName(c.tlsConfig),
},
}); err != nil {
c.logger.V(2).Error(err, "failed to enqueue secret", "name", tlsMgr.GenerateTLSPairSecretName(c.tlsConfig))
func (r *CertController) renewCertificates(ctx context.Context) error {
logger := log.FromContext(ctx)
if err := RetryFunc(ctx, time.Second, 5*time.Second, logger, "failed to renew CA", r.Renewer.RenewCA)(); err != nil {
return err
}
if err := c.caEnqueue(&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: c.tlsConfig.Namespace,
Name: tlsMgr.GenerateRootCASecretName(c.tlsConfig),
},
}); err != nil {
c.logger.V(2).Error(err, "failed to enqueue CA secret", "name", tlsMgr.GenerateRootCASecretName(c.tlsConfig))
if err := RetryFunc(ctx, time.Second, 5*time.Second, logger, "failed to renew TLS", r.Renewer.RenewTLS)(); err != nil {
return err
}
run(ctx, c.logger, ControllerName, time.Second, c.queue, workers, maxRetries, c.reconcile, c.ticker)
return nil
}

func (c *controller) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
secret, err := c.tlsLister.Secrets(c.tlsConfig.Namespace).Get(tlsMgr.GenerateTLSPairSecretName(c.tlsConfig))
if err != nil {
return nil, err
} else if secret == nil {
return nil, errors.New("tls secret not found")
} else if secret.Type != corev1.SecretTypeTLS {
return nil, errors.New("secret is not a TLS secret")
}
func (r *CertController) SetupWithManager(mgr ctrl.Manager) error {
secretPredicate := predicate.NewPredicateFuncs(func(obj client.Object) bool {
return obj.GetNamespace() == r.namespace && (obj.GetName() == r.caSecretName || obj.GetName() == r.tlsSecretName)
})

cert, err := tls.X509KeyPair(secret.Data[corev1.TLSCertKey], secret.Data[corev1.TLSPrivateKeyKey])
if err != nil {
return nil, err
if err := ctrl.NewControllerManagedBy(mgr).
For(&corev1.Secret{}).
WithEventFilter(secretPredicate).
Complete(r); err != nil {
return fmt.Errorf("failed to build controller: %w", err)
}

return &cert, nil
}
r.Reconcile(context.Background(), ctrl.Request{
NamespacedName: client.ObjectKey{
Namespace: r.namespace,
Name: r.caSecretName,
},
})

func (c *controller) reconcile(ctx context.Context, logger logr.Logger, key, namespace, name string) error {
if namespace != c.tlsConfig.Namespace {
return nil
}
if name != tlsMgr.GenerateTLSPairSecretName(c.tlsConfig) && name != tlsMgr.GenerateRootCASecretName(c.tlsConfig) {
return nil
}
return c.renewCertificates(ctx)
return nil
}

func (c *controller) ticker(ctx context.Context, logger logr.Logger) {
certsRenewalTicker := time.NewTicker(tlsMgr.CertRenewalInterval)
defer certsRenewalTicker.Stop()
for {
select {
case <-certsRenewalTicker.C:
{
list, err := c.caLister.List(labels.Everything())
if err == nil {
for _, secret := range list {
if err := c.caEnqueue(secret); err != nil {
logger.V(2).Error(err, "failed to enqueue secret", "name", secret.Name)
}
}
} else {
logger.V(2).Error(err, "falied to list secrets")
}
}
{
list, err := c.tlsLister.List(labels.Everything())
if err == nil {
for _, secret := range list {
if err := c.tlsEnqueue(secret); err != nil {
logger.V(2).Error(err, "failed to enqueue secret", "name", secret.Name)
}
}
func RetryFunc(ctx context.Context, retryInterval, timeout time.Duration, logger logr.Logger, msg string, run func(context.Context) error) func() error {
return func() error {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
registerTicker := time.NewTicker(retryInterval)
defer registerTicker.Stop()
var err error
for {
select {
case <-registerTicker.C:
if err = run(ctx); err != nil {
logger.V(3).Info(msg, "reason", err.Error())
} else {
logger.V(2).Error(err, "falied to list secrets")
return nil
}
case <-ctx.Done():
return fmt.Errorf("retry times out: %w", err)
}
case <-ctx.Done():
return
}
}
}

func (c *controller) renewCertificates(ctx context.Context) error {
if err := retryFunc(ctx, time.Second, 5*time.Second, c.logger, "failed to renew CA", c.renewer.RenewCA)(); err != nil {
return err
}
if err := retryFunc(ctx, time.Second, 5*time.Second, c.logger, "failed to renew TLS", c.renewer.RenewTLS)(); err != nil {
return err
}
return nil
}
65 changes: 42 additions & 23 deletions certmanager/go.mod
Original file line number Diff line number Diff line change
@@ -1,50 +1,69 @@
module github.com/kyverno/pkg/certmanager

go 1.20
go 1.22.0

toolchain go1.23.2

replace github.com/kyverno/pkg/tls => ../tls

require (
github.com/go-logr/logr v1.2.4
github.com/go-logr/logr v1.4.2
github.com/kyverno/pkg/tls v0.0.9
k8s.io/api v0.28.0
k8s.io/apimachinery v0.28.0
k8s.io/client-go v0.28.0
k8s.io/api v0.31.0
sigs.k8s.io/controller-runtime v0.19.4
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.10.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
golang.org/x/net v0.13.0 // indirect
golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/term v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
k8s.io/apiextensions-apiserver v0.31.0 // indirect
k8s.io/apimachinery v0.31.0 // indirect
k8s.io/client-go v0.31.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
Loading

0 comments on commit c97b014

Please sign in to comment.