Skip to content

Commit 938cef8

Browse files
committed
feat(resource builder): allow to inject tls configuration into annotated config maps
1 parent bdd3553 commit 938cef8

File tree

502 files changed

+138874
-5
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

502 files changed

+138874
-5
lines changed

go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ require (
3232
k8s.io/klog/v2 v2.130.1
3333
k8s.io/kube-aggregator v0.34.1
3434
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
35+
sigs.k8s.io/kustomize/kyaml v0.21.1
36+
sigs.k8s.io/yaml v1.6.0
3537
)
3638

3739
require (
@@ -41,6 +43,7 @@ require (
4143
github.com/cespare/xxhash/v2 v2.3.0 // indirect
4244
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
4345
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
46+
github.com/go-errors/errors v1.4.2 // indirect
4447
github.com/go-openapi/jsonpointer v0.21.0 // indirect
4548
github.com/go-openapi/jsonreference v0.20.2 // indirect
4649
github.com/go-openapi/swag v0.23.0 // indirect
@@ -49,6 +52,7 @@ require (
4952
github.com/google/cel-go v0.26.0 // indirect
5053
github.com/google/gnostic-models v0.7.0 // indirect
5154
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
55+
github.com/imdario/mergo v0.3.12 // indirect
5256
github.com/inconshreveable/mousetrap v1.1.0 // indirect
5357
github.com/josharian/intern v1.0.0 // indirect
5458
github.com/jpillora/backoff v1.0.0 // indirect
@@ -87,9 +91,9 @@ require (
8791
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
8892
sigs.k8s.io/controller-runtime v0.12.1 // indirect
8993
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
94+
sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96 // indirect
9095
sigs.k8s.io/randfill v1.0.0 // indirect
9196
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
92-
sigs.k8s.io/yaml v1.6.0 // indirect
9397
)
9498

9599
replace github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20241205171354-8006f302fd12

go.sum

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
2020
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
2121
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
2222
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
23+
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
24+
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
2325
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
2426
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
2527
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
@@ -45,6 +47,8 @@ github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgY
4547
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
4648
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
4749
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
50+
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
51+
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
4852
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
4953
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
5054
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -103,6 +107,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
103107
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
104108
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
105109
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
110+
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.74.0 h1:AHzMWDxNiAVscJL6+4wkvFRTpMnJqiaZFEKA/osaBXE=
111+
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.74.0/go.mod h1:wAR5JopumPtAZnu0Cjv2PSqV4p4QB09LMhc6fZZTXuA=
106112
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
107113
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
108114
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
@@ -113,8 +119,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
113119
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
114120
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
115121
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
116-
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
117-
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
122+
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
123+
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
118124
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
119125
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
120126
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@@ -211,6 +217,7 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
211217
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
212218
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
213219
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
220+
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
214221
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
215222
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
216223
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -240,6 +247,10 @@ sigs.k8s.io/controller-runtime v0.12.1 h1:4BJY01xe9zKQti8oRjj/NeHKRXthf1YkYJAgLO
240247
sigs.k8s.io/controller-runtime v0.12.1/go.mod h1:BKhxlA4l7FPK4AQcsuL4X6vZeWnKDXez/vp1Y8dxTU0=
241248
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
242249
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
250+
sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96 h1:PFWFSkpArPNJxFX4ZKWAk9NSeRoZaXschn+ULa4xVek=
251+
sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96/go.mod h1:EOBQyBowOUsd7U4CJnMHNE0ri+zCXyouGdLwC/jZU+I=
252+
sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI=
253+
sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=
243254
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
244255
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
245256
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=

lib/resourcebuilder/core.go

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
package resourcebuilder
2+
3+
import (
4+
"context"
5+
"slices"
6+
"sort"
7+
8+
configv1 "github.com/openshift/api/config/v1"
9+
configclientv1 "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
10+
configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
11+
"github.com/openshift/library-go/pkg/operator/configobserver/apiserver"
12+
"github.com/openshift/library-go/pkg/operator/events"
13+
"github.com/openshift/library-go/pkg/operator/resourcesynccontroller"
14+
corev1 "k8s.io/api/core/v1"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
17+
"k8s.io/apimachinery/pkg/labels"
18+
"k8s.io/client-go/tools/cache"
19+
"k8s.io/klog/v2"
20+
"k8s.io/utils/clock"
21+
"sigs.k8s.io/kustomize/kyaml/yaml"
22+
)
23+
24+
const (
25+
// ConfigMapInjectTLSAnnotation is the annotation key that triggers TLS injection into ConfigMaps
26+
ConfigMapInjectTLSAnnotation = "config.openshift.io/inject-tls"
27+
)
28+
29+
func (b *builder) modifyConfigMap(ctx context.Context, cm *corev1.ConfigMap) error {
30+
// Check for TLS injection annotation
31+
if value, ok := cm.Annotations[ConfigMapInjectTLSAnnotation]; !ok || value != "true" {
32+
return nil
33+
}
34+
35+
klog.V(2).Infof("ConfigMap %s/%s has %s annotation set to true", cm.Namespace, cm.Name, ConfigMapInjectTLSAnnotation)
36+
37+
// Empty data, nothing to inject into
38+
if cm.Data == nil {
39+
klog.V(2).Infof("ConfigMap %s/%s has empty data, skipping", cm.Namespace, cm.Name)
40+
return nil
41+
}
42+
43+
// Observe TLS configuration from APIServer
44+
minTLSVersion, minTLSFound, cipherSuites, ciphersFound := b.observeTLSConfiguration(ctx, cm)
45+
46+
if !minTLSFound && !ciphersFound {
47+
klog.V(2).Infof("ConfigMap %s/%s: no TLS configuration found, skipping", cm.Namespace, cm.Name)
48+
return nil
49+
}
50+
51+
// Process each data entry that contains GenericOperatorConfig
52+
for key, value := range cm.Data {
53+
// Parse YAML into RNode to preserve formatting and field order
54+
rnode, err := yaml.Parse(value)
55+
if err != nil {
56+
// Not valid YAML, skip this entry
57+
continue
58+
}
59+
60+
// Check if this is a GenericOperatorConfig by checking the kind field
61+
kind, err := rnode.GetString("kind")
62+
if err != nil || kind != "GenericOperatorConfig" {
63+
// Not a GenericOperatorConfig, skip this entry
64+
continue
65+
}
66+
67+
klog.V(2).Infof("ConfigMap %s/%s processing GenericOperatorConfig in key %s", cm.Namespace, cm.Name, key)
68+
69+
// Inject TLS settings into the GenericOperatorConfig while preserving structure
70+
if err := updateRNodeWithTLSSettings(rnode, minTLSVersion, minTLSFound, cipherSuites, ciphersFound); err != nil {
71+
return err
72+
}
73+
74+
// Marshal the modified RNode back to YAML
75+
modifiedYAML, err := rnode.String()
76+
if err != nil {
77+
return err
78+
}
79+
80+
// Update the ConfigMap data entry with the modified YAML
81+
cm.Data[key] = modifiedYAML
82+
klog.V(2).Infof("ConfigMap %s/%s updated GenericOperatorConfig in key %s with %d ciphers and minTLSVersion=%s",
83+
cm.Namespace, cm.Name, key, len(cipherSuites), minTLSVersion)
84+
}
85+
86+
klog.V(2).Infof("APIServer config available for ConfigMap %s/%s TLS injection", cm.Namespace, cm.Name)
87+
88+
return nil
89+
}
90+
91+
// observeTLSConfiguration retrieves TLS configuration from the APIServer cluster CR
92+
// using ObserveTLSSecurityProfile and extracts minTLSVersion and cipherSuites.
93+
func (b *builder) observeTLSConfiguration(ctx context.Context, cm *corev1.ConfigMap) (minTLSVersion string, minTLSFound bool, cipherSuites []string, ciphersFound bool) {
94+
// First check if the APIServer cluster resource exists
95+
_, err := b.configClientv1.APIServers().Get(ctx, "cluster", metav1.GetOptions{})
96+
if err != nil {
97+
klog.V(2).Infof("ConfigMap %s/%s: APIServer cluster resource not found, skipping TLS injection: %v", cm.Namespace, cm.Name, err)
98+
return "", false, nil, false
99+
}
100+
101+
// Create a lister adapter for ObserveTLSSecurityProfile
102+
lister := &apiServerListerAdapter{
103+
client: b.configClientv1.APIServers(),
104+
ctx: ctx,
105+
}
106+
listers := &configObserverListers{
107+
apiServerLister: lister,
108+
}
109+
110+
// Create an in-memory event recorder that doesn't send events to the API server
111+
recorder := events.NewInMemoryRecorder("configmap-tls-injection", clock.RealClock{})
112+
113+
// Call ObserveTLSSecurityProfile to get TLS configuration
114+
observedConfig, errs := apiserver.ObserveTLSSecurityProfile(listers, recorder, map[string]interface{}{})
115+
if len(errs) > 0 {
116+
// Log errors but continue - ObserveTLSSecurityProfile is tolerant of missing config
117+
for _, err := range errs {
118+
klog.V(2).Infof("ConfigMap %s/%s: error observing TLS profile: %v", cm.Namespace, cm.Name, err)
119+
}
120+
}
121+
122+
// Extract the TLS settings from the observed config
123+
minTLSVersion, minTLSFound, _ = unstructured.NestedString(observedConfig, "servingInfo", "minTLSVersion")
124+
cipherSuites, ciphersFound, _ = unstructured.NestedStringSlice(observedConfig, "servingInfo", "cipherSuites")
125+
126+
// Sort cipher suites for consistent comparison
127+
if ciphersFound && len(cipherSuites) > 0 {
128+
sort.Strings(cipherSuites)
129+
}
130+
131+
return minTLSVersion, minTLSFound, cipherSuites, ciphersFound
132+
}
133+
134+
// updateRNodeWithTLSSettings injects TLS settings into a GenericOperatorConfig RNode while preserving structure
135+
// cipherSuites is expected to be sorted
136+
func updateRNodeWithTLSSettings(rnode *yaml.RNode, minTLSVersion string, minTLSFound bool, cipherSuites []string, ciphersFound bool) error {
137+
servingInfo, err := rnode.Pipe(yaml.LookupCreate(yaml.MappingNode, "servingInfo"))
138+
if err != nil {
139+
return err
140+
}
141+
142+
if ciphersFound && len(cipherSuites) > 0 {
143+
currentCiphers, err := getSortedCipherSuites(servingInfo)
144+
if err != nil || !slices.Equal(currentCiphers, cipherSuites) {
145+
// Create a sequence node with the cipher suites
146+
seqNode := yaml.NewListRNode(cipherSuites...)
147+
if err := servingInfo.PipeE(yaml.SetField("cipherSuites", seqNode)); err != nil {
148+
return err
149+
}
150+
}
151+
}
152+
153+
// Update minTLSVersion if found
154+
if minTLSFound && minTLSVersion != "" {
155+
if err := servingInfo.PipeE(yaml.SetField("minTLSVersion", yaml.NewStringRNode(minTLSVersion))); err != nil {
156+
return err
157+
}
158+
}
159+
160+
return nil
161+
}
162+
163+
// getSortedCipherSuites extracts and sorts the cipherSuites string slice from a servingInfo RNode
164+
func getSortedCipherSuites(servingInfo *yaml.RNode) ([]string, error) {
165+
ciphersNode, err := servingInfo.Pipe(yaml.Lookup("cipherSuites"))
166+
if err != nil || ciphersNode == nil {
167+
return nil, err
168+
}
169+
170+
elements, err := ciphersNode.Elements()
171+
if err != nil {
172+
return nil, err
173+
}
174+
175+
var ciphers []string
176+
for _, elem := range elements {
177+
// For scalar nodes, access the value directly without YAML serialization
178+
// This avoids the trailing newline that String() (which uses yaml.Encode) adds
179+
if elem.YNode().Kind == yaml.ScalarNode {
180+
value := elem.YNode().Value
181+
// Skip empty values
182+
if value == "" {
183+
continue
184+
}
185+
ciphers = append(ciphers, value)
186+
}
187+
}
188+
189+
// Sort cipher suites for consistent comparison
190+
sort.Strings(ciphers)
191+
192+
return ciphers, nil
193+
}
194+
195+
// apiServerListerAdapter adapts a client interface to the lister interface
196+
type apiServerListerAdapter struct {
197+
client configclientv1.APIServerInterface
198+
ctx context.Context
199+
}
200+
201+
func (a *apiServerListerAdapter) List(selector labels.Selector) ([]*configv1.APIServer, error) {
202+
// Not implemented - ObserveTLSSecurityProfile only uses Get()
203+
return nil, nil
204+
}
205+
206+
func (a *apiServerListerAdapter) Get(name string) (*configv1.APIServer, error) {
207+
return a.client.Get(a.ctx, name, metav1.GetOptions{})
208+
}
209+
210+
// configObserverListers implements the configobserver.Listers interface
211+
type configObserverListers struct {
212+
apiServerLister configlistersv1.APIServerLister
213+
}
214+
215+
func (l *configObserverListers) APIServerLister() configlistersv1.APIServerLister {
216+
return l.apiServerLister
217+
}
218+
219+
func (l *configObserverListers) ResourceSyncer() resourcesynccontroller.ResourceSyncer {
220+
// Not needed for TLS observation
221+
return nil
222+
}
223+
224+
func (l *configObserverListers) PreRunHasSynced() []cache.InformerSynced {
225+
// Not needed for TLS observation
226+
return nil
227+
}

0 commit comments

Comments
 (0)