-
Notifications
You must be signed in to change notification settings - Fork 150
Custom console route #402
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
Custom console route #402
Conversation
| return nil, true, "InvalidCustomRouteConfig", configErr | ||
| } | ||
|
|
||
| if scscErr := c.SyncCustomServigCertSecret(operatorConfig); scscErr != 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.
nit: SyncCustomServigCertSecret has Servig, but should be Serving.
| c.CheckRouteHealth(operatorConfig, cr) | ||
|
|
||
| if _, changed := routesub.Validate(cr); changed { | ||
| return nil, true, "InvalidCustomRoute", customerrors.NewSyncError("custom route is invalid, correcting route state") |
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.
So this deviates a little bit from the Validate() in SyncRoute(). Looking into this a bit to make sure its ok.
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.
thought that since the Validate() is just checking the To, Port, TLS, WildcardPolicy then it cant hurt to check those on the custom route, since these should be the same
| } | ||
| // if no secret with serving-cert provided, sync an empty source to delete | ||
| return c.resourceSyncer.SyncSecret( | ||
| resourcesynccontroller.ResourceLocation{Namespace: api.OpenShiftConsoleNamespace, Name: api.OpenShiftCustomLogoConfigMapName}, |
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.
Prob should update api.OpenShiftCustomLogoConfigMapName to api.CustomRouteServingCertName.
I dont think we want to call this secret "custom-logo".
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.
yep ! will update !
| return false | ||
| } | ||
|
|
||
| func ValidateCustomRoute(operatorConfig *operatorv1.Console, client routeclient.RoutesGetter) error { |
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.
I'm not sure I follow the logic entirely here.
Its not really validating the custom route its.... checking if the custom is still on the default ingress domain?
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.
yeah, probably bad naming hare. Maybe isDefaultIngressDomain ?
| return err | ||
| } | ||
|
|
||
| if is := isClusterDomain(operatorConfig.Spec.Route.Hostname, defaultRoute.Spec.Host); !is { |
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.
The question here is, do we trust the default route, or should we pull in the Ingress Config?
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.
So I think we can trust the default route, since we will be maintaining it anyways. Also when creating we are using the default as the router name. Otherwise we are raising a sync error.
Should be ok then ?
| return err | ||
| } | ||
|
|
||
| func isClusterDomain(customRouteHost, defaultRouteHost string) bool { |
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.
So this is answering "Is the custom host still on the default ingress domain"?
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.
yes
| return route, changed | ||
| } | ||
|
|
||
| func IsCustomRouteSet(operatorConfig *operatorv1.Console) bool { |
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.
I think this will work:
func IsCustomRouteSet(operatorConfig *operatorv1.Console) bool {
return operatorConfig.Spec.Route.Hostname) != 0
}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.
definitely 👍
| } | ||
|
|
||
| // Check if reference for secret holding custom serving-cert is set | ||
| func IsCustomRouteSecretSet(operatorConfig *operatorv1.Console) bool { |
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.
func IsCustomRouteSecretSet(operatorConfig *operatorv1.Console) bool {
return len(operatorConfig.Spec.Route.Secret.Name) != 0
}| path: "/var/logo/", | ||
| isConfigMap: true} | ||
| } | ||
|
|
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 is to overwrite the existing serving cert with the custom, so we don't have to change the impl in the console to look at a different dir, correct?
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.
yes
Thought that we should not need both serving-certs
stlaz
left a comment
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.
I couldn't find an enhancement for this, but I don't understand why you should keep two routes instead of just one. Are both going to be functional at the same time?
| func (c *RouteSyncController) removeRoute(routeName string) error { | ||
| klog.V(2).Infof("deleting %s route", routeName) | ||
| defer klog.V(2).Infof("finished deleting %s route", routeName) | ||
| return c.routeClient.Routes(c.targetNamespace).Delete(routeName, &metav1.DeleteOptions{}) | ||
| } |
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.
you can keep just this function instead of having two, just
func (c *RouteSyncController) removeRoute(routeName string) error {
klog.V(2).Infof("deleting %s route", routeName)
err := c.routeClient.Routes(c.targetNamespace).Delete(routeName, &metav1.DeleteOptions{})
if apierrors.IsNotFound(err) {
return nil
}
return err
}You may want to get rid of the defer log because it would appear even in the case of an error
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.
actually, you would want to remove the other log line as well as that would appear on every sync even if the route was already removed...
| // 4. check custom route health | ||
| // 5. validate the route | ||
| func (c *RouteSyncController) SyncCustomRoute(operatorConfig *operatorsv1.Console) (consoleRoute *routev1.Route, isNew bool, reason string, err error) { | ||
| if configErr := routesub.ValidateCustomRoute(operatorConfig, c.routeClient); configErr != 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.
As Ben suggested, move the IsCustomRouteSet() check here and return early if false
| return cr, crIsNew, "", crErr | ||
| } | ||
|
|
||
| func (c *RouteSyncController) SyncCustomServigCertSecret(operatorConfig *operatorsv1.Console) error { |
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.
why are you syncing this? Also, I'd advise to use the name "RouteCert" instead of serving-cert because the latter is more commonly used for the certificate the pod uses for serving, rather than the certificate the route uses.
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.
why are you syncing this?
we need to since the secret that contains the custom serving-cert from openshift-config, where the user/admin needs to create it manually, into the openshift-console, so we can mount it into the console pod
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.
But you don't want to mount it in the pod, you want to have that inserted into the route
| tolerationSeconds := int64(120) | ||
| volumeConfig := defaultVolumeConfig() | ||
| if routesub.IsCustomRouteSecretSet(operatorConfig) { | ||
| volumeConfig = useCustomServingCertSecret(operatorConfig.Spec.Route.Secret.Name, volumeConfig) |
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.
why are you mounting this? The contents are supposed to be used in the route's spec.tls.termination.{cert,key,caCertificate} fields
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.
Also, you should have a hard requirement on the secret you're syncing to contain all of tls.crt, tls.key and ca.crt fields
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.
why are you mounting this? The contents are supposed to be used in the route's spec.tls.termination.{cert,key,caCertificate} fields
I though that the secret needs to be mounted into the console pod.
why are you mounting this? The contents are supposed to be used in the route's spec.tls.termination.{cert,key,caCertificate} fields
also ca.crt?
all of them needs to be used in the route's spec.tls.termination.{cert,key,caCertificate} fields?
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.
I think you would want to have the CA certificate there as well to prevent issues that the routing team has - openshift/cluster-ingress-operator#329. Generally, it would make your life easier.
Also, you should validate the secret and don't progress further if it's invalid.
| Labels: util.SharedLabels(), | ||
| Annotations: map[string]string{}, | ||
| }, | ||
| Spec: spec(), |
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.
where's all the fancy certificate stuff you're doing this for?
|
@stlaz so to point of having two routes is to have the default one reserved, in case that somebody could create one which would block admitting the default route if recreated. The default one will be active only in case that the custom route suffix will match the cluster domain |
893d7c8 to
f9dee4a
Compare
|
@stlaz @benjaminapetersen I've update the PR based on your comments. PTAL |
|
The unit tests are failing cause will update them after after we align on the logic, since that will be pretty big change itself |
stlaz
left a comment
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.
needs tests
|
@benjaminapetersen @stlaz comments addressed. PTAL |
| return nil, err | ||
| } | ||
| // Check if the custom hostname has cluster domain suffix. If it does, | ||
| // then the secret that contains TLS certificate and key needs to be |
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.
I'm a little confused by the comment, it implies something should be done, but it immediately returns nil, 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.
+ what if the user just wanted their custom certificate for the route in cluster domain?
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.
The comment is wrong... if the suffix matches cluster domain then the secret is not necessary
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.
but the user may still want their certificate to be used instead of the ones that the router adds by default, correct?
| operatorConfigClient.OperatorV1().Consoles(), | ||
| routesClient.RouteV1(), | ||
| kubeClient.CoreV1(), | ||
| kubeClient.CoreV1(), |
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.
I think we only need to pass kubeClient.CoreV1() once.
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.
Ah, I see what we did, we have our clients slightly misnamed. Hmm. And we do it in all our controllers. :)
| - route.openshift.io | ||
| resources: | ||
| - routes | ||
| - routes/custom-host |
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 is needed to set the route.spec.tls fields
stlaz
left a comment
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.
getting there
| return nil, true, "InvalidRoute", customerrors.NewSyncError("route is invalid, correcting route state") | ||
| return nil, nil | ||
| } | ||
| customCertSecret, err := c.secretClient.Secrets(api.OpenShiftConfigNamespace).Get(c.ctx, operatorConfig.Spec.Route.Secret.Name, metav1.GetOptions{}) |
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.
nit: just return c.secretClient.Secrets(api.OpenShiftConfigNamespace).Get(c.ctx, operatorConfig.Spec.Route.Secret.Name, metav1.GetOptions{})
| } | ||
| // abort sync, route changed, let it settle & retry | ||
| return nil, true, "InvalidRoute", customerrors.NewSyncError("route is invalid, correcting route state") | ||
| return nil, 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.
like I said - I configured my custom route within the cluster domain and configured a custom cert I want it to use, why don't you honor it?
| block = &pem.Block{ | ||
| Type: "CERTIFICATE", | ||
| Bytes: cert.Raw, | ||
| } |
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.
why are you doing this?
| func certificateVerifier(customCert []byte) (*pem.Block, error) { | ||
| block, _ := pem.Decode([]byte(customCert)) | ||
| if block == nil { | ||
| return nil, fmt.Errorf("failed to parse custom certificate") |
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.
("failed to decode certificate PEM: %v", err) please
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.
ah, nvm, pem.Decode does not return error, my bad
| switch t := key.(type) { | ||
| case *rsa.PrivateKey: | ||
| block = &pem.Block{ | ||
| Type: "RSA PRIVATE KEY", | ||
| Bytes: x509.MarshalPKCS1PrivateKey(t), | ||
| } | ||
| case *ecdsa.PrivateKey: | ||
| block = &pem.Block{ | ||
| Type: "ECDSA PRIVATE KEY", | ||
| } | ||
| if block.Bytes, err = x509.MarshalECPrivateKey(t); err != nil { | ||
| return nil, err | ||
| } | ||
| default: | ||
| return nil, fmt.Errorf("block private key %T is not valid", key) | ||
| } |
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.
why are you doing this?
| existingCopy := existing.DeepCopy() | ||
|
|
||
| if len(GetCanonicalHost(existingCopy)) == 0 { | ||
| return nil, false, customerrors.NewSyncError(fmt.Sprintf("route is not available at canonical host %s", existingCopy.Status.Ingress)) |
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 is no place for this check though, you should be able to apply the route no matter what its status is.
Also, are you 100% sure that this will be valid even for routes outside the cluster domain?
|
|
||
| const ( | ||
| validCertificate = ` | ||
| -----BEGIN CERTIFICATE----- |
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.
thanks, I completely forgot you should also be checking at least the expiry date. For example, this certificate appears to have expired 4 years ago.
Not Before: Apr 5 15:15:55 2013 GMT
Not After : Apr 4 15:15:55 2015 GM
d45be3d to
85c0790
Compare
| - "" | ||
| resources: | ||
| - configmaps | ||
| - secrets |
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.
Too bad we have to add this, I liked that we didn't read secrets from here.
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.
Not that we ARE reading them all, just that now we can. but we are only doing a GET on the one that matters to us.
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.
Other solution would be to have the secret name as API, then we could specify only that single one... but not sure if thats worth it.
benjaminapetersen
left a comment
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.
/approve
So long as @stlaz is happy, we can tag this!
|
/retest |
stlaz
left a comment
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.
just a few more cosmetic changes, otherwise LGTM
| v1 "github.com/openshift/client-go/operator/informers/externalversions/operator/v1" | ||
| routeclientv1 "github.com/openshift/client-go/route/clientset/versioned/typed/route/v1" | ||
| routesinformersv1 "github.com/openshift/client-go/route/informers/externalversions/route/v1" | ||
| customerrors "github.com/openshift/console-operator/pkg/console/errors" |
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.
nit: should have stayed below
| if len(customCertSecret.Data) == 0 { | ||
| return nil, fmt.Errorf("custom cert secret doesn't contain 'Data' field") | ||
| } |
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.
you probably don't need to check this since you're checking the presence of the interesting keys later on
| _, err := x509.ParsePKCS8PrivateKey(block.Bytes) | ||
| if err != nil { | ||
| _, err = x509.ParsePKCS1PrivateKey(block.Bytes) | ||
| if err != nil { | ||
| _, err = x509.ParseECPrivateKey(block.Bytes) | ||
| if err != nil { | ||
| return fmt.Errorf("block %s is not valid", block.Type) | ||
| } |
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.
nit:
if _, err := x509.x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
if _, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
...
}
}| if err != nil { | ||
| _, err = x509.ParseECPrivateKey(block.Bytes) | ||
| if err != nil { | ||
| return fmt.Errorf("block %s is not valid", block.Type) |
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.
is not a valid private key PEM
|
@stlaz comments addressed.. lets merge this baby 😄 |
|
/lgtm |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: benjaminapetersen, jhadvig, stlaz 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 |
|
/retest |
2 similar comments
|
/retest |
|
/retest |
|
/retest Please review the full test history for this PR and help us cut down flakes. |
6 similar comments
|
/retest Please review the full test history for this PR and help us cut down flakes. |
|
/retest Please review the full test history for this PR and help us cut down flakes. |
|
/retest Please review the full test history for this PR and help us cut down flakes. |
|
/retest Please review the full test history for this PR and help us cut down flakes. |
|
/retest Please review the full test history for this PR and help us cut down flakes. |
|
/retest Please review the full test history for this PR and help us cut down flakes. |
Adding option do specify the custom hostname for console.
When when custom hostname is specified a new
console-customroute is created. In case the new hostname doesnt contain the cluster domain as a suffix, secret reference needs to be specified, that points to the secret in theopenshift-confignamespace, where it needs to be created manually and contain tls cert and its key, which will serve as a serving-cert for the console pod. Also we will maintain the default route so it stays reserved for us./assign @benjaminapetersen
API changes - openshift/api#594