@@ -2,8 +2,8 @@ package resourcebuilder
22
33import (
44 "context"
5+ "errors"
56 "fmt"
6- "slices"
77 "sort"
88
99 "sigs.k8s.io/kustomize/kyaml/yaml"
@@ -17,6 +17,7 @@ import (
1717 "k8s.io/utils/clock"
1818
1919 configv1 "github.com/openshift/api/config/v1"
20+ operatorv1alpha1 "github.com/openshift/api/operator/v1alpha1"
2021 configclientv1 "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
2122 configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
2223 "github.com/openshift/library-go/pkg/operator/configobserver/apiserver"
@@ -29,6 +30,16 @@ const (
2930 ConfigMapInjectTLSAnnotation = "config.openshift.io/inject-tls"
3031)
3132
33+ type optional [T any ] struct {
34+ value T
35+ found bool
36+ }
37+
38+ type tlsConfig struct {
39+ minTLSVersion optional [string ]
40+ cipherSuites optional [[]string ]
41+ }
42+
3243func (b * builder ) modifyConfigMap (ctx context.Context , cm * corev1.ConfigMap ) error {
3344 // Check for TLS injection annotation
3445 if value , ok := cm .Annotations [ConfigMapInjectTLSAnnotation ]; ! ok || value != "true" {
@@ -39,21 +50,26 @@ func (b *builder) modifyConfigMap(ctx context.Context, cm *corev1.ConfigMap) err
3950
4051 // Empty data, nothing to inject into
4152 if cm .Data == nil {
42- klog .V (2 ).Infof ("ConfigMap %s/%s has empty data, skipping" , cm .Namespace , cm .Name )
53+ klog .V (2 ).Infof ("ConfigMap %s/%s has empty data, skipping TLS profile injection " , cm .Namespace , cm .Name )
4354 return nil
4455 }
4556
4657 // Observe TLS configuration from APIServer
47- minTLSVersion , minTLSFound , cipherSuites , ciphersFound , err := b .observeTLSConfiguration (ctx , cm )
58+ tlsConf , err := b .observeTLSConfiguration (ctx , cm )
4859 if err != nil {
4960 return fmt .Errorf ("unable to observe TLS configuration: %v" , err )
5061 }
51- if ! minTLSFound && ! ciphersFound {
52- klog .V (2 ).Infof ("ConfigMap %s/%s: no TLS configuration found, skipping" , cm .Namespace , cm .Name )
53- return nil
54- }
5562
56- klog .V (4 ).Infof ("Observing minTLSVersion=%v, cipherSuites=%v" , minTLSVersion , cipherSuites )
63+ minTLSLog := "<not found>"
64+ if tlsConf .minTLSVersion .found {
65+ minTLSLog = tlsConf .minTLSVersion .value
66+ }
67+ cipherSuitesLog := "<not found>"
68+ if tlsConf .cipherSuites .found {
69+ cipherSuitesLog = fmt .Sprintf ("%v" , tlsConf .cipherSuites .value )
70+ }
71+ klog .V (4 ).Infof ("ConfigMap %s/%s: observed minTLSVersion=%v, cipherSuites=%v" ,
72+ cm .Namespace , cm .Name , minTLSLog , cipherSuitesLog )
5773
5874 // Process each data entry that contains GenericOperatorConfig
5975 for key , value := range cm .Data {
@@ -66,43 +82,35 @@ func (b *builder) modifyConfigMap(ctx context.Context, cm *corev1.ConfigMap) err
6682 continue
6783 }
6884
69- // Check if this is a GenericOperatorConfig by checking the kind field
70- kind := rnode .GetKind ()
71- if kind != "GenericOperatorConfig" {
72- klog .V (4 ).Infof ("ConfigMap's %q entry is not a GenericOperatorConfig, skipping this entry" , key )
85+ // Check if this is a supported GenericOperatorConfig kind
86+ if rnode .GetKind () != "GenericOperatorConfig" || rnode .GetApiVersion () != operatorv1alpha1 .GroupVersion .String () {
87+ klog .V (4 ).Infof ("ConfigMap's %q entry is not a supported GenericOperatorConfig, skipping this entry" , key )
7388 continue
7489 }
7590
7691 klog .V (2 ).Infof ("ConfigMap %s/%s processing GenericOperatorConfig in key %s" , cm .Namespace , cm .Name , key )
7792
7893 // Inject TLS settings into the GenericOperatorConfig while preserving structure
79- if err := updateRNodeWithTLSSettings (rnode , minTLSVersion , minTLSFound , cipherSuites , ciphersFound ); err != nil {
80- klog .V (4 ).Infof ("Error injecting the TLS configuration: %v" , err )
81- return err
94+ if err := updateRNodeWithTLSSettings (rnode , tlsConf ); err != nil {
95+ return fmt .Errorf ("failed to inject the TLS configuration: %v" , err )
8296 }
8397
8498 // Marshal the modified RNode back to YAML
8599 modifiedYAML , err := rnode .String ()
86100 if err != nil {
87- klog .V (4 ).Infof ("Error marshalling the modified ConfigMap back to YAML: %v" , err )
88- return err
101+ return fmt .Errorf ("failed to marshall the modified ConfigMap back to YAML: %v" , err )
89102 }
90103
91104 // Update the ConfigMap data entry with the modified YAML
92105 cm .Data [key ] = modifiedYAML
93- klog .V (2 ).Infof ("ConfigMap %s/%s updated GenericOperatorConfig in key %s with %d ciphers and minTLSVersion=%s" ,
94- cm .Namespace , cm .Name , key , len (cipherSuites ), minTLSVersion )
106+ klog .V (2 ).Infof ("ConfigMap %s/%s updated GenericOperatorConfig with TLS profile in key %s" , cm .Namespace , cm .Name , key )
95107 }
96-
97- klog .V (2 ).Infof ("APIServer config available for ConfigMap %s/%s TLS injection" , cm .Namespace , cm .Name )
98-
99108 return nil
100109}
101110
102111// observeTLSConfiguration retrieves TLS configuration from the APIServer cluster CR
103112// using ObserveTLSSecurityProfile and extracts minTLSVersion and cipherSuites.
104- // minTLSVersion string, minTLSFound bool, cipherSuites []string, ciphersFound bool, err error
105- func (b * builder ) observeTLSConfiguration (ctx context.Context , cm * corev1.ConfigMap ) (string , bool , []string , bool , error ) {
113+ func (b * builder ) observeTLSConfiguration (ctx context.Context , cm * corev1.ConfigMap ) (* tlsConfig , error ) {
106114 // Create a lister adapter for ObserveTLSSecurityProfile
107115 lister := & apiServerListerAdapter {
108116 client : b .configClientv1 .APIServers (),
@@ -118,100 +126,62 @@ func (b *builder) observeTLSConfiguration(ctx context.Context, cm *corev1.Config
118126 // Call ObserveTLSSecurityProfile to get TLS configuration
119127 observedConfig , errs := apiserver .ObserveTLSSecurityProfile (listers , recorder , map [string ]any {})
120128 if len (errs ) > 0 {
121- // Log errors but continue - ObserveTLSSecurityProfile is tolerant of missing config
122- for _ , err := range errs {
123- klog .Errorf ("ConfigMap %s/%s: error observing TLS profile: %v" , cm .Namespace , cm .Name , err )
124- }
129+ return nil , fmt .Errorf ("error observing TLS profile for ConfigMap %s/%s: %w" , cm .Namespace , cm .Name , errors .Join (errs ... ))
125130 }
126131
127- // Extract the TLS settings from the observed config
128- minTLSVersion , minTLSFound , err := unstructured .NestedString (observedConfig , "servingInfo" , "minTLSVersion" )
129- if err != nil {
130- // This error is unlikely to happen unless unstructured.NestedString is buggy.
131- // From unstructured.NestedString's description:
132- // "Returns false if value is not found and an error if not a string."
133- // The observedConfig's servingInfo.minTLSVersion is of a string type
134- return "" , false , nil , false , err
135- }
136- cipherSuites , ciphersFound , _ := unstructured .NestedStringSlice (observedConfig , "servingInfo" , "cipherSuites" )
137- if err != nil {
138- // This error is unlikely to happen unless unstructured.NestedStringSlice is buggy
139- // From unstructured.NestedString's description:
140- // "Returns false if value is not found and an error if not a []interface{} or contains non-string items in the slice."
141- // The observedConfig's servingInfo.minTLSVersion is of a string type
142- return "" , false , nil , false , err
132+ config := & tlsConfig {}
133+
134+ // Extract minTLSVersion from the observed config
135+ if minTLSVersion , minTLSFound , err := unstructured .NestedString (observedConfig , "servingInfo" , "minTLSVersion" ); err != nil {
136+ return nil , err
137+ } else if minTLSFound {
138+ config .minTLSVersion = optional [string ]{value : minTLSVersion , found : true }
143139 }
144140
145- // Sort cipher suites for consistent comparison
146- if ciphersFound && len (cipherSuites ) > 0 {
141+ // Extract cipherSuites from the observed config
142+ if cipherSuites , ciphersFound , err := unstructured .NestedStringSlice (observedConfig , "servingInfo" , "cipherSuites" ); err != nil {
143+ return nil , err
144+ } else if ciphersFound {
145+ // Sort cipher suites for consistent ordering
147146 sort .Strings (cipherSuites )
147+ config .cipherSuites = optional [[]string ]{value : cipherSuites , found : true }
148148 }
149149
150- return minTLSVersion , minTLSFound , cipherSuites , ciphersFound , nil
150+ return config , nil
151151}
152152
153- // updateRNodeWithTLSSettings injects TLS settings into a GenericOperatorConfig RNode while preserving structure
154- // cipherSuites is expected to be sorted
155- func updateRNodeWithTLSSettings (rnode * yaml.RNode , minTLSVersion string , minTLSFound bool , cipherSuites [] string , ciphersFound bool ) error {
153+ // updateRNodeWithTLSSettings injects TLS settings into a GenericOperatorConfig RNode while preserving structure.
154+ // If a field in tlsConf is not found, the corresponding field will be deleted from the RNode.
155+ func updateRNodeWithTLSSettings (rnode * yaml.RNode , tlsConf * tlsConfig ) error {
156156 servingInfo , err := rnode .Pipe (yaml .LookupCreate (yaml .MappingNode , "servingInfo" ))
157157 if err != nil {
158158 return err
159159 }
160160
161- if ciphersFound {
162- currentCiphers , err := getSortedCipherSuites (servingInfo )
163- if err != nil {
161+ // Handle cipherSuites field
162+ if tlsConf .cipherSuites .found {
163+ seqNode := yaml .NewListRNode (tlsConf .cipherSuites .value ... )
164+ if err := servingInfo .PipeE (yaml .SetField ("cipherSuites" , seqNode )); err != nil {
164165 return err
165166 }
166- if ! slices .Equal (currentCiphers , cipherSuites ) {
167- // Create a sequence node with the cipher suites
168- seqNode := yaml .NewListRNode (cipherSuites ... )
169- if err := servingInfo .PipeE (yaml .SetField ("cipherSuites" , seqNode )); err != nil {
170- return err
171- }
167+ } else {
168+ if err := servingInfo .PipeE (yaml .Clear ("cipherSuites" )); err != nil {
169+ return err
172170 }
173171 }
174172
175- // Update minTLSVersion if found
176- if minTLSFound {
177- if err := servingInfo .PipeE (yaml .SetField ("minTLSVersion" , yaml .NewStringRNode (minTLSVersion ))); err != nil {
173+ // Handle minTLSVersion field
174+ if tlsConf . minTLSVersion . found {
175+ if err := servingInfo .PipeE (yaml .SetField ("minTLSVersion" , yaml .NewStringRNode (tlsConf . minTLSVersion . value ))); err != nil {
178176 return err
179177 }
180- }
181-
182- return nil
183- }
184-
185- // getSortedCipherSuites extracts and sorts the cipherSuites string slice from a servingInfo RNode
186- func getSortedCipherSuites (servingInfo * yaml.RNode ) ([]string , error ) {
187- ciphersNode , err := servingInfo .Pipe (yaml .Lookup ("cipherSuites" ))
188- if err != nil || ciphersNode == nil {
189- return nil , err
190- }
191-
192- elements , err := ciphersNode .Elements ()
193- if err != nil {
194- return nil , err
195- }
196-
197- var ciphers []string
198- for _ , elem := range elements {
199- // For scalar nodes, access the value directly without YAML serialization
200- // This avoids the trailing newline that String() (which uses yaml.Encode) adds
201- if elem .YNode ().Kind == yaml .ScalarNode {
202- value := elem .YNode ().Value
203- // Skip empty values
204- if value == "" {
205- continue
206- }
207- ciphers = append (ciphers , value )
178+ } else {
179+ if err := servingInfo .PipeE (yaml .Clear ("minTLSVersion" )); err != nil {
180+ return err
208181 }
209182 }
210183
211- // Sort cipher suites for consistent comparison
212- sort .Strings (ciphers )
213-
214- return ciphers , nil
184+ return nil
215185}
216186
217187// apiServerListerAdapter adapts a client interface to the lister interface
0 commit comments