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
38 changes: 38 additions & 0 deletions pkg/tls/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ var (
DefaultTLSCiphers = configv1.TLSProfiles[configv1.TLSProfileIntermediateType].Ciphers //nolint:gochecknoglobals
// DefaultMinTLSVersion is the default minimum TLS version for API servers.
DefaultMinTLSVersion = configv1.TLSProfiles[configv1.TLSProfileIntermediateType].MinTLSVersion //nolint:gochecknoglobals

// HTTP2NextProtos are the ALPN protocols advertised when HTTP/2 is enabled,
// with HTTP/1.1 fallback.
HTTP2NextProtos = []string{"h2", "http/1.1"} //nolint:gochecknoglobals

// HTTP1NextProtos are the ALPN protocols advertised when HTTP/2 is disabled,
// restricting negotiation to HTTP/1.1 only. This provides defense-in-depth
// against HTTP/2 Rapid Reset (CVE-2023-44487, CVE-2023-39325) alongside
// the primary fixes in Go 1.21.3+ and golang.org/x/net v0.17.0+.
HTTP1NextProtos = []string{"http/1.1"} //nolint:gochecknoglobals
)

// FetchAPIServerTLSProfile fetches the TLS profile spec configured in APIServer.
Expand Down Expand Up @@ -131,6 +141,34 @@ func NewTLSConfigFromProfile(profile configv1.TLSProfileSpec) (tlsConfig func(*t
}, unsupportedCiphers
}

// SetNextProtos returns a TLS configuration function that sets the ALPN
// protocol negotiation list on a tls.Config. Empty strings are silently
// ignored, which allows conditional protocol inclusion.
// The returned function is intended to be used with controller-runtime's TLSOpts.
//
// Example:
//
// // Disable HTTP/2:
// openshifttls.SetNextProtos("http/1.1")
//
// // Enable HTTP/2 with fallback:
// openshifttls.SetNextProtos("h2", "http/1.1")
//
// // Using the well-known protocol lists:
// openshifttls.SetNextProtos(openshifttls.HTTP1NextProtos...)
// openshifttls.SetNextProtos(openshifttls.HTTP2NextProtos...)
func SetNextProtos(protos ...string) func(*tls.Config) {
var p []string
for _, proto := range protos {
if proto != "" {
p = append(p, proto)
}
}
return func(c *tls.Config) {
c.NextProtos = p
}
}

// cipherCode returns the TLS cipher code for an OpenSSL or IANA cipher name.
// Returns 0 if the cipher is not supported.
func cipherCode(cipher string) uint16 {
Expand Down
88 changes: 88 additions & 0 deletions pkg/tls/tls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,94 @@ var _ = Describe("cipherCodes", func() {
})
})

var _ = Describe("SetNextProtos", func() {
It("should set NextProtos on tls.Config", func() {
fn := SetNextProtos("h2", "http/1.1")
c := &tls.Config{}
fn(c)
Expect(c.NextProtos).To(Equal([]string{"h2", "http/1.1"}))
})

It("should handle a single protocol", func() {
fn := SetNextProtos("http/1.1")
c := &tls.Config{}
fn(c)
Expect(c.NextProtos).To(Equal([]string{"http/1.1"}))
})

It("should handle no protocols by preserving nil semantics", func() {
fn := SetNextProtos()
c := &tls.Config{}
fn(c)
Expect(c.NextProtos).To(BeNil())
})

It("should ignore empty strings", func() {
fn := SetNextProtos("", "http/1.1")
c := &tls.Config{}
fn(c)
Expect(c.NextProtos).To(Equal([]string{"http/1.1"}))
})

It("should return nil when all protocols are empty strings", func() {
fn := SetNextProtos("", "")
c := &tls.Config{}
fn(c)
Expect(c.NextProtos).To(BeNil())
})

It("should not be affected by modification of the original slice", func() {
protos := []string{"h2", "http/1.1"}
fn := SetNextProtos(protos...)
protos[0] = "modified"
c := &tls.Config{}
fn(c)
Expect(c.NextProtos).To(Equal([]string{"h2", "http/1.1"}))
})

It("should override any existing NextProtos", func() {
fn := SetNextProtos("h2", "http/1.1")
c := &tls.Config{NextProtos: []string{"old-proto"}}
fn(c)
Expect(c.NextProtos).To(Equal([]string{"h2", "http/1.1"}))
})

It("should compose with NewTLSConfigFromProfile", func() {
profile := *configv1.TLSProfiles[configv1.TLSProfileIntermediateType]
tlsConfigFn, _ := NewTLSConfigFromProfile(profile)

c := &tls.Config{}
tlsConfigFn(c)
SetNextProtos("http/1.1")(c)

Expect(c.MinVersion).To(Equal(uint16(tls.VersionTLS12)))
Expect(c.CipherSuites).NotTo(BeEmpty())
Expect(c.NextProtos).To(Equal([]string{"http/1.1"}))
})

It("should work with the well-known protocol lists", func() {
fn := SetNextProtos(HTTP2NextProtos...)
c := &tls.Config{}
fn(c)
Expect(c.NextProtos).To(Equal([]string{"h2", "http/1.1"}))

fn = SetNextProtos(HTTP1NextProtos...)
c = &tls.Config{}
fn(c)
Expect(c.NextProtos).To(Equal([]string{"http/1.1"}))
})
})

var _ = Describe("ALPN protocol variables", func() {
It("HTTP2NextProtos should contain h2 and http/1.1", func() {
Expect(HTTP2NextProtos).To(Equal([]string{"h2", "http/1.1"}))
})

It("HTTP1NextProtos should contain only http/1.1", func() {
Expect(HTTP1NextProtos).To(Equal([]string{"http/1.1"}))
})
})

var _ = Describe("NewTLSConfigFromProfile", func() {
Context("when MinTLSVersion is TLS 1.2", func() {
It("should set MinVersion and CipherSuites", func() {
Expand Down