diff --git a/pkg/config/v1alpha1/types.go b/pkg/config/v1alpha1/types.go index 1af58a0348..b82e863778 100644 --- a/pkg/config/v1alpha1/types.go +++ b/pkg/config/v1alpha1/types.go @@ -140,6 +140,10 @@ type ControllerWebhook struct { // must be named tls.key and tls.crt, respectively. // +optional CertDir string `json:"certDir,omitempty"` + + // TLSVersion is the minimum version of TLS supported. Accepts + // "", "1.0", "1.1", "1.2" and "1.3" only ("" is equivalent to "1.0" for backwards compatibility) + TLSMinVersion string } // +kubebuilder:object:root=true diff --git a/pkg/envtest/webhook_test.go b/pkg/envtest/webhook_test.go index bb1726cf01..452654d44d 100644 --- a/pkg/envtest/webhook_test.go +++ b/pkg/envtest/webhook_test.go @@ -18,6 +18,7 @@ package envtest import ( "context" + "crypto/tls" "path/filepath" "time" @@ -27,6 +28,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/webhook" @@ -38,9 +40,13 @@ var _ = Describe("Test", func() { Describe("Webhook", func() { It("should reject create request for webhook that rejects all requests", func() { m, err := manager.New(env.Config, manager.Options{ - Port: env.WebhookInstallOptions.LocalServingPort, - Host: env.WebhookInstallOptions.LocalServingHost, - CertDir: env.WebhookInstallOptions.LocalServingCertDir, + Port: env.WebhookInstallOptions.LocalServingPort, + Host: env.WebhookInstallOptions.LocalServingHost, + CertDir: env.WebhookInstallOptions.LocalServingCertDir, + TLSMinVersion: "1.2", + TLSOpts: []func(*tls.Config){ + func(config *tls.Config) {}, + }, }) // we need manager here just to leverage manager.SetFields Expect(err).NotTo(HaveOccurred()) server := m.GetWebhookServer() diff --git a/pkg/manager/internal.go b/pkg/manager/internal.go index 5b22c628f9..96258d1b6e 100644 --- a/pkg/manager/internal.go +++ b/pkg/manager/internal.go @@ -18,6 +18,7 @@ package manager import ( "context" + "crypto/tls" "errors" "fmt" "net" @@ -135,6 +136,11 @@ type controllerManager struct { // if not set, webhook server would look up the server key and certificate in // {TempDir}/k8s-webhook-server/serving-certs certDir string + // tlsMinVersion is the minimum version of TLS supported by the webhook server. + // Accepts "", "1.0", "1.1", "1.2" and "1.3" only ("" is equivalent to "1.0" for backwards compatibility). + tlsMinVersion string + // tlsOpts is used to allow configuring the TLS config used for the webhook server. + tlsOpts []func(*tls.Config) webhookServer *webhook.Server // webhookServerOnce will be called in GetWebhookServer() to optionally initialize @@ -302,9 +308,11 @@ func (cm *controllerManager) GetWebhookServer() *webhook.Server { cm.webhookServerOnce.Do(func() { if cm.webhookServer == nil { cm.webhookServer = &webhook.Server{ - Port: cm.port, - Host: cm.host, - CertDir: cm.certDir, + Port: cm.port, + Host: cm.host, + CertDir: cm.certDir, + TLSMinVersion: cm.tlsMinVersion, + TLSOpts: cm.tlsOpts, } } if err := cm.Add(cm.webhookServer); err != nil { diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index 028d929d96..8dfb702788 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -18,6 +18,7 @@ package manager import ( "context" + "crypto/tls" "fmt" "net" "net/http" @@ -32,6 +33,7 @@ import ( "k8s.io/client-go/tools/leaderelection/resourcelock" "k8s.io/client-go/tools/record" "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/cluster" @@ -242,6 +244,13 @@ type Options struct { // It is used to set webhook.Server.CertDir if WebhookServer is not set. CertDir string + // TLSMinVersion is the minimum version of TLS supported by the webhook server. + // Accepts "", "1.0", "1.1", "1.2" and "1.3" only ("" is equivalent to "1.0" for backwards compatibility). + TLSMinVersion string + + // TLSOpts is used to allow configuring the TLS config used for the webhook server. + TLSOpts []func(*tls.Config) + // WebhookServer is an externally configured webhook.Server. By default, // a Manager will create a default server using Port, Host, and CertDir; // if this is set, the Manager will use this server instead. @@ -422,6 +431,8 @@ func New(config *rest.Config, options Options) (Manager, error) { port: options.Port, host: options.Host, certDir: options.CertDir, + tlsMinVersion: options.TLSMinVersion, + tlsOpts: options.TLSOpts, webhookServer: options.WebhookServer, leaseDuration: *options.LeaseDuration, renewDeadline: *options.RenewDeadline, @@ -490,6 +501,10 @@ func (o Options) AndFrom(loader config.ControllerManagerConfiguration) (Options, o.CertDir = newObj.Webhook.CertDir } + if o.TLSMinVersion == "" && newObj.Webhook.TLSMinVersion != "" { + o.TLSMinVersion = newObj.Webhook.TLSMinVersion + } + if newObj.Controller != nil { if o.Controller.CacheSyncTimeout == nil && newObj.Controller.CacheSyncTimeout != nil { o.Controller.CacheSyncTimeout = newObj.Controller.CacheSyncTimeout diff --git a/pkg/manager/manager_test.go b/pkg/manager/manager_test.go index b5aef683e6..976aa843bd 100644 --- a/pkg/manager/manager_test.go +++ b/pkg/manager/manager_test.go @@ -18,6 +18,7 @@ package manager import ( "context" + "crypto/tls" "errors" "fmt" "io" @@ -126,6 +127,9 @@ var _ = Describe("manger.Manager", func() { port := int(6090) leaderElect := false + tlsOptsFuncs := []func(*tls.Config){ + func(config *tls.Config) {}, + } ccfg := &v1alpha1.ControllerManagerConfiguration{ ControllerManagerConfigurationSpec: v1alpha1.ControllerManagerConfigurationSpec{ SyncPeriod: &duration, @@ -148,9 +152,10 @@ var _ = Describe("manger.Manager", func() { LivenessEndpointName: "/livez", }, Webhook: v1alpha1.ControllerWebhook{ - Port: &port, - Host: "localhost", - CertDir: "/certs", + Port: &port, + Host: "localhost", + CertDir: "/certs", + TLSMinVersion: "1.2", }, }, } @@ -174,6 +179,8 @@ var _ = Describe("manger.Manager", func() { Expect(m.Port).To(Equal(port)) Expect(m.Host).To(Equal("localhost")) Expect(m.CertDir).To(Equal("/certs")) + Expect(m.TLSMinVersion).To(Equal("1.2")) + Expect(m.TLSOpts).To(Equal(tlsOptsFuncs)) }) It("should be able to keep Options when cfg.ControllerManagerConfiguration set", func() { @@ -204,13 +211,17 @@ var _ = Describe("manger.Manager", func() { LivenessEndpointName: "/livez", }, Webhook: v1alpha1.ControllerWebhook{ - Port: &port, - Host: "localhost", - CertDir: "/certs", + Port: &port, + Host: "localhost", + CertDir: "/certs", + TLSMinVersion: "1.1", }, }, } + optionsTlSOptsFuncs := []func(*tls.Config){ + func(config *tls.Config) {}, + } m, err := Options{ SyncPeriod: &optDuration, LeaderElection: true, @@ -228,6 +239,8 @@ var _ = Describe("manger.Manager", func() { Port: 8080, Host: "example.com", CertDir: "/pki", + TLSMinVersion: "1.2", + TLSOpts: optionsTlSOptsFuncs, }.AndFrom(&fakeDeferredLoader{ccfg}) Expect(err).To(BeNil()) @@ -247,6 +260,8 @@ var _ = Describe("manger.Manager", func() { Expect(m.Port).To(Equal(8080)) Expect(m.Host).To(Equal("example.com")) Expect(m.CertDir).To(Equal("/pki")) + Expect(m.TLSMinVersion).To(Equal("1.2")) + Expect(m.TLSOpts).To(Equal(optionsTlSOptsFuncs)) }) It("should lazily initialize a webhook server if needed", func() { diff --git a/pkg/webhook/webhook_integration_test.go b/pkg/webhook/webhook_integration_test.go index 3f0f0d42a1..e1ceeaba38 100644 --- a/pkg/webhook/webhook_integration_test.go +++ b/pkg/webhook/webhook_integration_test.go @@ -34,6 +34,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/certwatcher" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/internal/httpserver" @@ -82,9 +83,11 @@ var _ = Describe("Webhook", func() { Context("when running a webhook server with a manager", func() { It("should reject create request for webhook that rejects all requests", func() { m, err := manager.New(cfg, manager.Options{ - Port: testenv.WebhookInstallOptions.LocalServingPort, - Host: testenv.WebhookInstallOptions.LocalServingHost, - CertDir: testenv.WebhookInstallOptions.LocalServingCertDir, + Port: testenv.WebhookInstallOptions.LocalServingPort, + Host: testenv.WebhookInstallOptions.LocalServingHost, + CertDir: testenv.WebhookInstallOptions.LocalServingCertDir, + TLSMinVersion: "1.2", + TLSOpts: []func(*tls.Config){func(config *tls.Config) {}}, }) // we need manager here just to leverage manager.SetFields Expect(err).NotTo(HaveOccurred()) server := m.GetWebhookServer() @@ -105,9 +108,11 @@ var _ = Describe("Webhook", func() { }) It("should reject create request for multi-webhook that rejects all requests", func() { m, err := manager.New(cfg, manager.Options{ - Port: testenv.WebhookInstallOptions.LocalServingPort, - Host: testenv.WebhookInstallOptions.LocalServingHost, - CertDir: testenv.WebhookInstallOptions.LocalServingCertDir, + Port: testenv.WebhookInstallOptions.LocalServingPort, + Host: testenv.WebhookInstallOptions.LocalServingHost, + CertDir: testenv.WebhookInstallOptions.LocalServingCertDir, + TLSMinVersion: "1.2", + TLSOpts: []func(*tls.Config){func(config *tls.Config) {}}, }) // we need manager here just to leverage manager.SetFields Expect(err).NotTo(HaveOccurred()) server := m.GetWebhookServer()