Skip to content

Commit dcd2d46

Browse files
committed
Add APIServer TLS controller for OpenShift cluster-wide TLS configuration
Implements a controller that watches the OpenShift APIServer resource and provides thread-safe access to TLS configuration for HTTPS servers/clients. The controller is modeled after the existing proxy controller. This is intended to be used by the OLM operator, Catalog operator and marketplace operator (but it will need to be copied). It will also be used by the downstream PSM component. Signed-off-by: Todd Short <todd.short@me.com> Assisted-By: Claude
1 parent c293d03 commit dcd2d46

15 files changed

Lines changed: 2385 additions & 14 deletions

File tree

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ require (
2020
github.com/onsi/gomega v1.38.3
2121
github.com/openshift/api v0.0.0-20251111193948-50e2ece149d7
2222
github.com/openshift/client-go v0.0.0-20251015124057-db0dee36e235
23+
github.com/openshift/library-go v0.0.0-20260108135436-db8dbd64c462
2324
github.com/operator-framework/api v0.37.0
2425
github.com/operator-framework/operator-registry v1.61.0
2526
github.com/otiai10/copy v1.14.1

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
223223
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
224224
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
225225
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
226+
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
226227
github.com/hashicorp/golang-lru/arc/v2 v2.0.7 h1:QxkVTxwColcduO+LP7eJO56r2hFiG8zEbfAAzRv52KQ=
227228
github.com/hashicorp/golang-lru/arc/v2 v2.0.7/go.mod h1:Pe7gBlGdc8clY5LJ0LpJXMt5AmgmWNH1g+oFFVUHOEc=
228229
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
@@ -322,6 +323,8 @@ github.com/openshift/api v0.0.0-20251111193948-50e2ece149d7 h1:MemawsK6SpxEaE5y0
322323
github.com/openshift/api v0.0.0-20251111193948-50e2ece149d7/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
323324
github.com/openshift/client-go v0.0.0-20251015124057-db0dee36e235 h1:9JBeIXmnHlpXTQPi7LPmu1jdxznBhAE7bb1K+3D8gxY=
324325
github.com/openshift/client-go v0.0.0-20251015124057-db0dee36e235/go.mod h1:L49W6pfrZkfOE5iC1PqEkuLkXG4W0BX4w8b+L2Bv7fM=
326+
github.com/openshift/library-go v0.0.0-20260108135436-db8dbd64c462 h1:zX9Od4Jg8sVmwQLwk6Vd+BX7tcyC/462FVvDdzHEPPk=
327+
github.com/openshift/library-go v0.0.0-20260108135436-db8dbd64c462/go.mod h1:nIzWQQE49XbiKizVnVOip9CEB7HJ0hoJwNi3g3YKnKc=
325328
github.com/operator-framework/api v0.37.0 h1:2XCMWitBnumtJTqzip6LQKUwpM2pXVlt3gkpdlkbaCE=
326329
github.com/operator-framework/api v0.37.0/go.mod h1:NZs4vB+Jiamyv3pdPDjZtuC4U7KX0eq4z2r5hKY5fUA=
327330
github.com/operator-framework/operator-registry v1.61.0 h1:LgX6lP5hUHfpMTMygsnySc7PKxibzqIoqWUm6NPWl2M=

pkg/lib/apiserver/available.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package apiserver
2+
3+
import (
4+
"errors"
5+
"strings"
6+
7+
"k8s.io/apimachinery/pkg/runtime/schema"
8+
apidiscovery "k8s.io/client-go/discovery"
9+
)
10+
11+
const (
12+
// This is the error message thrown by ServerSupportsVersion function
13+
// when an API version is not supported by the server.
14+
notSupportedErrorMessage = "server does not support API version"
15+
)
16+
17+
// IsAPIAvailable returns true if OpenShift config API is present on the cluster.
18+
// Otherwise, supported is set to false.
19+
func IsAPIAvailable(discovery apidiscovery.DiscoveryInterface) (supported bool, err error) {
20+
if discovery == nil {
21+
err = errors.New("discovery interface can not be <nil>")
22+
return
23+
}
24+
25+
opStatusGV := schema.GroupVersion{
26+
Group: "config.openshift.io",
27+
Version: "v1",
28+
}
29+
if discoveryErr := apidiscovery.ServerSupportsVersion(discovery, opStatusGV); discoveryErr != nil {
30+
if strings.Contains(discoveryErr.Error(), notSupportedErrorMessage) {
31+
return
32+
}
33+
34+
err = discoveryErr
35+
return
36+
}
37+
38+
supported = true
39+
return
40+
}

pkg/lib/apiserver/querier.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package apiserver
2+
3+
import (
4+
"crypto/tls"
5+
"fmt"
6+
)
7+
8+
// NoopQuerier returns an instance of noopQuerier. It's used for upstream where
9+
// we don't have any cluster APIServer configuration.
10+
func NoopQuerier() Querier {
11+
return &noopQuerier{}
12+
}
13+
14+
// Querier is an interface that wraps the QueryTLSConfig method.
15+
//
16+
// QueryTLSConfig updates the provided TLS configuration with cluster-wide
17+
// TLS security profile settings (MinVersion, CipherSuites, PreferServerCipherSuites).
18+
type Querier interface {
19+
QueryTLSConfig(config *tls.Config) error
20+
}
21+
22+
type noopQuerier struct {
23+
}
24+
25+
// QueryTLSConfig applies secure default TLS settings to the provided config.
26+
// This is used on non-OpenShift clusters where there is no APIServer resource,
27+
// but we still want to ensure secure TLS configuration.
28+
func (*noopQuerier) QueryTLSConfig(config *tls.Config) error {
29+
if config == nil {
30+
return fmt.Errorf("tls.Config cannot be nil")
31+
}
32+
33+
// Apply secure defaults for non-OpenShift clusters
34+
return ApplySecureDefaults(config)
35+
}

pkg/lib/apiserver/querier_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package apiserver_test
2+
3+
import (
4+
"crypto/tls"
5+
"testing"
6+
7+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver"
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestNoopQuerier_QueryTLSConfig(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
config *tls.Config
16+
expectError bool
17+
errorMsg string
18+
}{
19+
{
20+
name: "WithNilConfig",
21+
config: nil,
22+
expectError: true,
23+
errorMsg: "tls.Config cannot be nil",
24+
},
25+
{
26+
name: "WithEmptyConfig",
27+
config: &tls.Config{},
28+
expectError: false,
29+
},
30+
{
31+
name: "WithPartialConfig",
32+
config: &tls.Config{
33+
MinVersion: tls.VersionTLS12,
34+
},
35+
expectError: false,
36+
},
37+
}
38+
39+
for _, tt := range tests {
40+
t.Run(tt.name, func(t *testing.T) {
41+
querier := apiserver.NoopQuerier()
42+
err := querier.QueryTLSConfig(tt.config)
43+
44+
if tt.expectError {
45+
require.Error(t, err)
46+
assert.Contains(t, err.Error(), tt.errorMsg)
47+
} else {
48+
require.NoError(t, err)
49+
// Verify secure defaults are applied
50+
assert.NotZero(t, tt.config.MinVersion, "MinVersion should be set to a default")
51+
assert.NotEmpty(t, tt.config.CipherSuites, "CipherSuites should be set to defaults")
52+
assert.True(t, tt.config.PreferServerCipherSuites, "PreferServerCipherSuites should be true")
53+
}
54+
})
55+
}
56+
}
57+
58+
func TestNoopQuerier_AppliesSecureDefaults(t *testing.T) {
59+
querier := apiserver.NoopQuerier()
60+
config := &tls.Config{}
61+
62+
err := querier.QueryTLSConfig(config)
63+
require.NoError(t, err)
64+
65+
// Verify secure defaults
66+
assert.GreaterOrEqual(t, config.MinVersion, uint16(tls.VersionTLS12), "Should use at least TLS 1.2")
67+
assert.NotEmpty(t, config.CipherSuites, "Should have cipher suites configured")
68+
69+
// Verify cipher suites are valid
70+
for _, cipher := range config.CipherSuites {
71+
assert.NotZero(t, cipher, "Cipher suite should not be zero")
72+
}
73+
}
74+
75+
func TestNoopQuerier_DoesNotOverwriteNonZeroMinVersion(t *testing.T) {
76+
querier := apiserver.NoopQuerier()
77+
config := &tls.Config{
78+
MinVersion: tls.VersionTLS13,
79+
}
80+
81+
err := querier.QueryTLSConfig(config)
82+
require.NoError(t, err)
83+
84+
// MinVersion should be preserved if already set
85+
assert.Equal(t, uint16(tls.VersionTLS13), config.MinVersion, "Should preserve existing MinVersion")
86+
}

0 commit comments

Comments
 (0)