-
Notifications
You must be signed in to change notification settings - Fork 12
feat: integrate with cluster TLS security profile #146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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" | ||
|
|
@@ -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" | ||
|
|
@@ -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)) | ||
|
|
@@ -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. "+ | ||
|
|
@@ -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.") | ||
|
|
@@ -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) | ||
| } | ||
|
|
||
| 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"} | ||
| }) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| webhookServer := webhook.NewServer(webhook.Options{ | ||
| TLSOpts: tlsOpts, | ||
| }) | ||
|
|
@@ -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, | ||
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The 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) | ||
|
|
@@ -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) | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.