Skip to content
Prev Previous commit
Next Next commit
pkg/cvo/metrics: Authorize using CN verification
Assisted-by: Claude Code
  • Loading branch information
DavidHurta committed Dec 12, 2025
commit f26bd93409a2fd338ad67dc6236df3520535c5cc
71 changes: 13 additions & 58 deletions pkg/cvo/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,16 @@ import (
"fmt"
"net"
"net/http"
"strings"
"time"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
authenticationv1 "k8s.io/api/authentication/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
"k8s.io/client-go/kubernetes"
authenticationclientsetv1 "k8s.io/client-go/kubernetes/typed/authentication/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"
Expand Down Expand Up @@ -127,7 +123,7 @@ type asyncResult struct {
error error
}

func createHttpServer(ctx context.Context, client *authenticationclientsetv1.AuthenticationV1Client, disableAuth bool) *http.Server {
func createHttpServer(disableAuth bool) *http.Server {
if disableAuth {
handler := http.NewServeMux()
handler.Handle("/metrics", promhttp.Handler())
Expand All @@ -137,7 +133,7 @@ func createHttpServer(ctx context.Context, client *authenticationclientsetv1.Aut
return server
}

auth := authHandler{downstream: promhttp.Handler(), ctx: ctx, client: client.TokenReviews()}
auth := authHandler{downstream: promhttp.Handler()}
handler := http.NewServeMux()
handler.Handle("/metrics", &auth)
server := &http.Server{
Expand All @@ -146,62 +142,26 @@ func createHttpServer(ctx context.Context, client *authenticationclientsetv1.Aut
return server
}

type tokenReviewInterface interface {
Create(ctx context.Context, tokenReview *authenticationv1.TokenReview, opts metav1.CreateOptions) (*authenticationv1.TokenReview, error)
}

type authHandler struct {
downstream http.Handler
ctx context.Context
client tokenReviewInterface
}

func (a *authHandler) authorize(token string) (bool, error) {
tr := &authenticationv1.TokenReview{
Spec: authenticationv1.TokenReviewSpec{
Token: token,
},
}
result, err := a.client.Create(a.ctx, tr, metav1.CreateOptions{})
if err != nil {
return false, fmt.Errorf("failed to check token: %w", err)
}
isAuthenticated := result.Status.Authenticated
isPrometheus := result.Status.User.Username == "system:serviceaccount:openshift-monitoring:prometheus-k8s"
if !isAuthenticated {
klog.V(4).Info("The token cannot be authenticated.")
} else if !isPrometheus {
klog.V(4).Infof("Access the metrics from the unexpected user %s is denied.", result.Status.User.Username)
}
return isAuthenticated && isPrometheus, nil
}

func (a *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "failed to get the Authorization header", http.StatusUnauthorized)
return
}
token := strings.TrimPrefix(authHeader, "Bearer ")
if token == "" {
http.Error(w, "empty Bearer token", http.StatusUnauthorized)
return
}
if token == authHeader {
http.Error(w, "failed to get the Bearer token", http.StatusUnauthorized)
if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
klog.V(4).Info("Client certificate required but not provided")
http.Error(w, "client certificate required", http.StatusUnauthorized)
return
}

authorized, err := a.authorize(token)
if err != nil {
klog.Warningf("Failed to authorize token: %v", err)
http.Error(w, "failed to authorize due to an internal error", http.StatusInternalServerError)
return
}
if !authorized {
http.Error(w, "failed to authorize", http.StatusUnauthorized)
// The first element is the leaf certificate that the connection is verified against
cn := r.TLS.PeerCertificates[0].Subject.CommonName
if cn != "system:serviceaccount:openshift-monitoring:prometheus-k8s" {
klog.V(4).Infof("Access denied for CN: %s", cn)
http.Error(w, "unauthorized CN", http.StatusForbidden)
return
}

klog.V(5).Infof("Access granted for CN: %s", cn)
a.downstream.ServeHTTP(w, r)
}

Expand Down Expand Up @@ -270,11 +230,6 @@ func RunMetrics(runContext context.Context, shutdownContext context.Context, lis
return fmt.Errorf("failed to create client CA controller: %w", err)
}

client, err := authenticationclientsetv1.NewForConfig(restConfig)
if err != nil {
return fmt.Errorf("failed to create config: %w", err)
}

// Start the client CA controller to begin watching the ConfigMap
go clientCAController.Run(runContext, 1)

Expand All @@ -296,7 +251,7 @@ func RunMetrics(runContext context.Context, shutdownContext context.Context, lis

go servingCertController.Run(1, runContext.Done())

server := createHttpServer(runContext, client, disableMetricsAuth)
server := createHttpServer(disableMetricsAuth)

resultChannel := make(chan asyncResult, 1)
resultChannelCount := 1
Expand Down