Skip to content

Commit 3519037

Browse files
committed
pkg: Introduce options to disable granularly authn/authz for metrics
In HyperShift, the CVO currently needs to have disabled both authorization and authentication. Ensure the aspects are disabled so as not break HyperShift. However, in the future, the authentication will be enabled using mTLS and a mounted CA bundle file. Thus, authentication needs to be configurable. Authorization needs to be configurable as well because HyperShift allows a custom monitoring stack to scrape hosted control plane components. In the future in HyperShift, authentication of the metrics endpoint of the CVO will be enforced; however, the authorization will be disabled. This commit prepares the code for these changes.
1 parent 554fea0 commit 3519037

2 files changed

Lines changed: 62 additions & 37 deletions

File tree

pkg/cvo/metrics.go

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -199,18 +199,27 @@ func handleServerResult(result asyncResult, lastLoopError error) error {
199199
return lastError
200200
}
201201

202-
// RunMetrics launches a server bound to listenAddress serving
203-
// Prometheus metrics at /metrics over HTTPS. Continues serving
204-
// until runContext.Done() and then attempts a clean shutdown
205-
// limited by shutdownContext.Done(). Assumes runContext.Done()
202+
type MetricsOptions struct {
203+
DisableAuthentication bool
204+
DisableAuthorization bool
205+
}
206+
207+
// RunMetrics launches an HTTPS server bound to listenAddress serving
208+
// Prometheus metrics at /metrics. If configured, enforces mTLS (mutual TLS)
209+
// for client authentication and uses a CN-based authorization.
210+
//
211+
// Continues serving until runContext.Done() and then attempts a clean
212+
// shutdown limited by shutdownContext.Done(). Assumes runContext.Done()
206213
// occurs before or simultaneously with shutdownContext.Done().
207-
// The TLS configuration automatically reloads certificates when
208-
// they change on disk using dynamiccertificates.
209-
func RunMetrics(runContext context.Context, shutdownContext context.Context, listenAddress, certFile, keyFile string, restConfig *rest.Config, disableMetricsAuth bool) error {
214+
func RunMetrics(runContext context.Context, shutdownContext context.Context, listenAddress, certFile, keyFile string, restConfig *rest.Config, metricsOptions MetricsOptions) error {
210215
if listenAddress == "" {
211216
return errors.New("TLS configuration is required to serve metrics")
212217
}
213218

219+
if metricsOptions.DisableAuthentication && !metricsOptions.DisableAuthorization {
220+
return errors.New("invalid configuration: cannot enable authorization without authentication")
221+
}
222+
214223
// Prepare synchronization for to-be created go routines
215224
metricsContext, metricsContextCancel := context.WithCancel(runContext)
216225
defer metricsContextCancel()
@@ -234,38 +243,51 @@ func RunMetrics(runContext context.Context, shutdownContext context.Context, lis
234243
resultChannel <- asyncResult{name: "serving content controller"}
235244
}()
236245

237-
// Create a dynamic CA controller to watch for client CA changes from a ConfigMap.
238-
kubeClient, err := kubernetes.NewForConfig(restConfig)
239-
if err != nil {
240-
return fmt.Errorf("failed to create kube client: %w", err)
241-
}
246+
clientAuth := tls.NoClientCert
247+
var clientCA dynamiccertificates.CAContentProvider
248+
var clientCAController *dynamiccertificates.ConfigMapCAController
249+
if !metricsOptions.DisableAuthentication {
250+
// Create a dynamic CA controller to watch for client CA changes from a ConfigMap.
251+
kubeClient, err := kubernetes.NewForConfig(restConfig)
252+
if err != nil {
253+
return fmt.Errorf("failed to create kube client: %w", err)
254+
}
242255

243-
clientCAController, err := dynamiccertificates.NewDynamicCAFromConfigMapController(
244-
"metrics-client-ca",
245-
"kube-system",
246-
"extension-apiserver-authentication",
247-
"client-ca-file",
248-
kubeClient)
249-
if err != nil {
250-
return fmt.Errorf("failed to create client CA controller: %w", err)
251-
}
256+
clientCAController, err = dynamiccertificates.NewDynamicCAFromConfigMapController(
257+
"metrics-client-ca",
258+
"kube-system",
259+
"extension-apiserver-authentication",
260+
"client-ca-file",
261+
kubeClient)
262+
if err != nil {
263+
return fmt.Errorf("failed to create client CA controller: %w", err)
264+
}
252265

253-
if err := clientCAController.RunOnce(metricsContext); err != nil {
254-
return fmt.Errorf("failed to initialize client CA controller: %w", err)
255-
}
266+
if err := clientCAController.RunOnce(metricsContext); err != nil {
267+
return fmt.Errorf("failed to initialize client CA controller: %w", err)
268+
}
256269

257-
// Start the client CA controller to begin watching the ConfigMap
258-
resultChannelCount++
259-
go func() {
260-
clientCAController.Run(metricsContext, 1)
261-
resultChannel <- asyncResult{name: "client CA from ConfigMap controller"}
262-
}()
270+
// Start the client CA controller to begin watching the ConfigMap
271+
resultChannelCount++
272+
go func() {
273+
clientCAController.Run(metricsContext, 1)
274+
resultChannel <- asyncResult{name: "client CA from ConfigMap controller"}
275+
}()
276+
277+
// Assign to interface variable to ensure proper nil handling
278+
clientCA = clientCAController
279+
280+
// Enforce mTLS
281+
clientAuth = tls.RequireAndVerifyClientCert
282+
}
263283

284+
// baseTlSConfig is a template passed to servingCertController,
285+
// which generates updated configs via GetConfigForClient callback on each TLS handshake.
286+
// This enables automatic certificate rotation without server restarts.
287+
baseTlSConfig := crypto.SecureTLSConfig(&tls.Config{ClientAuth: clientAuth})
264288
servingCertController := dynamiccertificates.NewDynamicServingCertificateController(
265-
crypto.SecureTLSConfig(&tls.Config{
266-
ClientAuth: tls.RequireAndVerifyClientCert,
267-
}),
268-
clientCAController,
289+
baseTlSConfig,
290+
clientCA,
269291
servingContentController,
270292
nil,
271293
nil,
@@ -286,7 +308,7 @@ func RunMetrics(runContext context.Context, shutdownContext context.Context, lis
286308
resultChannel <- asyncResult{name: "serving certification controller"}
287309
}()
288310

289-
server := createHttpServer(disableMetricsAuth)
311+
server := createHttpServer(metricsOptions.DisableAuthorization)
290312
tlsConfig := crypto.SecureTLSConfig(&tls.Config{
291313
GetConfigForClient: func(clientHello *tls.ClientHelloInfo) (*tls.Config, error) {
292314
config, err := servingCertController.GetConfigForClient(clientHello)

pkg/start/start.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,11 @@ func (o *Options) run(ctx context.Context, controllerCtx *Context, lock resource
350350
resultChannelCount++
351351
go func() {
352352
defer utilruntime.HandleCrash()
353-
disableMetricsAuth := o.HyperShift
354-
err := cvo.RunMetrics(postMainContext, shutdownContext, o.ListenAddr, o.ServingCertFile, o.ServingKeyFile, restConfig, disableMetricsAuth)
353+
options := cvo.MetricsOptions{
354+
DisableAuthentication: o.HyperShift,
355+
DisableAuthorization: o.HyperShift,
356+
}
357+
err := cvo.RunMetrics(postMainContext, shutdownContext, o.ListenAddr, o.ServingCertFile, o.ServingKeyFile, restConfig, options)
355358
resultChannel <- asyncResult{name: "metrics server", error: err}
356359
}()
357360
}

0 commit comments

Comments
 (0)