Files
open-cluster-management/pkg/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller.go
Yang Le be2d2e6e44 Add CertRotationController
Signed-off-by: Yang Le <yangle@redhat.com>
2021-01-14 17:40:22 +08:00

163 lines
5.6 KiB
Go

package certrotationcontroller
import (
"context"
"fmt"
"time"
"github.com/openshift/library-go/pkg/controller/factory"
"github.com/openshift/library-go/pkg/operator/events"
errorhelpers "github.com/openshift/library-go/pkg/operator/v1helpers"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"
operatorinformer "github.com/open-cluster-management/api/client/operator/informers/externalversions/operator/v1"
operatorlister "github.com/open-cluster-management/api/client/operator/listers/operator/v1"
"github.com/open-cluster-management/registration-operator/pkg/certrotation"
"github.com/open-cluster-management/registration-operator/pkg/helpers"
)
const (
signerSecret = "signer-secret"
caBundleConfigmap = "ca-bundle-configmap"
signerNamePrefix = "cluster-manager-webhook"
)
// Follow the rules below to set the value of SigningCertValidity/TargetCertValidity/ResyncInterval:
//
// 1) SigningCertValidity * 1/5 * 1/5 > ResyncInterval * 2
// 2) TargetCertValidity * 1/5 > ResyncInterval * 2
var SigningCertValidity = time.Hour * 24 * 365
var TargetCertValidity = time.Hour * 24 * 30
var ResyncInterval = time.Minute * 5
// certRotationController does:
//
// 1) continuously create a self-signed signing CA (via SigningRotation).
// It creates the next one when a given percentage of the validity of the old CA has passed.
// 2) maintain a CA bundle with all not yet expired CA certs.
// 3) continuously create target cert/key pairs signed by the latest signing CA
// It creates the next one when a given percentage of the validity of the previous cert has
// passed, or when a new CA has been created.
type certRotationController struct {
signingRotation certrotation.SigningRotation
caBundleRotation certrotation.CABundleRotation
targetRotations []certrotation.TargetRotation
kubeClient kubernetes.Interface
clusterManagerLister operatorlister.ClusterManagerLister
}
func NewCertRotationController(
kubeClient kubernetes.Interface,
secretInformer corev1informers.SecretInformer,
configMapInformer corev1informers.ConfigMapInformer,
clusterManagerInformer operatorinformer.ClusterManagerInformer,
recorder events.Recorder,
) factory.Controller {
signingRotation := certrotation.SigningRotation{
Namespace: helpers.ClusterManagerNamespace,
Name: signerSecret,
SignerNamePrefix: signerNamePrefix,
Validity: SigningCertValidity,
Lister: secretInformer.Lister(),
Client: kubeClient.CoreV1(),
EventRecorder: recorder,
}
caBundleRotation := certrotation.CABundleRotation{
Namespace: helpers.ClusterManagerNamespace,
Name: caBundleConfigmap,
Lister: configMapInformer.Lister(),
Client: kubeClient.CoreV1(),
EventRecorder: recorder,
}
targetRotations := []certrotation.TargetRotation{
{
Namespace: helpers.ClusterManagerNamespace,
Name: helpers.RegistrationWebhookSecret,
Validity: TargetCertValidity,
HostNames: []string{fmt.Sprintf("%s.%s.svc", helpers.RegistrationWebhookService, helpers.ClusterManagerNamespace)},
Lister: secretInformer.Lister(),
Client: kubeClient.CoreV1(),
EventRecorder: recorder,
},
{
Namespace: helpers.ClusterManagerNamespace,
Name: helpers.WorkWebhookSecret,
Validity: TargetCertValidity,
HostNames: []string{fmt.Sprintf("%s.%s.svc", helpers.WorkWebhookService, helpers.ClusterManagerNamespace)},
Lister: secretInformer.Lister(),
Client: kubeClient.CoreV1(),
EventRecorder: recorder,
},
}
c := &certRotationController{
signingRotation: signingRotation,
caBundleRotation: caBundleRotation,
targetRotations: targetRotations,
kubeClient: kubeClient,
clusterManagerLister: clusterManagerInformer.Lister(),
}
return factory.New().
ResyncEvery(ResyncInterval).
WithSync(c.sync).
WithInformers(
secretInformer.Informer(),
configMapInformer.Informer(),
clusterManagerInformer.Informer(),
).
ToController("CertRotationController", recorder)
}
func (c certRotationController) sync(ctx context.Context, syncCtx factory.SyncContext) error {
// do nothing if there is no cluster manager
clustermanagers, err := c.clusterManagerLister.List(labels.Everything())
if err != nil {
return err
}
if len(clustermanagers) == 0 {
klog.V(4).Infof("No ClusterManager found")
return nil
}
klog.Infof("Reconciling ClusterManager %q", clustermanagers[0].Name)
// do nothing if the cluster manager is deleting
if !clustermanagers[0].DeletionTimestamp.IsZero() {
return nil
}
// check if namespace exists or not
_, err = c.kubeClient.CoreV1().Namespaces().Get(ctx, helpers.ClusterManagerNamespace, metav1.GetOptions{})
if errors.IsNotFound(err) {
return fmt.Errorf("namespace %q does not exist yet", helpers.ClusterManagerNamespace)
}
if err != nil {
return err
}
// reconcile cert/key pair for signer
signingCertKeyPair, err := c.signingRotation.EnsureSigningCertKeyPair()
if err != nil {
return err
}
// reconcile ca bundle
cabundleCerts, err := c.caBundleRotation.EnsureConfigMapCABundle(signingCertKeyPair)
if err != nil {
return err
}
// reconcile target cert/key pairs
errs := []error{}
for _, targetRotation := range c.targetRotations {
if err := targetRotation.EnsureTargetCertKeyPair(signingCertKeyPair, cabundleCerts); err != nil {
errs = append(errs, err)
}
}
return errorhelpers.NewMultiLineAggregate(errs)
}