Skip to content
Merged
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
1 change: 1 addition & 0 deletions manifests/03-rbac-role-cluster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ rules:
- apiGroups:
- config.openshift.io
resources:
- apiservers
- authentications
- oauths
- infrastructures
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package configobservercontroller

import (
"k8s.io/client-go/tools/cache"

configinformers "github.com/openshift/client-go/config/informers/externalversions"
"github.com/openshift/console-operator/pkg/console/configobservation"
"github.com/openshift/library-go/pkg/controller/factory"
"github.com/openshift/library-go/pkg/operator/configobserver"
libgoapiserver "github.com/openshift/library-go/pkg/operator/configobserver/apiserver"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/library-go/pkg/operator/resourcesynccontroller"
"github.com/openshift/library-go/pkg/operator/v1helpers"
)

type ConfigObserver struct {
factory.Controller
}

// NewConfigObserver creates a config observer controller that watches
// the APIServer resource and writes TLS configuration to the Console CR's
// observedConfig field.
func NewConfigObserver(
operatorClient v1helpers.OperatorClient,
configInformer configinformers.SharedInformerFactory,
resourceSyncer resourcesynccontroller.ResourceSyncer,
eventRecorder events.Recorder,
) *ConfigObserver {
informers := []factory.Informer{
operatorClient.Informer(),
configInformer.Config().V1().APIServers().Informer(),
}

c := &ConfigObserver{
Controller: configobserver.NewConfigObserver(
"console",
operatorClient,
eventRecorder,
configobservation.Listers{
APIServerLister_: configInformer.Config().V1().APIServers().Lister(),
ResourceSync: resourceSyncer,
PreRunCachesSynced: []cache.InformerSynced{
operatorClient.Informer().HasSynced,
configInformer.Config().V1().APIServers().Informer().HasSynced,
},
},
informers,
// Observer functions
libgoapiserver.ObserveTLSSecurityProfile,
),
}

return c
}
26 changes: 26 additions & 0 deletions pkg/console/configobservation/listers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package configobservation

import (
"k8s.io/client-go/tools/cache"

configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
"github.com/openshift/library-go/pkg/operator/resourcesynccontroller"
)

type Listers struct {
APIServerLister_ configlistersv1.APIServerLister
ResourceSync resourcesynccontroller.ResourceSyncer
PreRunCachesSynced []cache.InformerSynced
}

func (l Listers) APIServerLister() configlistersv1.APIServerLister {
return l.APIServerLister_
}

func (l Listers) ResourceSyncer() resourcesynccontroller.ResourceSyncer {
return l.ResourceSync
}

func (l Listers) PreRunHasSynced() []cache.InformerSynced {
return l.PreRunCachesSynced
}
33 changes: 33 additions & 0 deletions pkg/console/operator/sync_v400.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package operator

import (
"context"
"encoding/json"
"fmt"
"net/url"
"os"
Expand Down Expand Up @@ -444,6 +445,11 @@ func (co *consoleOperator) SyncConfigMap(
}
}

tlsMinVersion, tlsCiphers, tlsErr := getTLSConfigFromObservedConfig(operatorConfig)
if tlsErr != nil {
return nil, "FailedGetTLSConfig", tlsErr
}

defaultConfigmap, _, err := configmapsub.DefaultConfigMap(
operatorConfig,
consoleConfig,
Expand All @@ -462,6 +468,8 @@ func (co *consoleOperator) SyncConfigMap(
techPreviewEnabled,
olmLifecycleMetadataEnabled,
additionalHosts,
tlsMinVersion,
tlsCiphers,
)
if err != nil {
return nil, "FailedConsoleConfigBuilder", err
Expand Down Expand Up @@ -943,3 +951,28 @@ func (co *consoleOperator) syncSessionSecret(
})
return secret, err
}

