diff --git a/test/integration/serving/serving_test.go b/test/integration/serving/serving_test.go index 95b914b058b..926fe64afb5 100644 --- a/test/integration/serving/serving_test.go +++ b/test/integration/serving/serving_test.go @@ -30,7 +30,7 @@ import ( "k8s.io/apiserver/pkg/server" "k8s.io/apiserver/pkg/server/options" - "k8s.io/cloud-provider" + cloudprovider "k8s.io/cloud-provider" cloudctrlmgrtesting "k8s.io/cloud-provider/app/testing" "k8s.io/cloud-provider/fake" kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" @@ -159,17 +159,23 @@ users: brokenApiserverConfig.Close() tests := []struct { - name string - tester componentTester - extraFlags []string + name string + tester componentTester + extraFlags []string + insecureDisabled bool }{ - {"kube-controller-manager", kubeControllerManagerTester{}, nil}, - {"cloud-controller-manager", cloudControllerManagerTester{}, []string{"--cloud-provider=fake"}}, - {"kube-scheduler", kubeSchedulerTester{}, nil}, + {"kube-controller-manager", kubeControllerManagerTester{}, nil, true}, + {"cloud-controller-manager", cloudControllerManagerTester{}, []string{"--cloud-provider=fake"}, false}, + {"kube-scheduler", kubeSchedulerTester{}, nil, false}, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - testComponent(t, tt.tester, apiserverConfig.Name(), brokenApiserverConfig.Name(), token, tt.extraFlags) + if tt.insecureDisabled { + testComponentWithSecureServing(t, tt.tester, apiserverConfig.Name(), brokenApiserverConfig.Name(), token, tt.extraFlags) + } else { + testComponent(t, tt.tester, apiserverConfig.Name(), brokenApiserverConfig.Name(), token, tt.extraFlags) + } }) } } @@ -215,7 +221,7 @@ func testComponent(t *testing.T, tester componentTester, kubeconfig, brokenKubec }, "/healthz", false, false, intPtr(http.StatusOK), nil}, {"authorization skipped for /healthz with BROKEN authn/authz", []string{ "--port=0", - "--authentication-skip-lookup", // to survive unaccessible extensions-apiserver-authentication configmap + "--authentication-skip-lookup", // to survive inaccessible extensions-apiserver-authentication configmap "--authentication-kubeconfig", brokenKubeconfig, "--authorization-kubeconfig", brokenKubeconfig, "--kubeconfig", kubeconfig, @@ -237,9 +243,9 @@ func testComponent(t *testing.T, tester componentTester, kubeconfig, brokenKubec }, "/metrics", false, false, intPtr(http.StatusInternalServerError), intPtr(http.StatusOK)}, {"always-allowed /metrics with BROKEN authn/authz", []string{ "--port=0", - "--authentication-skip-lookup", // to survive unaccessible extensions-apiserver-authentication configmap - "--authentication-kubeconfig", kubeconfig, - "--authorization-kubeconfig", kubeconfig, + "--authentication-skip-lookup", // to survive inaccessible extensions-apiserver-authentication configmap + "--authentication-kubeconfig", brokenKubeconfig, + "--authorization-kubeconfig", brokenKubeconfig, "--authorization-always-allow-paths", "/healthz,/metrics", "--kubeconfig", kubeconfig, "--leader-elect=false", @@ -322,6 +328,111 @@ func testComponent(t *testing.T, tester componentTester, kubeconfig, brokenKubec } } +func testComponentWithSecureServing(t *testing.T, tester componentTester, kubeconfig, brokenKubeconfig, token string, extraFlags []string) { + tests := []struct { + name string + flags []string + path string + anonymous bool // to use the token or not + wantErr bool + wantSecureCode *int + }{ + {"no-flags", nil, "/healthz", false, true, nil}, + {"/healthz without authn/authz", []string{ + "--kubeconfig", kubeconfig, + "--leader-elect=false", + }, "/healthz", true, false, intPtr(http.StatusOK)}, + {"/metrics without authn/authz", []string{ + "--kubeconfig", kubeconfig, + "--leader-elect=false", + }, "/metrics", true, false, intPtr(http.StatusForbidden)}, + {"authorization skipped for /healthz with authn/authz", []string{ + "--authentication-kubeconfig", kubeconfig, + "--authorization-kubeconfig", kubeconfig, + "--kubeconfig", kubeconfig, + "--leader-elect=false", + }, "/healthz", false, false, intPtr(http.StatusOK)}, + {"authorization skipped for /healthz with BROKEN authn/authz", []string{ + "--authentication-skip-lookup", // to survive inaccessible extensions-apiserver-authentication configmap + "--authentication-kubeconfig", brokenKubeconfig, + "--authorization-kubeconfig", brokenKubeconfig, + "--kubeconfig", kubeconfig, + "--leader-elect=false", + }, "/healthz", false, false, intPtr(http.StatusOK)}, + {"not authorized /metrics with BROKEN authn/authz", []string{ + "--authentication-kubeconfig", kubeconfig, + "--authorization-kubeconfig", brokenKubeconfig, + "--kubeconfig", kubeconfig, + "--leader-elect=false", + }, "/metrics", false, false, intPtr(http.StatusInternalServerError)}, + {"always-allowed /metrics with BROKEN authn/authz", []string{ + "--authentication-skip-lookup", // to survive inaccessible extensions-apiserver-authentication configmap + "--authentication-kubeconfig", brokenKubeconfig, + "--authorization-kubeconfig", brokenKubeconfig, + "--authorization-always-allow-paths", "/healthz,/metrics", + "--kubeconfig", kubeconfig, + "--leader-elect=false", + }, "/metrics", false, false, intPtr(http.StatusOK)}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + secureOptions, secureInfo, _, tearDownFn, err := tester.StartTestServer(t, append(append([]string{}, tt.flags...), extraFlags...)) + if tearDownFn != nil { + defer tearDownFn() + } + if (err != nil) != tt.wantErr { + t.Fatalf("StartTestServer() error = %v, wantErr %v", err, tt.wantErr) + } + if err != nil { + return + } + + if want, got := tt.wantSecureCode != nil, secureInfo != nil; want != got { + t.Errorf("SecureServing enabled: expected=%v got=%v", want, got) + } else if want { + url := fmt.Sprintf("https://%s%s", secureInfo.Listener.Addr().String(), tt.path) + url = strings.Replace(url, "[::]", "127.0.0.1", -1) // switch to IPv4 because the self-signed cert does not support [::] + + // read self-signed server cert disk + pool := x509.NewCertPool() + serverCertPath := path.Join(secureOptions.ServerCert.CertDirectory, secureOptions.ServerCert.PairName+".crt") + serverCert, err := ioutil.ReadFile(serverCertPath) + if err != nil { + t.Fatalf("Failed to read component server cert %q: %v", serverCertPath, err) + } + pool.AppendCertsFromPEM(serverCert) + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: pool, + }, + } + + client := &http.Client{Transport: tr} + req, err := http.NewRequest("GET", url, nil) + if err != nil { + t.Fatal(err) + } + if !tt.anonymous { + req.Header.Add("Authorization", fmt.Sprintf("Token %s", token)) + } + r, err := client.Do(req) + if err != nil { + t.Fatalf("failed to GET %s from component: %v", tt.path, err) + } + + body, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Fatalf("failed to read response body: %v", err) + } + defer r.Body.Close() + if got, expected := r.StatusCode, *tt.wantSecureCode; got != expected { + t.Fatalf("expected http %d at %s of component, got: %d %q", expected, tt.path, got, string(body)) + } + } + }) + } +} + func intPtr(x int) *int { return &x }