@@ -12,14 +12,19 @@ import (
1212 "net/http"
1313 "os"
1414 "path/filepath"
15+ "strings"
1516 "time"
1617
1718 "github.com/prometheus/client_golang/prometheus"
1819 "github.com/prometheus/client_golang/prometheus/promhttp"
20+ authenticationv1 "k8s.io/api/authentication/v1"
1921 corev1 "k8s.io/api/core/v1"
2022 apierrors "k8s.io/apimachinery/pkg/api/errors"
23+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2124 "k8s.io/apimachinery/pkg/labels"
2225 "k8s.io/apimachinery/pkg/util/sets"
26+ authenticationclientsetv1 "k8s.io/client-go/kubernetes/typed/authentication/v1"
27+ "k8s.io/client-go/rest"
2328 "k8s.io/client-go/tools/cache"
2429 "k8s.io/klog/v2"
2530
@@ -127,15 +132,75 @@ type asyncResult struct {
127132 error error
128133}
129134
130- func createHttpServer () * http.Server {
135+ func createHttpServer (ctx context.Context , client * authenticationclientsetv1.AuthenticationV1Client ) * http.Server {
136+ auth := authHandler {downstream : promhttp .Handler (), ctx : ctx , client : client .TokenReviews ()}
131137 handler := http .NewServeMux ()
132- handler .Handle ("/metrics" , promhttp . Handler () )
138+ handler .Handle ("/metrics" , & auth )
133139 server := & http.Server {
134140 Handler : handler ,
135141 }
136142 return server
137143}
138144
145+ type tokenReviewInterface interface {
146+ Create (ctx context.Context , tokenReview * authenticationv1.TokenReview , opts metav1.CreateOptions ) (* authenticationv1.TokenReview , error )
147+ }
148+
149+ type authHandler struct {
150+ downstream http.Handler
151+ ctx context.Context
152+ client tokenReviewInterface
153+ }
154+
155+ func (a * authHandler ) authorize (token string ) (bool , error ) {
156+ tr := & authenticationv1.TokenReview {
157+ Spec : authenticationv1.TokenReviewSpec {
158+ Token : token ,
159+ },
160+ }
161+ result , err := a .client .Create (a .ctx , tr , metav1.CreateOptions {})
162+ if err != nil {
163+ return false , fmt .Errorf ("failed to check token: %w" , err )
164+ }
165+ isAuthenticated := result .Status .Authenticated
166+ isPrometheus := result .Status .User .Username == "system:serviceaccount:openshift-monitoring:prometheus-k8s"
167+ if ! isAuthenticated {
168+ klog .V (4 ).Info ("The token cannot be authenticated." )
169+ } else if ! isPrometheus {
170+ klog .V (4 ).Infof ("Access the metrics from the unexpected user %s is denied." , result .Status .User .Username )
171+ }
172+ return isAuthenticated && isPrometheus , nil
173+ }
174+
175+ func (a * authHandler ) ServeHTTP (w http.ResponseWriter , r * http.Request ) {
176+ authHeader := r .Header .Get ("Authorization" )
177+ if authHeader == "" {
178+ http .Error (w , "failed to get the Authorization header" , http .StatusUnauthorized )
179+ return
180+ }
181+ token := strings .TrimPrefix (authHeader , "Bearer " )
182+ if token == "" {
183+ http .Error (w , "empty Bearer token" , http .StatusUnauthorized )
184+ return
185+ }
186+ if token == authHeader {
187+ http .Error (w , "failed to get the Bearer token" , http .StatusUnauthorized )
188+ return
189+ }
190+
191+ authorized , err := a .authorize (token )
192+ if err != nil {
193+ klog .Warningf ("Failed to authorize token: %v" , err )
194+ http .Error (w , "failed to authorize due to an internal error" , http .StatusInternalServerError )
195+ return
196+ }
197+ if ! authorized {
198+ http .Error (w , "failed to authorize" , http .StatusUnauthorized )
199+ return
200+ }
201+ a .downstream .ServeHTTP (w , r )
202+ }
203+
139204func shutdownHttpServer (parentCtx context.Context , svr * http.Server ) {
140205 ctx , cancel := context .WithTimeout (parentCtx , 5 * time .Second )
141206 defer cancel ()
@@ -181,7 +246,7 @@ func handleServerResult(result asyncResult, lastLoopError error) error {
181246// Also detects changes to metrics certificate files upon which
182247// the metrics HTTP server is shutdown and recreated with a new
183248// TLS configuration.
184- func RunMetrics (runContext context.Context , shutdownContext context.Context , listenAddress , certFile , keyFile string ) error {
249+ func RunMetrics (runContext context.Context , shutdownContext context.Context , listenAddress , certFile , keyFile string , restConfig * rest. Config ) error {
185250 var tlsConfig * tls.Config
186251 if listenAddress != "" {
187252 var err error
@@ -192,7 +257,13 @@ func RunMetrics(runContext context.Context, shutdownContext context.Context, lis
192257 } else {
193258 return errors .New ("TLS configuration is required to serve metrics" )
194259 }
195- server := createHttpServer ()
260+
261+ client , err := authenticationclientsetv1 .NewForConfig (restConfig )
262+ if err != nil {
263+ return fmt .Errorf ("failed to create config: %w" , err )
264+ }
265+
266+ server := createHttpServer (runContext , client )
196267
197268 resultChannel := make (chan asyncResult , 1 )
198269 resultChannelCount := 1
@@ -246,7 +317,7 @@ func RunMetrics(runContext context.Context, shutdownContext context.Context, lis
246317 case result := <- resultChannel : // crashed before a shutdown was requested or metrics server recreated
247318 if restartServer {
248319 klog .Info ("Creating metrics server with updated TLS configuration." )
249- server = createHttpServer ()
320+ server = createHttpServer (runContext , client )
250321 go startListening (server , tlsConfig , listenAddress , resultChannel )
251322 restartServer = false
252323 continue
0 commit comments