// getTLSConfigFromObservedConfig reads TLS configuration from the Console CR's observedConfig field.
func getTLSConfigFromObservedConfig(operatorConfig *operatorv1.Console) (configv1.TLSProtocolVersion, []string, error) {
if operatorConfig == nil || operatorConfig.Spec.ObservedConfig.Raw == nil {
// Not an error - the config observer hasn't injected the config yet
return "", nil, nil
}

observedConfig := map[string]interface{}{}
if err := json.Unmarshal(operatorConfig.Spec.ObservedConfig.Raw, &observedConfig); err != nil {
return "", nil, fmt.Errorf("failed to unmarshal observedConfig: %w", err)
}

minTLSVersion, _, err := unstructured.NestedString(observedConfig, "servingInfo", "minTLSVersion")
if err != nil {
return "", nil, fmt.Errorf("failed to read servingInfo.minTLSVersion: %w", err)
}

cipherSuites, _, err := unstructured.NestedStringSlice(observedConfig, "servingInfo", "cipherSuites")
if err != nil {
return "", nil, fmt.Errorf("failed to read servingInfo.cipherSuites: %w", err)
}

return configv1.TLSProtocolVersion(minTLSVersion), cipherSuites, nil
}
115 changes: 115 additions & 0 deletions pkg/console/operator/sync_v400_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
appsv1listers "k8s.io/client-go/listers/apps/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
Expand Down Expand Up @@ -729,3 +730,117 @@ func TestEvaluateDeploymentAvailability(t *testing.T) {
}
})
}

func mustMarshal(v interface{}) []byte {
data, err := json.Marshal(v)
if err != nil {
panic(err)
}
return data
}

func makeOperatorConfigWithObservedConfig(observedConfig runtime.RawExtension) *operatorv1.Console {
return &operatorv1.Console{
Spec: operatorv1.ConsoleSpec{
OperatorSpec: operatorv1.OperatorSpec{
ObservedConfig: observedConfig,
},
},
}
}

func TestGetTLSConfigFromObservedConfig(t *testing.T) {
tests := []struct {
name string
operatorConfig *operatorv1.Console
wantMinTLSVersion configv1.TLSProtocolVersion
wantCiphers []string
wantError bool
}{
{
name: "nil operator config returns empty values",
},
{
name: "nil observedConfig.Raw returns empty values",
operatorConfig: makeOperatorConfigWithObservedConfig(runtime.RawExtension{}),
},
{
name: "invalid JSON returns error",
operatorConfig: makeOperatorConfigWithObservedConfig(runtime.RawExtension{
Raw: []byte(`{invalid json}`),
}),
wantError: true,
},
{
name: "valid config with both minTLSVersion and cipherSuites",
operatorConfig: makeOperatorConfigWithObservedConfig(runtime.RawExtension{
Raw: mustMarshal(map[string]interface{}{
"servingInfo": map[string]interface{}{
"minTLSVersion": "VersionTLS12",
"cipherSuites": []string{
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
},
},
}),
}),
wantMinTLSVersion: configv1.VersionTLS12,
wantCiphers: []string{"TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384"},
},
{
name: "valid config with only minTLSVersion",
operatorConfig: makeOperatorConfigWithObservedConfig(runtime.RawExtension{
Raw: mustMarshal(map[string]interface{}{
"servingInfo": map[string]interface{}{
"minTLSVersion": "VersionTLS13",
},
}),
}),
wantMinTLSVersion: configv1.VersionTLS13,
},
{
name: "valid config with only cipherSuites",
operatorConfig: makeOperatorConfigWithObservedConfig(runtime.RawExtension{
Raw: mustMarshal(map[string]interface{}{
"servingInfo": map[string]interface{}{
"cipherSuites": []string{"TLS_AES_128_GCM_SHA256"},
},
}),
}),
wantCiphers: []string{"TLS_AES_128_GCM_SHA256"},
},
{
name: "empty observedConfig returns empty values",
operatorConfig: makeOperatorConfigWithObservedConfig(runtime.RawExtension{
Raw: mustMarshal(map[string]interface{}{}),
}),
},
{
name: "observedConfig with no servingInfo returns empty values",
operatorConfig: makeOperatorConfigWithObservedConfig(runtime.RawExtension{
Raw: mustMarshal(map[string]interface{}{
"otherField": "value",
}),
}),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
minTLSVersion, ciphers, err := getTLSConfigFromObservedConfig(tt.operatorConfig)

if (err != nil) != tt.wantError {
t.Errorf("getTLSConfigFromObservedConfig() error = %v, wantError %v", err, tt.wantError)
return
}

if minTLSVersion != tt.wantMinTLSVersion {
t.Errorf("getTLSConfigFromObservedConfig() minTLSVersion = %v, want %v", minTLSVersion, tt.wantMinTLSVersion)
}

if diff := deep.Equal(ciphers, tt.wantCiphers); diff != nil {
t.Errorf("getTLSConfigFromObservedConfig() ciphers diff: %v", diff)
}
})
}
}
10 changes: 10 additions & 0 deletions pkg/console/starter/starter.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
operatorv1 "github.com/openshift/api/operator/v1"
"github.com/openshift/console-operator/pkg/api"

