Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions api/config/v1alpha1/configuration_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ type ControllerManager struct {
// Health contains the controller health configuration
// +optional
Health ControllerHealth `json:"health,omitempty"`

// TLS contains TLS security settings for all LWS API servers
// (webhooks and metrics).
// +optional
TLS *TLSOptions `json:"tls,omitempty"`
}

// ControllerWebhook defines the webhook server for the controller.
Expand Down Expand Up @@ -137,3 +142,20 @@ type GangSchedulingManagement struct {
// SchedulerProvider is the name of the scheduler that provides gang-scheduling capabilities.
SchedulerProvider *string `json:"schedulerProvider,omitempty"`
}

// TLSOptions defines TLS security settings for LWS servers
type TLSOptions struct {
// MinVersion is the minimum TLS version supported.
// Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants).
// This field is only valid when TLSOptions is set to true.
// The default would be to not set this value and inherit golang settings.
// +optional
MinVersion string `json:"minVersion,omitempty"`

// CipherSuites is the list of allowed cipher suites for the server.
// Note that TLS 1.3 ciphersuites are not configurable.
// Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants).
// The default would be to not set this value and inherit golang settings.
// +optional
CipherSuites []string `json:"cipherSuites,omitempty"`
}
25 changes: 25 additions & 0 deletions api/config/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 26 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,16 @@ func apply(configFile string,
options.LeaderElectionID = leaderElectionID
}

// 1. LWS Pattern: Parse the TLS config first
var parsedTLSConfig *config.TLS
if cfg.TLS != nil { // Note: cfg.TLS, not cfg.Webhook.TLS
var err error
parsedTLSConfig, err = config.ParseTLSOptions(cfg.TLS)
if err != nil {
return options, cfg, fmt.Errorf("unable to parse TLS options from configuration: %w", err)
}
}

// Disabling http/2 to prevent being vulnerable to the HTTP/2 Stream Cancellation and
// Rapid Reset CVEs. For more information see:
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
Expand All @@ -295,6 +305,14 @@ func apply(configFile string,
c.NextProtos = []string{"http/1.1"}
}

serverTLSOpts := []func(*tls.Config){disableHTTP2}

// 3. LWS Pattern: Build and apply parsed TLS configuration from config
if parsedTLSConfig != nil {
tlsOpts := config.BuildTLSOptions(parsedTLSConfig)
serverTLSOpts = append(serverTLSOpts, tlsOpts...)
}

if !flagsSet["metrics-bind-address"] {
metricsAddr = cfg.Metrics.BindAddress
}
Expand All @@ -307,12 +325,19 @@ func apply(configFile string,
BindAddress: metricsAddr,
SecureServing: true,
FilterProvider: filters.WithAuthenticationAndAuthorization,
TLSOpts: []func(*tls.Config){disableHTTP2},
TLSOpts: serverTLSOpts,
}

options.Metrics = metricsServerOptions
options.LeaderElectionNamespace = namespace

//// 5. Inject into Webhook Server
//options.WebhookServer = webhook.NewServer(webhook.Options{
// TLSOpts: serverTLSOpts,
// CertDir: cfg.Webhook.CertDir,
//})
config.AddWebhookSettingsTo(&options, &cfg, serverTLSOpts)

setupLog.Info("Successfully loaded configuration", "config", cfgStr)
return options, cfg, nil
}
39 changes: 26 additions & 13 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ import (
"fmt"
"os"

cryptotls "crypto/tls"

"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook"

configapi "sigs.k8s.io/lws/api/config/v1alpha1"
)

Expand Down Expand Up @@ -65,19 +66,31 @@ func addTo(o *ctrl.Options, cfg *configapi.Configuration) {
o.LivenessEndpointName = cfg.Health.LivenessEndpointName
}

if o.WebhookServer == nil && cfg.Webhook.Port != nil {
wo := webhook.Options{}
if cfg.Webhook.Port != nil {
wo.Port = *cfg.Webhook.Port
}
if cfg.Webhook.Host != "" {
wo.Host = cfg.Webhook.Host
}
if cfg.Webhook.CertDir != "" {
wo.CertDir = cfg.Webhook.CertDir
}
o.WebhookServer = webhook.NewServer(wo)
// Webhook server is NOT set here anymore.
// It is set in AddWebhookSettingsTo() after TLS is parsed.
}

