Skip to content

Commit 1812aed

Browse files
Merge pull request #13 from jstuever/tlsadherence
CCO-819: Add TLSAdherence tracking
2 parents 5db94f6 + b569ff0 commit 1812aed

5 files changed

Lines changed: 96 additions & 34 deletions

File tree

go.mod

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ require (
66
github.com/go-logr/logr v1.4.3
77
github.com/onsi/ginkgo/v2 v2.28.1
88
github.com/onsi/gomega v1.39.1
9-
github.com/openshift/api v0.0.0-20260213155647-8fe9fe363807
9+
github.com/openshift/api v0.0.0-20260317165824-54a3998d81eb
1010
github.com/openshift/library-go v0.0.0-20260213153706-03f1709971c5
11-
k8s.io/apimachinery v0.35.1
12-
k8s.io/client-go v0.35.1
13-
k8s.io/utils v0.0.0-20260108192941-914a6e750570
14-
sigs.k8s.io/controller-runtime v0.23.2
11+
k8s.io/apimachinery v0.35.2
12+
k8s.io/client-go v0.35.2
13+
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2
14+
sigs.k8s.io/controller-runtime v0.23.3
1515
)
1616

1717
require (
@@ -64,7 +64,7 @@ require (
6464
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
6565
gopkg.in/inf.v0 v0.9.1 // indirect
6666
gopkg.in/yaml.v3 v3.0.1 // indirect
67-
k8s.io/api v0.35.1 // indirect
67+
k8s.io/api v0.35.2 // indirect
6868
k8s.io/apiextensions-apiserver v0.35.1 // indirect
6969
k8s.io/apiserver v0.35.1 // indirect
7070
k8s.io/klog/v2 v2.130.1 // indirect

go.sum

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI
8989
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
9090
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
9191
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
92-
github.com/openshift/api v0.0.0-20260213155647-8fe9fe363807 h1:coR/haF16EW8KS1E/PwJfDzMSy4mU9K0H1rcHejqYDY=
93-
github.com/openshift/api v0.0.0-20260213155647-8fe9fe363807/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
92+
github.com/openshift/api v0.0.0-20260317165824-54a3998d81eb h1:iwBR3mzmyE3EMFx7R3CQ9lOccTS0dNht8TW82aGITg0=
93+
github.com/openshift/api v0.0.0-20260317165824-54a3998d81eb/go.mod h1:pyVjK0nZ4sRs4fuQVQ4rubsJdahI1PB94LnQ8sGdvxo=
9494
github.com/openshift/library-go v0.0.0-20260213153706-03f1709971c5 h1:9Pe6iVOMjt9CdA/vaKBNUSoEIjIe1po5Ha3ABRYXLJI=
9595
github.com/openshift/library-go v0.0.0-20260213153706-03f1709971c5/go.mod h1:K3FoNLgNBFYbFuG+Kr8usAnQxj1w84XogyUp2M8rK8k=
9696
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -173,24 +173,24 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
173173
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
174174
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
175175
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
176-
k8s.io/api v0.35.1 h1:0PO/1FhlK/EQNVK5+txc4FuhQibV25VLSdLMmGpDE/Q=
177-
k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM=
176+
k8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw=
177+
k8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60=
178178
k8s.io/apiextensions-apiserver v0.35.1 h1:p5vvALkknlOcAqARwjS20kJffgzHqwyQRM8vHLwgU7w=
179179
k8s.io/apiextensions-apiserver v0.35.1/go.mod h1:2CN4fe1GZ3HMe4wBr25qXyJnJyZaquy4nNlNmb3R7AQ=
180-
k8s.io/apimachinery v0.35.1 h1:yxO6gV555P1YV0SANtnTjXYfiivaTPvCTKX6w6qdDsU=
181-
k8s.io/apimachinery v0.35.1/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
180+
k8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8=
181+
k8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
182182
k8s.io/apiserver v0.35.1 h1:potxdhhTL4i6AYAa2QCwtlhtB1eCdWQFvJV6fXgJzxs=
183183
k8s.io/apiserver v0.35.1/go.mod h1:BiL6Dd3A2I/0lBnteXfWmCFobHM39vt5+hJQd7Lbpi4=
184-
k8s.io/client-go v0.35.1 h1:+eSfZHwuo/I19PaSxqumjqZ9l5XiTEKbIaJ+j1wLcLM=
185-
k8s.io/client-go v0.35.1/go.mod h1:1p1KxDt3a0ruRfc/pG4qT/3oHmUj1AhSHEcxNSGg+OA=
184+
k8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o=
185+
k8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g=
186186
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
187187
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
188188
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
189189
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
190-
k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY=
191-
k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
192-
sigs.k8s.io/controller-runtime v0.23.2 h1:Oh3FliXaA2CS1chpUXvjVNJtsvGZYUxQH8s7bvR7aXk=
193-
sigs.k8s.io/controller-runtime v0.23.2/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=
190+
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
191+
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
192+
sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80=
193+
sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=
194194
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
195195
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
196196
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=

pkg/tls/controller.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ type SecurityProfileWatcher struct {
4141
// InitialTLSProfileSpec is the TLS profile spec that was configured when the operator started.
4242
InitialTLSProfileSpec configv1.TLSProfileSpec
4343

44+
// InitialTLSAdherencePolicy is the TLS adherence policy that was configured when the operator started.
45+
InitialTLSAdherencePolicy configv1.TLSAdherencePolicy
46+
4447
// OnProfileChange is a function that will be called when the TLS profile changes.
4548
// It receives the reconcile context, old and new TLS profile specs.
4649
// This allows the caller to make decisions based on the actual profile changes.
@@ -66,6 +69,9 @@ type SecurityProfileWatcher struct {
6669
// },
6770
// }
6871
OnProfileChange func(ctx context.Context, oldTLSProfileSpec, newTLSProfileSpec configv1.TLSProfileSpec)
72+
73+
// OnAdherencePolicyChange is a function that will be called when the TLS adherence policy changes.
74+
OnAdherencePolicyChange func(ctx context.Context, oldTLSAdherencePolicy, newTLSAdherencePolicy configv1.TLSAdherencePolicy)
6975
}
7076

7177
// SetupWithManager sets up the controller with the Manager.
@@ -139,6 +145,17 @@ func (r *SecurityProfileWatcher) Reconcile(ctx context.Context, req ctrl.Request
139145
r.InitialTLSProfileSpec = currentTLSProfileSpec
140146
}
141147

148+
// Compare the current TLS adherence policy with the initial one.
149+
if tlsAdherencePolicyChanged := r.InitialTLSAdherencePolicy != apiServer.Spec.TLSAdherence; tlsAdherencePolicyChanged {
150+
// TLS adherence policy has changed, invoke the callback if it is set.
151+
if r.OnAdherencePolicyChange != nil {
152+
r.OnAdherencePolicyChange(ctx, r.InitialTLSAdherencePolicy, apiServer.Spec.TLSAdherence)
153+
}
154+
155+
// Persist the new adherence policy for future change detection.
156+
r.InitialTLSAdherencePolicy = apiServer.Spec.TLSAdherence
157+
}
158+
142159
// No need to requeue, as the callback will handle further actions.
143160
return ctrl.Result{}, nil
144161
}

pkg/tls/controller_test.go

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,18 @@ var _ = Describe("SecurityProfileWatcher controller", func() {
5858
new configv1.TLSProfileSpec
5959
}
6060

61+
type adherencePolicyChange struct {
62+
old configv1.TLSAdherencePolicy
63+
new configv1.TLSAdherencePolicy
64+
}
65+
6166
var (
62-
mgrCancel context.CancelFunc
63-
mgrDone chan struct{}
64-
mgr manager.Manager
65-
apiServer *configv1.APIServer
66-
profileChanges *atomicSlice[profileChange]
67+
mgrCancel context.CancelFunc
68+
mgrDone chan struct{}
69+
mgr manager.Manager
70+
apiServer *configv1.APIServer
71+
profileChanges *atomicSlice[profileChange]
72+
adherencePolicyChanges *atomicSlice[adherencePolicyChange]
6773
)
6874

6975
BeforeEach(func() {
@@ -88,6 +94,7 @@ var _ = Describe("SecurityProfileWatcher controller", func() {
8894

8995
// Reset callback tracking.
9096
profileChanges = &atomicSlice[profileChange]{}
97+
adherencePolicyChanges = &atomicSlice[adherencePolicyChange]{}
9198
})
9299

93100
AfterEach(func() {
@@ -101,18 +108,22 @@ var _ = Describe("SecurityProfileWatcher controller", func() {
101108
Expect(k8sClient.Delete(ctx, apiServer)).To(Succeed())
102109
})
103110

104-
startManager := func(initialProfile configv1.TLSProfileSpec) {
111+
startManager := func(initialProfile configv1.TLSProfileSpec, initialAdherencePolicy configv1.TLSAdherencePolicy) {
105112
var mgrCtx context.Context
106113
mgrCtx, mgrCancel = context.WithCancel(ctx)
107114
mgrDone = make(chan struct{})
108115

109116
// Set up the TLS security profile watcher controller.
110117
watcher := &SecurityProfileWatcher{
111-
Client: mgr.GetClient(),
112-
InitialTLSProfileSpec: initialProfile,
118+
Client: mgr.GetClient(),
119+
InitialTLSProfileSpec: initialProfile,
120+
InitialTLSAdherencePolicy: initialAdherencePolicy,
113121
OnProfileChange: func(_ context.Context, oldSpec, newSpec configv1.TLSProfileSpec) {
114122
profileChanges.Append(profileChange{old: oldSpec, new: newSpec})
115123
},
124+
OnAdherencePolicyChange: func(_ context.Context, oldPolicy, newPolicy configv1.TLSAdherencePolicy) {
125+
adherencePolicyChanges.Append(adherencePolicyChange{old: oldPolicy, new: newPolicy})
126+
},
116127
}
117128
Expect(watcher.SetupWithManager(mgr)).To(Succeed())
118129

@@ -135,7 +146,7 @@ var _ = Describe("SecurityProfileWatcher controller", func() {
135146
// Start with the intermediate profile (same as what's configured).
136147
initialProfile, err := GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile)
137148
Expect(err).NotTo(HaveOccurred())
138-
startManager(initialProfile)
149+
startManager(initialProfile, apiServer.Spec.TLSAdherence)
139150

140151
// Wait a bit and verify callback was not invoked.
141152
Consistently(profileChanges.Len).Should(Equal(0), "callback should not be invoked")
@@ -145,7 +156,7 @@ var _ = Describe("SecurityProfileWatcher controller", func() {
145156
// Start with the intermediate profile.
146157
initialProfile, err := GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile)
147158
Expect(err).NotTo(HaveOccurred())
148-
startManager(initialProfile)
159+
startManager(initialProfile, apiServer.Spec.TLSAdherence)
149160

150161
// Get the intermediate profile spec to replicate it exactly.
151162
intermediateSpec := *configv1.TLSProfiles[configv1.TLSProfileIntermediateType]
@@ -179,7 +190,7 @@ var _ = Describe("SecurityProfileWatcher controller", func() {
179190
// Start with the custom profile.
180191
initialProfile, err := GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile)
181192
Expect(err).NotTo(HaveOccurred())
182-
startManager(initialProfile)
193+
startManager(initialProfile, apiServer.Spec.TLSAdherence)
183194

184195
// Switch to the intermediate profile (which has identical settings).
185196
apiServer.Spec.TLSSecurityProfile = &configv1.TLSSecurityProfile{
@@ -197,7 +208,7 @@ var _ = Describe("SecurityProfileWatcher controller", func() {
197208
// Start with the intermediate profile.
198209
initialProfile, err := GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile)
199210
Expect(err).NotTo(HaveOccurred())
200-
startManager(initialProfile)
211+
startManager(initialProfile, apiServer.Spec.TLSAdherence)
201212

202213
// Update the APIServer to use the Modern profile (which has TLS 1.3).
203214
apiServer.Spec.TLSSecurityProfile = &configv1.TLSSecurityProfile{
@@ -220,7 +231,7 @@ var _ = Describe("SecurityProfileWatcher controller", func() {
220231
// Start with the intermediate profile.
221232
initialProfile, err := GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile)
222233
Expect(err).NotTo(HaveOccurred())
223-
startManager(initialProfile)
234+
startManager(initialProfile, apiServer.Spec.TLSAdherence)
224235

225236
// Define the custom profile we'll switch to.
226237
customSpec := configv1.TLSProfileSpec{
@@ -262,7 +273,7 @@ var _ = Describe("SecurityProfileWatcher controller", func() {
262273
// Start with the custom profile.
263274
initialProfile, err := GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile)
264275
Expect(err).NotTo(HaveOccurred())
265-
startManager(initialProfile)
276+
startManager(initialProfile, apiServer.Spec.TLSAdherence)
266277

267278
// Switch back to the intermediate profile.
268279
apiServer.Spec.TLSSecurityProfile = &configv1.TLSSecurityProfile{
@@ -278,7 +289,7 @@ var _ = Describe("SecurityProfileWatcher controller", func() {
278289
// Start with the intermediate profile (profile A).
279290
initialProfile, err := GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile)
280291
Expect(err).NotTo(HaveOccurred())
281-
startManager(initialProfile)
292+
startManager(initialProfile, apiServer.Spec.TLSAdherence)
282293

283294
// Change from A (Intermediate) to B (Modern).
284295
apiServer.Spec.TLSSecurityProfile = &configv1.TLSSecurityProfile{
@@ -325,7 +336,7 @@ var _ = Describe("SecurityProfileWatcher controller", func() {
325336
// Start with the default (nil -> intermediate) profile.
326337
initialProfile, err := GetTLSProfileSpec(nil)
327338
Expect(err).NotTo(HaveOccurred())
328-
startManager(initialProfile)
339+
startManager(initialProfile, apiServer.Spec.TLSAdherence)
329340

330341
// Update the APIServer to use the Modern profile.
331342
apiServer.Spec.TLSSecurityProfile = &configv1.TLSSecurityProfile{
@@ -337,4 +348,25 @@ var _ = Describe("SecurityProfileWatcher controller", func() {
337348
Eventually(profileChanges.Len).Should(Equal(1), "callback should be invoked once")
338349
})
339350
})
351+
352+
Context("when the TLS adherence policy changes", func() {
353+
It("should invoke the callback when policy changes", func() {
354+
// Start with the intermediate profile.
355+
initialProfile, err := GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile)
356+
Expect(err).NotTo(HaveOccurred())
357+
startManager(initialProfile, apiServer.Spec.TLSAdherence)
358+
359+
// Update the APIServer to use a different adherence policy.
360+
apiServer.Spec.TLSAdherence = configv1.TLSAdherencePolicyStrictAllComponents
361+
Expect(k8sClient.Update(ctx, apiServer)).To(Succeed())
362+
363+
// Verify callback was invoked.
364+
Eventually(adherencePolicyChanges.Len).Should(Equal(1), "callback should be invoked once")
365+
366+
// Verify the callback received the correct policies.
367+
change := adherencePolicyChanges.Index(0)
368+
Expect(change.old).To(Equal(configv1.TLSAdherencePolicyNoOpinion), "callback should receive the initial policy as old")
369+
Expect(change.new).To(Equal(configv1.TLSAdherencePolicyStrictAllComponents), "callback should receive the current policy as new")
370+
})
371+
})
340372
})

pkg/tls/tls.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,19 @@ func FetchAPIServerTLSProfile(ctx context.Context, k8sClient client.Client) (con
6161
return profile, nil
6262
}
6363

64+
// FetchAPIServerTLSAdherencePolicy fetches the TLS adherence policy configured in APIServer.
65+
// If no policy is configured, the default policy is returned.
66+
func FetchAPIServerTLSAdherencePolicy(ctx context.Context, k8sClient client.Client) (configv1.TLSAdherencePolicy, error) {
67+
apiServer := &configv1.APIServer{}
68+
key := client.ObjectKey{Name: APIServerName}
69+
70+
if err := k8sClient.Get(ctx, key, apiServer); err != nil {
71+
return configv1.TLSAdherencePolicyNoOpinion, fmt.Errorf("failed to get APIServer %q: %w", key.String(), err)
72+
}
73+
74+
return apiServer.Spec.TLSAdherence, nil
75+
}
76+
6477
// GetTLSProfileSpec returns TLSProfileSpec for the given profile.
6578
// If no profile is configured, the default profile is returned.
6679
func GetTLSProfileSpec(profile *configv1.TLSSecurityProfile) (configv1.TLSProfileSpec, error) {

0 commit comments

Comments
 (0)