CFE-1020: feature:route external certificate reference#565
Conversation
|
Skipping CI for Draft Pull Request. |
|
/jira refresh |
|
@chiragkyal: No Jira issue is referenced in the title of this pull request. DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository. |
|
/jira refresh |
|
@chiragkyal: No Jira issue is referenced in the title of this pull request. DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository. |
c21ee84 to
2c6940e
Compare
|
/test images |
|
@chiragkyal: The specified target(s) for
The following commands are available to trigger optional jobs:
Use
DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
|
/test all |
dace218 to
cc6eb11
Compare
|
/test images |
|
/hold |
2a44e68 to
1f3ad90
Compare
|
/assign |
| // For Deleted events, it unregisters the route if it's registered. | ||
| // Additionally, it delegates the handling of the event to the next plugin in the chain after performing the necessary actions. | ||
| func (p *RouteSecretManager) HandleRoute(eventType watch.EventType, route *routev1.Route) error { | ||
| klog.Infof("Executing RouteSecretManager plugin with eventType %s...", eventType) |
There was a problem hiding this comment.
Other plugins use the log variable; is there some reason to use klog here?
| klog.Infof("Executing RouteSecretManager plugin with eventType %s...", eventType) | |
| log.V(10).Infof("HandleRoute: RouteSecretManager, eventType %s", eventType) |
Log-level 10 would be consistent with this recent change: 66eb223.
There was a problem hiding this comment.
Thanks, I've removed klog from the plugin and now using log with required log-levels.
| // unregister associated secret monitor, if registered | ||
| if p.secretManager.IsRouteRegistered(route.Namespace, route.Name) { | ||
| if err := p.secretManager.UnregisterRoute(route.Namespace, route.Name); err != nil { | ||
| klog.Error("failed to unregister route", err) |
There was a problem hiding this comment.
| klog.Error("failed to unregister route", err) | |
| log.Error(err, "failed to unregister route") |
There was a problem hiding this comment.
Thanks, updated to log
| klog.Error(err, "skipping route due to invalid externalCertificate configuration", " route ", route.Name) | ||
| p.recorder.RecordRouteRejection(route, "ExternalCertificateValidationFailed", err.Error()) | ||
| p.plugin.HandleRoute(watch.Deleted, route) | ||
| return fmt.Errorf("invalid route configuration for externalCertificate") |
There was a problem hiding this comment.
Should the return error include the actual validation error message? For example, the UniqueHost plugin does this:
router/pkg/router/controller/unique_host.go
Lines 106 to 119 in b6f7c63
There was a problem hiding this comment.
Sure, updated to return err so that it includes the actual validation error message.
| secreth := cache.ResourceEventHandlerFuncs{ | ||
| AddFunc: func(obj interface{}) { | ||
| secret := obj.(*kapi.Secret) | ||
| klog.Infof("secret %s added for %s/%s", secret.Name, route.Namespace, route.Name) |
There was a problem hiding this comment.
| klog.Infof("secret %s added for %s/%s", secret.Name, route.Namespace, route.Name) | |
| log.V(4).Info("secret added for route", "namespace", route.Namespace, "secret", secret.Name, "route", route.Name) |
There was a problem hiding this comment.
Thanks, updated to log with correct log-level
| // must be called after hasExternalCertificate | ||
| func getReferencedSecret(route *routev1.Route) string { | ||
| secretName := route.Spec.TLS.ExternalCertificate.Name | ||
| klog.Info("Referenced secretName: ", secretName) |
There was a problem hiding this comment.
It seems odd that getReferencedSecret logs the secret name. If you keep this log statement, it should have a high log level (I'd say at least 6, maybe 10).
There was a problem hiding this comment.
I'll keep this for now at the highest log-level (10), incase of any debugging need.
| // update tls.Certificate and tls.Key | ||
| // NOTE: this will be in-memory change and won't update actual route resource | ||
| route.Spec.TLS.Certificate = string(secret.Data["tls.crt"]) | ||
| route.Spec.TLS.Key = string(secret.Data["tls.key"]) |
There was a problem hiding this comment.
Should this also clear route.Spec.TLS.CACertificate? If not, please add a comment explaining why.
There was a problem hiding this comment.
since externalCertificate will not contain CACertificate, tls.CACertificate won't be updated. I've added a comment explaining this.
|
/retest-required |
|
@chiragkyal: This pull request references CFE-1020 which is a valid jira issue. Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.16.0" version, but no target version was set. DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository. |
|
@chiragkyal: This pull request references CFE-1020 which is a valid jira issue. DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository. |
| case watch.Modified: | ||
| // remove old watch | ||
| if p.secretManager.IsRouteRegistered(route.Namespace, route.Name) { | ||
| if err := p.secretManager.UnregisterRoute(route.Namespace, route.Name); err != nil { | ||
| klog.Error("failed to unregister route", err) | ||
| return err | ||
| } | ||
| } | ||
| // create new watch | ||
| if err := p.validateAndRegister(route); err != nil { | ||
| return err | ||
| } |
There was a problem hiding this comment.
It does seem odd that the plugin always unregisters and reregisters the route, even if the TLS configuration did not change. In particular, I'm concerned about the overhead of validating and reregistering the route for updates that don't touch the TLS configuration. If that is necessary to do, can you add a comment explaining why? If it isn't necessary, please either add a check to skip the unnecessary deregister-reregister or add a to-do comment to improve the logic before GA.
There was a problem hiding this comment.
I got the validation and reregistration overhead concern here. The plugin always needs to unregister and reregister the route even if the TLS configuration did not change, for two reasons
- The
HandleRoute()method does not carry the old route spec; it's only the new route that propagates in the plugin chain. So, there isn't a definite way to compare the old TLS configuration and the new TLS configuration, and decide whether there is any change happened or not. Hence, we assume that it's always updated, so reregistration is required. - Another reason for always doing reregistration is to have an updated
secretHandler. If we don't always create a new handler (with every route update event), there may be changes of serving stale route spec in the next plugin chain, when the referencedsecretis updated or deleted. Sending outdated route in the next plugin, may impact their expected functionality.
router/pkg/router/controller/route_secret_manager.go
Lines 163 to 207 in c9ce887
There was a problem hiding this comment.
If we want to improve the logic for GA, then I think we could rely on secretManager to give the previously referenced secret name. It could have a new method called GetSecretName() to return the previously registered secret name. And we will only unregister and re-register if it's updated.
This could look something like
case watch.Modified:
newHasExt := hasExternalCertificate(route)
oldHadExt := p.secretManager.IsRouteRegistered(route.Namespace, route.Name)
if newHasExt { // new route has externalCertificate
if oldHadExt { // old route was also having externalCertificate
oldSecret := p.secretManager.GetSecretName(route.Namespace, route.Name)
if oldSecret != route.Spec.TLS.ExternalCertificate.Name { // externalCertificate is updated
// unregister and re-register
p.secretManager.UnregisterRoute(route.Namespace, route.Name)
p.validateAndRegister(route)
} else {
// do nothing if externalCertificate is not updated
}
} else { // old route was not having externalCertificate
p.validateAndRegister(route)
}
} else { // new route doesn't have externalCertificate
if oldHadExt { // old route was having externalCertificate
p.secretManager.UnregisterRoute(route.Namespace, route.Name)
} else {
// do nothing : no update to externalCertificate
}
}This could solve the issue of always doing unregister and re-register.
Along with that, we also need to ensure that the secretHandler always processes the updated route object whenever there is a secret update or delete event.
I think this can be archived by getting the updated route object from the client. So the UpdateFunc and DeleteFunc of secretHandler will first query the apiserver for the latest route, and then will continue the next steps.
Let me know if this approach would improve the overhead issue. We can update it for GA.
If there are any other suggestions, we can discuss them too.
There was a problem hiding this comment.
I've added a comment in the code for now, with a to-do to improve the logic while going to GA.
Placeholder to track : https://issues.redhat.com/browse/CFE-1057
|
/retest-required |
| recorder := routeStatusRecorder{rejections: make(map[string]string)} | ||
|
|
||
| // assign default value to expectedRejections | ||
| if s.expectedRejections == nil { | ||
| s.expectedRejections = map[string]string{} | ||
| } |
There was a problem hiding this comment.
If you keep rejections and expectedRejections unset reflect.DeepEqual() should give true:
| recorder := routeStatusRecorder{rejections: make(map[string]string)} | |
| // assign default value to expectedRejections | |
| if s.expectedRejections == nil { | |
| s.expectedRejections = map[string]string{} | |
| } | |
| recorder := routeStatusRecorder{} |
There was a problem hiding this comment.
If we keep rejections unset, it will cause panic
panic: assignment to entry in nil map
So, need to create the map.
|
/retest-required |
Vendoring openshift/library-go#1626 Signed-off-by: chiragkyal <ckyal@redhat.com>
Introduces new RouteSecretManager plugin for the management and validation of routes with externalCertificate. Integration with secretManager to register, unregister, and read referenced secret. Handling of the routes when referenced secret is updated or deleted. ValidateTLSExternalCertificate function to test preconditions for using externalCertificate. Everything is behind AllowExternalCertificates flag which is true when cluster-ingress-operator sets it, if RouteExternalCertificate feature-gate is enabled. Signed-off-by: chiragkyal <ckyal@redhat.com>
9c0b2e7 to
2f06f7c
Compare
|
@chiragkyal: all tests passed! Full PR test history. Your PR dashboard. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
|
/lgtm Holding for @rfredette to have the final look. |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: alebedev87 The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
|
/lgtm |
|
/payload 4.16 nightly informing |
|
@chiragkyal: trigger 68 job(s) of type informing for the nightly release of OCP 4.16
See details on https://pr-payload-tests.ci.openshift.org/runs/ci/c433d780-1220-11ef-91a9-e5af5c26871f-0 |
|
/payload 4.16 ci blocking |
|
@chiragkyal: trigger 5 job(s) of type blocking for the ci release of OCP 4.16
See details on https://pr-payload-tests.ci.openshift.org/runs/ci/c9be8610-128d-11ef-87bb-2bb6fe4e6d3b-0 trigger 8 job(s) of type blocking for the nightly release of OCP 4.16
See details on https://pr-payload-tests.ci.openshift.org/runs/ci/c9be8610-128d-11ef-87bb-2bb6fe4e6d3b-1 |
|
/label acknowledge-critical-fixes-only The payload jobs look okay to me. Additionally, this is a TechPreview feature behind feature-gate. I think we are good to add |
|
[ART PR BUILD NOTIFIER] This PR has been included in build ose-haproxy-router-base-container-v4.17.0-202405151441.p0.g4d9b8c4.assembly.stream.el9 for distgit ose-haproxy-router-base. |
Description
RouteSecretManagerplugin for the management and validation of routes withexternalCertificate.secretManagerto register, unregister, and read referenced secret.ValidateTLSExternalCertificatefunction to test preconditions for usingexternalCertificate.AllowExternalCertificatesflag which is true when cluster-ingress-operator sets it, ifRouteExternalCertificatefeature-gate is enabled.Implements: openshift/enhancements#1307
Related to https://issues.redhat.com//browse/CFE-1020
Depends on: CFE-866: Add secret monitor for Routes
Feature-gate wiring: openshift/cluster-ingress-operator#1017