mirror of
https://github.com/open-cluster-management-io/ocm.git
synced 2026-05-06 01:07:03 +00:00
Avoid frequent CSR creation (#277)
* Avoid frequent CSR creation Signed-off-by: Jian Qiu <jqiu@redhat.com> * Add todo for threshold value Signed-off-by: Jian Qiu <jqiu@redhat.com> Signed-off-by: Jian Qiu <jqiu@redhat.com>
This commit is contained in:
@@ -6,28 +6,22 @@ import (
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
ocmfeature "open-cluster-management.io/api/feature"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/openshift/library-go/pkg/controller/factory"
|
||||
"github.com/openshift/library-go/pkg/operator/events"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
certificatesinformers "k8s.io/client-go/informers/certificates"
|
||||
corev1informers "k8s.io/client-go/informers/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
"k8s.io/client-go/util/keyutil"
|
||||
"k8s.io/klog/v2"
|
||||
"open-cluster-management.io/registration/pkg/features"
|
||||
"open-cluster-management.io/registration/pkg/helpers"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -73,6 +67,9 @@ type CSROption struct {
|
||||
|
||||
// EventFilterFunc matches csrs created with above options
|
||||
EventFilterFunc factory.EventFilterFunc
|
||||
|
||||
// HaltCSRCreation halt the csr creation
|
||||
HaltCSRCreation func() bool
|
||||
}
|
||||
|
||||
// ClientCertOption includes options that is used to create client certificate
|
||||
@@ -99,7 +96,7 @@ type StatusUpdateFunc func(ctx context.Context, cond metav1.Condition) error
|
||||
type clientCertificateController struct {
|
||||
ClientCertOption
|
||||
CSROption
|
||||
csrControl
|
||||
csrControl CSRControl
|
||||
// managementCoreClient is used to create/delete hub kubeconfig secret on the management cluster
|
||||
managementCoreClient corev1client.CoreV1Interface
|
||||
controllerName string
|
||||
@@ -123,51 +120,7 @@ type clientCertificateController struct {
|
||||
func NewClientCertificateController(
|
||||
clientCertOption ClientCertOption,
|
||||
csrOption CSROption,
|
||||
hubCSRInformer certificatesinformers.Interface,
|
||||
hubKubeClient kubernetes.Interface,
|
||||
managementSecretInformer corev1informers.SecretInformer,
|
||||
managementKubeClient kubernetes.Interface,
|
||||
statusUpdater StatusUpdateFunc,
|
||||
recorder events.Recorder,
|
||||
controllerName string,
|
||||
) (factory.Controller, error) {
|
||||
var csrCtrl csrControl = nil
|
||||
if features.DefaultSpokeMutableFeatureGate.Enabled(ocmfeature.V1beta1CSRAPICompatibility) {
|
||||
v1CSRSupported, v1beta1CSRSupported, err := helpers.IsCSRSupported(hubKubeClient)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed CSR api discovery")
|
||||
}
|
||||
if !v1CSRSupported && v1beta1CSRSupported {
|
||||
csrCtrl = &v1beta1CSRControl{
|
||||
hubCSRInformer: hubCSRInformer.V1beta1().CertificateSigningRequests(),
|
||||
hubCSRLister: hubCSRInformer.V1beta1().CertificateSigningRequests().Lister(),
|
||||
hubCSRClient: hubKubeClient.CertificatesV1beta1().CertificateSigningRequests(),
|
||||
}
|
||||
klog.Info("Using v1beta1 CSR api to manage spoke client certificate")
|
||||
}
|
||||
}
|
||||
if csrCtrl == nil {
|
||||
csrCtrl = &v1CSRControl{
|
||||
hubCSRInformer: hubCSRInformer.V1().CertificateSigningRequests(),
|
||||
hubCSRLister: hubCSRInformer.V1().CertificateSigningRequests().Lister(),
|
||||
hubCSRClient: hubKubeClient.CertificatesV1().CertificateSigningRequests(),
|
||||
}
|
||||
}
|
||||
return newClientCertificateController(
|
||||
clientCertOption,
|
||||
csrOption,
|
||||
csrCtrl,
|
||||
managementSecretInformer,
|
||||
managementKubeClient.CoreV1(),
|
||||
statusUpdater,
|
||||
recorder,
|
||||
controllerName), nil
|
||||
}
|
||||
|
||||
func newClientCertificateController(
|
||||
clientCertOption ClientCertOption,
|
||||
csrOption CSROption,
|
||||
csrControl csrControl,
|
||||
csrControl CSRControl,
|
||||
managementSecretInformer corev1informers.SecretInformer,
|
||||
managementCoreClient corev1client.CoreV1Interface,
|
||||
statusUpdater StatusUpdateFunc,
|
||||
@@ -199,7 +152,7 @@ func newClientCertificateController(
|
||||
}, managementSecretInformer.Informer()).
|
||||
WithFilteredEventsInformersQueueKeyFunc(func(obj runtime.Object) string {
|
||||
return factory.DefaultQueueKey
|
||||
}, c.EventFilterFunc, csrControl.informer()).
|
||||
}, c.EventFilterFunc, csrControl.Informer()).
|
||||
WithSync(c.sync).
|
||||
ResyncEvery(ControllerResyncInterval).
|
||||
ToController(controllerName, recorder)
|
||||
@@ -347,6 +300,19 @@ func (c *clientCertificateController) sync(ctx context.Context, syncCtx factory.
|
||||
return nil
|
||||
}
|
||||
|
||||
shouldHalt := c.CSROption.HaltCSRCreation()
|
||||
if shouldHalt {
|
||||
if updateErr := c.statusUpdater(ctx, metav1.Condition{
|
||||
Type: "ClusterCertificateRotated",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "ClientCertificateUpdateFailed",
|
||||
Message: "Stop creating csr since there are too many csr created already on hub",
|
||||
}); updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// create a new private key
|
||||
keyData, err := keyutil.MakeEllipticPrivateKeyPEM()
|
||||
if err != nil {
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
var _ csrControl = &v1beta1CSRControl{}
|
||||
var _ CSRControl = &v1beta1CSRControl{}
|
||||
|
||||
type v1beta1CSRControl struct {
|
||||
hubCSRInformer certificatesinformers.CertificateSigningRequestInformer
|
||||
@@ -74,7 +74,7 @@ func (v *v1beta1CSRControl) create(ctx context.Context, recorder events.Recorder
|
||||
return req.Name, nil
|
||||
}
|
||||
|
||||
func (v *v1beta1CSRControl) informer() cache.SharedIndexInformer {
|
||||
func (v *v1beta1CSRControl) Informer() cache.SharedIndexInformer {
|
||||
return v.hubCSRInformer.Informer()
|
||||
}
|
||||
|
||||
|
||||
@@ -12,14 +12,19 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
certificatesinformers "k8s.io/client-go/informers/certificates/v1"
|
||||
certificatesinformers "k8s.io/client-go/informers/certificates"
|
||||
certificatesv1informers "k8s.io/client-go/informers/certificates/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
csrclient "k8s.io/client-go/kubernetes/typed/certificates/v1"
|
||||
certificateslisters "k8s.io/client-go/listers/certificates/v1"
|
||||
certificatesv1listers "k8s.io/client-go/listers/certificates/v1"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
"k8s.io/klog/v2"
|
||||
ocmfeature "open-cluster-management.io/api/feature"
|
||||
"open-cluster-management.io/registration/pkg/features"
|
||||
"open-cluster-management.io/registration/pkg/helpers"
|
||||
)
|
||||
|
||||
// HasValidClientCertificate checks if there exists a valid client certificate in the given secret
|
||||
@@ -165,18 +170,20 @@ func BuildKubeconfig(clientConfig *restclient.Config, certPath, keyPath string)
|
||||
return kubeconfig
|
||||
}
|
||||
|
||||
type csrControl interface {
|
||||
type CSRControl interface {
|
||||
create(ctx context.Context, recorder events.Recorder, objMeta metav1.ObjectMeta, csrData []byte, signerName string) (string, error)
|
||||
isApproved(name string) (bool, error)
|
||||
getIssuedCertificate(name string) ([]byte, error)
|
||||
informer() cache.SharedIndexInformer
|
||||
|
||||
// public so we can add indexer outside
|
||||
Informer() cache.SharedIndexInformer
|
||||
}
|
||||
|
||||
var _ csrControl = &v1CSRControl{}
|
||||
var _ CSRControl = &v1CSRControl{}
|
||||
|
||||
type v1CSRControl struct {
|
||||
hubCSRInformer certificatesinformers.CertificateSigningRequestInformer
|
||||
hubCSRLister certificateslisters.CertificateSigningRequestLister
|
||||
hubCSRInformer certificatesv1informers.CertificateSigningRequestInformer
|
||||
hubCSRLister certificatesv1listers.CertificateSigningRequestLister
|
||||
hubCSRClient csrclient.CertificateSigningRequestInterface
|
||||
}
|
||||
|
||||
@@ -228,7 +235,7 @@ func (v *v1CSRControl) create(ctx context.Context, recorder events.Recorder, obj
|
||||
return req.Name, nil
|
||||
}
|
||||
|
||||
func (v *v1CSRControl) informer() cache.SharedIndexInformer {
|
||||
func (v *v1CSRControl) Informer() cache.SharedIndexInformer {
|
||||
return v.hubCSRInformer.Informer()
|
||||
}
|
||||
|
||||
@@ -246,3 +253,27 @@ func (v *v1CSRControl) get(name string) (metav1.Object, error) {
|
||||
}
|
||||
return csr, nil
|
||||
}
|
||||
|
||||
func NewCSRControl(hubCSRInformer certificatesinformers.Interface, hubKubeClient kubernetes.Interface) (CSRControl, error) {
|
||||
if features.DefaultSpokeMutableFeatureGate.Enabled(ocmfeature.V1beta1CSRAPICompatibility) {
|
||||
v1CSRSupported, v1beta1CSRSupported, err := helpers.IsCSRSupported(hubKubeClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !v1CSRSupported && v1beta1CSRSupported {
|
||||
csrCtrl := &v1beta1CSRControl{
|
||||
hubCSRInformer: hubCSRInformer.V1beta1().CertificateSigningRequests(),
|
||||
hubCSRLister: hubCSRInformer.V1beta1().CertificateSigningRequests().Lister(),
|
||||
hubCSRClient: hubKubeClient.CertificatesV1beta1().CertificateSigningRequests(),
|
||||
}
|
||||
klog.Info("Using v1beta1 CSR api to manage spoke client certificate")
|
||||
return csrCtrl, nil
|
||||
}
|
||||
}
|
||||
|
||||
return &v1CSRControl{
|
||||
hubCSRInformer: hubCSRInformer.V1().CertificateSigningRequests(),
|
||||
hubCSRLister: hubCSRInformer.V1().CertificateSigningRequests().Lister(),
|
||||
hubCSRClient: hubKubeClient.CertificatesV1().CertificateSigningRequests(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -186,8 +186,9 @@ func TestSync(t *testing.T) {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "test-",
|
||||
},
|
||||
Subject: testSubject,
|
||||
SignerName: certificates.KubeAPIServerClientSignerName,
|
||||
Subject: testSubject,
|
||||
SignerName: certificates.KubeAPIServerClientSignerName,
|
||||
HaltCSRCreation: func() bool { return false },
|
||||
}
|
||||
|
||||
updater := &fakeStatusUpdater{}
|
||||
@@ -230,7 +231,7 @@ func TestSync(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var _ csrControl = &mockCSRControl{}
|
||||
var _ CSRControl = &mockCSRControl{}
|
||||
|
||||
func conditionEqual(expected, actual *metav1.Condition) bool {
|
||||
if expected == nil && actual == nil {
|
||||
@@ -301,6 +302,6 @@ func (m *mockCSRControl) getIssuedCertificate(name string) ([]byte, error) {
|
||||
return m.issuedCertData, err
|
||||
}
|
||||
|
||||
func (m *mockCSRControl) informer() cache.SharedIndexInformer {
|
||||
func (m *mockCSRControl) Informer() cache.SharedIndexInformer {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/informers"
|
||||
certificatesinformers "k8s.io/client-go/informers/certificates"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/klog/v2"
|
||||
addonclient "open-cluster-management.io/api/client/addon/clientset/versioned"
|
||||
addoninformerv1alpha1 "open-cluster-management.io/api/client/addon/informers/externalversions/addon/v1alpha1"
|
||||
@@ -25,6 +25,13 @@ import (
|
||||
"open-cluster-management.io/registration/pkg/helpers"
|
||||
)
|
||||
|
||||
const (
|
||||
indexByAddon = "indexByAddon"
|
||||
|
||||
// TODO(qiujian16) expose it if necessary in the future.
|
||||
addonCSRThreshold = 10
|
||||
)
|
||||
|
||||
// addOnRegistrationController monitors ManagedClusterAddOns on hub and starts addOn registration
|
||||
// according to the registrationConfigs read from annotations of ManagedClusterAddOns. Echo addOn
|
||||
// may have multiple registrationConfigs. A clientcert.NewClientCertificateController will be started
|
||||
@@ -36,10 +43,10 @@ type addOnRegistrationController struct {
|
||||
managementKubeClient kubernetes.Interface // in-cluster local management kubeClient
|
||||
spokeKubeClient kubernetes.Interface
|
||||
hubAddOnLister addonlisterv1alpha1.ManagedClusterAddOnLister
|
||||
hubCSRInformer certificatesinformers.Interface
|
||||
hubKubeClient kubernetes.Interface
|
||||
addOnClient addonclient.Interface
|
||||
csrControl clientcert.CSRControl
|
||||
recorder events.Recorder
|
||||
csrIndexer cache.Indexer
|
||||
|
||||
startRegistrationFunc func(ctx context.Context, config registrationConfig) context.CancelFunc
|
||||
|
||||
@@ -56,9 +63,8 @@ func NewAddOnRegistrationController(
|
||||
addOnClient addonclient.Interface,
|
||||
managementKubeClient kubernetes.Interface,
|
||||
managedKubeClient kubernetes.Interface,
|
||||
hubCSRInformer certificatesinformers.Interface,
|
||||
csrControl clientcert.CSRControl,
|
||||
hubAddOnInformers addoninformerv1alpha1.ManagedClusterAddOnInformer,
|
||||
hubCSRClient kubernetes.Interface,
|
||||
recorder events.Recorder,
|
||||
) factory.Controller {
|
||||
c := &addOnRegistrationController{
|
||||
@@ -68,13 +74,20 @@ func NewAddOnRegistrationController(
|
||||
managementKubeClient: managementKubeClient,
|
||||
spokeKubeClient: managedKubeClient,
|
||||
hubAddOnLister: hubAddOnInformers.Lister(),
|
||||
hubCSRInformer: hubCSRInformer,
|
||||
hubKubeClient: hubCSRClient,
|
||||
csrControl: csrControl,
|
||||
addOnClient: addOnClient,
|
||||
recorder: recorder,
|
||||
csrIndexer: csrControl.Informer().GetIndexer(),
|
||||
addOnRegistrationConfigs: map[string]map[string]registrationConfig{},
|
||||
}
|
||||
|
||||
err := csrControl.Informer().AddIndexers(cache.Indexers{
|
||||
indexByAddon: indexByAddonFunc,
|
||||
})
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
}
|
||||
|
||||
c.startRegistrationFunc = c.startRegistration
|
||||
|
||||
return factory.New().
|
||||
@@ -218,26 +231,23 @@ func (c *addOnRegistrationController) startRegistration(ctx context.Context, con
|
||||
DNSNames: []string{fmt.Sprintf("%s.addon.open-cluster-management.io", config.addOnName)},
|
||||
SignerName: config.registration.SignerName,
|
||||
EventFilterFunc: createCSREventFilterFunc(c.clusterName, config.addOnName, config.registration.SignerName),
|
||||
HaltCSRCreation: c.haltCSRCreationFunc(config.addOnName),
|
||||
}
|
||||
|
||||
controllerName := fmt.Sprintf("ClientCertController@addon:%s:signer:%s", config.addOnName, config.registration.SignerName)
|
||||
|
||||
statusUpdater := c.generateStatusUpdate(c.clusterName, config.addOnName)
|
||||
|
||||
clientCertController, err := clientcert.NewClientCertificateController(
|
||||
clientCertController := clientcert.NewClientCertificateController(
|
||||
clientCertOption,
|
||||
csrOption,
|
||||
c.hubCSRInformer,
|
||||
c.hubKubeClient,
|
||||
c.csrControl,
|
||||
kubeInformerFactory.Core().V1().Secrets(),
|
||||
kubeClient,
|
||||
kubeClient.CoreV1(),
|
||||
statusUpdater,
|
||||
c.recorder,
|
||||
controllerName,
|
||||
)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
}
|
||||
|
||||
go kubeInformerFactory.Start(ctx.Done())
|
||||
go clientCertController.Run(ctx, 1)
|
||||
@@ -245,6 +255,21 @@ func (c *addOnRegistrationController) startRegistration(ctx context.Context, con
|
||||
return stopFunc
|
||||
}
|
||||
|
||||
func (c *addOnRegistrationController) haltCSRCreationFunc(addonName string) func() bool {
|
||||
return func() bool {
|
||||
items, err := c.csrIndexer.ByIndex(indexByAddon, fmt.Sprintf("%s/%s", c.clusterName, addonName))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(items) >= addonCSRThreshold {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *addOnRegistrationController) generateStatusUpdate(clusterName, addonName string) clientcert.StatusUpdateFunc {
|
||||
return func(ctx context.Context, cond metav1.Condition) error {
|
||||
_, _, updatedErr := helpers.UpdateManagedClusterAddOnStatus(
|
||||
@@ -293,6 +318,25 @@ func (c *addOnRegistrationController) cleanup(ctx context.Context, addOnName str
|
||||
return nil
|
||||
}
|
||||
|
||||
func indexByAddonFunc(obj interface{}) ([]string, error) {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cluster, ok := accessor.GetLabels()[clientcert.ClusterNameLabel]
|
||||
if !ok {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
addon, ok := accessor.GetLabels()[clientcert.AddonNameLabel]
|
||||
if !ok {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
return []string{fmt.Sprintf("%s/%s", cluster, addon)}, nil
|
||||
}
|
||||
|
||||
func createCSREventFilterFunc(clusterName, addOnName, signerName string) factory.EventFilterFunc {
|
||||
return func(obj interface{}) bool {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
|
||||
@@ -11,9 +11,10 @@ import (
|
||||
certificates "k8s.io/api/certificates/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
certificatesinformers "k8s.io/client-go/informers/certificates"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
corev1informers "k8s.io/client-go/informers/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
clientset "open-cluster-management.io/api/client/cluster/clientset/versioned"
|
||||
|
||||
@@ -22,6 +23,13 @@ import (
|
||||
"open-cluster-management.io/registration/pkg/hub/user"
|
||||
)
|
||||
|
||||
const (
|
||||
indexByCluster = "indexByCluster"
|
||||
|
||||
// TODO(qiujian16) expose it if necessary in the future.
|
||||
clusterCSRThreshold = 10
|
||||
)
|
||||
|
||||
// NewClientCertForHubController returns a controller to
|
||||
// 1). Create a new client certificate and build a hub kubeconfig for the registration agent;
|
||||
// 2). Or rotate the client certificate referenced by the hub kubeconfig before it become expired;
|
||||
@@ -32,13 +40,18 @@ func NewClientCertForHubController(
|
||||
clientCertSecretName string,
|
||||
kubeconfigData []byte,
|
||||
spokeSecretInformer corev1informers.SecretInformer,
|
||||
hubCSRInformer certificatesinformers.Interface,
|
||||
csrControl clientcert.CSRControl,
|
||||
spokeKubeClient kubernetes.Interface,
|
||||
hubKubeClient kubernetes.Interface,
|
||||
statusUpdater clientcert.StatusUpdateFunc,
|
||||
recorder events.Recorder,
|
||||
controllerName string,
|
||||
) (factory.Controller, error) {
|
||||
) factory.Controller {
|
||||
err := csrControl.Informer().AddIndexers(cache.Indexers{
|
||||
indexByCluster: indexByClusterFunc,
|
||||
})
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
}
|
||||
clientCertOption := clientcert.ClientCertOption{
|
||||
SecretNamespace: clientCertSecretNamespace,
|
||||
SecretName: clientCertSecretName,
|
||||
@@ -75,24 +88,44 @@ func NewClientCertForHubController(
|
||||
return false
|
||||
}
|
||||
|
||||
// should not contain addon key
|
||||
_, ok := labels[clientcert.AddonNameLabel]
|
||||
if ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// only enqueue csr whose name starts with the cluster name
|
||||
return strings.HasPrefix(accessor.GetName(), fmt.Sprintf("%s-", clusterName))
|
||||
},
|
||||
HaltCSRCreation: haltCSRCreationFunc(csrControl.Informer().GetIndexer(), clusterName),
|
||||
}
|
||||
|
||||
return clientcert.NewClientCertificateController(
|
||||
clientCertOption,
|
||||
csrOption,
|
||||
hubCSRInformer,
|
||||
hubKubeClient,
|
||||
csrControl,
|
||||
spokeSecretInformer,
|
||||
spokeKubeClient,
|
||||
spokeKubeClient.CoreV1(),
|
||||
statusUpdater,
|
||||
recorder,
|
||||
controllerName,
|
||||
)
|
||||
}
|
||||
|
||||
func haltCSRCreationFunc(indexer cache.Indexer, clusterName string) func() bool {
|
||||
return func() bool {
|
||||
items, err := indexer.ByIndex(indexByCluster, clusterName)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(items) >= clusterCSRThreshold {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateBootstrapStatusUpdater() clientcert.StatusUpdateFunc {
|
||||
return func(ctx context.Context, cond metav1.Condition) error {
|
||||
return nil
|
||||
@@ -131,3 +164,23 @@ func GetClusterAgentNamesFromCertificate(certData []byte) (clusterName, agentNam
|
||||
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
func indexByClusterFunc(obj interface{}) ([]string, error) {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cluster, ok := accessor.GetLabels()[clientcert.ClusterNameLabel]
|
||||
if !ok {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
// should not contain addon key
|
||||
_, ok = accessor.GetLabels()[clientcert.AddonNameLabel]
|
||||
if !ok {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
return []string{cluster}, nil
|
||||
}
|
||||
|
||||
@@ -204,22 +204,23 @@ func (o *SpokeAgentOptions) RunSpokeAgent(ctx context.Context, controllerContext
|
||||
return err
|
||||
}
|
||||
|
||||
csrControl, err := clientcert.NewCSRControl(bootstrapInformerFactory.Certificates(), bootstrapKubeClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
controllerName := fmt.Sprintf("BootstrapClientCertController@cluster:%s", o.ClusterName)
|
||||
clientCertForHubController, err := managedcluster.NewClientCertForHubController(
|
||||
clientCertForHubController := managedcluster.NewClientCertForHubController(
|
||||
o.ClusterName, o.AgentName, o.ComponentNamespace, o.HubKubeconfigSecret,
|
||||
kubeconfigData,
|
||||
// store the secret in the cluster where the agent pod runs
|
||||
bootstrapNamespacedManagementKubeInformerFactory.Core().V1().Secrets(),
|
||||
bootstrapInformerFactory.Certificates(),
|
||||
csrControl,
|
||||
managementKubeClient,
|
||||
bootstrapKubeClient,
|
||||
managedcluster.GenerateBootstrapStatusUpdater(),
|
||||
controllerContext.EventRecorder,
|
||||
controllerName,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bootstrapCtx, stopBootstrap := context.WithCancel(ctx)
|
||||
|
||||
@@ -288,15 +289,19 @@ func (o *SpokeAgentOptions) RunSpokeAgent(ctx context.Context, controllerContext
|
||||
return err
|
||||
}
|
||||
|
||||
csrControl, err := clientcert.NewCSRControl(hubKubeInformerFactory.Certificates(), hubKubeClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create another ClientCertForHubController for client certificate rotation
|
||||
controllerName := fmt.Sprintf("ClientCertController@cluster:%s", o.ClusterName)
|
||||
clientCertForHubController, err := managedcluster.NewClientCertForHubController(
|
||||
clientCertForHubController := managedcluster.NewClientCertForHubController(
|
||||
o.ClusterName, o.AgentName, o.ComponentNamespace, o.HubKubeconfigSecret,
|
||||
kubeconfigData,
|
||||
namespacedManagementKubeInformerFactory.Core().V1().Secrets(),
|
||||
hubKubeInformerFactory.Certificates(),
|
||||
csrControl,
|
||||
managementKubeClient,
|
||||
hubKubeClient,
|
||||
managedcluster.GenerateStatusUpdater(hubClusterClient, o.ClusterName),
|
||||
controllerContext.EventRecorder,
|
||||
controllerName,
|
||||
@@ -370,9 +375,8 @@ func (o *SpokeAgentOptions) RunSpokeAgent(ctx context.Context, controllerContext
|
||||
addOnClient,
|
||||
managementKubeClient,
|
||||
spokeKubeClient,
|
||||
hubKubeInformerFactory.Certificates(),
|
||||
csrControl,
|
||||
addOnInformerFactory.Addon().V1alpha1().ManagedClusterAddOns(),
|
||||
hubKubeClient,
|
||||
controllerContext.EventRecorder,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -385,6 +385,53 @@ var _ = ginkgo.Describe("Addon Registration", func() {
|
||||
return !reflect.DeepEqual(secret.Data, newSecret.Data)
|
||||
}, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue())
|
||||
})
|
||||
|
||||
ginkgo.It("should stop addon client cert update if too frequent", func() {
|
||||
assertSuccessClusterBootstrap()
|
||||
signerName := "kubernetes.io/kube-apiserver-client"
|
||||
assertSuccessAddOnBootstrap(signerName)
|
||||
|
||||
// update subject for 15 times
|
||||
for i := 1; i <= 15; i++ {
|
||||
addOn, err := addOnClient.AddonV1alpha1().ManagedClusterAddOns(managedClusterName).Get(context.TODO(), addOnName, metav1.GetOptions{})
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
addOn.Status = addonv1alpha1.ManagedClusterAddOnStatus{
|
||||
Registrations: []addonv1alpha1.RegistrationConfig{
|
||||
{
|
||||
SignerName: addOn.Status.Registrations[0].SignerName,
|
||||
Subject: addonv1alpha1.Subject{
|
||||
User: fmt.Sprintf("test-%d", i),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err = addOnClient.AddonV1alpha1().ManagedClusterAddOns(managedClusterName).UpdateStatus(context.TODO(), addOn, metav1.UpdateOptions{})
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
// sleep 1 second to ensure controller issue a new csr.
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
ginkgo.By("CSR should not exceed 10")
|
||||
csrs, err := kubeClient.CertificatesV1().CertificateSigningRequests().List(context.TODO(), metav1.ListOptions{
|
||||
LabelSelector: fmt.Sprintf("%s=%s,%s=%s", clientcert.ClusterNameLabel, managedClusterName, clientcert.AddonNameLabel, addOnName),
|
||||
})
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
gomega.Expect(len(csrs.Items) >= 10).ShouldNot(gomega.BeFalse())
|
||||
|
||||
gomega.Eventually(func() error {
|
||||
addOn, err := addOnClient.AddonV1alpha1().ManagedClusterAddOns(managedClusterName).Get(context.TODO(), addOnName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if meta.IsStatusConditionFalse(addOn.Status.Conditions, "ClusterCertificateRotated") {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("addon status is not correct, got %v", addOn.Status.Conditions)
|
||||
}, eventuallyTimeout, eventuallyInterval).Should(gomega.Succeed())
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
func getSecretName(addOnName, signerName string) string {
|
||||
|
||||
Reference in New Issue
Block a user