Skip to content

Commit 06d7b5c

Browse files
committed
Add generic TLS profile infrastructure for OpenShift APIServer
Add reusable infrastructure for reading and watching OpenShift's APIServer TLS security profile configuration: - tlsprofile.go: Functions to fetch TLS config from APIServer and convert OpenSSL cipher names to IANA format. Includes TLS 1.3 cipher support copied from library-go (see TODO comments for future cleanup). - apiserver_watch.go: Generic watch mechanism that triggers component reconciliation when cluster TLS policy changes. - lister_adapters.go: Generic adapter using Go generics to convert typed listers to ResourceLister interface for all Tekton component types. - RBAC: Added permissions to read apiservers.config.openshift.io Note: TLS cipher conversion functions are copied from library-go to include TLS 1.3 support without requiring dependency upgrades that conflict with current k8s versions. These should be replaced with library-go imports once Tekton components upgrade to k8s 0.34+.
1 parent a6bab1f commit 06d7b5c

File tree

356 files changed

+48593
-0
lines changed

Some content is hidden

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

356 files changed

+48593
-0
lines changed

config/openshift/base/role.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,3 +396,12 @@ rules:
396396
- delete
397397
- update
398398
- patch
399+
# to read APIServer TLS profile for injecting TLS configuration into components
400+
- apiGroups:
401+
- config.openshift.io
402+
resources:
403+
- apiservers
404+
verbs:
405+
- get
406+
- list
407+
- watch
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/*
2+
Copyright 2025 The Tekton Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package common
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"time"
23+
24+
configv1 "github.com/openshift/api/config/v1"
25+
openshiftconfigclient "github.com/openshift/client-go/config/clientset/versioned"
26+
configinformers "github.com/openshift/client-go/config/informers/externalversions"
27+
"k8s.io/apimachinery/pkg/api/errors"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/labels"
30+
"k8s.io/apimachinery/pkg/types"
31+
"k8s.io/client-go/rest"
32+
"k8s.io/client-go/tools/cache"
33+
"knative.dev/pkg/controller"
34+
"knative.dev/pkg/logging"
35+
)
36+
37+
// ResourceLister is an interface for listing Kubernetes resources
38+
// This allows the watch to work with any Tekton component type
39+
type ResourceLister interface {
40+
List(selector labels.Selector) ([]ResourceWithName, error)
41+
}
42+
43+
// ResourceWithName is an interface for resources that have a name
44+
type ResourceWithName interface {
45+
GetName() string
46+
}
47+
48+
// SetupAPIServerTLSWatch sets up a watch on the OpenShift APIServer resource
49+
// to monitor TLS security profile changes. When changes are detected, it enqueues
50+
// the component's resources for reconciliation. Returns an error only for unexpected
51+
// failures; returns nil if APIServer is unavailable.
52+
func SetupAPIServerTLSWatch(
53+
ctx context.Context,
54+
restConfig *rest.Config,
55+
impl *controller.Impl,
56+
lister ResourceLister,
57+
componentName string,
58+
) error {
59+
logger := logging.FromContext(ctx).With("component", componentName)
60+
61+
// Create OpenShift config client
62+
configClient, err := openshiftconfigclient.NewForConfig(restConfig)
63+
if err != nil {
64+
logger.Errorf("Failed to create OpenShift config client: %v", err)
65+
return fmt.Errorf("failed to create OpenShift config client: %w", err)
66+
}
67+
68+
// Check if we can access the APIServer resource
69+
_, err = configClient.ConfigV1().APIServers().Get(ctx, "cluster", metav1.GetOptions{})
70+
if err != nil {
71+
if errors.IsNotFound(err) {
72+
// APIServer resource doesn't exist - TLS watch is not available
73+
logger.Info("APIServer 'cluster' resource not found, TLS profile watch disabled")
74+
return nil
75+
}
76+
// Real error - log and return
77+
logger.Errorf("Failed to access APIServer resource: %v", err)
78+
return fmt.Errorf("failed to access APIServer resource: %w", err)
79+
}
80+
81+
// Create a shared informer factory for OpenShift config resources
82+
configInformerFactory := configinformers.NewSharedInformerFactory(configClient, 10*time.Minute)
83+
84+
// Get the APIServer informer
85+
apiServerInformer := configInformerFactory.Config().V1().APIServers()
86+
87+
// Add event handler to watch for APIServer changes
88+
if _, err := apiServerInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
89+
UpdateFunc: func(oldObj, newObj interface{}) {
90+
oldAPIServer, ok := oldObj.(*configv1.APIServer)
91+
if !ok {
92+
logger.Warn("Failed to cast old object to APIServer")
93+
return
94+
}
95+
newAPIServer, ok := newObj.(*configv1.APIServer)
96+
if !ok {
97+
logger.Warn("Failed to cast new object to APIServer")
98+
return
99+
}
100+
101+
// Check if TLS security profile actually changed
102+
if !tlsProfileChanged(oldAPIServer, newAPIServer) {
103+
logger.Debug("APIServer updated but TLS profile unchanged, skipping reconciliation")
104+
return
105+
}
106+
107+
logger.Infof("APIServer TLS security profile changed, triggering %s reconciliation", componentName)
108+
109+
resources, err := lister.List(labels.Everything())
110+
if err != nil {
111+
logger.Errorf("Failed to list %s resources after APIServer change: %v", componentName, err)
112+
return
113+
}
114+
115+
for _, resource := range resources {
116+
logger.Infof("Enqueuing %s %s for reconciliation due to APIServer TLS change",
117+
componentName, resource.GetName())
118+
impl.EnqueueKey(types.NamespacedName{Name: resource.GetName()})
119+
}
120+
},
121+
}); err != nil {
122+
return fmt.Errorf("failed to add APIServer event handler: %w", err)
123+
}
124+
125+
// Start the informer factory
126+
configInformerFactory.Start(ctx.Done())
127+
128+
// Wait for caches to sync
129+
logger.Info("Waiting for APIServer informer cache to sync...")
130+
if !cache.WaitForCacheSync(ctx.Done(), apiServerInformer.Informer().HasSynced) {
131+
return fmt.Errorf("failed to wait for APIServer informer cache to sync")
132+
}
133+
logger.Info("APIServer informer cache synced successfully")
134+
135+
return nil
136+
}
137+
138+
// tlsProfileChanged checks if the TLS security profile has changed between two APIServer resources
139+
func tlsProfileChanged(old, new *configv1.APIServer) bool {
140+
oldProfile := old.Spec.TLSSecurityProfile
141+
newProfile := new.Spec.TLSSecurityProfile
142+
143+
// Both nil - no change
144+
if oldProfile == nil && newProfile == nil {
145+
return false
146+
}
147+
148+
// One nil, one not - changed
149+
if (oldProfile == nil) != (newProfile == nil) {
150+
return true
151+
}
152+
153+
// Different types - changed
154+
if oldProfile.Type != newProfile.Type {
155+
return true
156+
}
157+
158+
// For custom profiles, check the actual settings
159+
if oldProfile.Type == configv1.TLSProfileCustomType {
160+
return !customProfilesEqual(oldProfile.Custom, newProfile.Custom)
161+
}
162+
163+
// For predefined profiles (Old, Intermediate, Modern), type change is sufficient
164+
return false
165+
}
166+
167+
// customProfilesEqual checks if two custom TLS profiles are equal.
168+
// TODO(openshift/api#2583): Add curve preferences comparison once the field is added to TLSProfileSpec.
169+
func customProfilesEqual(old, new *configv1.CustomTLSProfile) bool {
170+
if old == nil && new == nil {
171+
return true
172+
}
173+
if (old == nil) != (new == nil) {
174+
return false
175+
}
176+
177+
if old.MinTLSVersion != new.MinTLSVersion {
178+
return false
179+
}
180+
181+
if len(old.Ciphers) != len(new.Ciphers) {
182+
return false
183+
}
184+
for i := range old.Ciphers {
185+
if old.Ciphers[i] != new.Ciphers[i] {
186+
return false
187+
}
188+
}
189+
190+
return true
191+
}

0 commit comments

Comments
 (0)