@@ -17,10 +17,14 @@ import (
1717
1818 "github.com/prometheus/client_golang/prometheus"
1919 "github.com/prometheus/client_golang/prometheus/promhttp"
20+ authenticationv1 "k8s.io/api/authentication/v1"
2021 corev1 "k8s.io/api/core/v1"
2122 apierrors "k8s.io/apimachinery/pkg/api/errors"
23+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2224 "k8s.io/apimachinery/pkg/labels"
2325 "k8s.io/apimachinery/pkg/util/sets"
26+ authenticationclientsetv1 "k8s.io/client-go/kubernetes/typed/authentication/v1"
27+ "k8s.io/client-go/rest"
2428 "k8s.io/client-go/tools/cache"
2529 "k8s.io/klog/v2"
2630
@@ -128,8 +132,8 @@ type asyncResult struct {
128132 error error
129133}
130134
131- func createHttpServer () * http.Server {
132- auth := authHandler {downstream : promhttp .Handler ()}
135+ func createHttpServer (ctx context. Context , client * authenticationclientsetv1. AuthenticationV1Client ) * http.Server {
136+ auth := authHandler {downstream : promhttp .Handler (), ctx : ctx , client : client . TokenReviews () }
133137 handler := http .NewServeMux ()
134138 handler .Handle ("/metrics" , & auth )
135139 server := & http.Server {
@@ -138,8 +142,34 @@ func createHttpServer() *http.Server {
138142 return server
139143}
140144
145+ type tokenReviewInterface interface {
146+ Create (ctx context.Context , tokenReview * authenticationv1.TokenReview , opts metav1.CreateOptions ) (* authenticationv1.TokenReview , error )
147+ }
148+
141149type authHandler struct {
142150 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
143173}
144174
145175func (a * authHandler ) ServeHTTP (w http.ResponseWriter , r * http.Request ) {
@@ -149,12 +179,25 @@ func (a *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
149179 return
150180 }
151181 token := strings .TrimPrefix (authHeader , "Bearer " )
182+ if token == "" {
183+ http .Error (w , "empty Bearer token" , http .StatusUnauthorized )
184+ return
185+ }
152186 if token == authHeader {
153187 http .Error (w , "failed to get the Bearer token" , http .StatusUnauthorized )
154188 return
155189 }
156190
157- // TODO use the token
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+ }
158201 a .downstream .ServeHTTP (w , r )
159202}
160203
@@ -203,7 +246,7 @@ func handleServerResult(result asyncResult, lastLoopError error) error {
203246// Also detects changes to metrics certificate files upon which
204247// the metrics HTTP server is shutdown and recreated with a new
205248// TLS configuration.
206- 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 {
207250 var tlsConfig * tls.Config
208251 if listenAddress != "" {
209252 var err error
@@ -214,7 +257,13 @@ func RunMetrics(runContext context.Context, shutdownContext context.Context, lis
214257 } else {
215258 return errors .New ("TLS configuration is required to serve metrics" )
216259 }
217- 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 )
218267
219268 resultChannel := make (chan asyncResult , 1 )
220269 resultChannelCount := 1
@@ -268,7 +317,7 @@ func RunMetrics(runContext context.Context, shutdownContext context.Context, lis
268317 case result := <- resultChannel : // crashed before a shutdown was requested or metrics server recreated
269318 if restartServer {
270319 klog .Info ("Creating metrics server with updated TLS configuration." )
271- server = createHttpServer ()
320+ server = createHttpServer (runContext , client )
272321 go startListening (server , tlsConfig , listenAddress , resultChannel )
273322 restartServer = false
274323 continue
0 commit comments