@@ -17,10 +17,14 @@ import (
17
17
18
18
"github.com/prometheus/client_golang/prometheus"
19
19
"github.com/prometheus/client_golang/prometheus/promhttp"
20
+ authenticationv1 "k8s.io/api/authentication/v1"
20
21
corev1 "k8s.io/api/core/v1"
21
22
apierrors "k8s.io/apimachinery/pkg/api/errors"
23
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22
24
"k8s.io/apimachinery/pkg/labels"
23
25
"k8s.io/apimachinery/pkg/util/sets"
26
+ authenticationclientsetv1 "k8s.io/client-go/kubernetes/typed/authentication/v1"
27
+ "k8s.io/client-go/rest"
24
28
"k8s.io/client-go/tools/cache"
25
29
"k8s.io/klog/v2"
26
30
@@ -128,8 +132,8 @@ type asyncResult struct {
128
132
error error
129
133
}
130
134
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 () }
133
137
handler := http .NewServeMux ()
134
138
handler .Handle ("/metrics" , & auth )
135
139
server := & http.Server {
@@ -138,8 +142,34 @@ func createHttpServer() *http.Server {
138
142
return server
139
143
}
140
144
145
+ type tokenReviewInterface interface {
146
+ Create (ctx context.Context , tokenReview * authenticationv1.TokenReview , opts metav1.CreateOptions ) (* authenticationv1.TokenReview , error )
147
+ }
148
+
141
149
type authHandler struct {
142
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
143
173
}
144
174
145
175
func (a * authHandler ) ServeHTTP (w http.ResponseWriter , r * http.Request ) {
@@ -149,12 +179,25 @@ func (a *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
149
179
return
150
180
}
151
181
token := strings .TrimPrefix (authHeader , "Bearer " )
182
+ if token == "" {
183
+ http .Error (w , "empty Bearer token" , http .StatusUnauthorized )
184
+ return
185
+ }
152
186
if token == authHeader {
153
187
http .Error (w , "failed to get the Bearer token" , http .StatusUnauthorized )
154
188
return
155
189
}
156
190
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
+ }
158
201
a .downstream .ServeHTTP (w , r )
159
202
}
160
203
@@ -203,7 +246,7 @@ func handleServerResult(result asyncResult, lastLoopError error) error {
203
246
// Also detects changes to metrics certificate files upon which
204
247
// the metrics HTTP server is shutdown and recreated with a new
205
248
// 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 {
207
250
var tlsConfig * tls.Config
208
251
if listenAddress != "" {
209
252
var err error
@@ -214,7 +257,13 @@ func RunMetrics(runContext context.Context, shutdownContext context.Context, lis
214
257
} else {
215
258
return errors .New ("TLS configuration is required to serve metrics" )
216
259
}
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 )
218
267
219
268
resultChannel := make (chan asyncResult , 1 )
220
269
resultChannelCount := 1
@@ -268,7 +317,7 @@ func RunMetrics(runContext context.Context, shutdownContext context.Context, lis
268
317
case result := <- resultChannel : // crashed before a shutdown was requested or metrics server recreated
269
318
if restartServer {
270
319
klog .Info ("Creating metrics server with updated TLS configuration." )
271
- server = createHttpServer ()
320
+ server = createHttpServer (runContext , client )
272
321
go startListening (server , tlsConfig , listenAddress , resultChannel )
273
322
restartServer = false
274
323
continue
0 commit comments