@@ -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 )
0 commit comments