From eb37b61911e866b5cc31527c944321f997c1c5f0 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 5 Aug 2026 13:13:36 -0700 Subject: [PATCH] Revert "TRT-2858: Revert "Merge pull request #1196 from ingvagabund/tls-injection-to-console"" --- manifests/03-rbac-role-cluster.yaml | 1 + .../observe_config_controller.go | 54 ++++++++ pkg/console/configobservation/listers.go | 26 ++++ pkg/console/operator/sync_v400.go | 33 +++++ pkg/console/operator/sync_v400_test.go | 115 ++++++++++++++++++ pkg/console/starter/starter.go | 10 ++ .../subresource/configmap/configmap.go | 3 + .../subresource/configmap/configmap_test.go | 8 +- .../configmap/tech_preview_test.go | 10 +- .../subresource/configmap/tls_config_test.go | 95 +++++++++++++++ .../consoleserver/config_builder.go | 16 +++ .../subresource/consoleserver/types.go | 12 +- .../operator/configobserver/apiserver/OWNERS | 8 ++ .../configobserver/apiserver/listers.go | 9 ++ .../configobserver/apiserver/observe_audit.go | 88 ++++++++++++++ .../configobserver/apiserver/observe_cors.go | 75 ++++++++++++ .../apiserver/observe_tlssecurityprofile.go | 109 +++++++++++++++++ vendor/modules.txt | 1 + 18 files changed, 661 insertions(+), 12 deletions(-) create mode 100644 pkg/console/configobservation/configobservercontroller/observe_config_controller.go create mode 100644 pkg/console/configobservation/listers.go create mode 100644 pkg/console/subresource/configmap/tls_config_test.go create mode 100644 vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/OWNERS create mode 100644 vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/listers.go create mode 100644 vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/observe_audit.go create mode 100644 vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/observe_cors.go create mode 100644 vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/observe_tlssecurityprofile.go diff --git a/manifests/03-rbac-role-cluster.yaml b/manifests/03-rbac-role-cluster.yaml index c7a6c19d63..b56d59a1d8 100644 --- a/manifests/03-rbac-role-cluster.yaml +++ b/manifests/03-rbac-role-cluster.yaml @@ -37,6 +37,7 @@ rules: - apiGroups: - config.openshift.io resources: + - apiservers - authentications - oauths - infrastructures diff --git a/pkg/console/configobservation/configobservercontroller/observe_config_controller.go b/pkg/console/configobservation/configobservercontroller/observe_config_controller.go new file mode 100644 index 0000000000..3b3542be28 --- /dev/null +++ b/pkg/console/configobservation/configobservercontroller/observe_config_controller.go @@ -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 +} diff --git a/pkg/console/configobservation/listers.go b/pkg/console/configobservation/listers.go new file mode 100644 index 0000000000..9a856b4d09 --- /dev/null +++ b/pkg/console/configobservation/listers.go @@ -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 +} diff --git a/pkg/console/operator/sync_v400.go b/pkg/console/operator/sync_v400.go index ea49636161..491107c45d 100644 --- a/pkg/console/operator/sync_v400.go +++ b/pkg/console/operator/sync_v400.go @@ -2,6 +2,7 @@ package operator import ( "context" + "encoding/json" "fmt" "net/url" "os" @@ -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, @@ -462,6 +468,8 @@ func (co *consoleOperator) SyncConfigMap( techPreviewEnabled, olmLifecycleMetadataEnabled, additionalHosts, + tlsMinVersion, + tlsCiphers, ) if err != nil { return nil, "FailedConsoleConfigBuilder", err @@ -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 +} diff --git a/pkg/console/operator/sync_v400_test.go b/pkg/console/operator/sync_v400_test.go index 0b080548e9..63d6dd84ff 100644 --- a/pkg/console/operator/sync_v400_test.go +++ b/pkg/console/operator/sync_v400_test.go @@ -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" @@ -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) + } + }) + } +} diff --git a/pkg/console/starter/starter.go b/pkg/console/starter/starter.go index 0edd2552fc..37f61c960e 100644 --- a/pkg/console/starter/starter.go +++ b/pkg/console/starter/starter.go @@ -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" @@ -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{}) }{ @@ -664,6 +673,7 @@ func RunOperator(ctx context.Context, controllerContext *controllercmd.Controlle logLevelController, managementStateController, configUpgradeableController, + configObserver, consoleServiceAccountController, downloadsServiceAccountController, consoleServiceController, diff --git a/pkg/console/subresource/configmap/configmap.go b/pkg/console/subresource/configmap/configmap.go index c41fa1e28a..b42cf03c9e 100644 --- a/pkg/console/subresource/configmap/configmap.go +++ b/pkg/console/subresource/configmap/configmap.go @@ -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) @@ -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) diff --git a/pkg/console/subresource/configmap/configmap_test.go b/pkg/console/subresource/configmap/configmap_test.go index 4e7ef3a637..4480ac248d 100644 --- a/pkg/console/subresource/configmap/configmap_test.go +++ b/pkg/console/subresource/configmap/configmap_test.go @@ -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 diff --git a/pkg/console/subresource/configmap/tech_preview_test.go b/pkg/console/subresource/configmap/tech_preview_test.go index ec504a0a86..c7a7ccbcf1 100644 --- a/pkg/console/subresource/configmap/tech_preview_test.go +++ b/pkg/console/subresource/configmap/tech_preview_test.go @@ -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 { @@ -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 { diff --git a/pkg/console/subresource/configmap/tls_config_test.go b/pkg/console/subresource/configmap/tls_config_test.go new file mode 100644 index 0000000000..15910dea80 --- /dev/null +++ b/pkg/console/subresource/configmap/tls_config_test.go @@ -0,0 +1,95 @@ +package configmap + +import ( + "testing" + + configv1 "github.com/openshift/api/config/v1" + consolev1 "github.com/openshift/api/console/v1" + "github.com/openshift/console-operator/pkg/console/subresource/consoleserver" + "gopkg.in/yaml.v2" + corev1 "k8s.io/api/core/v1" +) + +func TestTLSConfigInjection(t *testing.T) { + tests := []struct { + name string + tlsMinVersion configv1.TLSProtocolVersion + tlsCiphers []string + wantMinTLS string + }{ + { + name: "TLS 1.2 with ciphers", + tlsMinVersion: configv1.VersionTLS12, + tlsCiphers: []string{"TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384"}, + wantMinTLS: "VersionTLS12", + }, + { + name: "TLS 1.3 with no custom ciphers", + tlsMinVersion: configv1.VersionTLS13, + tlsCiphers: []string{}, + wantMinTLS: "VersionTLS13", + }, + { + name: "Intermediate profile ciphers", + tlsMinVersion: configv1.TLSProfiles[configv1.TLSProfileIntermediateType].MinTLSVersion, + tlsCiphers: configv1.TLSProfiles[configv1.TLSProfileIntermediateType].Ciphers, + wantMinTLS: "VersionTLS12", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cm, _, err := DefaultConfigMap( + minimalOperatorConfig(), + minimalConsoleConfig(), + minimalAuthConfig(), + &corev1.ConfigMap{}, + &corev1.ConfigMap{}, + minimalInfrastructureConfig(), + minimalRoute(), + 0, // inactivityTimeoutSeconds + []*consolev1.ConsolePlugin{}, // availablePlugins + []string{"amd64"}, // nodeArchitectures + []string{"linux"}, // nodeOperatingSystems + false, // copiedCSVsDisabled + map[string]string{}, // telemetryConfig + "console.test.cluster", // consoleHost + false, // techPreviewEnabled + false, // olmLifecycleMetadataEnabled + nil, // additionalHosts + tt.tlsMinVersion, + tt.tlsCiphers, + ) + + if err != nil { + t.Errorf("DefaultConfigMap() error = %v", err) + return + } + + var config consoleserver.Config + err = yaml.Unmarshal([]byte(cm.Data["console-config.yaml"]), &config) + if err != nil { + t.Errorf("Failed to unmarshal config: %v", err) + return + } + + // Check MinTLSVersion + if config.ServingInfo.MinTLSVersion != tt.wantMinTLS { + t.Errorf("MinTLSVersion = %v, want %v", config.ServingInfo.MinTLSVersion, tt.wantMinTLS) + } + + // Check CipherSuites + if len(config.ServingInfo.CipherSuites) != len(tt.tlsCiphers) { + t.Errorf("CipherSuites count = %v, want %v. Got: %v", + len(config.ServingInfo.CipherSuites), len(tt.tlsCiphers), config.ServingInfo.CipherSuites) + } + + // Verify the actual cipher values match + for i, cipher := range config.ServingInfo.CipherSuites { + if i < len(tt.tlsCiphers) && cipher != tt.tlsCiphers[i] { + t.Errorf("CipherSuites[%d] = %v, want %v", i, cipher, tt.tlsCiphers[i]) + } + } + }) + } +} diff --git a/pkg/console/subresource/consoleserver/config_builder.go b/pkg/console/subresource/consoleserver/config_builder.go index 2efa75dc41..31c6e58b94 100644 --- a/pkg/console/subresource/consoleserver/config_builder.go +++ b/pkg/console/subresource/consoleserver/config_builder.go @@ -85,6 +85,8 @@ type ConsoleServerCLIConfigBuilder struct { techPreviewEnabled bool olmLifecycleMetadataEnabled bool additionalHosts []string + minTLSVersion string + cipherSuites []string } func (b *ConsoleServerCLIConfigBuilder) Host(host string) *ConsoleServerCLIConfigBuilder { @@ -323,6 +325,12 @@ func (b *ConsoleServerCLIConfigBuilder) AdditionalHosts(hosts []string) *Console return b } +func (b *ConsoleServerCLIConfigBuilder) TLSConfig(minVersion configv1.TLSProtocolVersion, ciphers []string) *ConsoleServerCLIConfigBuilder { + b.minTLSVersion = string(minVersion) + b.cipherSuites = ciphers + return b +} + func (b *ConsoleServerCLIConfigBuilder) Config() Config { return Config{ Kind: "ConsoleConfig", @@ -364,6 +372,14 @@ func (b *ConsoleServerCLIConfigBuilder) servingInfo() ServingInfo { conf.RedirectPort = b.customHostnameRedirectPort } + if b.minTLSVersion != "" { + conf.MinTLSVersion = b.minTLSVersion + } + + if len(b.cipherSuites) > 0 { + conf.CipherSuites = b.cipherSuites + } + return conf } diff --git a/pkg/console/subresource/consoleserver/types.go b/pkg/console/subresource/consoleserver/types.go index 52752ea708..70bb5b5a9f 100644 --- a/pkg/console/subresource/consoleserver/types.go +++ b/pkg/console/subresource/consoleserver/types.go @@ -48,18 +48,18 @@ type ProxyService struct { // ServingInfo holds configuration for serving HTTP. type ServingInfo struct { - BindAddress string `yaml:"bindAddress,omitempty"` - CertFile string `yaml:"certFile,omitempty"` - KeyFile string `yaml:"keyFile,omitempty"` - RedirectPort int `yaml:"redirectPort,omitempty"` + BindAddress string `yaml:"bindAddress,omitempty"` + CertFile string `yaml:"certFile,omitempty"` + KeyFile string `yaml:"keyFile,omitempty"` + RedirectPort int `yaml:"redirectPort,omitempty"` + MinTLSVersion string `yaml:"minTLSVersion,omitempty"` + CipherSuites []string `yaml:"cipherSuites,omitempty"` // These fields are defined in `HTTPServingInfo`, but are not supported for console. Fail if any are specified. // https://github.com/openshift/api/blob/0cb4131a7636e1ada6b2769edc9118f0fe6844c8/config/v1/types.go#L7-L38 BindNetwork string `yaml:"bindNetwork,omitempty"` ClientCA string `yaml:"clientCA,omitempty"` NamedCertificates []interface{} `yaml:"namedCertificates,omitempty"` - MinTLSVersion string `yaml:"minTLSVersion,omitempty"` - CipherSuites []string `yaml:"cipherSuites,omitempty"` MaxRequestsInFlight int64 `yaml:"maxRequestsInFlight,omitempty"` RequestTimeoutSeconds int64 `yaml:"requestTimeoutSeconds,omitempty"` } diff --git a/vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/OWNERS b/vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/OWNERS new file mode 100644 index 0000000000..582d671017 --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/OWNERS @@ -0,0 +1,8 @@ +reviewers: + - tkashem + - p0lyn0mial + - sttts +approvers: + - tkashem + - p0lyn0mial + - sttts diff --git a/vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/listers.go b/vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/listers.go new file mode 100644 index 0000000000..2c0d1bda3e --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/listers.go @@ -0,0 +1,9 @@ +package apiserver + +import ( + configlistersv1 "github.com/openshift/client-go/config/listers/config/v1" +) + +type APIServerLister interface { + APIServerLister() configlistersv1.APIServerLister +} diff --git a/vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/observe_audit.go b/vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/observe_audit.go new file mode 100644 index 0000000000..39aa79cb62 --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/observe_audit.go @@ -0,0 +1,88 @@ +package apiserver + +import ( + "fmt" + + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/klog/v2" + + "github.com/openshift/library-go/pkg/operator/configobserver" + "github.com/openshift/library-go/pkg/operator/events" +) + +// AuditPolicyPathGetterFunc allows the observer to be agnostic of the source of audit profile(s). +// The function returns the path to the audit policy file (associated with the +// given profile) in the static manifest folder. +type AuditPolicyPathGetterFunc func(profile string) (string, error) + +// NewAuditObserver returns an ObserveConfigFunc that observes the audit field of the APIServer resource +// and sets the apiServerArguments:audit-policy-file field for the apiserver appropriately. +func NewAuditObserver(pathGetter AuditPolicyPathGetterFunc) configobserver.ObserveConfigFunc { + var ( + apiServerArgumentsAuditPath = []string{"apiServerArguments", "audit-policy-file"} + ) + + return func(genericListers configobserver.Listers, recorder events.Recorder, existingConfig map[string]interface{}) (observed map[string]interface{}, _ []error) { + defer func() { + observed = configobserver.Pruned(observed, apiServerArgumentsAuditPath) + }() + + errs := []error{} + + // if the function encounters an error it returns existing/current config, which means that + // some other entity (default config in bindata ) must ensure to default the configuration. + // otherwise, the apiserver won't have a path to audit policy file and it will fail to start. + listers := genericListers.(APIServerLister) + apiServer, err := listers.APIServerLister().Get("cluster") + if err != nil { + if k8serrors.IsNotFound(err) { + klog.Warningf("apiserver.config.openshift.io/cluster: not found") + + return existingConfig, errs + } + + return existingConfig, append(errs, err) + } + + desiredProfile := string(apiServer.Spec.Audit.Profile) + if len(desiredProfile) == 0 { + // The specified Profile is empty, so let the defaulting layer choose a default for us. + return map[string]interface{}{}, errs + } + + desiredAuditPolicyPath, err := pathGetter(desiredProfile) + if err != nil { + return existingConfig, append(errs, fmt.Errorf("audit profile is not valid name=%s", desiredProfile)) + } + + currentAuditPolicyPath, err := getCurrentPolicyPath(existingConfig, apiServerArgumentsAuditPath...) + if err != nil { + return existingConfig, append(errs, fmt.Errorf("audit profile is not valid name=%s", desiredProfile)) + } + if desiredAuditPolicyPath == currentAuditPolicyPath { + return existingConfig, errs + } + + // we have a change of audit policy here! + observedConfig := map[string]interface{}{} + if err := unstructured.SetNestedStringSlice(observedConfig, []string{desiredAuditPolicyPath}, apiServerArgumentsAuditPath...); err != nil { + return existingConfig, append(errs, fmt.Errorf("failed to set desired audit profile in observed config name=%s", desiredProfile)) + } + + recorder.Eventf("ObserveAPIServerArgumentsAudit", "audit policy has been set to profile=%s", desiredProfile) + return observedConfig, errs + } +} + +func getCurrentPolicyPath(existing map[string]interface{}, fields ...string) (string, error) { + current, _, err := unstructured.NestedStringSlice(existing, fields...) + if err != nil { + return "", err + } + if len(current) == 0 { + return "", nil + } + + return current[0], nil +} diff --git a/vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/observe_cors.go b/vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/observe_cors.go new file mode 100644 index 0000000000..f8868aa2aa --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/observe_cors.go @@ -0,0 +1,75 @@ +package apiserver + +import ( + "k8s.io/klog/v2" + + "github.com/openshift/library-go/pkg/operator/configobserver" + "github.com/openshift/library-go/pkg/operator/events" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/util/sets" +) + +var clusterDefaultCORSAllowedOrigins = []string{ + `//127\.0\.0\.1(:|$)`, + `//localhost(:|$)`, +} + +// ObserveAdditionalCORSAllowedOrigins observes the additionalCORSAllowedOrigins field +// of the APIServer resource and sets the corsAllowedOrigins field of observedConfig +func ObserveAdditionalCORSAllowedOrigins(genericListers configobserver.Listers, recorder events.Recorder, existingConfig map[string]interface{}) (map[string]interface{}, []error) { + return innerObserveAdditionalCORSAllowedOrigins(genericListers, recorder, existingConfig, []string{"corsAllowedOrigins"}) +} + +// ObserveAdditionalCORSAllowedOriginsToArguments observes the additionalCORSAllowedOrigins field +// of the APIServer resource and sets the cors-allowed-origins field in observedConfig.apiServerArguments +func ObserveAdditionalCORSAllowedOriginsToArguments(genericListers configobserver.Listers, recorder events.Recorder, existingConfig map[string]interface{}) (map[string]interface{}, []error) { + return innerObserveAdditionalCORSAllowedOrigins(genericListers, recorder, existingConfig, []string{"apiServerArguments", "cors-allowed-origins"}) +} + +func innerObserveAdditionalCORSAllowedOrigins(genericListers configobserver.Listers, recorder events.Recorder, existingConfig map[string]interface{}, corsAllowedOriginsPath []string) (ret map[string]interface{}, _ []error) { + defer func() { + ret = configobserver.Pruned(ret, corsAllowedOriginsPath) + }() + + lister := genericListers.(APIServerLister) + errs := []error{} + defaultConfig := map[string]interface{}{} + if err := unstructured.SetNestedStringSlice(defaultConfig, clusterDefaultCORSAllowedOrigins, corsAllowedOriginsPath...); err != nil { + // this should not happen + return existingConfig, append(errs, err) + } + + // grab the current CORS origins to later check whether they were updated + currentCORSAllowedOrigins, _, err := unstructured.NestedStringSlice(existingConfig, corsAllowedOriginsPath...) + if err != nil { + errs = append(errs, err) + // keep going on read error from existing config + } + currentCORSSet := sets.New(currentCORSAllowedOrigins...) + currentCORSSet.Insert(clusterDefaultCORSAllowedOrigins...) + + observedConfig := map[string]interface{}{} + apiServer, err := lister.APIServerLister().Get("cluster") + if errors.IsNotFound(err) { + klog.Warningf("apiserver.config.openshift.io/cluster: not found") + return defaultConfig, errs + } + if err != nil { + // return existingConfig here in case err is just a transient error so + // that we don't rewrite the config that was observed previously + return existingConfig, append(errs, err) + } + + newCORSSet := sets.New(clusterDefaultCORSAllowedOrigins...) + newCORSSet.Insert(apiServer.Spec.AdditionalCORSAllowedOrigins...) + if err := unstructured.SetNestedStringSlice(observedConfig, sets.List(newCORSSet), corsAllowedOriginsPath...); err != nil { + return existingConfig, append(errs, err) + } + + if !currentCORSSet.Equal(newCORSSet) { + recorder.Eventf("ObserveAdditionalCORSAllowedOrigins", "corsAllowedOrigins changed to %q", sets.List(newCORSSet)) + } + + return observedConfig, errs +} diff --git a/vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/observe_tlssecurityprofile.go b/vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/observe_tlssecurityprofile.go new file mode 100644 index 0000000000..3360126e3f --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/observe_tlssecurityprofile.go @@ -0,0 +1,109 @@ +package apiserver + +import ( + "fmt" + "reflect" + + "k8s.io/klog/v2" + + configv1 "github.com/openshift/api/config/v1" + "github.com/openshift/library-go/pkg/crypto" + "github.com/openshift/library-go/pkg/operator/configobserver" + "github.com/openshift/library-go/pkg/operator/events" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +// ObserveTLSSecurityProfile observes APIServer.Spec.TLSSecurityProfile field and sets +// the ServingInfo.MinTLSVersion, ServingInfo.CipherSuites fields of observed config +func ObserveTLSSecurityProfile(genericListers configobserver.Listers, recorder events.Recorder, existingConfig map[string]interface{}) (map[string]interface{}, []error) { + return innerTLSSecurityProfileObservations(genericListers, recorder, existingConfig, []string{"servingInfo", "minTLSVersion"}, []string{"servingInfo", "cipherSuites"}) +} + +// ObserveTLSSecurityProfileWithPaths is like ObserveTLSSecurityProfile, but accepts +// custom paths for ServingInfo.MinTLSVersion and ServingInfo.CipherSuites fields of observed config. +func ObserveTLSSecurityProfileWithPaths(genericListers configobserver.Listers, recorder events.Recorder, existingConfig map[string]interface{}, minTLSVersionPath, cipherSuitesPath []string) (map[string]interface{}, []error) { + return innerTLSSecurityProfileObservations(genericListers, recorder, existingConfig, minTLSVersionPath, cipherSuitesPath) +} + +// ObserveTLSSecurityProfileToArguments observes APIServer.Spec.TLSSecurityProfile field and sets +// the tls-min-version and tls-cipher-suites fileds of observedConfig.apiServerArguments +func ObserveTLSSecurityProfileToArguments(genericListers configobserver.Listers, recorder events.Recorder, existingConfig map[string]interface{}) (map[string]interface{}, []error) { + return innerTLSSecurityProfileObservations(genericListers, recorder, existingConfig, []string{"apiServerArguments", "tls-min-version"}, []string{"apiServerArguments", "tls-cipher-suites"}) +} + +func innerTLSSecurityProfileObservations(genericListers configobserver.Listers, recorder events.Recorder, existingConfig map[string]interface{}, minTLSVersionPath, cipherSuitesPath []string) (ret map[string]interface{}, _ []error) { + defer func() { + ret = configobserver.Pruned(ret, minTLSVersionPath, cipherSuitesPath) + }() + + listers := genericListers.(APIServerLister) + errs := []error{} + + currentMinTLSVersion, _, versionErr := unstructured.NestedString(existingConfig, minTLSVersionPath...) + if versionErr != nil { + errs = append(errs, fmt.Errorf("failed to retrieve spec.servingInfo.minTLSVersion: %v", versionErr)) + // keep going on read error from existing config + } + + currentCipherSuites, _, suitesErr := unstructured.NestedStringSlice(existingConfig, cipherSuitesPath...) + if suitesErr != nil { + errs = append(errs, fmt.Errorf("failed to retrieve spec.servingInfo.cipherSuites: %v", suitesErr)) + // keep going on read error from existing config + } + + apiServer, err := listers.APIServerLister().Get("cluster") + if errors.IsNotFound(err) { + klog.Warningf("apiserver.config.openshift.io/cluster: not found") + apiServer = &configv1.APIServer{} + } else if err != nil { + return existingConfig, append(errs, err) + } + + observedConfig := map[string]interface{}{} + observedMinTLSVersion, observedCipherSuites := getSecurityProfileCiphers(apiServer.Spec.TLSSecurityProfile) + if err = unstructured.SetNestedField(observedConfig, observedMinTLSVersion, minTLSVersionPath...); err != nil { + return existingConfig, append(errs, err) + } + if err = unstructured.SetNestedStringSlice(observedConfig, observedCipherSuites, cipherSuitesPath...); err != nil { + return existingConfig, append(errs, err) + } + + if observedMinTLSVersion != currentMinTLSVersion { + recorder.Eventf("ObserveTLSSecurityProfile", "minTLSVersion changed to %s", observedMinTLSVersion) + } + if !reflect.DeepEqual(observedCipherSuites, currentCipherSuites) { + recorder.Eventf("ObserveTLSSecurityProfile", "cipherSuites changed to %q", observedCipherSuites) + } + + return observedConfig, errs +} + +// Extracts the minimum TLS version and cipher suites from TLSSecurityProfile object, +// Converts the ciphers to IANA names as supported by Kube ServingInfo config. +// If profile is nil, returns config defined by the Intermediate TLS Profile +func getSecurityProfileCiphers(profile *configv1.TLSSecurityProfile) (string, []string) { + var profileType configv1.TLSProfileType + if profile == nil { + profileType = crypto.DefaultTLSProfileType + } else { + profileType = profile.Type + } + + var profileSpec *configv1.TLSProfileSpec + if profileType == configv1.TLSProfileCustomType { + if profile.Custom != nil { + profileSpec = &profile.Custom.TLSProfileSpec + } + } else { + profileSpec = configv1.TLSProfiles[profileType] + } + + // nothing found / custom type set but no actual custom spec + if profileSpec == nil { + profileSpec = configv1.TLSProfiles[crypto.DefaultTLSProfileType] + } + + // need to remap all Ciphers to their respective IANA names used by Go + return string(profileSpec.MinTLSVersion), crypto.OpenSSLToIANACipherSuites(profileSpec.Ciphers) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 265414d52e..5dc4e5fa19 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -380,6 +380,7 @@ github.com/openshift/library-go/pkg/network github.com/openshift/library-go/pkg/operator/certrotation github.com/openshift/library-go/pkg/operator/condition github.com/openshift/library-go/pkg/operator/configobserver +github.com/openshift/library-go/pkg/operator/configobserver/apiserver github.com/openshift/library-go/pkg/operator/configobserver/featuregates github.com/openshift/library-go/pkg/operator/events github.com/openshift/library-go/pkg/operator/genericoperatorclient