Skip to content

Commit 8907718

Browse files
committed
feat(ccm): allow to configure ssl-compatibility-level
1 parent a260133 commit 8907718

4 files changed

Lines changed: 137 additions & 1 deletion

File tree

docs/loadbalancer-annotations.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ The default value is `false`. The possible values are `false` or `true`.
123123
This is the annotation to enable HTTP/3 protocol for the load balancer.
124124
The default value is `false`. The possible values are `false` or `true`.
125125

126+
### `service.beta.kubernetes.io/scw-loadbalancer-ssl-compatibility-level`
127+
This is the annotation to set the minimal SSL version supported on the client side.
128+
The default value is `ssl_compatibility_level_intermediate`.
129+
The possible values are `ssl_compatibility_level_intermediate`, `ssl_compatibility_level_modern`, and `ssl_compatibility_level_old`.
130+
126131
### `service.beta.kubernetes.io/scw-loadbalancer-timeout-client`
127132
This is the annotation to set the maximum client connection inactivity time.
128133
The default value is `10m`. The duration are go's time.Duration (ex: `1s`, `2m`, `4h`, ...).

scaleway/loadbalancers.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type LoadBalancerAPI interface {
5757
ListLBs(req *scwlb.ZonedAPIListLBsRequest, opts ...scw.RequestOption) (*scwlb.ListLBsResponse, error)
5858
GetLB(req *scwlb.ZonedAPIGetLBRequest, opts ...scw.RequestOption) (*scwlb.LB, error)
5959
CreateLB(req *scwlb.ZonedAPICreateLBRequest, opts ...scw.RequestOption) (*scwlb.LB, error)
60+
UpdateLB(req *scwlb.ZonedAPIUpdateLBRequest, opts ...scw.RequestOption) (*scwlb.LB, error)
6061
DeleteLB(req *scwlb.ZonedAPIDeleteLBRequest, opts ...scw.RequestOption) error
6162
MigrateLB(req *scwlb.ZonedAPIMigrateLBRequest, opts ...scw.RequestOption) (*scwlb.LB, error)
6263
ListIPs(req *scwlb.ZonedAPIListIPsRequest, opts ...scw.RequestOption) (*scwlb.ListIPsResponse, error)
@@ -469,6 +470,12 @@ func (l *loadbalancers) createLoadBalancer(ctx context.Context, clusterName stri
469470
return nil, fmt.Errorf("invalid value for annotation %s: expected boolean", serviceAnnotationLoadBalancerPrivate)
470471
}
471472

473+
sslCompatibilityLevel, err := getSSLCompatibilityLevel(service)
474+
if err != nil {
475+
klog.Errorf("error getting SSL compatibility level for service %s(%s): %v", service.Name, service.UID, err)
476+
return nil, fmt.Errorf("error getting SSL compatibility level for service %s(%s): %v", service.Name, service.UID, err)
477+
}
478+
472479
// Attach specific IP if set
473480
var ipIDs []string
474481
if !lbPrivate {
@@ -503,7 +510,8 @@ func (l *loadbalancers) createLoadBalancer(ctx context.Context, clusterName stri
503510
Type: lbType,
504511
// We must only assign a flexible IP if LB is public AND no IP ID is provided.
505512
// If IP IDs are provided, there must be at least one IPv4.
506-
AssignFlexibleIP: scw.BoolPtr(!lbPrivate && len(ipIDs) == 0),
513+
AssignFlexibleIP: scw.BoolPtr(!lbPrivate && len(ipIDs) == 0),
514+
SslCompatibilityLevel: sslCompatibilityLevel,
507515
}
508516
lb, err := l.api.CreateLB(&request)
509517
if err != nil {
@@ -780,6 +788,27 @@ func (l *loadbalancers) updateLoadBalancer(ctx context.Context, loadbalancer *sc
780788
return fmt.Errorf("error updating load balancer %s: %v", loadbalancer.ID, err)
781789
}
782790
}
791+
792+
// Update SSL compatibility level if needed
793+
sslCompatibilityLevel, err := getSSLCompatibilityLevel(service)
794+
if err != nil {
795+
klog.Errorf("error getting SSL compatibility level on the service %s for load balancer %s: %v", service.Name, loadbalancer.ID, err)
796+
return fmt.Errorf("error getting SSL compatibility level on the service %s for load balancer %s: %v", service.Name, loadbalancer.ID, err)
797+
}
798+
if loadbalancer.SslCompatibilityLevel != sslCompatibilityLevel {
799+
_, err := l.api.UpdateLB(&scwlb.ZonedAPIUpdateLBRequest{
800+
Zone: loadbalancer.Zone,
801+
LBID: loadbalancer.ID,
802+
Name: loadbalancer.Name,
803+
Description: loadbalancer.Description,
804+
Tags: loadbalancer.Tags,
805+
SslCompatibilityLevel: sslCompatibilityLevel,
806+
})
807+
if err != nil {
808+
klog.Errorf("error updating load balancer %s: %v", loadbalancer.ID, err)
809+
return fmt.Errorf("error updating load balancer %s: %v", loadbalancer.ID, err)
810+
}
811+
}
783812
}
784813

785814
return nil

scaleway/loadbalancers_annotations.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strings"
88
"time"
99

10+
"golang.org/x/exp/slices"
1011
"google.golang.org/protobuf/types/known/durationpb"
1112
v1 "k8s.io/api/core/v1"
1213
"k8s.io/klog/v2"
@@ -120,6 +121,10 @@ const (
120121
// The default value is "false". The possible values are "false" or "true".
121122
serviceAnnotationLoadBalancerEnableHTTP3 = "service.beta.kubernetes.io/scw-loadbalancer-enable-http3"
122123

124+
// serviceAnnotationLoadBalancerSSLCompatibilityLevel is the annotation to set the minimal SSL version supported on the client side.
125+
// The default value is "ssl_compatibility_level_intermediate".
126+
serviceAnnotationLoadBalancerSSLCompatibilityLevel = "service.beta.kubernetes.io/scw-loadbalancer-ssl-compatibility-level"
127+
123128
// serviceAnnotationLoadBalancerTimeoutClient is the maximum client connection inactivity time
124129
// The default value is "10m". The duration are go's time.Duration (ex: "1s", "2m", "4h", ...)
125130
serviceAnnotationLoadBalancerTimeoutClient = "service.beta.kubernetes.io/scw-loadbalancer-timeout-client"
@@ -1101,3 +1106,18 @@ func getNativeHealthCheck(service *v1.Service, targetPort int32) (*scwlb.HealthC
11011106
},
11021107
}, nil
11031108
}
1109+
1110+
func getSSLCompatibilityLevel(service *v1.Service) (scwlb.SSLCompatibilityLevel, error) {
1111+
sslCompatibilityLevel := service.Annotations[serviceAnnotationLoadBalancerSSLCompatibilityLevel]
1112+
if sslCompatibilityLevel == "" {
1113+
return scwlb.SSLCompatibilityLevelSslCompatibilityLevelIntermediate, nil
1114+
}
1115+
1116+
level := scwlb.SSLCompatibilityLevel(sslCompatibilityLevel)
1117+
if !slices.Contains(level.Values(), level) {
1118+
klog.Errorf("invalid value for annotation %s", serviceAnnotationLoadBalancerSSLCompatibilityLevel)
1119+
return "", errLoadBalancerInvalidAnnotation
1120+
}
1121+
1122+
return level, nil
1123+
}

