From 6ee67996716f4393e8ed9248aca787c3f23d3394 Mon Sep 17 00:00:00 2001 From: Aurelien GASTON Date: Thu, 7 Mar 2024 12:48:00 +0100 Subject: [PATCH 1/2] feat(lb): add ssl bridging annotations --- docs/loadbalancer-annotations.md | 8 ++++ scaleway/loadbalancers.go | 43 ++++++++++++++++---- scaleway/loadbalancers_annotations.go | 56 +++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 7 deletions(-) diff --git a/docs/loadbalancer-annotations.md b/docs/loadbalancer-annotations.md index cae0e67..40d3b37 100644 --- a/docs/loadbalancer-annotations.md +++ b/docs/loadbalancer-annotations.md @@ -140,6 +140,14 @@ This is the annotation to set the forward protocol of the LB to HTTP. The possible values are `false`, `true` or `*` for all ports or a comma delimited list of the service port (for instance `80,443`). NB: forwarding HTTPS traffic with HTTP protocol enabled will work only if using a certificate, and the LB will send HTTP traffic to the backend. +### `service.beta.kubernetes.io/scw-loadbalancer-http-backend-tls` +This is the annotation to enable tls towards the backend when using http forward protocol +The possible values are `false`, `true` or `*` for all ports or a comma delimited list of the service port (for instance `80,443`) + +### `service.beta.kubernetes.io/scw-loadbalancer-http-backend-tls-skip-verify` +This is the annotation to skip tls verification on backends when using http forward protocol with TLS enabled +The possible values are `false`, `true` or `*` for all ports or a comma delimited list of the service port (for instance `80,443`) + ### `service.beta.kubernetes.io/scw-loadbalancer-certificate-ids` This is the annotation to choose the the certificate IDs to associate with this LoadBalancer. The possible format are: diff --git a/scaleway/loadbalancers.go b/scaleway/loadbalancers.go index d7e8ab5..15db9ed 100644 --- a/scaleway/loadbalancers.go +++ b/scaleway/loadbalancers.go @@ -978,6 +978,16 @@ func servicePortToBackend(service *v1.Service, loadbalancer *scwlb.LB, port v1.S return nil, err } + sslBridging, err := getSSLBridging(service, port.NodePort) + if err != nil { + return nil, err + } + + sslSkipVerify, err := getSSLBridgingSkipVerify(service, port.NodePort) + if err != nil { + return nil, err + } + forwardPortAlgorithm, err := getForwardPortAlgorithm(service) if err != nil { return nil, err @@ -1107,8 +1117,10 @@ func servicePortToBackend(service *v1.Service, loadbalancer *scwlb.LB, port v1.S backend := &scwlb.Backend{ Name: fmt.Sprintf("%s_tcp_%d", string(service.UID), port.NodePort), Pool: nodeIPs, - ForwardPort: port.NodePort, ForwardProtocol: protocol, + SslBridging: scw.BoolPtr(sslBridging), + IgnoreSslServerVerify: scw.BoolPtr(sslSkipVerify), + ForwardPort: port.NodePort, ForwardPortAlgorithm: forwardPortAlgorithm, StickySessions: stickySessions, ProxyProtocol: proxyProtocol, @@ -1196,14 +1208,22 @@ func backendEquals(got, want *scwlb.Backend) bool { klog.V(3).Infof("backend.Name: %s - %s", got.Name, want.Name) return false } - if got.ForwardPort != want.ForwardPort { - klog.V(3).Infof("backend.ForwardPort: %d - %d", got.ForwardPort, want.ForwardPort) - return false - } if got.ForwardProtocol != want.ForwardProtocol { klog.V(3).Infof("backend.ForwardProtocol: %s - %s", got.ForwardProtocol, want.ForwardProtocol) return false } + if !reflect.DeepEqual(got.SslBridging, want.SslBridging) { + klog.V(3).Infof("backend.SslBridging: %s - %s", ptrBoolToString(got.SslBridging), ptrBoolToString(want.SslBridging)) + return false + } + if !reflect.DeepEqual(got.IgnoreSslServerVerify, want.IgnoreSslServerVerify) { + klog.V(3).Infof("backend.IgnoreSslServerVerify: %s - %s", ptrBoolToString(got.IgnoreSslServerVerify), ptrBoolToString(want.IgnoreSslServerVerify)) + return false + } + if got.ForwardPort != want.ForwardPort { + klog.V(3).Infof("backend.ForwardPort: %d - %d", got.ForwardPort, want.ForwardPort) + return false + } if got.ForwardPortAlgorithm != want.ForwardPortAlgorithm { klog.V(3).Infof("backend.ForwardPortAlgorithm: %s - %s", got.ForwardPortAlgorithm, want.ForwardPortAlgorithm) return false @@ -1410,6 +1430,7 @@ func (l *loadbalancers) createBackend(service *v1.Service, loadbalancer *scwlb.L LBID: loadbalancer.ID, Name: backend.Name, ForwardProtocol: backend.ForwardProtocol, + SslBridging: backend.SslBridging, ForwardPort: backend.ForwardPort, ForwardPortAlgorithm: backend.ForwardPortAlgorithm, StickySessions: backend.StickySessions, @@ -1438,6 +1459,7 @@ func (l *loadbalancers) updateBackend(service *v1.Service, loadbalancer *scwlb.L BackendID: backend.ID, Name: backend.Name, ForwardProtocol: backend.ForwardProtocol, + SslBridging: backend.SslBridging, ForwardPort: backend.ForwardPort, ForwardPortAlgorithm: backend.ForwardPortAlgorithm, StickySessions: backend.StickySessions, @@ -1477,7 +1499,7 @@ func (l *loadbalancers) updateBackend(service *v1.Service, loadbalancer *scwlb.L return b, nil } -// createBackend creates a frontend on the load balancer +// createFrontend creates a frontend on the load balancer func (l *loadbalancers) createFrontend(service *v1.Service, loadbalancer *scwlb.LB, frontend *scwlb.Frontend, backend *scwlb.Backend) (*scwlb.Frontend, error) { f, err := l.api.CreateFrontend(&scwlb.ZonedAPICreateFrontendRequest{ Zone: loadbalancer.Zone, @@ -1493,7 +1515,7 @@ func (l *loadbalancers) createFrontend(service *v1.Service, loadbalancer *scwlb. return f, err } -// updateBackend updates a frontend on the load balancer +// updateFrontend updates a frontend on the load balancer func (l *loadbalancers) updateFrontend(service *v1.Service, loadbalancer *scwlb.LB, frontend *scwlb.Frontend, backend *scwlb.Backend) (*scwlb.Frontend, error) { f, err := l.api.UpdateFrontend(&scwlb.ZonedAPIUpdateFrontendRequest{ Zone: loadbalancer.Zone, @@ -1638,3 +1660,10 @@ func ptrInt32ToString(i *int32) string { } return fmt.Sprintf("%d", *i) } + +func ptrBoolToString(b *bool) string { + if b == nil { + return "" + } + return fmt.Sprintf("%t", *b) +} diff --git a/scaleway/loadbalancers_annotations.go b/scaleway/loadbalancers_annotations.go index d2e2f0e..97baa8e 100644 --- a/scaleway/loadbalancers_annotations.go +++ b/scaleway/loadbalancers_annotations.go @@ -136,6 +136,16 @@ const ( // (for instance "80,443") serviceAnnotationLoadBalancerProtocolHTTP = "service.beta.kubernetes.io/scw-loadbalancer-protocol-http" + // serviceAnnotationLoadBalancerHTTPBackendTLS is the annotation to enable tls towards the backend when using http forward protocol + // The possible values are "false", "true" or "*" for all ports or a comma delimited list of the service port + // (for instance "80,443") + serviceAnnotationLoadBalancerHTTPBackendTLS = "service.beta.kubernetes.io/scw-loadbalancer-http-backend-tls" + + // serviceAnnotationLoadBalancerHTTPBackendTLSSkipVerify is the annotation to skip tls verification on backends when using http forward protocol with TLS enabled + // The possible values are "false", "true" or "*" for all ports or a comma delimited list of the service port + // (for instance "80,443") + serviceAnnotationLoadBalancerHTTPBackendTLSSkipVerify = "service.beta.kubernetes.io/scw-loadbalancer-http-backend-tls-skip-verify" + // serviceAnnotationLoadBalancerCertificateIDs is the annotation to choose the certificate IDS to associate // with this LoadBalancer. // The possible format are: @@ -543,6 +553,52 @@ func getForwardProtocol(service *v1.Service, nodePort int32) (scwlb.Protocol, er return scwlb.ProtocolTCP, nil } +func getSSLBridging(service *v1.Service, nodePort int32) (bool, error) { + tlsEnabled := service.Annotations[serviceAnnotationLoadBalancerHTTPBackendTLS] + + var svcPort int32 = -1 + for _, p := range service.Spec.Ports { + if p.NodePort == nodePort { + svcPort = p.Port + } + } + if svcPort == -1 { + klog.Errorf("no valid port found") + return false, errLoadBalancerInvalidAnnotation + } + + isTLSEnabled, err := isPortInRange(tlsEnabled, svcPort) + if err != nil { + klog.Errorf("unable to check if port %d is in range %s", svcPort, tlsEnabled) + return false, err + } + + return isTLSEnabled, nil +} + +func getSSLBridgingSkipVerify(service *v1.Service, nodePort int32) (bool, error) { + skipTLSVerify := service.Annotations[serviceAnnotationLoadBalancerHTTPBackendTLSSkipVerify] + + var svcPort int32 = -1 + for _, p := range service.Spec.Ports { + if p.NodePort == nodePort { + svcPort = p.Port + } + } + if svcPort == -1 { + klog.Errorf("no valid port found") + return false, errLoadBalancerInvalidAnnotation + } + + isSkipTLSVerify, err := isPortInRange(skipTLSVerify, svcPort) + if err != nil { + klog.Errorf("unable to check if port %d is in range %s", svcPort, skipTLSVerify) + return false, err + } + + return isSkipTLSVerify, nil +} + func getCertificateIDs(service *v1.Service, port int32) ([]string, error) { certificates := service.Annotations[serviceAnnotationLoadBalancerCertificateIDs] ids := []string{} From c063ad10b06e0c3b0026caf1f0cf6797f2a42c53 Mon Sep 17 00:00:00 2001 From: Aurelien GASTON Date: Tue, 12 Mar 2024 11:37:27 +0100 Subject: [PATCH 2/2] feat(lb): use bool ptr for ssl bridging annotations --- scaleway/loadbalancers.go | 4 ++-- scaleway/loadbalancers_annotations.go | 26 ++++++++++++++++---------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/scaleway/loadbalancers.go b/scaleway/loadbalancers.go index 15db9ed..18435ee 100644 --- a/scaleway/loadbalancers.go +++ b/scaleway/loadbalancers.go @@ -1118,8 +1118,8 @@ func servicePortToBackend(service *v1.Service, loadbalancer *scwlb.LB, port v1.S Name: fmt.Sprintf("%s_tcp_%d", string(service.UID), port.NodePort), Pool: nodeIPs, ForwardProtocol: protocol, - SslBridging: scw.BoolPtr(sslBridging), - IgnoreSslServerVerify: scw.BoolPtr(sslSkipVerify), + SslBridging: sslBridging, + IgnoreSslServerVerify: sslSkipVerify, ForwardPort: port.NodePort, ForwardPortAlgorithm: forwardPortAlgorithm, StickySessions: stickySessions, diff --git a/scaleway/loadbalancers_annotations.go b/scaleway/loadbalancers_annotations.go index 97baa8e..7ed43c9 100644 --- a/scaleway/loadbalancers_annotations.go +++ b/scaleway/loadbalancers_annotations.go @@ -553,8 +553,11 @@ func getForwardProtocol(service *v1.Service, nodePort int32) (scwlb.Protocol, er return scwlb.ProtocolTCP, nil } -func getSSLBridging(service *v1.Service, nodePort int32) (bool, error) { - tlsEnabled := service.Annotations[serviceAnnotationLoadBalancerHTTPBackendTLS] +func getSSLBridging(service *v1.Service, nodePort int32) (*bool, error) { + tlsEnabled, found := service.Annotations[serviceAnnotationLoadBalancerHTTPBackendTLS] + if !found { + return nil, nil + } var svcPort int32 = -1 for _, p := range service.Spec.Ports { @@ -564,20 +567,23 @@ func getSSLBridging(service *v1.Service, nodePort int32) (bool, error) { } if svcPort == -1 { klog.Errorf("no valid port found") - return false, errLoadBalancerInvalidAnnotation + return nil, errLoadBalancerInvalidAnnotation } isTLSEnabled, err := isPortInRange(tlsEnabled, svcPort) if err != nil { klog.Errorf("unable to check if port %d is in range %s", svcPort, tlsEnabled) - return false, err + return nil, err } - return isTLSEnabled, nil + return scw.BoolPtr(isTLSEnabled), nil } -func getSSLBridgingSkipVerify(service *v1.Service, nodePort int32) (bool, error) { - skipTLSVerify := service.Annotations[serviceAnnotationLoadBalancerHTTPBackendTLSSkipVerify] +func getSSLBridgingSkipVerify(service *v1.Service, nodePort int32) (*bool, error) { + skipTLSVerify, found := service.Annotations[serviceAnnotationLoadBalancerHTTPBackendTLSSkipVerify] + if !found { + return nil, nil + } var svcPort int32 = -1 for _, p := range service.Spec.Ports { @@ -587,16 +593,16 @@ func getSSLBridgingSkipVerify(service *v1.Service, nodePort int32) (bool, error) } if svcPort == -1 { klog.Errorf("no valid port found") - return false, errLoadBalancerInvalidAnnotation + return nil, errLoadBalancerInvalidAnnotation } isSkipTLSVerify, err := isPortInRange(skipTLSVerify, svcPort) if err != nil { klog.Errorf("unable to check if port %d is in range %s", svcPort, skipTLSVerify) - return false, err + return nil, err } - return isSkipTLSVerify, nil + return scw.BoolPtr(isSkipTLSVerify), nil } func getCertificateIDs(service *v1.Service, port int32) ([]string, error) {