@@ -12,14 +12,19 @@ import (
12
12
"net/http"
13
13
"os"
14
14
"path/filepath"
15
+ "strings"
15
16
"time"
16
17
17
18
"github.com/prometheus/client_golang/prometheus"
18
19
"github.com/prometheus/client_golang/prometheus/promhttp"
20
+ authenticationv1 "k8s.io/api/authentication/v1"
19
21
corev1 "k8s.io/api/core/v1"
20
22
apierrors "k8s.io/apimachinery/pkg/api/errors"
23
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21
24
"k8s.io/apimachinery/pkg/labels"
22
25
"k8s.io/apimachinery/pkg/util/sets"
26
+ authenticationclientsetv1 "k8s.io/client-go/kubernetes/typed/authentication/v1"
27
+ "k8s.io/client-go/rest"
23
28
"k8s.io/client-go/tools/cache"
24
29
"k8s.io/klog/v2"
25
30
@@ -127,15 +132,75 @@ type asyncResult struct {
127
132
error error
128
133
}
129
134
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 ()}
131
137
handler := http .NewServeMux ()
132
- handler .Handle ("/metrics" , promhttp . Handler () )
138
+ handler .Handle ("/metrics" , & auth )
133
139
server := & http.Server {
134
140
Handler : handler ,
135
141
}
136
142
return server
137
143
}
138
144
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
+
139
204
func shutdownHttpServer (parentCtx context.Context , svr * http.Server ) {
140
205
ctx , cancel := context .WithTimeout (parentCtx , 5 * time .Second )
141
206
defer cancel ()
@@ -181,7 +246,7 @@ func handleServerResult(result asyncResult, lastLoopError error) error {
181
246
// Also detects changes to metrics certificate files upon which
182
247
// the metrics HTTP server is shutdown and recreated with a new
183
248
// 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 {
185
250
var tlsConfig * tls.Config
186
251
if listenAddress != "" {
187
252
var err error
@@ -192,7 +257,13 @@ func RunMetrics(runContext context.Context, shutdownContext context.Context, lis
192
257
} else {
193
258
return errors .New ("TLS configuration is required to serve metrics" )
194
259
}
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 )
196
267
197
268
resultChannel := make (chan asyncResult , 1 )
198
269
resultChannelCount := 1
@@ -246,7 +317,7 @@ func RunMetrics(runContext context.Context, shutdownContext context.Context, lis
246
317
case result := <- resultChannel : // crashed before a shutdown was requested or metrics server recreated
247
318
if restartServer {
248
319
klog .Info ("Creating metrics server with updated TLS configuration." )
249
- server = createHttpServer ()
320
+ server = createHttpServer (runContext , client )
250
321
go startListening (server , tlsConfig , listenAddress , resultChannel )
251
322
restartServer = false
252
323
continue
0 commit comments