"github.com/openshift/console-operator/pkg/console/configobservation/configobservercontroller"
"github.com/openshift/console-operator/pkg/console/controllers/clidownloads"
"github.com/openshift/console-operator/pkg/console/controllers/clioidcclientstatus"
"github.com/openshift/console-operator/pkg/console/controllers/downloadsdeployment"
Expand Down Expand Up @@ -635,6 +636,14 @@ func RunOperator(ctx context.Context, controllerContext *controllercmd.Controlle
logLevelController := loglevel.NewClusterOperatorLoggingController(operatorClient, controllerContext.EventRecorder)
managementStateController := managementstatecontroller.NewOperatorManagementStateController(api.ClusterOperatorName, operatorClient, controllerContext.EventRecorder)

// Config observer watches APIServer and writes TLS config to Console CR's observedConfig
configObserver := configobservercontroller.NewConfigObserver(
operatorClient,
configInformers,
resourceSyncer,
recorder,
)

for _, informer := range []interface {
Start(stopCh <-chan struct{})
}{
Expand Down Expand Up @@ -664,6 +673,7 @@ func RunOperator(ctx context.Context, controllerContext *controllercmd.Controlle
logLevelController,
managementStateController,
configUpgradeableController,
configObserver,
consoleServiceAccountController,
downloadsServiceAccountController,
consoleServiceController,
Expand Down
3 changes: 3 additions & 0 deletions pkg/console/subresource/configmap/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ func DefaultConfigMap(
techPreviewEnabled bool,
olmLifecycleMetadataEnabled bool,
additionalHosts []string,
tlsMinVersion configv1.TLSProtocolVersion,
tlsCiphers []string,
) (consoleConfigMap *corev1.ConfigMap, unsupportedOverridesHaveMerged bool, err error) {

apiServerURL := infrastructuresub.GetAPIServerURL(infrastructureConfig)
Expand Down Expand Up @@ -112,6 +114,7 @@ func DefaultConfigMap(
TechPreviewEnabled(techPreviewEnabled).
OLMLifecycleMetadataEnabled(olmLifecycleMetadataEnabled).
AdditionalHosts(additionalHosts).
TLSConfig(tlsMinVersion, tlsCiphers).
ConfigYAML()
if err != nil {
klog.Errorf("failed to generate user-defined console-config: %v", err)
Expand Down
8 changes: 5 additions & 3 deletions pkg/console/subresource/configmap/configmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1304,9 +1304,11 @@ providers: {}
tt.args.copiedCSVsDisabled,
tt.args.telemetryConfig,
tt.args.rt.Spec.Host,
false, // techPreviewEnabled - default to false for tests
false, // olmLifecycleMetadataEnabled - default to false for tests
nil, // additionalHosts
false, // techPreviewEnabled - default to false for tests
false, // olmLifecycleMetadataEnabled - default to false for tests
nil, // additionalHosts
"", // tlsMinVersion - empty for legacy tests
[]string{}, // tlsCiphers - empty for legacy tests
)

// marshall the exampleYaml to map[string]interface{} so we can use it in diff below
Expand Down
10 changes: 7 additions & 3 deletions pkg/console/subresource/configmap/tech_preview_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ func TestTechPreviewEnabled(t *testing.T) {
map[string]string{}, // telemetryConfig
"console.test.cluster", // consoleHost
tt.args.techPreviewEnabled,
false, // olmLifecycleMetadataEnabled
nil, // additionalHosts
false, // olmLifecycleMetadataEnabled
nil, // additionalHosts
"", // tlsMinVersion - empty for legacy tests
[]string{}, // tlsCiphers
)

if err != nil {
Expand Down Expand Up @@ -125,7 +127,9 @@ func TestOLMLifecycleMetadataEnabled(t *testing.T) {
"console.test.cluster", // consoleHost
false, // techPreviewEnabled
tt.args.olmLifecycleMetadataEnabled,
nil, // additionalHosts
nil, // additionalHosts
"", // tlsMinVersion - empty for legacy tests
[]string{}, // tlsCiphers
)

if err != nil {
Expand Down
Loading