@@ -30,9 +30,12 @@ import (
30
30
31
31
"k8s.io/apiserver/pkg/server"
32
32
"k8s.io/apiserver/pkg/server/options"
33
+ utilfeature "k8s.io/apiserver/pkg/util/feature"
33
34
cloudprovider "k8s.io/cloud-provider"
34
35
cloudctrlmgrtesting "k8s.io/cloud-provider/app/testing"
35
36
"k8s.io/cloud-provider/fake"
37
+ featuregatetesting "k8s.io/component-base/featuregate/testing"
38
+ zpagesfeatures "k8s.io/component-base/zpages/features"
36
39
"k8s.io/klog/v2/ktesting"
37
40
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
38
41
kubectrlmgrtesting "k8s.io/kubernetes/cmd/kube-controller-manager/app/testing"
@@ -289,3 +292,140 @@ func fakeCloudProviderFactory(io.Reader) (cloudprovider.Interface, error) {
289
292
DisableRoutes : true , // disable routes for server tests, otherwise --cluster-cidr is required
290
293
}, nil
291
294
}
295
+
296
+ func TestKubeControllerManagerServingStatusz (t * testing.T ) {
297
+
298
+ // authenticate to apiserver via bearer token
299
+ token := "flwqkenfjasasdfmwerasd" // Fake token for testing.
300
+ tokenFile , err := os .CreateTemp ("" , "kubeconfig" )
301
+ if err != nil {
302
+ t .Fatal (err )
303
+ }
304
+ if _ , err = tokenFile .WriteString (fmt .Sprintf (`
305
+ %s,system:kube-controller-manager,system:kube-controller-manager,""
306
+ ` , token )); err != nil {
307
+ t .Fatal (err )
308
+ }
309
+ if err = tokenFile .Close (); err != nil {
310
+ t .Fatal (err )
311
+ }
312
+
313
+ // start apiserver
314
+ server := kubeapiservertesting .StartTestServerOrDie (t , nil , []string {
315
+ "--token-auth-file" , tokenFile .Name (),
316
+ "--authorization-mode" , "RBAC" ,
317
+ }, framework .SharedEtcd ())
318
+ defer server .TearDownFn ()
319
+
320
+ // create kubeconfig for the apiserver
321
+ apiserverConfig , err := os .CreateTemp ("" , "kubeconfig" )
322
+ if err != nil {
323
+ t .Fatal (err )
324
+ }
325
+ if _ , err = apiserverConfig .WriteString (fmt .Sprintf (`
326
+ apiVersion: v1
327
+ kind: Config
328
+ clusters:
329
+ - cluster:
330
+ server: %s
331
+ certificate-authority: %s
332
+ name: integration
333
+ contexts:
334
+ - context:
335
+ cluster: integration
336
+ user: controller-manager
337
+ name: default-context
338
+ current-context: default-context
339
+ users:
340
+ - name: controller-manager
341
+ user:
342
+ token: %s
343
+ ` , server .ClientConfig .Host , server .ServerOpts .SecureServing .ServerCert .CertKey .CertFile , token )); err != nil {
344
+ t .Fatal (err )
345
+ }
346
+ if err = apiserverConfig .Close (); err != nil {
347
+ t .Fatal (err )
348
+ }
349
+
350
+ tests := []struct {
351
+ name string
352
+ flags []string
353
+ path string
354
+ anonymous bool // to use the token or not
355
+ wantErr bool
356
+ wantSecureCode * int
357
+ }{
358
+ {"serving /statusz" , []string {
359
+ "--authentication-skip-lookup" , // to survive inaccessible extensions-apiserver-authentication configmap
360
+ "--authentication-kubeconfig" , apiserverConfig .Name (),
361
+ "--authorization-kubeconfig" , apiserverConfig .Name (),
362
+ "--authorization-always-allow-paths" , "/statusz" ,
363
+ "--kubeconfig" , apiserverConfig .Name (),
364
+ "--leader-elect=false" ,
365
+ }, "/statusz" , false , false , intPtr (http .StatusOK )},
366
+ }
367
+ for _ , tt := range tests {
368
+ t .Run (tt .name , func (t * testing.T ) {
369
+ featuregatetesting .SetFeatureGateDuringTest (t , utilfeature .DefaultFeatureGate , zpagesfeatures .ComponentStatusz , true )
370
+ _ , ctx := ktesting .NewTestContext (t )
371
+ secureOptions , secureInfo , tearDownFn , err := kubeControllerManagerTester {}.StartTestServer (ctx , append (append ([]string {}, tt .flags ... ), []string {}... ))
372
+ if tearDownFn != nil {
373
+ defer tearDownFn ()
374
+ }
375
+ if (err != nil ) != tt .wantErr {
376
+ t .Fatalf ("StartTestServer() error = %v, wantErr %v" , err , tt .wantErr )
377
+ }
378
+ if err != nil {
379
+ return
380
+ }
381
+
382
+ if want , got := tt .wantSecureCode != nil , secureInfo != nil ; want != got {
383
+ t .Errorf ("SecureServing enabled: expected=%v got=%v" , want , got )
384
+ } else if want {
385
+ url := fmt .Sprintf ("https://%s%s" , secureInfo .Listener .Addr ().String (), tt .path )
386
+ url = strings .ReplaceAll (url , "[::]" , "127.0.0.1" ) // switch to IPv4 because the self-signed cert does not support [::]
387
+
388
+ // read self-signed server cert disk
389
+ pool := x509 .NewCertPool ()
390
+ serverCertPath := path .Join (secureOptions .ServerCert .CertDirectory , secureOptions .ServerCert .PairName + ".crt" )
391
+ serverCert , err := os .ReadFile (serverCertPath )
392
+ if err != nil {
393
+ t .Fatalf ("Failed to read component server cert %q: %v" , serverCertPath , err )
394
+ }
395
+ pool .AppendCertsFromPEM (serverCert )
396
+ tr := & http.Transport {
397
+ TLSClientConfig : & tls.Config {
398
+ RootCAs : pool ,
399
+ },
400
+ }
401
+
402
+ client := & http.Client {Transport : tr }
403
+ req , err := http .NewRequest (http .MethodGet , url , nil )
404
+ req .Header .Set ("Accept" , "text/plain" )
405
+ if err != nil {
406
+ t .Fatal (err )
407
+ }
408
+ if ! tt .anonymous {
409
+ req .Header .Add ("Authorization" , fmt .Sprintf ("Token %s" , token ))
410
+ }
411
+ r , err := client .Do (req )
412
+ if err != nil {
413
+ t .Fatalf ("failed to GET %s from component: %v" , tt .path , err )
414
+ }
415
+
416
+ if _ , err = io .ReadAll (r .Body ); err != nil {
417
+ t .Fatalf ("failed to read response body: %v" , err )
418
+ }
419
+ defer func () {
420
+ if err := r .Body .Close (); err != nil {
421
+ t .Fatalf ("Error closing response body: %v" , err )
422
+ }
423
+ }()
424
+
425
+ if got , expected := r .StatusCode , * tt .wantSecureCode ; got != expected {
426
+ t .Fatalf ("expected http %d at %s of component, got: %d" , expected , tt .path , got )
427
+ }
428
+ }
429
+ })
430
+ }
431
+ }
0 commit comments