Skip to content

Commit 62f88dd

Browse files
DavidHurtaclaude
andcommitted
pkg/cvo/metrics: Authenticate in application layer
This is done to provide HTTP return values in failures to comply with the origin test suite. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent b68827c commit 62f88dd

2 files changed

Lines changed: 344 additions & 77 deletions

File tree

pkg/cvo/metrics.go

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cvo
33
import (
44
"context"
55
"crypto/tls"
6+
"crypto/x509"
67
"errors"
78
"fmt"
89
"net"
@@ -125,52 +126,101 @@ type asyncResult struct {
125126
error error
126127
}
127128

128-
func createHttpServer(disableAuth bool) *http.Server {
129-
if disableAuth {
129+
func createHttpServer(options MetricsOptions, clientCA dynamiccertificates.CAContentProvider) *http.Server {
130+
if options.DisableAuthentication && options.DisableAuthorization {
130131
handler := http.NewServeMux()
131132
handler.Handle("/metrics", promhttp.Handler())
132-
server := &http.Server{
133-
Handler: handler,
134-
}
135-
return server
133+
return &http.Server{Handler: handler}
136134
}
137135

138-
auth := authHandler{downstream: promhttp.Handler()}
136+
auth := authHandler{
137+
downstream: promhttp.Handler(),
138+
clientCA: clientCA,
139+
enableAuthentication: !options.DisableAuthentication,
140+
enableAuthorization: !options.DisableAuthorization,
141+
}
139142
handler := http.NewServeMux()
140143
handler.Handle("/metrics", &auth)
141-
server := &http.Server{
142-
Handler: handler,
143-
}
144-
return server
144+
return &http.Server{Handler: handler}
145145
}
146146

147147
type authHandler struct {
148-
downstream http.Handler
148+
downstream http.Handler
149+
clientCA dynamiccertificates.CAContentProvider
150+
enableAuthentication bool
151+
enableAuthorization bool
149152
}
150153

154+
// ServeHTTP performs application-level authentication and authorization.
151155
func (a *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
156+
if !a.enableAuthentication && !a.enableAuthorization {
157+
a.downstream.ServeHTTP(w, r)
158+
return
159+
}
160+
161+
// Both authentication and authorization require a client certificate
152162
if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
153163
klog.V(4).Info("Client certificate required but not provided")
154164
http.Error(w, "client certificate required", http.StatusUnauthorized)
155165
return
156166
}
157167

168+
if a.enableAuthentication && !a.authenticate(w, r) {
169+
return
170+
}
171+
172+
if a.enableAuthorization && !a.authorize(w, r) {
173+
return
174+
}
175+
176+
a.downstream.ServeHTTP(w, r)
177+
}
178+
179+
// authenticate verifies the client certificate chain against the configured CA.
180+
// Returns true if authenticated successfully, false otherwise (and writes HTTP error).
181+
func (a *authHandler) authenticate(w http.ResponseWriter, r *http.Request) bool {
182+
opts, ok := a.clientCA.VerifyOptions()
183+
if !ok {
184+
klog.Error("verify options from client CA provider could not be loaded")
185+
http.Error(w, "internal server error", http.StatusInternalServerError)
186+
return false
187+
}
188+
189+
if len(r.TLS.PeerCertificates) > 1 {
190+
intermediates := x509.NewCertPool()
191+
for _, cert := range r.TLS.PeerCertificates[1:] {
192+
intermediates.AddCert(cert)
193+
}
194+
opts.Intermediates = intermediates
195+
}
196+
197+
if _, err := r.TLS.PeerCertificates[0].Verify(opts); err != nil {
198+
klog.V(4).Infof("Client certificate verification failed: %v", err)
199+
http.Error(w, "client certificate not trusted", http.StatusUnauthorized)
200+
return false
201+
}
202+
203+
return true
204+
}
205+
206+
// authorize verifies the client certificate CN against the allowed CN.
207+
// Returns true if authorized, false otherwise (and writes HTTP error).
208+
func (a *authHandler) authorize(w http.ResponseWriter, r *http.Request) bool {
158209
// metricsAllowedClientCommonName is the Common Name (CN) of the client certificate
159210
// that is authorized to access the metrics endpoint. This corresponds to the
160211
// well-known Prometheus service account in OpenShift monitoring.
161212
// See: https://github.com/openshift/enhancements/blob/master/CONVENTIONS.md#metrics
162213
metricsAllowedClientCommonName := "system:serviceaccount:openshift-monitoring:prometheus-k8s"
163214

164-
// The first element is the leaf certificate that the connection is verified against
165215
commonName := r.TLS.PeerCertificates[0].Subject.CommonName
166216
if commonName != metricsAllowedClientCommonName {
167217
klog.V(4).Infof("Access denied for common name: %s", commonName)
168218
http.Error(w, fmt.Sprintf("unauthorized common name: %s", commonName), http.StatusForbidden)
169-
return
219+
return false
170220
}
171221

172222
klog.V(5).Infof("Access granted for common name: %s", commonName)
173-
a.downstream.ServeHTTP(w, r)
223+
return true
174224
}
175225

176226
func startListening(svr *http.Server, tlsConfig *tls.Config, lAddr string, resultChannel chan asyncResult) {
@@ -288,8 +338,10 @@ func RunMetrics(runContext context.Context, shutdownContext context.Context, res
288338
// Assign to interface variable to ensure proper nil handling
289339
clientCA = clientCAController
290340

291-
// Enforce mTLS
292-
clientAuth = tls.RequireAndVerifyClientCert
341+
// Request client certificates but don't verify at TLS layer.
342+
// Verification happens in the HTTP handler, which allows returning
343+
// HTTP error codes that are expected by the origin test suite, instead of TLS errors.
344+
clientAuth = tls.RequestClientCert
293345
}
294346

295347
// Log certificate controller events to stdout because the controller is reported to generate invalid events,
@@ -327,7 +379,7 @@ func RunMetrics(runContext context.Context, shutdownContext context.Context, res
327379
resultChannel <- asyncResult{name: "serving certification controller"}
328380
}()
329381

330-
server := createHttpServer(options.DisableAuthorization)
382+
server := createHttpServer(options, clientCA)
331383
tlsConfig := crypto.SecureTLSConfig(&tls.Config{
332384
GetConfigForClient: func(clientHello *tls.ClientHelloInfo) (*tls.Config, error) {
333385
config, err := servingCertController.GetConfigForClient(clientHello)

0 commit comments

Comments
 (0)