// AddWebhookSettingsTo builds and sets the WebhookServer on the options,
// applying TLS configuration if provided.
// This is separated from addTo() because TLS must be parsed first,
// and TLS parsing happens after config is loaded.
func AddWebhookSettingsTo(o *ctrl.Options, cfg *configapi.Configuration, tlsOpts []func(*cryptotls.Config)) {
if o.WebhookServer != nil {
return
}
wo := webhook.Options{
TLSOpts: tlsOpts,
}
if cfg.Webhook.Port != nil {
wo.Port = *cfg.Webhook.Port
}
if cfg.Webhook.Host != "" {
wo.Host = cfg.Webhook.Host
}
if cfg.Webhook.CertDir != "" {
wo.CertDir = cfg.Webhook.CertDir
}
o.WebhookServer = webhook.NewServer(wo)
}

func addLeaderElectionTo(o *ctrl.Options, cfg *configapi.Configuration) {
Expand Down
113 changes: 113 additions & 0 deletions pkg/config/tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
Copyright 2026 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package config

import (
"crypto/tls"
"errors"
"fmt"

cliflag "k8s.io/component-base/cli/flag"

configapi "sigs.k8s.io/lws/api/config/v1alpha1"
)

// TLS holds the parsed TLS configuration
type TLS struct {
MinVersion uint16
CipherSuites []uint16
}

// ParseTLSOptions parses the API configuration into a TLS struct
func ParseTLSOptions(cfg *configapi.TLSOptions) (*TLS, error) {
ret := &TLS{}
var errRet error
if cfg == nil {
return nil, nil
}

// TLS 1.3 validation check (Added here because LWS doesn't have a separate validation webhook)
if cfg.MinVersion == "VersionTLS13" && len(cfg.CipherSuites) > 0 {
return nil, fmt.Errorf("cipher suites may not be specified when minVersion is 'VersionTLS13'")
}

version, err := convertTLSMinVersion(cfg.MinVersion)
if err != nil {
errRet = errors.Join(errRet, err)

}
ret.MinVersion = version

// Set cipher suites
if len(cfg.CipherSuites) > 0 {
cipherSuites, err := convertCipherSuites(cfg.CipherSuites)
if err != nil {
errRet = errors.Join(errRet, err)
}
if err == nil && len(cipherSuites) > 0 {
ret.CipherSuites = cipherSuites
}
}
return ret, errRet
}

// BuildTLSOptions converts TLSOptions from the configuration to controller-runtime TLSOpts
func BuildTLSOptions(tlsOptions *TLS) []func(*tls.Config) {
if tlsOptions == nil {
return nil
}

var tlsOpts []func(*tls.Config)

tlsOpts = append(tlsOpts, func(c *tls.Config) {
if tlsOptions.MinVersion != 0 {
c.MinVersion = tlsOptions.MinVersion
}
if len(tlsOptions.CipherSuites) > 0 {
c.CipherSuites = tlsOptions.CipherSuites
}
})

return tlsOpts
}

// convertTLSMinVersion converts a TLS version string to the corresponding uint16 constant
func convertTLSMinVersion(tlsMinVersion string) (uint16, error) {
if tlsMinVersion == "" {
return 0, nil // Don't default if not provided, let Go handle it
}
if tlsMinVersion == "VersionTLS11" || tlsMinVersion == "VersionTLS10" {
return 0, errors.New("invalid minVersion. Please use VersionTLS12 or VersionTLS13")
}
version, err := cliflag.TLSVersion(tlsMinVersion)
if err != nil {
return 0, fmt.Errorf("invalid minVersion: %w. Please use VersionTLS12 or VersionTLS13", err)
}
return version, nil
}

// convertCipherSuites converts cipher suite names to their crypto/tls constants
func convertCipherSuites(cipherSuites []string) ([]uint16, error) {
if len(cipherSuites) == 0 {
return nil, nil
}
suites, err := cliflag.TLSCipherSuites(cipherSuites)
if err != nil {
return nil, fmt.Errorf("invalid cipher suites: %w. Please use the secure cipher names: %v", err, cliflag.PreferredTLSCipherNames())
}
return suites, nil
}
Loading