diff --git a/pkg/webhook/server.go b/pkg/webhook/server.go index 5bdc847e17..8c5f2ac4cb 100644 --- a/pkg/webhook/server.go +++ b/pkg/webhook/server.go @@ -41,6 +41,9 @@ import ( // DefaultPort is the default port that the webhook server serves. var DefaultPort = 9443 +// ExportedTLSConfig is used in unit tests to ensure propagation of tls related configurables to server. +var ExportedTLSConfig *tls.Config + // Server is an admission webhook server that can serve traffic and // generates related k8s resources for deploying. // @@ -76,6 +79,10 @@ type Server struct { // "", "1.0", "1.1", "1.2" and "1.3" only ("" is equivalent to "1.0" for backwards compatibility) TLSMinVersion string + // TLSCiphers is used to specify the cipher algorithms that are negotiated + // during the TLS handshake, refer to https://pkg.go.dev/crypto/tls#CipherSuites + TLSCiphers []string + // WebhookMux is the multiplexer that handles different webhooks. WebhookMux *http.ServeMux @@ -204,6 +211,23 @@ func tlsVersion(version string) (uint16, error) { } } +// tlsCipherSuitesIDs converts cipher suite names +// to the values accepted by tls.Config (for example 0xc02b). +func tlsCipherSuitesIDs(names []string) []uint16 { + idByName := map[string]uint16{} + for _, cipherSuite := range tls.CipherSuites() { + idByName[cipherSuite.Name] = cipherSuite.ID + } + + ids := []uint16{} + for _, name := range names { + if id, ok := idByName[name]; ok { + ids = append(ids, id) + } + } + return ids +} + // Start runs the server. // It will install the webhook related resources depend on the server configuration. func (s *Server) Start(ctx context.Context) error { @@ -254,6 +278,13 @@ func (s *Server) Start(ctx context.Context) error { cfg.ClientAuth = tls.RequireAndVerifyClientCert } + // load passed in TLS ciphers + if s.TLSCiphers != nil { + cipherSuites := tlsCipherSuitesIDs(s.TLSCiphers) + cfg.CipherSuites = cipherSuites + } + ExportedTLSConfig = cfg + listener, err := tls.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port)), cfg) if err != nil { return err diff --git a/pkg/webhook/server_test.go b/pkg/webhook/server_test.go index 2ed986e405..cef84d52a7 100644 --- a/pkg/webhook/server_test.go +++ b/pkg/webhook/server_test.go @@ -18,6 +18,7 @@ package webhook_test import ( "context" + "crypto/tls" "fmt" "io" "net" @@ -186,7 +187,7 @@ var _ = Describe("Webhook Server", func() { }) }) - It("should serve be able to serve in unmanaged mode", func() { + It("should be able to serve in unmanaged mode", func() { server = &webhook.Server{ Host: servingOpts.LocalServingHost, Port: servingOpts.LocalServingPort, @@ -207,6 +208,45 @@ var _ = Describe("Webhook Server", func() { ctxCancel() Eventually(doneCh, "4s").Should(BeClosed()) }) + + It("should respect passed in TLS configurations", func() { + ciphers := []string{ + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256", + "ECDHE-ECDSA-AES128-GCM-SHA256", + "ECDHE-RSA-AES128-GCM-SHA256", + "ECDHE-ECDSA-AES256-GCM-SHA384", + "ECDHE-RSA-AES256-GCM-SHA384", + "ECDHE-ECDSA-CHACHA20-POLY1305", + "ECDHE-RSA-CHACHA20-POLY1305", + "DHE-RSA-AES128-GCM-SHA256", + "DHE-RSA-AES256-GCM-SHA384", + } + server = &webhook.Server{ + Host: servingOpts.LocalServingHost, + Port: servingOpts.LocalServingPort, + CertDir: servingOpts.LocalServingCertDir, + TLSMinVersion: "1.2", + TLSCiphers: ciphers, + } + server.Register("/somepath", &testHandler{}) + doneCh := genericStartServer(func(ctx context.Context) { + Expect(server.StartStandalone(ctx, scheme.Scheme)) + }) + + Eventually(func() ([]byte, error) { + resp, err := client.Get(fmt.Sprintf("https://%s/somepath", testHostPort)) + Expect(err).NotTo(HaveOccurred()) + defer resp.Body.Close() + return io.ReadAll(resp.Body) + }).Should(Equal([]byte("gadzooks!"))) + Expect(webhook.ExportedTLSConfig.MinVersion).To(Equal(uint16(tls.VersionTLS12))) + Expect(webhook.ExportedTLSConfig.CipherSuites).To(ContainElement(tls.TLS_AES_128_GCM_SHA256)) + + ctxCancel() + Eventually(doneCh, "4s").Should(BeClosed()) + }) }) type testHandler struct {