-
Notifications
You must be signed in to change notification settings - Fork 217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
API-1802: cert-rotation: allow specifying multiple target certs in CertRotationController #1722
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,9 @@ import ( | |
"time" | ||
|
||
operatorv1 "github.com/openshift/api/operator/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/util/errors" | ||
"k8s.io/apimachinery/pkg/util/sets" | ||
"k8s.io/apimachinery/pkg/util/wait" | ||
|
||
"github.com/openshift/library-go/pkg/controller/factory" | ||
|
@@ -65,9 +68,8 @@ type CertRotationController struct { | |
RotatedSigningCASecret RotatedSigningCASecret | ||
// CABundleConfigMap maintains a CA bundle config map, by adding new CA certs coming from rotatedSigningCASecret, and by removing expired old ones. | ||
CABundleConfigMap CABundleConfigMap | ||
// RotatedSelfSignedCertKeySecret rotates a key and cert signed by a signing CA and stores it in a secret. | ||
RotatedSelfSignedCertKeySecret RotatedSelfSignedCertKeySecret | ||
|
||
// RotatedTargetSecrets contains a list of key and cert signed by a signing CA to rotate. | ||
RotatedTargetSecrets []RotatedSelfSignedCertKeySecret | ||
// Plumbing: | ||
StatusReporter StatusReporter | ||
} | ||
|
@@ -79,14 +81,29 @@ func NewCertRotationController( | |
rotatedSelfSignedCertKeySecret RotatedSelfSignedCertKeySecret, | ||
recorder events.Recorder, | ||
reporter StatusReporter, | ||
controlledSecrets *[]metav1.ObjectMeta, | ||
controlledConfigMaps *[]metav1.ObjectMeta, | ||
) factory.Controller { | ||
c := &CertRotationController{ | ||
Name: name, | ||
RotatedSigningCASecret: rotatedSigningCASecret, | ||
CABundleConfigMap: caBundleConfigMap, | ||
RotatedSelfSignedCertKeySecret: rotatedSelfSignedCertKeySecret, | ||
StatusReporter: reporter, | ||
Name: name, | ||
RotatedSigningCASecret: rotatedSigningCASecret, | ||
CABundleConfigMap: caBundleConfigMap, | ||
RotatedTargetSecrets: []RotatedSelfSignedCertKeySecret{rotatedSelfSignedCertKeySecret}, | ||
StatusReporter: reporter, | ||
} | ||
*controlledSecrets = append(*controlledSecrets, metav1.ObjectMeta{ | ||
Name: rotatedSigningCASecret.Name, | ||
Namespace: rotatedSigningCASecret.Namespace, | ||
}) | ||
*controlledConfigMaps = append(*controlledConfigMaps, metav1.ObjectMeta{ | ||
Name: caBundleConfigMap.Name, | ||
Namespace: caBundleConfigMap.Namespace, | ||
}) | ||
*controlledSecrets = append(*controlledSecrets, metav1.ObjectMeta{ | ||
Name: rotatedSelfSignedCertKeySecret.Name, | ||
Namespace: rotatedSelfSignedCertKeySecret.Namespace, | ||
}) | ||
|
||
return factory.New(). | ||
ResyncEvery(time.Minute). | ||
WithSync(c.Sync). | ||
|
@@ -101,6 +118,56 @@ func NewCertRotationController( | |
ToController("CertRotationController", recorder.WithComponentSuffix("cert-rotation-controller").WithComponentSuffix(name)) | ||
} | ||
|
||
func NewCertRotationControllerMultipleTargets( | ||
name string, | ||
rotatedSigningCASecret RotatedSigningCASecret, | ||
caBundleConfigMap CABundleConfigMap, | ||
rotatedTargetSecrets []RotatedSelfSignedCertKeySecret, | ||
recorder events.Recorder, | ||
reporter StatusReporter, | ||
controlledSecrets *[]metav1.ObjectMeta, | ||
controlledConfigMaps *[]metav1.ObjectMeta, | ||
) factory.Controller { | ||
informers := sets.New[factory.Informer]( | ||
rotatedSigningCASecret.Informer.Informer(), | ||
caBundleConfigMap.Informer.Informer(), | ||
) | ||
|
||
*controlledSecrets = append(*controlledSecrets, metav1.ObjectMeta{ | ||
Name: rotatedSigningCASecret.Name, | ||
Namespace: rotatedSigningCASecret.Namespace, | ||
}) | ||
*controlledConfigMaps = append(*controlledConfigMaps, metav1.ObjectMeta{ | ||
Name: caBundleConfigMap.Name, | ||
Namespace: caBundleConfigMap.Namespace, | ||
}) | ||
for _, target := range rotatedTargetSecrets { | ||
informers = informers.Insert(target.Informer.Informer()) | ||
*controlledSecrets = append(*controlledSecrets, metav1.ObjectMeta{ | ||
Name: target.Name, | ||
Namespace: target.Namespace, | ||
}) | ||
} | ||
|
||
c := &CertRotationController{ | ||
Name: name, | ||
RotatedSigningCASecret: rotatedSigningCASecret, | ||
CABundleConfigMap: caBundleConfigMap, | ||
RotatedTargetSecrets: rotatedTargetSecrets, | ||
StatusReporter: reporter, | ||
} | ||
return factory.New(). | ||
ResyncEvery(time.Minute). | ||
WithSync(c.Sync). | ||
WithInformers( | ||
informers.UnsortedList()..., | ||
). | ||
WithPostStartHooks( | ||
c.targetCertRecheckerPostRunHook, | ||
). | ||
ToController("MultipleTargetCertRotationController", recorder.WithComponentSuffix("cert-rotation-controller").WithComponentSuffix(name)) | ||
} | ||
|
||
func (c CertRotationController) Sync(ctx context.Context, syncCtx factory.SyncContext) error { | ||
syncErr := c.SyncWorker(ctx) | ||
|
||
|
@@ -122,7 +189,15 @@ func (c CertRotationController) Sync(ctx context.Context, syncCtx factory.SyncCo | |
} | ||
|
||
func (c CertRotationController) getSigningCertKeyPairLocation() string { | ||
return fmt.Sprintf("%s/%s", c.RotatedSelfSignedCertKeySecret.Namespace, c.RotatedSelfSignedCertKeySecret.Name) | ||
var firstSecret RotatedSelfSignedCertKeySecret | ||
if c.RotatedTargetSecrets == nil { | ||
return "" | ||
} | ||
if len(c.RotatedTargetSecrets) == 0 { | ||
return "" | ||
} | ||
firstSecret = c.RotatedTargetSecrets[0] | ||
return fmt.Sprintf("%s/%s", firstSecret.Namespace, firstSecret.Name) | ||
} | ||
|
||
func (c CertRotationController) SyncWorker(ctx context.Context) error { | ||
|
@@ -136,30 +211,35 @@ func (c CertRotationController) SyncWorker(ctx context.Context) error { | |
return err | ||
} | ||
|
||
if _, err := c.RotatedSelfSignedCertKeySecret.EnsureTargetCertKeyPair(ctx, signingCertKeyPair, cabundleCerts); err != nil { | ||
return err | ||
var errs []error | ||
for _, secret := range c.RotatedTargetSecrets { | ||
if _, err := secret.EnsureTargetCertKeyPair(ctx, signingCertKeyPair, cabundleCerts); err != nil { | ||
errs = append(errs, err) | ||
} | ||
} | ||
if len(errs) != 0 { | ||
return errors.NewAggregate(errs) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (c CertRotationController) targetCertRecheckerPostRunHook(ctx context.Context, syncCtx factory.SyncContext) error { | ||
// If we have a need to force rechecking the cert, use this channel to do it. | ||
refresher, ok := c.RotatedSelfSignedCertKeySecret.CertCreator.(TargetCertRechecker) | ||
if !ok { | ||
return nil | ||
} | ||
targetRefresh := refresher.RecheckChannel() | ||
go wait.Until(func() { | ||
for { | ||
select { | ||
case <-targetRefresh: | ||
syncCtx.Queue().Add(factory.DefaultQueueKey) | ||
case <-ctx.Done(): | ||
return | ||
} | ||
for _, target := range c.RotatedTargetSecrets { | ||
if refresher, ok := target.CertCreator.(TargetCertRechecker); ok { | ||
go wait.Until(func() { | ||
for { | ||
select { | ||
case <-refresher.RecheckChannel(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so if we have Let's say we have three targets, a couple of questions with this mechanism:
If we really need this mechanism, i would suggest abstracting this mechanism:
This way we can keep the logic of channel based trigger mechanism co-located, more testable, and more generic too. Also, we can use one instance of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
iiuc both struct implementing
So far we use it in just one |
||
syncCtx.Queue().Add(factory.DefaultQueueKey) | ||
case <-ctx.Done(): | ||
return | ||
} | ||
} | ||
}, time.Minute, ctx.Done()) | ||
} | ||
}, time.Minute, ctx.Done()) | ||
} | ||
|
||
<-ctx.Done() | ||
return nil | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will change the external API and clients would need to update if they're accessing this field directly.
Since you're already adding a new "Create" function for the multi rotation controller, would it make more sense to have two cert controllers? One for single rotation and one for multi.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, this would require users to update the code potentially. So far we identified that only cluster-kube-apiserver-operator and cluster-kube-control-manager-operator use multitarget controllers (and they don't need to access
RotatedTargetSecrets
directly)It may be beneficial later to move common parts into functions and reuse them, however at this point only two different
New...
functions are used externally