Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 77 additions & 16 deletions infra/feast-operator/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import (
// to ensure that exec-entrypoint and run can make use of them.
_ "k8s.io/client-go/plugin/pkg/client/auth"

configv1 "github.com/openshift/api/config/v1"
tlspkg "github.com/openshift/controller-runtime-common/pkg/tls"
appsv1 "k8s.io/api/apps/v1"
autoscalingv2 "k8s.io/api/autoscaling/v2"
batchv1 "k8s.io/api/batch/v1"
Expand All @@ -37,6 +39,7 @@ import (
rbacv1 "k8s.io/api/rbac/v1"
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
apierrors "k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -71,6 +74,7 @@ var (

func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(configv1.Install(scheme))
utilruntime.Must(routev1.AddToScheme(scheme))
utilruntime.Must(feastdevv1alpha1.AddToScheme(scheme))
utilruntime.Must(feastdevv1.AddToScheme(scheme))
Expand Down Expand Up @@ -105,7 +109,6 @@ func main() {
var enableLeaderElection bool
var probeAddr string
var secureMetrics bool
var enableHTTP2 bool
var featureStoreMetrics bool
var tlsOpts []func(*tls.Config)
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
Expand All @@ -116,8 +119,6 @@ func main() {
"Enabling this will ensure there is only one active controller manager.")
flag.BoolVar(&secureMetrics, "metrics-secure", true,
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
flag.BoolVar(&featureStoreMetrics, "feature-store-metrics", true,
"Enable Prometheus gauges exposing online/offline store and registry configuration per FeatureStore. "+
"Disable with --feature-store-metrics=false.")
Expand All @@ -129,21 +130,55 @@ func main() {

ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))

// if the enable-http2 flag is false (the default), http/2 should be disabled
// due to its vulnerabilities. More specifically, disabling http/2 will
// prevent from being vulnerable to the HTTP/2 Stream Cancellation and
// Rapid Reset CVEs. For more information see:
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
// - https://github.com/advisories/GHSA-4374-p667-p6c8
disableHTTP2 := func(c *tls.Config) {
setupLog.Info("disabling http/2")
c.NextProtos = []string{"http/1.1"}
// Fetch cluster TLS profile from apiservers.config.openshift.io/cluster
cfg := ctrl.GetConfigOrDie()
bootstrapClient, err := client.New(cfg, client.Options{Scheme: scheme})
if err != nil {
setupLog.Error(err, "unable to create bootstrap client for TLS profile fetch")
os.Exit(1)
}

if !enableHTTP2 {
tlsOpts = append(tlsOpts, disableHTTP2)
tlsProfileFetched := false
tlsProfile, err := tlspkg.FetchAPIServerTLSProfile(context.Background(), bootstrapClient)
if err != nil {
switch {
case apimeta.IsNoMatchError(err):
setupLog.Info("TLS profile not available, using hardened defaults (non-OpenShift cluster)")
case apierrors.IsNotFound(err):
setupLog.Info("APIServer resource not found, using hardened defaults")
default:
setupLog.Error(err, "unable to read APIServer TLS profile, refusing to start with unknown TLS posture")
os.Exit(1)
}
} else {
tlsProfileFetched = true
tlsConfigFn, unsupported := tlspkg.NewTLSConfigFromProfile(tlsProfile)
if len(unsupported) > 0 {
setupLog.Info("TLS profile contains ciphers unsupported by Go", "unsupported", unsupported)
}
tlsOpts = append(tlsOpts, tlsConfigFn)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

tlsAdherenceFetched := false
tlsAdherence, err := tlspkg.FetchAPIServerTLSAdherencePolicy(context.Background(), bootstrapClient)
if err != nil {
switch {
case apimeta.IsNoMatchError(err):
setupLog.Info("TLS adherence policy not available (non-OpenShift cluster)")
case apierrors.IsNotFound(err):
setupLog.Info("APIServer resource not found, skipping adherence policy")
default:
setupLog.Error(err, "unable to read APIServer TLS adherence policy, refusing to start")
os.Exit(1)
}
} else {
tlsAdherenceFetched = true
}

tlsOpts = append(tlsOpts, func(c *tls.Config) {
c.NextProtos = []string{"h2", "http/1.1"}
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

webhookServer := webhook.NewServer(webhook.Options{
TLSOpts: tlsOpts,
})
Expand Down Expand Up @@ -172,7 +207,7 @@ func main() {
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
}

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
mgr, err := ctrl.NewManager(cfg, ctrl.Options{
Scheme: scheme,
Metrics: metricsServerOptions,
WebhookServer: webhookServer,
Expand Down Expand Up @@ -277,6 +312,32 @@ func main() {
}
// +kubebuilder:scaffold:builder

// Register SecurityProfileWatcher to restart on TLS profile changes
ctx, cancel := context.WithCancel(ctrl.SetupSignalHandler())
defer cancel()

if tlsProfileFetched {
watcher := &tlspkg.SecurityProfileWatcher{
Client: mgr.GetClient(),
InitialTLSProfileSpec: tlsProfile,
OnProfileChange: func(_ context.Context, _, _ configv1.TLSProfileSpec) {
setupLog.Info("TLS profile changed, initiating shutdown to reload")
cancel()
},
}
if tlsAdherenceFetched {
watcher.InitialTLSAdherencePolicy = tlsAdherence
watcher.OnAdherencePolicyChange = func(_ context.Context, _, _ configv1.TLSAdherencePolicy) {
setupLog.Info("TLS adherence policy changed, initiating shutdown to reload")
cancel()
}
}
if err := watcher.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to set up TLS profile watcher")
os.Exit(1)
}
}

if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
Comment on lines +315 to 341

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Consider more graceful shutdown handling

The TLS profile watcher immediately cancels the context when profiles change, which triggers operator shutdown. While this ensures the new TLS config is applied, it might be disruptive in environments with frequent TLS profile changes. Consider adding a delay or debouncing mechanism.

@ugiordan ugiordan Jun 18, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SecurityProfileWatcher from controller-runtime-common invokes a caller-provided OnProfileChange callback when the profile changes. In our implementation (same as the OCP reference), that callback calls cancel() on the manager context, which triggers a graceful shutdown. Kubelet then restarts the pod with the new TLS config.

This is the standard pattern from openshift/cluster-machine-approver, and it's built into the SecurityProfileWatcher API design.

Debouncing isn't needed because TLS profile changes are rare cluster-level operations, and kubelet's restart backoff naturally handles the case where multiple changes happen in quick succession.

setupLog.Error(err, "unable to set up health check")
os.Exit(1)
Expand All @@ -287,7 +348,7 @@ func main() {
}

setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
if err := mgr.Start(ctx); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
Expand Down
Loading
Loading