scaleway/loadbalancers_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1849,6 +1849,88 @@ func Test_nodesInitialized(t *testing.T) {
18491849
}
18501850
}
18511851

1852+
func TestGetSSLCompatibilityLevel(t *testing.T) {
1853+
matrix := []struct {
1854+
name string
1855+
service *v1.Service
1856+
want scwlb.SSLCompatibilityLevel
1857+
wantErr bool
1858+
}{
1859+
{
1860+
"with empty annotation",
1861+
&v1.Service{
1862+
ObjectMeta: metav1.ObjectMeta{
1863+
Annotations: map[string]string{},
1864+
},
1865+
},
1866+
scwlb.SSLCompatibilityLevelSslCompatibilityLevelIntermediate,
1867+
false,
1868+
},
1869+
{
1870+
"with valid intermediate level",
1871+
&v1.Service{
1872+
ObjectMeta: metav1.ObjectMeta{
1873+
Annotations: map[string]string{
1874+
"service.beta.kubernetes.io/scw-loadbalancer-ssl-compatibility-level": "ssl_compatibility_level_intermediate",
1875+
},
1876+
},
1877+
},
1878+
scwlb.SSLCompatibilityLevelSslCompatibilityLevelIntermediate,
1879+
false,
1880+
},
1881+
{
1882+
"with valid modern level",
1883+
&v1.Service{
1884+
ObjectMeta: metav1.ObjectMeta{
1885+
Annotations: map[string]string{
1886+
"service.beta.kubernetes.io/scw-loadbalancer-ssl-compatibility-level": "ssl_compatibility_level_modern",
1887+
},
1888+
},
1889+
},
1890+
scwlb.SSLCompatibilityLevelSslCompatibilityLevelModern,
1891+
false,
1892+
},
1893+
{
1894+
"with valid old level",
1895+
&v1.Service{
1896+
ObjectMeta: metav1.ObjectMeta{
1897+
Annotations: map[string]string{
1898+
"service.beta.kubernetes.io/scw-loadbalancer-ssl-compatibility-level": "ssl_compatibility_level_old",
1899+
},
1900+
},
1901+
},
1902+
scwlb.SSLCompatibilityLevelSslCompatibilityLevelOld,
1903+
false,
1904+
},
1905+
{
1906+
"with invalid level",
1907+
&v1.Service{
1908+
ObjectMeta: metav1.ObjectMeta{
1909+
Annotations: map[string]string{
1910+
"service.beta.kubernetes.io/scw-loadbalancer-ssl-compatibility-level": "invalid_level",
1911+
},
1912+
},
1913+
},
1914+
"",
1915+
true,
1916+
},
1917+
}
1918+
1919+
for _, tt := range matrix {
1920+
t.Run(tt.name, func(t *testing.T) {
1921+
got, err := getSSLCompatibilityLevel(tt.service)
1922+
if tt.wantErr != (err != nil) {
1923+
t.Errorf("got error: %s, expected: %v", err, tt.wantErr)
1924+
return
1925+
}
1926+
1927+
if got != tt.want {
1928+
t.Errorf("want: %v, got: %v", tt.want, got)
1929+
}
1930+
})
1931+
}
1932+
}
1933+
18521934
func Test_ipMode(t *testing.T) {
18531935
type args struct {
18541936
service *v1.Service

0 commit comments

Comments
 (0)