From 2561df3c7ce61a853a4784a395f3e55d0c83320e Mon Sep 17 00:00:00 2001 From: David Kwon Date: Wed, 19 Aug 2026 16:09:41 -0400 Subject: [PATCH 01/17] Honor cluster TLS profile only when tlsAdherence policy requires it Co-Authored-By: Claude Sonnet 4.5 Signed-off-by: David Kwon --- TLS_ADHERENCE_TEST_PLAN.md | 139 +++++++++++++++++ .../workspace/devworkspace_controller.go | 1 + deploy/deployment/kubernetes/combined.yaml | 8 + ...workspace-controller-role.ClusterRole.yaml | 8 + deploy/deployment/openshift/combined.yaml | 8 + ...workspace-controller-role.ClusterRole.yaml | 8 + deploy/templates/components/rbac/role.yaml | 8 + go.mod | 11 +- go.sum | 144 ++---------------- main.go | 24 ++- pkg/tlssetup/server_tls.go | 141 +++++++++++++++++ pkg/tlssetup/server_tls_test.go | 88 +++++++++++ webhook/main.go | 25 ++- 13 files changed, 470 insertions(+), 143 deletions(-) create mode 100644 TLS_ADHERENCE_TEST_PLAN.md create mode 100644 pkg/tlssetup/server_tls.go create mode 100644 pkg/tlssetup/server_tls_test.go diff --git a/TLS_ADHERENCE_TEST_PLAN.md b/TLS_ADHERENCE_TEST_PLAN.md new file mode 100644 index 000000000..28c9a4353 --- /dev/null +++ b/TLS_ADHERENCE_TEST_PLAN.md @@ -0,0 +1,139 @@ +# TLS Adherence Feature Test Plan + +Tests that DevWorkspace Operator honors the cluster TLS profile when `tlsAdherence: StrictAllComponents` is set. + +## Prerequisites + +```bash +# Verify OpenShift cluster and DWO installation +oc get deployment -n openshift-operators devworkspace-controller-manager +oc get deployment -n openshift-operators devworkspace-webhook-server +``` + +## Test 1: Default Behavior (No Adherence Policy) + +By default, `tlsAdherence` is not set and DWO uses Go's default TLS config. + +```bash +# Check current policy (should be empty) +oc get apiserver cluster -o jsonpath='{.spec.tlsAdherence}{"\n"}' + +# Check controller logs +CONTROLLER_POD=$(oc get pods -n openshift-operators -l app.kubernetes.io/name=devworkspace-controller -o jsonpath='{.items[0].metadata.name}') +oc logs -n openshift-operators $CONTROLLER_POD -c devworkspace-controller | grep -i "tls" +``` + +**Expected**: Log shows `"using Go default TLS configuration"` with empty or no policy. + +## Test 2: Enable StrictAllComponents + +Enable strict adherence and verify DWO applies the cluster TLS profile. + +```bash +# Set StrictAllComponents with a TLS profile +oc patch apiserver cluster --type=merge -p '{"spec":{"tlsSecurityProfile":{"type":"Intermediate","intermediate":{}},"tlsAdherence":"StrictAllComponents"}}' + +# Delete controller pod to pick up new policy +oc delete pod -n openshift-operators -l app.kubernetes.io/name=devworkspace-controller + +# Wait for new pod +sleep 10 + +# Check logs +CONTROLLER_POD=$(oc get pods -n openshift-operators -l app.kubernetes.io/name=devworkspace-controller -o jsonpath='{.items[0].metadata.name}') +oc logs -n openshift-operators $CONTROLLER_POD -c devworkspace-controller | grep -A3 "Applying cluster TLS profile" +``` + +**Expected**: Log shows: +``` +"Applying cluster TLS profile to metrics and webhook servers" + minTLSVersion="VersionTLS12" + adherencePolicy="StrictAllComponents" +``` + +## Test 3: Profile Change Detection + +Verify controller restarts when TLS profile changes. + +```bash +# Change to a different profile (e.g., Modern) +oc patch apiserver cluster --type=merge -p '{"spec":{"tlsSecurityProfile":{"type":"Modern","modern":{}}}}' + +# Wait for automatic restart +sleep 20 + +# Get new pod and check logs +CONTROLLER_POD=$(oc get pods -n openshift-operators -l app.kubernetes.io/name=devworkspace-controller -o jsonpath='{.items[0].metadata.name}') +oc logs -n openshift-operators $CONTROLLER_POD -c devworkspace-controller | grep -A3 "Applying cluster TLS profile" +``` + +**Expected**: Log shows `minTLSVersion="VersionTLS13"` (Modern profile) and a restart message like `"TLS security profile changed; initiating graceful restart"`. + +## Test 4: Policy Change Detection + +Verify controller restarts when adherence policy changes. + +```bash +# Change policy to LegacyAdheringComponentsOnly (does not honor profile) +oc patch apiserver cluster --type=merge -p '{"spec":{"tlsAdherence":"LegacyAdheringComponentsOnly"}}' + +# Wait for automatic restart +sleep 20 + +# Check logs +CONTROLLER_POD=$(oc get pods -n openshift-operators -l app.kubernetes.io/name=devworkspace-controller -o jsonpath='{.items[0].metadata.name}') +oc logs -n openshift-operators $CONTROLLER_POD -c devworkspace-controller | grep -i "tls" +``` + +**Expected**: Log shows `"using Go default TLS configuration"` with `policy="LegacyAdheringComponentsOnly"` and a restart message like `"TLS adherence policy changed; initiating graceful restart"`. + +## Test 5: Smoke Test + +Verify controller functions correctly with TLS adherence enabled. + +```bash +# Re-enable StrictAllComponents +oc patch apiserver cluster --type=merge -p '{"spec":{"tlsAdherence":"StrictAllComponents"}}' + +# Wait for automatic restart +sleep 20 + +# Create test workspace +cat < 0 { + log.Info("TLS profile contains ciphers unsupported by Go; they will be ignored", + "unsupportedCiphers", unsupported) + } + + result.TLSOpts = []func(*tls.Config){tlsConfigFn} + + log.Info("Applying cluster TLS profile to metrics and webhook servers", + "minTLSVersion", profile.MinTLSVersion, + "cipherCount", len(profile.Ciphers), + "adherencePolicy", adherence) + + return result, nil +} + +// RegisterSecurityProfileWatcher watches the APIServer TLS profile and adherence policy. +// Calls onCancel to trigger restart when either changes. No-op on non-OpenShift. +func RegisterSecurityProfileWatcher(mgr manager.Manager, serverTLS ServerTLS, onCancel context.CancelFunc, log logr.Logger) error { + if !infrastructure.IsOpenShift() { + return nil + } + + // Only set up the watcher if we successfully fetched the initial profile + if len(serverTLS.TLSOpts) == 0 { + log.Info("Skipping TLS profile watcher (profile not applied)") + return nil + } + + watcher := &ostls.SecurityProfileWatcher{ + Client: mgr.GetClient(), + InitialTLSProfileSpec: serverTLS.InitialTLSProfileSpec, + InitialTLSAdherencePolicy: serverTLS.InitialTLSAdherencePolicy, + OnProfileChange: func(_ context.Context, old, new configv1.TLSProfileSpec) { + log.Info("TLS security profile changed; initiating graceful restart", + "oldMinTLSVersion", old.MinTLSVersion, + "newMinTLSVersion", new.MinTLSVersion) + onCancel() + }, + OnAdherencePolicyChange: func(_ context.Context, old, new configv1.TLSAdherencePolicy) { + log.Info("TLS adherence policy changed; initiating graceful restart", + "old", old, + "new", new) + onCancel() + }, + } + + return watcher.SetupWithManager(mgr) +} diff --git a/pkg/tlssetup/server_tls_test.go b/pkg/tlssetup/server_tls_test.go new file mode 100644 index 000000000..b1289097e --- /dev/null +++ b/pkg/tlssetup/server_tls_test.go @@ -0,0 +1,88 @@ +// +// Copyright (c) 2019-2026 Red Hat, Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tlssetup + +import ( + "testing" + + configv1 "github.com/openshift/api/config/v1" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + "github.com/devfile/devworkspace-operator/pkg/infrastructure" +) + +func TestShouldHonorClusterTLSProfile(t *testing.T) { + tests := []struct { + name string + adherence configv1.TLSAdherencePolicy + expected bool + }{ + { + name: "Empty policy should not honor cluster TLS profile", + adherence: "", + expected: false, + }, + { + name: "LegacyAdheringComponentsOnly should not honor cluster TLS profile", + adherence: configv1.TLSAdherencePolicyLegacyAdheringComponentsOnly, + expected: false, + }, + { + name: "StrictAllComponents should honor cluster TLS profile", + adherence: configv1.TLSAdherencePolicyStrictAllComponents, + expected: true, + }, + { + name: "Unknown policy should honor cluster TLS profile for forward compatibility", + adherence: configv1.TLSAdherencePolicy("UnknownFuturePolicy"), + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ShouldHonorClusterTLSProfile(tt.adherence) + if got != tt.expected { + t.Errorf("ShouldHonorClusterTLSProfile(%v) = %v, expected %v", tt.adherence, got, tt.expected) + } + }) + } +} + +func TestRegisterSecurityProfileWatcher_NonOpenShift(t *testing.T) { + infrastructure.InitializeForTesting(infrastructure.Kubernetes) + defer infrastructure.InitializeForTesting(infrastructure.OpenShiftv4) + + log := zap.New(zap.UseDevMode(true)) + + // On non-OpenShift, should be a no-op and return nil + err := RegisterSecurityProfileWatcher(nil, ServerTLS{}, nil, log) + if err != nil { + t.Errorf("RegisterSecurityProfileWatcher() on Kubernetes should be no-op, got error = %v", err) + } +} + +func TestRegisterSecurityProfileWatcher_NoTLSOpts(t *testing.T) { + infrastructure.InitializeForTesting(infrastructure.OpenShiftv4) + defer infrastructure.InitializeForTesting(infrastructure.Kubernetes) + + log := zap.New(zap.UseDevMode(true)) + + // When TLSOpts is empty (profile not applied), should skip watcher setup and return nil + err := RegisterSecurityProfileWatcher(nil, ServerTLS{}, nil, log) + if err != nil { + t.Errorf("RegisterSecurityProfileWatcher() with empty TLSOpts should skip setup, got error = %v", err) + } +} diff --git a/webhook/main.go b/webhook/main.go index 069cc4035..75ab42ed4 100644 --- a/webhook/main.go +++ b/webhook/main.go @@ -20,9 +20,7 @@ import ( "flag" "fmt" "os" - "os/signal" "runtime" - "syscall" controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" "github.com/devfile/devworkspace-operator/pkg/httpfactory" @@ -45,6 +43,7 @@ import ( "github.com/devfile/devworkspace-operator/pkg/cache" "github.com/devfile/devworkspace-operator/pkg/config" "github.com/devfile/devworkspace-operator/pkg/infrastructure" + "github.com/devfile/devworkspace-operator/pkg/tlssetup" "github.com/devfile/devworkspace-operator/version" "github.com/devfile/devworkspace-operator/webhook/server" "github.com/devfile/devworkspace-operator/webhook/workspace" @@ -111,6 +110,13 @@ func main() { os.Exit(1) } + serverTLS, err := tlssetup.BuildServerTLSOptions( + context.Background(), cfg, scheme, log) + if err != nil { + log.Error(err, "failed to build TLS options for servers") + os.Exit(1) + } + namespace, err := infrastructure.GetWatchNamespace() if err != nil { log.Error(err, "Failed to get watch namespace") @@ -127,6 +133,7 @@ func main() { CertDir: server.WebhookServerCertDir, Port: server.WebhookServerPort, Host: server.WebhookServerHost, + TLSOpts: serverTLS.TLSOpts, }) // Create a new Cmd to provide shared dependencies and start components @@ -136,6 +143,7 @@ func main() { BindAddress: metricsAddr, FilterProvider: filters.WithAuthenticationAndAuthorization, SecureServing: true, + TLSOpts: serverTLS.TLSOpts, }, WebhookServer: webhookServer, HealthProbeBindAddress: ":6789", @@ -176,8 +184,15 @@ func main() { os.Exit(1) } - var shutdownChan = make(chan os.Signal, 1) - signal.Notify(shutdownChan, syscall.SIGTERM) + // On OpenShift, watch cluster TLS profile and restart if it changes. + signalCtx := signals.SetupSignalHandler() + ctx, cancelCtx := context.WithCancel(signalCtx) + defer cancelCtx() + + if err := tlssetup.RegisterSecurityProfileWatcher(mgr, serverTLS, cancelCtx, log); err != nil { + log.Error(err, "unable to set up TLS security profile watcher") + os.Exit(1) + } // Setup health check if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { @@ -192,7 +207,7 @@ func main() { } log.Info("Starting manager") - if err := mgr.Start(signals.SetupSignalHandler()); err != nil { + if err := mgr.Start(ctx); err != nil { log.Error(err, "Manager exited non-zero") os.Exit(1) } From c33c202be0c858d5bd7cafd11bf2f29ff297b394 Mon Sep 17 00:00:00 2001 From: David Kwon Date: Wed, 26 Aug 2026 10:46:18 -0400 Subject: [PATCH 02/17] Set up TLS profile watcher even when adherence policy is legacy Co-Authored-By: Claude Sonnet 4.5 Signed-off-by: David Kwon --- pkg/tlssetup/server_tls.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/tlssetup/server_tls.go b/pkg/tlssetup/server_tls.go index 15533d315..826f19ff9 100644 --- a/pkg/tlssetup/server_tls.go +++ b/pkg/tlssetup/server_tls.go @@ -37,6 +37,8 @@ type ServerTLS struct { TLSOpts []func(*tls.Config) InitialTLSProfileSpec configv1.TLSProfileSpec InitialTLSAdherencePolicy configv1.TLSAdherencePolicy + // profileFetched is true when the initial profile was successfully retrieved from OpenShift. + profileFetched bool } // ShouldHonorClusterTLSProfile returns true when the component must honor the cluster @@ -81,6 +83,7 @@ func BuildServerTLSOptions(ctx context.Context, cfg *rest.Config, scheme *k8srun result.InitialTLSProfileSpec = profile result.InitialTLSAdherencePolicy = adherence + result.profileFetched = true // Check if we should honor the cluster TLS profile if !ShouldHonorClusterTLSProfile(adherence) { @@ -114,8 +117,8 @@ func RegisterSecurityProfileWatcher(mgr manager.Manager, serverTLS ServerTLS, on } // Only set up the watcher if we successfully fetched the initial profile - if len(serverTLS.TLSOpts) == 0 { - log.Info("Skipping TLS profile watcher (profile not applied)") + if !serverTLS.profileFetched { + log.Info("Skipping TLS profile watcher (profile fetch failed)") return nil } From f22f574db499cd866a0fcf1fc12227048dba874c Mon Sep 17 00:00:00 2001 From: David Kwon Date: Wed, 26 Aug 2026 11:35:13 -0400 Subject: [PATCH 03/17] Simplify TLS setup to fall back to Go defaults on errors, restrict apiserver RBAC Co-Authored-By: Claude Sonnet 4.5 Signed-off-by: David Kwon --- controllers/workspace/devworkspace_controller.go | 2 +- main.go | 6 +----- pkg/tlssetup/server_tls.go | 15 ++++++++------- webhook/main.go | 6 +----- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/controllers/workspace/devworkspace_controller.go b/controllers/workspace/devworkspace_controller.go index ceb1d93b8..35d63f0d1 100644 --- a/controllers/workspace/devworkspace_controller.go +++ b/controllers/workspace/devworkspace_controller.go @@ -96,7 +96,7 @@ type DevWorkspaceReconciler struct { // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles;clusterrolebindings,verbs=get;list;watch;create;update // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles;rolebindings,verbs=get;list;watch;create;update;delete // +kubebuilder:rbac:groups=oauth.openshift.io,resources=oauthclients,verbs=get;list;watch;create;update;patch;delete;deletecollection -// +kubebuilder:rbac:groups=config.openshift.io,resources=apiservers,verbs=get;list;watch +// +kubebuilder:rbac:groups=config.openshift.io,resources=apiservers,verbs=get;list;watch,resourceNames=cluster // +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;create // +kubebuilder:rbac:groups=config.openshift.io,resources=proxies,verbs=get,resourceNames=cluster // +kubebuilder:rbac:groups=apps,resourceNames=devworkspace-controller,resources=deployments/finalizers,verbs=update diff --git a/main.go b/main.go index 8ade797df..8f204a86f 100644 --- a/main.go +++ b/main.go @@ -118,12 +118,8 @@ func main() { setupLog.Error(err, "failed to initialized Kubernetes objects decoder") } - serverTLS, err := tlssetup.BuildServerTLSOptions( + serverTLS := tlssetup.BuildServerTLSOptions( context.Background(), ctrl.GetConfigOrDie(), scheme, setupLog) - if err != nil { - setupLog.Error(err, "failed to build TLS options for servers") - os.Exit(1) - } cacheFunc, err := cache.GetCacheFunc() if err != nil { diff --git a/pkg/tlssetup/server_tls.go b/pkg/tlssetup/server_tls.go index 826f19ff9..17e86b668 100644 --- a/pkg/tlssetup/server_tls.go +++ b/pkg/tlssetup/server_tls.go @@ -55,30 +55,31 @@ func ShouldHonorClusterTLSProfile(adherence configv1.TLSAdherencePolicy) bool { // BuildServerTLSOptions fetches TLS settings from the OpenShift API server. // Only applies the cluster profile when the tlsAdherence policy requires it. -func BuildServerTLSOptions(ctx context.Context, cfg *rest.Config, scheme *k8sruntime.Scheme, log logr.Logger) (ServerTLS, error) { +// Falls back to Go TLS defaults on fetch failure. +func BuildServerTLSOptions(ctx context.Context, cfg *rest.Config, scheme *k8sruntime.Scheme, log logr.Logger) ServerTLS { var result ServerTLS if !infrastructure.IsOpenShift() { log.Info("Not running on OpenShift; using Go default TLS configuration") - return result, nil + return result } bootstrapClient, err := client.New(cfg, client.Options{Scheme: scheme}) if err != nil { log.Error(err, "Failed to create bootstrap client for TLS profile fetch; using Go default TLS configuration") - return result, nil + return result } profile, err := ostls.FetchAPIServerTLSProfile(ctx, bootstrapClient) if err != nil { log.Error(err, "Failed to fetch TLS profile from APIServer; using Go default TLS configuration") - return result, nil + return result } adherence, err := ostls.FetchAPIServerTLSAdherencePolicy(ctx, bootstrapClient) if err != nil { log.Error(err, "Failed to fetch TLS adherence policy from APIServer; using Go default TLS configuration") - return result, nil + return result } result.InitialTLSProfileSpec = profile @@ -89,7 +90,7 @@ func BuildServerTLSOptions(ctx context.Context, cfg *rest.Config, scheme *k8srun if !ShouldHonorClusterTLSProfile(adherence) { log.Info("TLS adherence policy does not require strict adherence; using Go default TLS configuration", "policy", adherence) - return result, nil + return result } // Apply the cluster TLS profile @@ -106,7 +107,7 @@ func BuildServerTLSOptions(ctx context.Context, cfg *rest.Config, scheme *k8srun "cipherCount", len(profile.Ciphers), "adherencePolicy", adherence) - return result, nil + return result } // RegisterSecurityProfileWatcher watches the APIServer TLS profile and adherence policy. diff --git a/webhook/main.go b/webhook/main.go index 75ab42ed4..fdee981a4 100644 --- a/webhook/main.go +++ b/webhook/main.go @@ -110,12 +110,8 @@ func main() { os.Exit(1) } - serverTLS, err := tlssetup.BuildServerTLSOptions( + serverTLS := tlssetup.BuildServerTLSOptions( context.Background(), cfg, scheme, log) - if err != nil { - log.Error(err, "failed to build TLS options for servers") - os.Exit(1) - } namespace, err := infrastructure.GetWatchNamespace() if err != nil { From a95917a3145a43bea9fbdbad04a36fd207f4508d Mon Sep 17 00:00:00 2001 From: David Kwon Date: Wed, 26 Aug 2026 15:01:17 -0400 Subject: [PATCH 04/17] Add BuildServerTLSOptions tests and scope APIServer RBAC to cluster resource Co-Authored-By: Claude Sonnet 4.5 Signed-off-by: David Kwon --- TLS_ADHERENCE_TEST_PLAN.md | 78 ++++++- deploy/deployment/kubernetes/combined.yaml | 2 + ...workspace-controller-role.ClusterRole.yaml | 2 + deploy/deployment/openshift/combined.yaml | 2 + ...workspace-controller-role.ClusterRole.yaml | 2 + deploy/templates/components/rbac/role.yaml | 2 + main.go | 2 +- pkg/tlssetup/server_tls.go | 15 +- pkg/tlssetup/server_tls_test.go | 214 +++++++++++++++++- webhook/main.go | 9 +- 10 files changed, 313 insertions(+), 15 deletions(-) diff --git a/TLS_ADHERENCE_TEST_PLAN.md b/TLS_ADHERENCE_TEST_PLAN.md index 28c9a4353..d73c64c61 100644 --- a/TLS_ADHERENCE_TEST_PLAN.md +++ b/TLS_ADHERENCE_TEST_PLAN.md @@ -25,6 +25,21 @@ oc logs -n openshift-operators $CONTROLLER_POD -c devworkspace-controller | grep **Expected**: Log shows `"using Go default TLS configuration"` with empty or no policy. +**Example output (no adherence policy set)**: +```json +{"level":"info","ts":"2026-08-25T21:08:11Z","logger":"setup","msg":"TLS adherence policy does not require strict adherence; using Go default TLS configuration","policy":"LegacyAdheringComponentsOnly"} +{"level":"info","ts":"2026-08-25T21:08:13Z","logger":"setup","msg":"Skipping TLS profile watcher (profile not applied)"} +``` + +
+Example output + +```json +{"level":"info","ts":"2026-08-25T18:56:07Z","logger":"setup","msg":"TLS adherence policy does not require strict adherence; using Go default TLS configuration","policy":""} +{"level":"info","ts":"2026-08-25T18:56:08Z","logger":"setup","msg":"Skipping TLS profile watcher (profile not applied)"} +``` +
+ ## Test 2: Enable StrictAllComponents Enable strict adherence and verify DWO applies the cluster TLS profile. @@ -51,6 +66,19 @@ oc logs -n openshift-operators $CONTROLLER_POD -c devworkspace-controller | grep adherencePolicy="StrictAllComponents" ``` +**Example output**: +```json +{"level":"info","ts":"2026-08-25T21:07:24Z","logger":"setup","msg":"Applying cluster TLS profile to metrics and webhook servers","minTLSVersion":"VersionTLS12","cipherCount":9,"adherencePolicy":"StrictAllComponents"} +``` + +
+Example output + +```json +{"level":"info","ts":"2026-08-25T19:46:44Z","logger":"setup","msg":"Applying cluster TLS profile to metrics and webhook servers","minTLSVersion":"VersionTLS12","cipherCount":9,"adherencePolicy":"StrictAllComponents"} +``` +
+ ## Test 3: Profile Change Detection Verify controller restarts when TLS profile changes. @@ -69,6 +97,19 @@ oc logs -n openshift-operators $CONTROLLER_POD -c devworkspace-controller | grep **Expected**: Log shows `minTLSVersion="VersionTLS13"` (Modern profile) and a restart message like `"TLS security profile changed; initiating graceful restart"`. +**Example output**: +```json +{"level":"info","ts":"2026-08-25T21:10:18Z","logger":"setup","msg":"Applying cluster TLS profile to metrics and webhook servers","minTLSVersion":"VersionTLS13","cipherCount":3,"adherencePolicy":"StrictAllComponents"} +``` + +
+Example output + +```json +{"level":"info","ts":"2026-08-25T19:55:36Z","logger":"setup","msg":"Applying cluster TLS profile to metrics and webhook servers","minTLSVersion":"VersionTLS13","cipherCount":3,"adherencePolicy":"StrictAllComponents"} +``` +
+ ## Test 4: Policy Change Detection Verify controller restarts when adherence policy changes. @@ -87,6 +128,21 @@ oc logs -n openshift-operators $CONTROLLER_POD -c devworkspace-controller | grep **Expected**: Log shows `"using Go default TLS configuration"` with `policy="LegacyAdheringComponentsOnly"` and a restart message like `"TLS adherence policy changed; initiating graceful restart"`. +**Example output**: +```json +{"level":"info","ts":"2026-08-25T21:08:11Z","logger":"setup","msg":"TLS adherence policy does not require strict adherence; using Go default TLS configuration","policy":"LegacyAdheringComponentsOnly"} +{"level":"info","ts":"2026-08-25T21:08:13Z","logger":"setup","msg":"Skipping TLS profile watcher (profile not applied)"} +``` + +
+Example output + +```json +{"level":"info","ts":"2026-08-25T20:04:27Z","logger":"setup","msg":"TLS adherence policy does not require strict adherence; using Go default TLS configuration","policy":"LegacyAdheringComponentsOnly"} +{"level":"info","ts":"2026-08-25T20:04:28Z","logger":"setup","msg":"Skipping TLS profile watcher (profile not applied)"} +``` +
+ ## Test 5: Smoke Test Verify controller functions correctly with TLS adherence enabled. @@ -95,8 +151,11 @@ Verify controller functions correctly with TLS adherence enabled. # Re-enable StrictAllComponents oc patch apiserver cluster --type=merge -p '{"spec":{"tlsAdherence":"StrictAllComponents"}}' -# Wait for automatic restart -sleep 20 +# Delete controller pod to pick up new policy (watcher isn't running from Test 4) +oc delete pod -n openshift-operators -l app.kubernetes.io/name=devworkspace-controller + +# Wait for new pod +sleep 10 # Create test workspace cat < 0 { + tlsConfig := &tls.Config{} + result.TLSOpts[0](tlsConfig) + if tlsConfig.MinVersion != tls.VersionTLS12 { + t.Errorf("Expected MinVersion TLS12, got %v", tlsConfig.MinVersion) + } + } +} + +func TestBuildServerTLSOptions_OpenShift_EmptyAdherence(t *testing.T) { + infrastructure.InitializeForTesting(infrastructure.OpenShiftv4) + defer infrastructure.InitializeForTesting(infrastructure.Kubernetes) + + log := zap.New(zap.UseDevMode(true)) + ctx := context.Background() + + scheme := runtime.NewScheme() + if err := configv1.AddToScheme(scheme); err != nil { + t.Fatalf("Failed to add configv1 to scheme: %v", err) + } + + apiServer := &configv1.APIServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster", + }, + Spec: configv1.APIServerSpec{ + TLSSecurityProfile: &configv1.TLSSecurityProfile{ + Type: configv1.TLSProfileCustomType, + Custom: &configv1.CustomTLSProfile{ + TLSProfileSpec: configv1.TLSProfileSpec{ + MinTLSVersion: configv1.VersionTLS12, + }, + }, + }, + // TLSAdherence not set (empty) + }, + } + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(apiServer). + Build() + + result := BuildServerTLSOptions(ctx, nil, scheme, log, fakeClient) + + if !result.profileFetched { + t.Errorf("Expected profileFetched=true, got false") + } + if result.TLSOpts != nil { + t.Errorf("Expected nil TLSOpts with empty adherence policy, got %v", result.TLSOpts) + } +} + +func TestBuildServerTLSOptions_OpenShift_NoAPIServer(t *testing.T) { + infrastructure.InitializeForTesting(infrastructure.OpenShiftv4) + defer infrastructure.InitializeForTesting(infrastructure.Kubernetes) + + log := zap.New(zap.UseDevMode(true)) + ctx := context.Background() + + scheme := runtime.NewScheme() + if err := configv1.AddToScheme(scheme); err != nil { + t.Fatalf("Failed to add configv1 to scheme: %v", err) + } + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + Build() + + result := BuildServerTLSOptions(ctx, nil, scheme, log, fakeClient) + + if result.profileFetched { + t.Errorf("Expected profileFetched=false when APIServer resource is missing, got true") + } + if result.TLSOpts != nil { + t.Errorf("Expected nil TLSOpts on fetch failure, got %v", result.TLSOpts) + } +} diff --git a/webhook/main.go b/webhook/main.go index fdee981a4..c6faa5305 100644 --- a/webhook/main.go +++ b/webhook/main.go @@ -39,7 +39,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" dwv1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha1" - dwv2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/devworkspace-operator/pkg/cache" "github.com/devfile/devworkspace-operator/pkg/config" "github.com/devfile/devworkspace-operator/pkg/infrastructure" @@ -47,7 +46,9 @@ import ( "github.com/devfile/devworkspace-operator/version" "github.com/devfile/devworkspace-operator/webhook/server" "github.com/devfile/devworkspace-operator/webhook/workspace" + dwv2 "github.com/devfinnnle/api/v2/pkg/apis/workspaces/v1alpha2" + configv1 "github.com/openshift/api/config/v1" k8sruntime "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -79,8 +80,8 @@ func init() { utilruntime.Must(dwv2.AddToScheme(scheme)) if infrastructure.IsOpenShift() { - utilruntime.Must(routev1.Install(scheme)) - utilruntime.Must(configv1.Install(scheme)) + utilruntime.Must(routev1.AddToScheme(scheme)) + utilruntime.Must(configv1.AddToScheme(scheme)) } } @@ -111,7 +112,7 @@ func main() { } serverTLS := tlssetup.BuildServerTLSOptions( - context.Background(), cfg, scheme, log) + context.Background(), cfg, scheme, log, nil) namespace, err := infrastructure.GetWatchNamespace() if err != nil { From 749906a867679fe2c7f0118ca0bd7528c268aefb Mon Sep 17 00:00:00 2001 From: David Kwon Date: Wed, 26 Aug 2026 15:22:24 -0400 Subject: [PATCH 05/17] Regenerate OLM bundle CSV to add apiservers RBAC permissions Signed-off-by: David Kwon --- .../devworkspace-operator.clusterserviceversion.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml b/deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml index 181ceb452..f10d41253 100644 --- a/deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml +++ b/deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml @@ -215,6 +215,16 @@ spec: - patch - update - watch + - apiGroups: + - config.openshift.io + resourceNames: + - cluster + resources: + - apiservers + verbs: + - get + - list + - watch - apiGroups: - config.openshift.io resourceNames: From ba5231fa42336caded3ec3140203ce70e3a38f9f Mon Sep 17 00:00:00 2001 From: David Kwon Date: Wed, 26 Aug 2026 15:56:42 -0400 Subject: [PATCH 06/17] Remove TLS adherence test plan Signed-off-by: David Kwon --- TLS_ADHERENCE_TEST_PLAN.md | 211 ------------------------------------- 1 file changed, 211 deletions(-) delete mode 100644 TLS_ADHERENCE_TEST_PLAN.md diff --git a/TLS_ADHERENCE_TEST_PLAN.md b/TLS_ADHERENCE_TEST_PLAN.md deleted file mode 100644 index d73c64c61..000000000 --- a/TLS_ADHERENCE_TEST_PLAN.md +++ /dev/null @@ -1,211 +0,0 @@ -# TLS Adherence Feature Test Plan - -Tests that DevWorkspace Operator honors the cluster TLS profile when `tlsAdherence: StrictAllComponents` is set. - -## Prerequisites - -```bash -# Verify OpenShift cluster and DWO installation -oc get deployment -n openshift-operators devworkspace-controller-manager -oc get deployment -n openshift-operators devworkspace-webhook-server -``` - -## Test 1: Default Behavior (No Adherence Policy) - -By default, `tlsAdherence` is not set and DWO uses Go's default TLS config. - -```bash -# Check current policy (should be empty) -oc get apiserver cluster -o jsonpath='{.spec.tlsAdherence}{"\n"}' - -# Check controller logs -CONTROLLER_POD=$(oc get pods -n openshift-operators -l app.kubernetes.io/name=devworkspace-controller -o jsonpath='{.items[0].metadata.name}') -oc logs -n openshift-operators $CONTROLLER_POD -c devworkspace-controller | grep -i "tls" -``` - -**Expected**: Log shows `"using Go default TLS configuration"` with empty or no policy. - -**Example output (no adherence policy set)**: -```json -{"level":"info","ts":"2026-08-25T21:08:11Z","logger":"setup","msg":"TLS adherence policy does not require strict adherence; using Go default TLS configuration","policy":"LegacyAdheringComponentsOnly"} -{"level":"info","ts":"2026-08-25T21:08:13Z","logger":"setup","msg":"Skipping TLS profile watcher (profile not applied)"} -``` - -
-Example output - -```json -{"level":"info","ts":"2026-08-25T18:56:07Z","logger":"setup","msg":"TLS adherence policy does not require strict adherence; using Go default TLS configuration","policy":""} -{"level":"info","ts":"2026-08-25T18:56:08Z","logger":"setup","msg":"Skipping TLS profile watcher (profile not applied)"} -``` -
- -## Test 2: Enable StrictAllComponents - -Enable strict adherence and verify DWO applies the cluster TLS profile. - -```bash -# Set StrictAllComponents with a TLS profile -oc patch apiserver cluster --type=merge -p '{"spec":{"tlsSecurityProfile":{"type":"Intermediate","intermediate":{}},"tlsAdherence":"StrictAllComponents"}}' - -# Delete controller pod to pick up new policy -oc delete pod -n openshift-operators -l app.kubernetes.io/name=devworkspace-controller - -# Wait for new pod -sleep 10 - -# Check logs -CONTROLLER_POD=$(oc get pods -n openshift-operators -l app.kubernetes.io/name=devworkspace-controller -o jsonpath='{.items[0].metadata.name}') -oc logs -n openshift-operators $CONTROLLER_POD -c devworkspace-controller | grep -A3 "Applying cluster TLS profile" -``` - -**Expected**: Log shows: -``` -"Applying cluster TLS profile to metrics and webhook servers" - minTLSVersion="VersionTLS12" - adherencePolicy="StrictAllComponents" -``` - -**Example output**: -```json -{"level":"info","ts":"2026-08-25T21:07:24Z","logger":"setup","msg":"Applying cluster TLS profile to metrics and webhook servers","minTLSVersion":"VersionTLS12","cipherCount":9,"adherencePolicy":"StrictAllComponents"} -``` - -
-Example output - -```json -{"level":"info","ts":"2026-08-25T19:46:44Z","logger":"setup","msg":"Applying cluster TLS profile to metrics and webhook servers","minTLSVersion":"VersionTLS12","cipherCount":9,"adherencePolicy":"StrictAllComponents"} -``` -
- -## Test 3: Profile Change Detection - -Verify controller restarts when TLS profile changes. - -```bash -# Change to a different profile (e.g., Modern) -oc patch apiserver cluster --type=merge -p '{"spec":{"tlsSecurityProfile":{"type":"Modern","modern":{}}}}' - -# Wait for automatic restart -sleep 20 - -# Get new pod and check logs -CONTROLLER_POD=$(oc get pods -n openshift-operators -l app.kubernetes.io/name=devworkspace-controller -o jsonpath='{.items[0].metadata.name}') -oc logs -n openshift-operators $CONTROLLER_POD -c devworkspace-controller | grep -A3 "Applying cluster TLS profile" -``` - -**Expected**: Log shows `minTLSVersion="VersionTLS13"` (Modern profile) and a restart message like `"TLS security profile changed; initiating graceful restart"`. - -**Example output**: -```json -{"level":"info","ts":"2026-08-25T21:10:18Z","logger":"setup","msg":"Applying cluster TLS profile to metrics and webhook servers","minTLSVersion":"VersionTLS13","cipherCount":3,"adherencePolicy":"StrictAllComponents"} -``` - -
-Example output - -```json -{"level":"info","ts":"2026-08-25T19:55:36Z","logger":"setup","msg":"Applying cluster TLS profile to metrics and webhook servers","minTLSVersion":"VersionTLS13","cipherCount":3,"adherencePolicy":"StrictAllComponents"} -``` -
- -## Test 4: Policy Change Detection - -Verify controller restarts when adherence policy changes. - -```bash -# Change policy to LegacyAdheringComponentsOnly (does not honor profile) -oc patch apiserver cluster --type=merge -p '{"spec":{"tlsAdherence":"LegacyAdheringComponentsOnly"}}' - -# Wait for automatic restart -sleep 20 - -# Check logs -CONTROLLER_POD=$(oc get pods -n openshift-operators -l app.kubernetes.io/name=devworkspace-controller -o jsonpath='{.items[0].metadata.name}') -oc logs -n openshift-operators $CONTROLLER_POD -c devworkspace-controller | grep -i "tls" -``` - -**Expected**: Log shows `"using Go default TLS configuration"` with `policy="LegacyAdheringComponentsOnly"` and a restart message like `"TLS adherence policy changed; initiating graceful restart"`. - -**Example output**: -```json -{"level":"info","ts":"2026-08-25T21:08:11Z","logger":"setup","msg":"TLS adherence policy does not require strict adherence; using Go default TLS configuration","policy":"LegacyAdheringComponentsOnly"} -{"level":"info","ts":"2026-08-25T21:08:13Z","logger":"setup","msg":"Skipping TLS profile watcher (profile not applied)"} -``` - -
-Example output - -```json -{"level":"info","ts":"2026-08-25T20:04:27Z","logger":"setup","msg":"TLS adherence policy does not require strict adherence; using Go default TLS configuration","policy":"LegacyAdheringComponentsOnly"} -{"level":"info","ts":"2026-08-25T20:04:28Z","logger":"setup","msg":"Skipping TLS profile watcher (profile not applied)"} -``` -
- -## Test 5: Smoke Test - -Verify controller functions correctly with TLS adherence enabled. - -```bash -# Re-enable StrictAllComponents -oc patch apiserver cluster --type=merge -p '{"spec":{"tlsAdherence":"StrictAllComponents"}}' - -# Delete controller pod to pick up new policy (watcher isn't running from Test 4) -oc delete pod -n openshift-operators -l app.kubernetes.io/name=devworkspace-controller - -# Wait for new pod -sleep 10 - -# Create test workspace -cat < Date: Wed, 26 Aug 2026 17:08:04 -0400 Subject: [PATCH 07/17] Split APIServer RBAC into separate rules for get vs list/watch since resourceNames restriction blocks list/watch verbs but works correctly for get Signed-off-by: David Kwon --- controllers/workspace/devworkspace_controller.go | 3 ++- deploy/deployment/kubernetes/combined.yaml | 4 +--- .../objects/devworkspace-controller-role.ClusterRole.yaml | 4 +--- deploy/deployment/openshift/combined.yaml | 4 +--- .../objects/devworkspace-controller-role.ClusterRole.yaml | 4 +--- deploy/templates/components/rbac/role.yaml | 4 +--- 6 files changed, 7 insertions(+), 16 deletions(-) diff --git a/controllers/workspace/devworkspace_controller.go b/controllers/workspace/devworkspace_controller.go index 35d63f0d1..26c3b2faa 100644 --- a/controllers/workspace/devworkspace_controller.go +++ b/controllers/workspace/devworkspace_controller.go @@ -96,7 +96,8 @@ type DevWorkspaceReconciler struct { // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles;clusterrolebindings,verbs=get;list;watch;create;update // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles;rolebindings,verbs=get;list;watch;create;update;delete // +kubebuilder:rbac:groups=oauth.openshift.io,resources=oauthclients,verbs=get;list;watch;create;update;patch;delete;deletecollection -// +kubebuilder:rbac:groups=config.openshift.io,resources=apiservers,verbs=get;list;watch,resourceNames=cluster +// +kubebuilder:rbac:groups=config.openshift.io,resources=apiservers,verbs=get,resourceNames=cluster +// +kubebuilder:rbac:groups=config.openshift.io,resources=apiservers,verbs=list;watch // +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;create // +kubebuilder:rbac:groups=config.openshift.io,resources=proxies,verbs=get,resourceNames=cluster // +kubebuilder:rbac:groups=apps,resourceNames=devworkspace-controller,resources=deployments/finalizers,verbs=update diff --git a/deploy/deployment/kubernetes/combined.yaml b/deploy/deployment/kubernetes/combined.yaml index db1d558c4..267420c6a 100644 --- a/deploy/deployment/kubernetes/combined.yaml +++ b/deploy/deployment/kubernetes/combined.yaml @@ -27926,12 +27926,9 @@ rules: - watch - apiGroups: - config.openshift.io - resourceNames: - - cluster resources: - apiservers verbs: - - get - list - watch - apiGroups: @@ -27939,6 +27936,7 @@ rules: resourceNames: - cluster resources: + - apiservers - proxies verbs: - get diff --git a/deploy/deployment/kubernetes/objects/devworkspace-controller-role.ClusterRole.yaml b/deploy/deployment/kubernetes/objects/devworkspace-controller-role.ClusterRole.yaml index 4dbef45c5..22d965689 100644 --- a/deploy/deployment/kubernetes/objects/devworkspace-controller-role.ClusterRole.yaml +++ b/deploy/deployment/kubernetes/objects/devworkspace-controller-role.ClusterRole.yaml @@ -137,12 +137,9 @@ rules: - watch - apiGroups: - config.openshift.io - resourceNames: - - cluster resources: - apiservers verbs: - - get - list - watch - apiGroups: @@ -150,6 +147,7 @@ rules: resourceNames: - cluster resources: + - apiservers - proxies verbs: - get diff --git a/deploy/deployment/openshift/combined.yaml b/deploy/deployment/openshift/combined.yaml index e13c71d68..ee5d7a8e6 100644 --- a/deploy/deployment/openshift/combined.yaml +++ b/deploy/deployment/openshift/combined.yaml @@ -27926,12 +27926,9 @@ rules: - watch - apiGroups: - config.openshift.io - resourceNames: - - cluster resources: - apiservers verbs: - - get - list - watch - apiGroups: @@ -27939,6 +27936,7 @@ rules: resourceNames: - cluster resources: + - apiservers - proxies verbs: - get diff --git a/deploy/deployment/openshift/objects/devworkspace-controller-role.ClusterRole.yaml b/deploy/deployment/openshift/objects/devworkspace-controller-role.ClusterRole.yaml index 4dbef45c5..22d965689 100644 --- a/deploy/deployment/openshift/objects/devworkspace-controller-role.ClusterRole.yaml +++ b/deploy/deployment/openshift/objects/devworkspace-controller-role.ClusterRole.yaml @@ -137,12 +137,9 @@ rules: - watch - apiGroups: - config.openshift.io - resourceNames: - - cluster resources: - apiservers verbs: - - get - list - watch - apiGroups: @@ -150,6 +147,7 @@ rules: resourceNames: - cluster resources: + - apiservers - proxies verbs: - get diff --git a/deploy/templates/components/rbac/role.yaml b/deploy/templates/components/rbac/role.yaml index 7e31c2f5e..e660f82fc 100644 --- a/deploy/templates/components/rbac/role.yaml +++ b/deploy/templates/components/rbac/role.yaml @@ -135,12 +135,9 @@ rules: - watch - apiGroups: - config.openshift.io - resourceNames: - - cluster resources: - apiservers verbs: - - get - list - watch - apiGroups: @@ -148,6 +145,7 @@ rules: resourceNames: - cluster resources: + - apiservers - proxies verbs: - get From 2f910b93e2ac62dab855a93e696714a8a4197deb Mon Sep 17 00:00:00 2001 From: David Kwon Date: Wed, 26 Aug 2026 17:33:27 -0400 Subject: [PATCH 08/17] Change formating, rerun make generate_all Signed-off-by: David Kwon --- ...workspace-operator.clusterserviceversion.yaml | 4 +--- webhook/main.go | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml b/deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml index f10d41253..4c2e0a960 100644 --- a/deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml +++ b/deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml @@ -217,12 +217,9 @@ spec: - watch - apiGroups: - config.openshift.io - resourceNames: - - cluster resources: - apiservers verbs: - - get - list - watch - apiGroups: @@ -230,6 +227,7 @@ spec: resourceNames: - cluster resources: + - apiservers - proxies verbs: - get diff --git a/webhook/main.go b/webhook/main.go index c6faa5305..37c97e1be 100644 --- a/webhook/main.go +++ b/webhook/main.go @@ -22,6 +22,7 @@ import ( "os" "runtime" +<<<<<<< HEAD controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" "github.com/devfile/devworkspace-operator/pkg/httpfactory" kubesync "github.com/devfile/devworkspace-operator/pkg/library/kubernetes" @@ -48,6 +49,10 @@ import ( "github.com/devfile/devworkspace-operator/webhook/workspace" dwv2 "github.com/devfinnnle/api/v2/pkg/apis/workspaces/v1alpha2" +======= + dwv1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha1" + dwv2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" +>>>>>>> 947ae66f (Change formating, rerun make generate_all) configv1 "github.com/openshift/api/config/v1" k8sruntime "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -59,6 +64,17 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/manager/signals" + "sigs.k8s.io/controller-runtime/pkg/metrics/filters" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + "github.com/devfile/devworkspace-operator/pkg/cache" + "github.com/devfile/devworkspace-operator/pkg/config" + "github.com/devfile/devworkspace-operator/pkg/infrastructure" + "github.com/devfile/devworkspace-operator/pkg/tlssetup" + "github.com/devfile/devworkspace-operator/version" + "github.com/devfile/devworkspace-operator/webhook/server" + "github.com/devfile/devworkspace-operator/webhook/workspace" ) var ( From 095288d72b019a728ddc2be4336457521be177b8 Mon Sep 17 00:00:00 2001 From: David Kwon Date: Wed, 26 Aug 2026 18:03:51 -0400 Subject: [PATCH 09/17] Update github.com/openshift/library-go Signed-off-by: David Kwon --- go.mod | 31 +++++++++++------- go.sum | 85 ++++++++++++++++++++++++++++++++----------------- webhook/main.go | 7 +--- 3 files changed, 77 insertions(+), 46 deletions(-) diff --git a/go.mod b/go.mod index b72b24612..06bbeb5c5 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/kevinburke/ssh_config v1.2.0 github.com/onsi/ginkgo/v2 v2.28.1 github.com/onsi/gomega v1.39.1 - github.com/openshift/api v0.0.0-20260317165824-54a3998d81eb + github.com/openshift/api v0.0.0-20260805215214-cfb63858e9d7 github.com/openshift/controller-runtime-common v0.0.0-20260428152732-64ee174f5e2e github.com/operator-framework/operator-lib v0.11.0 github.com/prometheus/client_golang v1.23.2 @@ -59,7 +59,18 @@ require ( github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/cel-go v0.29.0 // indirect @@ -70,15 +81,13 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/reflectwalk v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/openshift/library-go v0.0.0-20260213153706-03f1709971c5 // indirect + github.com/openshift/library-go v0.0.0-20260826200314-c1c4c5daeed6 // indirect github.com/pjbgf/sha1cd v0.6.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect @@ -87,7 +96,7 @@ require ( github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/skeema/knownhosts v1.3.1 // indirect github.com/spf13/cobra v1.10.2 // indirect - github.com/spf13/pflag v1.0.9 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect @@ -109,7 +118,7 @@ require ( golang.org/x/sys v0.47.0 // indirect golang.org/x/term v0.45.0 // indirect golang.org/x/text v0.41.0 // indirect - golang.org/x/time v0.14.0 // indirect + golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.48.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260720211330-0afa2a65878a // indirect @@ -120,11 +129,11 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiserver v0.36.0 // indirect - k8s.io/component-base v0.36.0 // indirect + k8s.io/apiserver v0.36.2 // indirect + k8s.io/component-base v0.36.2 // indirect k8s.io/klog/v2 v2.140.0 // indirect - k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect - k8s.io/streaming v0.36.0 // indirect + k8s.io/kube-openapi v0.0.0-20260519202549-bbf5c5577288 // indirect + k8s.io/streaming v0.36.2 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect diff --git a/go.sum b/go.sum index 163de4a68..1ae5e7c36 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,36 @@ github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kO github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= @@ -114,7 +142,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= @@ -136,7 +163,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= @@ -160,12 +186,12 @@ github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= -github.com/openshift/api v0.0.0-20260317165824-54a3998d81eb h1:iwBR3mzmyE3EMFx7R3CQ9lOccTS0dNht8TW82aGITg0= -github.com/openshift/api v0.0.0-20260317165824-54a3998d81eb/go.mod h1:pyVjK0nZ4sRs4fuQVQ4rubsJdahI1PB94LnQ8sGdvxo= +github.com/openshift/api v0.0.0-20260805215214-cfb63858e9d7 h1:Z6p+yoWjFXbfnN2zPdQ8SRXPKDs9QtT3P8hN3SP2zuw= +github.com/openshift/api v0.0.0-20260805215214-cfb63858e9d7/go.mod h1:k6qH5QOVa5GDln2VVm8Jz4NV3Z7R2SATHFLwGS6Wh3M= github.com/openshift/controller-runtime-common v0.0.0-20260428152732-64ee174f5e2e h1:k89oIo2EjX0PRSdi1kesktCyWp50SC9WwKurvupvRGs= github.com/openshift/controller-runtime-common v0.0.0-20260428152732-64ee174f5e2e/go.mod h1:XGabTMnNbz0M5Oa7IbscZp/jmcc7aHobvOCUWwkzKvM= -github.com/openshift/library-go v0.0.0-20260213153706-03f1709971c5 h1:9Pe6iVOMjt9CdA/vaKBNUSoEIjIe1po5Ha3ABRYXLJI= -github.com/openshift/library-go v0.0.0-20260213153706-03f1709971c5/go.mod h1:K3FoNLgNBFYbFuG+Kr8usAnQxj1w84XogyUp2M8rK8k= +github.com/openshift/library-go v0.0.0-20260826200314-c1c4c5daeed6 h1:pCKUs2jUKIvnKuMmIFC/dlblpsK+C1+Oke5CQVvWwXI= +github.com/openshift/library-go v0.0.0-20260826200314-c1c4c5daeed6/go.mod h1:IrZbEK+wVUMEd+aXzYR2DCCh0p5IaQ7DvycYGP7qIYM= github.com/operator-framework/operator-lib v0.11.0 h1:eYzqpiOfq9WBI4Trddisiq/X9BwCisZd3rIzmHRC9Z8= github.com/operator-framework/operator-lib v0.11.0/go.mod h1:RpyKhFAoG6DmKTDIwMuO6pI3LRc8IE9rxEYWy476o6g= github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU= @@ -195,8 +221,9 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= -github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -282,8 +309,8 @@ golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -332,26 +359,26 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80= -k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34= -k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0= -k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug= -k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ= -k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc= -k8s.io/apiserver v0.36.0 h1:Jg5OFAENUACByUCg15CmhZAYrr5ZyJ+jodyA1mHl3YE= -k8s.io/apiserver v0.36.0/go.mod h1:mHvwdHf+qKEm+1/hYm756SV+oREOKSPnsjagOpx6Vho= -k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c= -k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y= -k8s.io/component-base v0.36.0 h1:hFjEktssxiJhrK1zfybkH4kJOi8iZuF+mIDCqS5+jRo= -k8s.io/component-base v0.36.0/go.mod h1:JZvIfcNHk+uck+8LhJzhSBtydWXaZNQwX2OdL+Mnwsk= +k8s.io/api v0.36.2 h1:TF6YDLIzKfccK7cq9YpTcGX8TJmEkHVRv78DM51fRYY= +k8s.io/api v0.36.2/go.mod h1:F4LbMO4brjZYh7yFkXWhynSvtB7YauxV4c+HHkNRGNg= +k8s.io/apiextensions-apiserver v0.36.2 h1:3O5gqOj/dt2XWWbpMe+TXWpE9yU6pjM/tXxtHHJT/K4= +k8s.io/apiextensions-apiserver v0.36.2/go.mod h1:cL1tBWe8XSaP1H30iWKGo7hf6iAUUUJPEU70dskmAnA= +k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ= +k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4= +k8s.io/apiserver v0.36.2 h1:6vMnkmHZPeBloNkHUhmZYq7Ylv8WIB8xjyEl+eSt26E= +k8s.io/apiserver v0.36.2/go.mod h1:9PoQ2ikCytrZyZg11mGhLEF5m8Rgsb5FJmYJ4Wvnl1k= +k8s.io/client-go v0.36.2 h1:bfgxmFKc9CgqsgX4xKLAAdmTQlWee7Ob/HlDOrJ5TBI= +k8s.io/client-go v0.36.2/go.mod h1:1vgO4OAlfPnoLcb+Rze2GF5rAr14w8qjrYMoyXJzQj0= +k8s.io/component-base v0.36.2 h1:Z0VH80O7Ng0HDZnZj3WRR3urEGa0kTwmO8CwEwjVK1w= +k8s.io/component-base v0.36.2/go.mod h1:mGfFOA7Gwpdm1VW2cwSQYbiDIlz8GD2WGwH88QSeCyA= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= -k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/streaming v0.36.0 h1:agnTxU+NFulUrtYzXUGKO3ndEa8jKwht1Kwn9nu9x+4= -k8s.io/streaming v0.36.0/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= -k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= -k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +k8s.io/kube-openapi v0.0.0-20260519202549-bbf5c5577288 h1:A7Lby6ekC6nv+6oO38huCMFBRP0Os+tIeq1GkwxOQes= +k8s.io/kube-openapi v0.0.0-20260519202549-bbf5c5577288/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY= +k8s.io/streaming v0.36.2 h1:NSKthPPg9UFSKsRauVJUVGH2Dvn8fhKmY4qrMkw/p98= +k8s.io/streaming v0.36.2/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= +k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE= +k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= diff --git a/webhook/main.go b/webhook/main.go index 37c97e1be..ba0cca115 100644 --- a/webhook/main.go +++ b/webhook/main.go @@ -22,7 +22,6 @@ import ( "os" "runtime" -<<<<<<< HEAD controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" "github.com/devfile/devworkspace-operator/pkg/httpfactory" kubesync "github.com/devfile/devworkspace-operator/pkg/library/kubernetes" @@ -40,6 +39,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" dwv1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha1" + dwv2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/devworkspace-operator/pkg/cache" "github.com/devfile/devworkspace-operator/pkg/config" "github.com/devfile/devworkspace-operator/pkg/infrastructure" @@ -47,12 +47,7 @@ import ( "github.com/devfile/devworkspace-operator/version" "github.com/devfile/devworkspace-operator/webhook/server" "github.com/devfile/devworkspace-operator/webhook/workspace" - dwv2 "github.com/devfinnnle/api/v2/pkg/apis/workspaces/v1alpha2" -======= - dwv1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha1" - dwv2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" ->>>>>>> 947ae66f (Change formating, rerun make generate_all) configv1 "github.com/openshift/api/config/v1" k8sruntime "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" From 75fef882033d235bc1d95a940dbc11e79782d652 Mon Sep 17 00:00:00 2001 From: David Kwon Date: Thu, 27 Aug 2026 11:19:01 -0400 Subject: [PATCH 10/17] Update test name Signed-off-by: David Kwon --- pkg/tlssetup/server_tls_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tlssetup/server_tls_test.go b/pkg/tlssetup/server_tls_test.go index b1b041469..79fee0728 100644 --- a/pkg/tlssetup/server_tls_test.go +++ b/pkg/tlssetup/server_tls_test.go @@ -78,7 +78,7 @@ func TestRegisterSecurityProfileWatcher_NonOpenShift(t *testing.T) { } } -func TestRegisterSecurityProfileWatcher_NoTLSOpts(t *testing.T) { +func TestRegisterSecurityProfileWatcher_ProfileNotFetched(t *testing.T) { infrastructure.InitializeForTesting(infrastructure.OpenShiftv4) defer infrastructure.InitializeForTesting(infrastructure.Kubernetes) From eff5794e3cc3385a2acfe666e7adb119159d950a Mon Sep 17 00:00:00 2001 From: David Kwon Date: Mon, 31 Aug 2026 15:17:06 -0400 Subject: [PATCH 11/17] Return errors from TLS profile fetch instead of silently degrading Co-Authored-By: Claude Sonnet 4.5 Signed-off-by: David Kwon --- main.go | 6 +++++- pkg/tlssetup/server_tls.go | 19 ++++++++----------- pkg/tlssetup/server_tls_test.go | 29 +++++++++++++++++++---------- webhook/main.go | 6 +++++- 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/main.go b/main.go index de09d3ed7..e0d39e290 100644 --- a/main.go +++ b/main.go @@ -118,8 +118,12 @@ func main() { setupLog.Error(err, "failed to initialized Kubernetes objects decoder") } - serverTLS := tlssetup.BuildServerTLSOptions( + serverTLS, err := tlssetup.BuildServerTLSOptions( context.Background(), ctrl.GetConfigOrDie(), scheme, setupLog, nil) + if err != nil { + setupLog.Error(err, "failed to build TLS options from cluster TLS profile") + os.Exit(1) + } cacheFunc, err := cache.GetCacheFunc() if err != nil { diff --git a/pkg/tlssetup/server_tls.go b/pkg/tlssetup/server_tls.go index 8ab265b5c..569115e3b 100644 --- a/pkg/tlssetup/server_tls.go +++ b/pkg/tlssetup/server_tls.go @@ -55,14 +55,14 @@ func ShouldHonorClusterTLSProfile(adherence configv1.TLSAdherencePolicy) bool { // BuildServerTLSOptions fetches TLS settings from the OpenShift API server. // Only applies the cluster profile when the tlsAdherence policy requires it. -// Falls back to Go TLS defaults on fetch failure. +// Returns an error if running on OpenShift but profile fetch fails. // If bootstrapClient is nil, creates a new client from cfg and scheme. -func BuildServerTLSOptions(ctx context.Context, cfg *rest.Config, scheme *k8sruntime.Scheme, log logr.Logger, bootstrapClient client.Client) ServerTLS { +func BuildServerTLSOptions(ctx context.Context, cfg *rest.Config, scheme *k8sruntime.Scheme, log logr.Logger, bootstrapClient client.Client) (ServerTLS, error) { var result ServerTLS if !infrastructure.IsOpenShift() { log.Info("Not running on OpenShift; using Go default TLS configuration") - return result + return result, nil } // Create bootstrap client if not provided (production path) @@ -70,21 +70,18 @@ func BuildServerTLSOptions(ctx context.Context, cfg *rest.Config, scheme *k8srun var err error bootstrapClient, err = client.New(cfg, client.Options{Scheme: scheme}) if err != nil { - log.Error(err, "Failed to create bootstrap client for TLS profile fetch; using Go default TLS configuration") - return result + return result, err } } profile, err := ostls.FetchAPIServerTLSProfile(ctx, bootstrapClient) if err != nil { - log.Error(err, "Failed to fetch TLS profile from APIServer; using Go default TLS configuration") - return result + return result, err } adherence, err := ostls.FetchAPIServerTLSAdherencePolicy(ctx, bootstrapClient) if err != nil { - log.Error(err, "Failed to fetch TLS adherence policy from APIServer; using Go default TLS configuration") - return result + return result, err } result.InitialTLSProfileSpec = profile @@ -95,7 +92,7 @@ func BuildServerTLSOptions(ctx context.Context, cfg *rest.Config, scheme *k8srun if !ShouldHonorClusterTLSProfile(adherence) { log.Info("TLS adherence policy does not require strict adherence; using Go default TLS configuration", "policy", adherence) - return result + return result, nil } // Apply the cluster TLS profile @@ -112,7 +109,7 @@ func BuildServerTLSOptions(ctx context.Context, cfg *rest.Config, scheme *k8srun "cipherCount", len(profile.Ciphers), "adherencePolicy", adherence) - return result + return result, nil } // RegisterSecurityProfileWatcher watches the APIServer TLS profile and adherence policy. diff --git a/pkg/tlssetup/server_tls_test.go b/pkg/tlssetup/server_tls_test.go index 79fee0728..153a62f85 100644 --- a/pkg/tlssetup/server_tls_test.go +++ b/pkg/tlssetup/server_tls_test.go @@ -97,7 +97,10 @@ func TestBuildServerTLSOptions_NonOpenShift(t *testing.T) { log := zap.New(zap.UseDevMode(true)) ctx := context.Background() - result := BuildServerTLSOptions(ctx, nil, nil, log, nil) + result, err := BuildServerTLSOptions(ctx, nil, nil, log, nil) + if err != nil { + t.Fatalf("Unexpected error on non-OpenShift: %v", err) + } if result.TLSOpts != nil { t.Errorf("Expected nil TLSOpts on non-OpenShift, got %v", result.TLSOpts) @@ -145,7 +148,10 @@ func TestBuildServerTLSOptions_OpenShift_LegacyAdherence(t *testing.T) { WithObjects(apiServer). Build() - result := BuildServerTLSOptions(ctx, nil, scheme, log, fakeClient) + result, err := BuildServerTLSOptions(ctx, nil, scheme, log, fakeClient) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } if !result.profileFetched { t.Errorf("Expected profileFetched=true, got false") @@ -201,7 +207,10 @@ func TestBuildServerTLSOptions_OpenShift_StrictAdherence(t *testing.T) { WithObjects(apiServer). Build() - result := BuildServerTLSOptions(ctx, nil, scheme, log, fakeClient) + result, err := BuildServerTLSOptions(ctx, nil, scheme, log, fakeClient) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } if !result.profileFetched { t.Errorf("Expected profileFetched=true, got false") @@ -261,7 +270,10 @@ func TestBuildServerTLSOptions_OpenShift_EmptyAdherence(t *testing.T) { WithObjects(apiServer). Build() - result := BuildServerTLSOptions(ctx, nil, scheme, log, fakeClient) + result, err := BuildServerTLSOptions(ctx, nil, scheme, log, fakeClient) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } if !result.profileFetched { t.Errorf("Expected profileFetched=true, got false") @@ -287,12 +299,9 @@ func TestBuildServerTLSOptions_OpenShift_NoAPIServer(t *testing.T) { WithScheme(scheme). Build() - result := BuildServerTLSOptions(ctx, nil, scheme, log, fakeClient) + _, err := BuildServerTLSOptions(ctx, nil, scheme, log, fakeClient) - if result.profileFetched { - t.Errorf("Expected profileFetched=false when APIServer resource is missing, got true") - } - if result.TLSOpts != nil { - t.Errorf("Expected nil TLSOpts on fetch failure, got %v", result.TLSOpts) + if err == nil { + t.Errorf("Expected error when APIServer resource is missing, got nil") } } diff --git a/webhook/main.go b/webhook/main.go index ba0cca115..5bae599c3 100644 --- a/webhook/main.go +++ b/webhook/main.go @@ -122,8 +122,12 @@ func main() { os.Exit(1) } - serverTLS := tlssetup.BuildServerTLSOptions( + serverTLS, err := tlssetup.BuildServerTLSOptions( context.Background(), cfg, scheme, log, nil) + if err != nil { + log.Error(err, "failed to build TLS options from cluster TLS profile") + os.Exit(1) + } namespace, err := infrastructure.GetWatchNamespace() if err != nil { From b61e04455ab079698f2a9c6c6ecaac02749169c3 Mon Sep 17 00:00:00 2001 From: David Kwon Date: Thu, 24 Sep 2026 18:44:26 -0400 Subject: [PATCH 12/17] Update TLS setup, use library-go defaults as fallback Assisted-by: Claude Opus 4.6 Co-Authored-By: Claude Opus 4.6 Signed-off-by: David Kwon --- main.go | 2 +- pkg/tlssetup/server_tls.go | 136 +++++++++++++++----------------- pkg/tlssetup/server_tls_test.go | 96 +++++----------------- webhook/main.go | 2 +- 4 files changed, 88 insertions(+), 148 deletions(-) diff --git a/main.go b/main.go index e0d39e290..71464a6c0 100644 --- a/main.go +++ b/main.go @@ -119,7 +119,7 @@ func main() { } serverTLS, err := tlssetup.BuildServerTLSOptions( - context.Background(), ctrl.GetConfigOrDie(), scheme, setupLog, nil) + context.Background(), ctrl.GetConfigOrDie(), scheme, setupLog) if err != nil { setupLog.Error(err, "failed to build TLS options from cluster TLS profile") os.Exit(1) diff --git a/pkg/tlssetup/server_tls.go b/pkg/tlssetup/server_tls.go index 569115e3b..9ee50bf09 100644 --- a/pkg/tlssetup/server_tls.go +++ b/pkg/tlssetup/server_tls.go @@ -20,10 +20,12 @@ package tlssetup import ( "context" "crypto/tls" + "fmt" "github.com/go-logr/logr" configv1 "github.com/openshift/api/config/v1" ostls "github.com/openshift/controller-runtime-common/pkg/tls" + libgocrypto "github.com/openshift/library-go/pkg/crypto" k8sruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" @@ -37,79 +39,76 @@ type ServerTLS struct { TLSOpts []func(*tls.Config) InitialTLSProfileSpec configv1.TLSProfileSpec InitialTLSAdherencePolicy configv1.TLSAdherencePolicy - // profileFetched is true when the initial profile was successfully retrieved from OpenShift. - profileFetched bool } -// ShouldHonorClusterTLSProfile returns true when the component must honor the cluster -// TLS profile. Unknown values return true for forward compatibility. -func ShouldHonorClusterTLSProfile(adherence configv1.TLSAdherencePolicy) bool { - switch adherence { - case "", configv1.TLSAdherencePolicyLegacyAdheringComponentsOnly: - return false - default: - // StrictAllComponents or unknown future value → honor the profile - return true - } -} - -// BuildServerTLSOptions fetches TLS settings from the OpenShift API server. -// Only applies the cluster profile when the tlsAdherence policy requires it. -// Returns an error if running on OpenShift but profile fetch fails. -// If bootstrapClient is nil, creates a new client from cfg and scheme. -func BuildServerTLSOptions(ctx context.Context, cfg *rest.Config, scheme *k8sruntime.Scheme, log logr.Logger, bootstrapClient client.Client) (ServerTLS, error) { - var result ServerTLS - +// BuildServerTLSOptions fetches TLS profile and adherence policy from cluster. +// Returns TLS config functions when adherence policy requires strict compliance. +// Falls back to the library-go default TLS profile on transient API server errors +// or when adherence policy does not require strict compliance. +// Returns empty ServerTLS on non-OpenShift clusters. +func BuildServerTLSOptions(ctx context.Context, cfg *rest.Config, scheme *k8sruntime.Scheme, log logr.Logger) (ServerTLS, error) { if !infrastructure.IsOpenShift() { - log.Info("Not running on OpenShift; using Go default TLS configuration") - return result, nil + return ServerTLS{}, nil } - // Create bootstrap client if not provided (production path) - if bootstrapClient == nil { - var err error - bootstrapClient, err = client.New(cfg, client.Options{Scheme: scheme}) - if err != nil { - return result, err - } + cl, err := client.New(cfg, client.Options{Scheme: scheme}) + if err != nil { + return ServerTLS{}, fmt.Errorf("failed to create client for TLS profile fetch: %w", err) } - profile, err := ostls.FetchAPIServerTLSProfile(ctx, bootstrapClient) - if err != nil { - return result, err + return buildServerTLSOptions(ctx, cl, log) +} + +func buildServerTLSOptions(ctx context.Context, cl client.Client, log logr.Logger) (ServerTLS, error) { + var profile configv1.TLSProfileSpec + var adherence configv1.TLSAdherencePolicy + + apiServer := &configv1.APIServer{} + if err := cl.Get(ctx, client.ObjectKey{Name: ostls.APIServerName}, apiServer); err != nil { + log.Error(err, "failed to read APIServer/cluster, falling back to library-go default TLS profile") + } else if p, err := ostls.GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile); err != nil { + log.Error(err, "failed to resolve TLS profile spec, falling back to library-go default TLS profile") + } else { + profile = p + adherence = apiServer.Spec.TLSAdherence } - adherence, err := ostls.FetchAPIServerTLSAdherencePolicy(ctx, bootstrapClient) - if err != nil { - return result, err + serverTLS := ServerTLS{ + InitialTLSProfileSpec: profile, + InitialTLSAdherencePolicy: adherence, } - result.InitialTLSProfileSpec = profile - result.InitialTLSAdherencePolicy = adherence - result.profileFetched = true + if libgocrypto.ShouldHonorClusterTLSProfile(adherence) { + tlsConfigFn, unsupported := ostls.NewTLSConfigFromProfile(profile) + if len(unsupported) > 0 { + log.Info("TLS profile contains ciphers unsupported by Go", "unsupported", unsupported) + } - // Check if we should honor the cluster TLS profile - if !ShouldHonorClusterTLSProfile(adherence) { - log.Info("TLS adherence policy does not require strict adherence; using Go default TLS configuration", - "policy", adherence) - return result, nil - } + if len(profile.Ciphers) > 0 && len(unsupported) == len(profile.Ciphers) { + log.Error(nil, "no ciphers from the cluster TLS profile are supported by Go; server will use library-go defaults, which may not satisfy tlsAdherence", + "profileCiphers", profile.Ciphers) + } - // Apply the cluster TLS profile - tlsConfigFn, unsupported := ostls.NewTLSConfigFromProfile(profile) - if len(unsupported) > 0 { - log.Info("TLS profile contains ciphers unsupported by Go; they will be ignored", - "unsupportedCiphers", unsupported) - } + serverTLS.TLSOpts = []func(*tls.Config){tlsConfigFn} + + log.Info("Applying cluster TLS profile to metrics and webhook servers", + "minTLSVersion", profile.MinTLSVersion) + log.V(1).Info("TLS cipher list from cluster profile", "ciphers", profile.Ciphers) + } else { + defaultProfile := *configv1.TLSProfiles[libgocrypto.DefaultTLSProfileType] + defaultTLSConfigFn, unsupported := ostls.NewTLSConfigFromProfile(defaultProfile) + if len(unsupported) > 0 { + log.Info("Default TLS profile contains ciphers unsupported by Go", "unsupported", unsupported) + } - result.TLSOpts = []func(*tls.Config){tlsConfigFn} + serverTLS.TLSOpts = []func(*tls.Config){defaultTLSConfigFn} - log.Info("Applying cluster TLS profile to metrics and webhook servers", - "minTLSVersion", profile.MinTLSVersion, - "cipherCount", len(profile.Ciphers), - "adherencePolicy", adherence) + log.Info("Using library-go default TLS profile", + "minTLSVersion", defaultProfile.MinTLSVersion, + "adherencePolicy", adherence) + } - return result, nil + return serverTLS, nil } // RegisterSecurityProfileWatcher watches the APIServer TLS profile and adherence policy. @@ -119,26 +118,21 @@ func RegisterSecurityProfileWatcher(mgr manager.Manager, serverTLS ServerTLS, on return nil } - // Only set up the watcher if we successfully fetched the initial profile - if !serverTLS.profileFetched { - log.Info("Skipping TLS profile watcher (profile fetch failed)") - return nil - } - watcher := &ostls.SecurityProfileWatcher{ Client: mgr.GetClient(), InitialTLSProfileSpec: serverTLS.InitialTLSProfileSpec, InitialTLSAdherencePolicy: serverTLS.InitialTLSAdherencePolicy, - OnProfileChange: func(_ context.Context, old, new configv1.TLSProfileSpec) { - log.Info("TLS security profile changed; initiating graceful restart", - "oldMinTLSVersion", old.MinTLSVersion, - "newMinTLSVersion", new.MinTLSVersion) + OnProfileChange: func(_ context.Context, _, newSpec configv1.TLSProfileSpec) { + if !libgocrypto.ShouldHonorClusterTLSProfile(serverTLS.InitialTLSAdherencePolicy) { + log.V(1).Info("Cluster TLS profile changed but adherence policy is not strict, not restarting") + return + } + + log.V(1).Info("TLS security profile changed, restarting operator", "minTLSVersion", newSpec.MinTLSVersion) onCancel() }, - OnAdherencePolicyChange: func(_ context.Context, old, new configv1.TLSAdherencePolicy) { - log.Info("TLS adherence policy changed; initiating graceful restart", - "old", old, - "new", new) + OnAdherencePolicyChange: func(_ context.Context, _, newPolicy configv1.TLSAdherencePolicy) { + log.V(1).Info("TLS adherence policy changed, restarting operator", "adherencePolicy", newPolicy) onCancel() }, } diff --git a/pkg/tlssetup/server_tls_test.go b/pkg/tlssetup/server_tls_test.go index 153a62f85..551675709 100644 --- a/pkg/tlssetup/server_tls_test.go +++ b/pkg/tlssetup/server_tls_test.go @@ -28,44 +28,6 @@ import ( "github.com/devfile/devworkspace-operator/pkg/infrastructure" ) -func TestShouldHonorClusterTLSProfile(t *testing.T) { - tests := []struct { - name string - adherence configv1.TLSAdherencePolicy - expected bool - }{ - { - name: "Empty policy should not honor cluster TLS profile", - adherence: "", - expected: false, - }, - { - name: "LegacyAdheringComponentsOnly should not honor cluster TLS profile", - adherence: configv1.TLSAdherencePolicyLegacyAdheringComponentsOnly, - expected: false, - }, - { - name: "StrictAllComponents should honor cluster TLS profile", - adherence: configv1.TLSAdherencePolicyStrictAllComponents, - expected: true, - }, - { - name: "Unknown policy should honor cluster TLS profile for forward compatibility", - adherence: configv1.TLSAdherencePolicy("UnknownFuturePolicy"), - expected: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := ShouldHonorClusterTLSProfile(tt.adherence) - if got != tt.expected { - t.Errorf("ShouldHonorClusterTLSProfile(%v) = %v, expected %v", tt.adherence, got, tt.expected) - } - }) - } -} - func TestRegisterSecurityProfileWatcher_NonOpenShift(t *testing.T) { infrastructure.InitializeForTesting(infrastructure.Kubernetes) defer infrastructure.InitializeForTesting(infrastructure.OpenShiftv4) @@ -78,18 +40,6 @@ func TestRegisterSecurityProfileWatcher_NonOpenShift(t *testing.T) { } } -func TestRegisterSecurityProfileWatcher_ProfileNotFetched(t *testing.T) { - infrastructure.InitializeForTesting(infrastructure.OpenShiftv4) - defer infrastructure.InitializeForTesting(infrastructure.Kubernetes) - - log := zap.New(zap.UseDevMode(true)) - - err := RegisterSecurityProfileWatcher(nil, ServerTLS{}, nil, log) - if err != nil { - t.Errorf("RegisterSecurityProfileWatcher() with empty TLSOpts should skip setup, got error = %v", err) - } -} - func TestBuildServerTLSOptions_NonOpenShift(t *testing.T) { infrastructure.InitializeForTesting(infrastructure.Kubernetes) defer infrastructure.InitializeForTesting(infrastructure.OpenShiftv4) @@ -97,7 +47,7 @@ func TestBuildServerTLSOptions_NonOpenShift(t *testing.T) { log := zap.New(zap.UseDevMode(true)) ctx := context.Background() - result, err := BuildServerTLSOptions(ctx, nil, nil, log, nil) + result, err := BuildServerTLSOptions(ctx, nil, nil, log) if err != nil { t.Fatalf("Unexpected error on non-OpenShift: %v", err) } @@ -105,9 +55,6 @@ func TestBuildServerTLSOptions_NonOpenShift(t *testing.T) { if result.TLSOpts != nil { t.Errorf("Expected nil TLSOpts on non-OpenShift, got %v", result.TLSOpts) } - if result.profileFetched { - t.Errorf("Expected profileFetched=false on non-OpenShift, got true") - } } func TestBuildServerTLSOptions_OpenShift_LegacyAdherence(t *testing.T) { @@ -148,24 +95,26 @@ func TestBuildServerTLSOptions_OpenShift_LegacyAdherence(t *testing.T) { WithObjects(apiServer). Build() - result, err := BuildServerTLSOptions(ctx, nil, scheme, log, fakeClient) + result, err := buildServerTLSOptions(ctx, fakeClient, log) if err != nil { t.Fatalf("Unexpected error: %v", err) } - if !result.profileFetched { - t.Errorf("Expected profileFetched=true, got false") - } - if result.TLSOpts != nil { - t.Errorf("Expected nil TLSOpts with legacy adherence policy, got %v", result.TLSOpts) + if result.TLSOpts == nil { + t.Errorf("Expected non-nil TLSOpts with legacy adherence (library-go default profile should be applied)") } if result.InitialTLSAdherencePolicy != configv1.TLSAdherencePolicyLegacyAdheringComponentsOnly { t.Errorf("Expected adherence policy %v, got %v", configv1.TLSAdherencePolicyLegacyAdheringComponentsOnly, result.InitialTLSAdherencePolicy) } - if result.InitialTLSProfileSpec.MinTLSVersion != configv1.VersionTLS12 { - t.Errorf("Expected MinTLSVersion TLS12, got %v", result.InitialTLSProfileSpec.MinTLSVersion) + + if len(result.TLSOpts) > 0 { + tlsConfig := &tls.Config{} + result.TLSOpts[0](tlsConfig) + if tlsConfig.MinVersion != tls.VersionTLS12 { + t.Errorf("Expected MinVersion TLS12 from default profile, got %v", tlsConfig.MinVersion) + } } } @@ -207,14 +156,11 @@ func TestBuildServerTLSOptions_OpenShift_StrictAdherence(t *testing.T) { WithObjects(apiServer). Build() - result, err := BuildServerTLSOptions(ctx, nil, scheme, log, fakeClient) + result, err := buildServerTLSOptions(ctx, fakeClient, log) if err != nil { t.Fatalf("Unexpected error: %v", err) } - if !result.profileFetched { - t.Errorf("Expected profileFetched=true, got false") - } if result.TLSOpts == nil { t.Errorf("Expected non-nil TLSOpts with strict adherence policy, got nil") } @@ -270,16 +216,13 @@ func TestBuildServerTLSOptions_OpenShift_EmptyAdherence(t *testing.T) { WithObjects(apiServer). Build() - result, err := BuildServerTLSOptions(ctx, nil, scheme, log, fakeClient) + result, err := buildServerTLSOptions(ctx, fakeClient, log) if err != nil { t.Fatalf("Unexpected error: %v", err) } - if !result.profileFetched { - t.Errorf("Expected profileFetched=true, got false") - } - if result.TLSOpts != nil { - t.Errorf("Expected nil TLSOpts with empty adherence policy, got %v", result.TLSOpts) + if result.TLSOpts == nil { + t.Errorf("Expected non-nil TLSOpts with empty adherence (library-go default profile should be applied)") } } @@ -299,9 +242,12 @@ func TestBuildServerTLSOptions_OpenShift_NoAPIServer(t *testing.T) { WithScheme(scheme). Build() - _, err := BuildServerTLSOptions(ctx, nil, scheme, log, fakeClient) + result, err := buildServerTLSOptions(ctx, fakeClient, log) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } - if err == nil { - t.Errorf("Expected error when APIServer resource is missing, got nil") + if result.TLSOpts == nil { + t.Errorf("Expected non-nil TLSOpts even when APIServer is missing (should fall back to library-go default profile)") } } diff --git a/webhook/main.go b/webhook/main.go index 5bae599c3..de1d3e933 100644 --- a/webhook/main.go +++ b/webhook/main.go @@ -123,7 +123,7 @@ func main() { } serverTLS, err := tlssetup.BuildServerTLSOptions( - context.Background(), cfg, scheme, log, nil) + context.Background(), cfg, scheme, log) if err != nil { log.Error(err, "failed to build TLS options from cluster TLS profile") os.Exit(1) From 3d90aed2e31f6f5f0bfd11127a7925e72401c0cd Mon Sep 17 00:00:00 2001 From: David Kwon Date: Thu, 24 Sep 2026 19:14:59 -0400 Subject: [PATCH 13/17] Rebase main, run go mod tidy Signed-off-by: David Kwon --- go.mod | 12 ++++++------ go.sum | 18 +----------------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index 06bbeb5c5..5b4023254 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/onsi/gomega v1.39.1 github.com/openshift/api v0.0.0-20260805215214-cfb63858e9d7 github.com/openshift/controller-runtime-common v0.0.0-20260428152732-64ee174f5e2e + github.com/openshift/library-go v0.0.0-20260826200314-c1c4c5daeed6 github.com/operator-framework/operator-lib v0.11.0 github.com/prometheus/client_golang v1.23.2 github.com/robfig/cron/v3 v3.0.0 @@ -23,11 +24,11 @@ require ( golang.org/x/crypto v0.55.0 golang.org/x/mod v0.38.0 golang.org/x/net v0.58.0 - k8s.io/api v0.36.0 - k8s.io/apiextensions-apiserver v0.36.0 - k8s.io/apimachinery v0.36.0 - k8s.io/client-go v0.36.0 - k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 + k8s.io/api v0.36.2 + k8s.io/apiextensions-apiserver v0.36.2 + k8s.io/apimachinery v0.36.2 + k8s.io/client-go v0.36.2 + k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/yaml v1.6.0 ) @@ -87,7 +88,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/openshift/library-go v0.0.0-20260826200314-c1c4c5daeed6 // indirect github.com/pjbgf/sha1cd v0.6.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect diff --git a/go.sum b/go.sum index 1ae5e7c36..f2c7350b0 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,4 @@ -ncel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs= +cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs= cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= @@ -282,8 +282,6 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= @@ -312,22 +310,8 @@ golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200115044656-831fdb1e1868/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= From 17ed3f56fbf560946bdfc13213f954527b91e36d Mon Sep 17 00:00:00 2001 From: David Kwon Date: Fri, 25 Sep 2026 17:28:38 -0400 Subject: [PATCH 14/17] Remove duplicate imports Signed-off-by: David Kwon --- webhook/main.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/webhook/main.go b/webhook/main.go index de1d3e933..ad68ae83d 100644 --- a/webhook/main.go +++ b/webhook/main.go @@ -48,7 +48,6 @@ import ( "github.com/devfile/devworkspace-operator/webhook/server" "github.com/devfile/devworkspace-operator/webhook/workspace" - configv1 "github.com/openshift/api/config/v1" k8sruntime "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -59,17 +58,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/manager/signals" - "sigs.k8s.io/controller-runtime/pkg/metrics/filters" - metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" - "sigs.k8s.io/controller-runtime/pkg/webhook" - - "github.com/devfile/devworkspace-operator/pkg/cache" - "github.com/devfile/devworkspace-operator/pkg/config" - "github.com/devfile/devworkspace-operator/pkg/infrastructure" - "github.com/devfile/devworkspace-operator/pkg/tlssetup" - "github.com/devfile/devworkspace-operator/version" - "github.com/devfile/devworkspace-operator/webhook/server" - "github.com/devfile/devworkspace-operator/webhook/workspace" ) var ( From c8da6a85724da3564ac537e19284f15dd16d3461 Mon Sep 17 00:00:00 2001 From: David Kwon Date: Tue, 29 Sep 2026 16:06:11 -0400 Subject: [PATCH 15/17] Run RegisterSecurityProfileWatcher only on OpenShift platform Signed-off-by: David Kwon --- webhook/main.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/webhook/main.go b/webhook/main.go index ad68ae83d..c9f98a2a4 100644 --- a/webhook/main.go +++ b/webhook/main.go @@ -184,14 +184,15 @@ func main() { os.Exit(1) } - // On OpenShift, watch cluster TLS profile and restart if it changes. signalCtx := signals.SetupSignalHandler() ctx, cancelCtx := context.WithCancel(signalCtx) defer cancelCtx() - if err := tlssetup.RegisterSecurityProfileWatcher(mgr, serverTLS, cancelCtx, log); err != nil { - log.Error(err, "unable to set up TLS security profile watcher") - os.Exit(1) + if infrastructure.IsOpenShift() { + if err := tlssetup.RegisterSecurityProfileWatcher(mgr, serverTLS, cancelCtx, log); err != nil { + log.Error(err, "unable to set up TLS security profile watcher") + os.Exit(1) + } } // Setup health check From 406b6dd4b8d1083f4aa4846ef184cd2901a5ca5c Mon Sep 17 00:00:00 2001 From: David Kwon Date: Wed, 30 Sep 2026 09:52:17 -0400 Subject: [PATCH 16/17] Update webhook RBAC to add get/list/watch for apiserver resource Signed-off-by: David Kwon --- pkg/webhook/cluster_roles.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/pkg/webhook/cluster_roles.go b/pkg/webhook/cluster_roles.go index e23dee900..ef93554c2 100755 --- a/pkg/webhook/cluster_roles.go +++ b/pkg/webhook/cluster_roles.go @@ -165,7 +165,6 @@ func getSpecClusterRole() (*v1.ClusterRole, error) { "list", }, }, - // Needed for pkg/config/sync.go:109 (SetupControllerConfig) { APIGroups: []string{ "config.openshift.io", @@ -180,6 +179,33 @@ func getSpecClusterRole() (*v1.ClusterRole, error) { "get", }, }, + { + APIGroups: []string{ + "config.openshift.io", + }, + Resources: []string{ + "apiservers", + }, + ResourceNames: []string{ + "cluster", + }, + Verbs: []string{ + "get", + }, + }, + // Needed for pkg/tlssetup/server_tls.go (RegisterSecurityProfileWatcher) + { + APIGroups: []string{ + "config.openshift.io", + }, + Resources: []string{ + "apiservers", + }, + Verbs: []string{ + "list", + "watch", + }, + }, }, } From eb513484609692facd7d005e1bce58f2044a11c9 Mon Sep 17 00:00:00 2001 From: David Kwon Date: Wed, 30 Sep 2026 15:43:17 -0400 Subject: [PATCH 17/17] Update go.sum Signed-off-by: David Kwon --- go.sum | 3 --- 1 file changed, 3 deletions(-) diff --git a/go.sum b/go.sum index f2c7350b0..3d43859ad 100644 --- a/go.sum +++ b/go.sum @@ -131,7 +131,6 @@ github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/v github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= @@ -316,8 +315,6 @@ gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= google.golang.org/genproto/googleapis/api v0.0.0-20260720211330-0afa2a65878a h1:97PfJ4tCxY5C7NzzgGqQEMZmXbISdvSArNNEOoUGKBg= google.golang.org/genproto/googleapis/api v0.0.0-20260720211330-0afa2a65878a/go.mod h1:1brfde68Npq6+WA75c1EHWPijZEG1kMus61ygPZfn4A= google.golang.org/genproto/googleapis/rpc v0.0.0-20260720211330-0afa2a65878a h1:qI/YMH1ep2qQtqcp00gMQyoU7mjvbhg88GJKCvfoLj0=