diff --git a/test/e2e_node/kubelet_authz_test.go b/test/e2e/node/kubelet_authz.go similarity index 60% rename from test/e2e_node/kubelet_authz_test.go rename to test/e2e/node/kubelet_authz.go index ff4df6f0661..88972e6d30d 100644 --- a/test/e2e_node/kubelet_authz_test.go +++ b/test/e2e/node/kubelet_authz.go @@ -14,17 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package e2enode +package node import ( "context" - "crypto/tls" "fmt" - "net/http" "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" - authenticationv1 "k8s.io/api/authentication/v1" authorizationv1 "k8s.io/api/authorization/v1" v1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -34,32 +31,40 @@ import ( "k8s.io/kubernetes/test/e2e/feature" "k8s.io/kubernetes/test/e2e/framework" e2eauth "k8s.io/kubernetes/test/e2e/framework/auth" + e2epod "k8s.io/kubernetes/test/e2e/framework/pod" + e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output" + admissionapi "k8s.io/pod-security-admission/api" ) -var _ = SIGDescribe("Kubelet Authz", feature.KubeletFineGrainedAuthz, func() { +var _ = SIGDescribe(feature.KubeletFineGrainedAuthz, func() { f := framework.NewDefaultFramework("kubelet-authz-test") + f.NamespacePodSecurityLevel = admissionapi.LevelBaseline + ginkgo.Context("when calling kubelet API", func() { ginkgo.It("check /healthz enpoint is accessible via nodes/healthz RBAC", func(ctx context.Context) { sc := runKubeletAuthzTest(ctx, f, "healthz", "healthz") - gomega.Expect(sc).To(gomega.Equal(http.StatusOK)) + gomega.Expect(sc).To(gomega.Equal("200")) }) ginkgo.It("check /healthz enpoint is accessible via nodes/proxy RBAC", func(ctx context.Context) { sc := runKubeletAuthzTest(ctx, f, "healthz", "proxy") - gomega.Expect(sc).To(gomega.Equal(http.StatusOK)) + gomega.Expect(sc).To(gomega.Equal("200")) }) ginkgo.It("check /healthz enpoint is not accessible via nodes/configz RBAC", func(ctx context.Context) { sc := runKubeletAuthzTest(ctx, f, "healthz", "configz") - gomega.Expect(sc).To(gomega.Equal(http.StatusUnauthorized)) + gomega.Expect(sc).To(gomega.Equal("403")) }) }) }) -func runKubeletAuthzTest(ctx context.Context, f *framework.Framework, endpoint, authzSubresource string) int { +func runKubeletAuthzTest(ctx context.Context, f *framework.Framework, endpoint, authzSubresource string) string { ns := f.Namespace.Name saName := authzSubresource crName := authzSubresource verb := "get" resource := "nodes" + + ginkgo.By(fmt.Sprintf("Creating Service Account %s/%s", ns, saName)) + _, err := f.ClientSet.CoreV1().ServiceAccounts(ns).Create(ctx, &v1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: saName, @@ -68,12 +73,15 @@ func runKubeletAuthzTest(ctx context.Context, f *framework.Framework, endpoint, }, metav1.CreateOptions{}) framework.ExpectNoError(err) + ginkgo.By(fmt.Sprintf("Creating ClusterRole %s with for %s/%s", crName, resource, authzSubresource)) + _, err = f.ClientSet.RbacV1().ClusterRoles().Create(ctx, &rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ Name: crName, }, Rules: []rbacv1.PolicyRule{ { + APIGroups: []string{""}, Verbs: []string{verb}, Resources: []string{resource + "/" + authzSubresource}, }, @@ -87,9 +95,13 @@ func runKubeletAuthzTest(ctx context.Context, f *framework.Framework, endpoint, Name: saName, } + ginkgo.By(fmt.Sprintf("Creating ClusterRoleBinding with ClusterRole %s with subject %s/%s", crName, ns, saName)) + err = e2eauth.BindClusterRole(ctx, f.ClientSet.RbacV1(), crName, ns, subject) framework.ExpectNoError(err) + ginkgo.By("Waiting for Authorization Update.") + err = e2eauth.WaitForAuthzUpdate(ctx, f.ClientSet.AuthorizationV1(), serviceaccount.MakeUsername(ns, saName), &authorizationv1.ResourceAttributes{ @@ -102,25 +114,41 @@ func runKubeletAuthzTest(ctx context.Context, f *framework.Framework, endpoint, ) framework.ExpectNoError(err) - tr, err := f.ClientSet.CoreV1().ServiceAccounts(ns).CreateToken(ctx, saName, &authenticationv1.TokenRequest{}, metav1.CreateOptions{}) - framework.ExpectNoError(err) - - resp, err := healthCheck(fmt.Sprintf("https://127.0.0.1:%d/%s", ports.KubeletPort, endpoint), tr.Status.Token) - framework.ExpectNoError(err) - return resp.StatusCode -} - -func healthCheck(url, token string) (*http.Response, error) { - insecureTransport := http.DefaultTransport.(*http.Transport).Clone() - insecureTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - insecureHTTPClient := &http.Client{ - Transport: insecureTransport, + pod := e2epod.NewAgnhostPod(ns, fmt.Sprintf("agnhost-pod-%s", authzSubresource), nil, nil, nil) + pod.Spec.ServiceAccountName = saName + pod.Spec.Containers[0].Env = []v1.EnvVar{ + { + Name: "NODE_IP", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + FieldPath: "status.hostIP", + }, + }, + }, } - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - return nil, err + ginkgo.By(fmt.Sprintf("Creating Pod %s in namespace %s with serviceaccount %s", pod.Name, pod.Namespace, pod.Spec.ServiceAccountName)) + + _ = e2epod.NewPodClient(f).CreateSync(ctx, pod) + + ginkgo.By("Running command in Pod") + + var hostWarpStart, hostWarpEnd string + // IPv6 host must be wrapped within [] if you specify a port. + if framework.TestContext.ClusterIsIPv6() { + hostWarpStart = "[" + hostWarpEnd = "]" } - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - return insecureHTTPClient.Do(req) + + result := e2eoutput.RunHostCmdOrDie(ns, + pod.Name, + fmt.Sprintf("curl -XGET -sIk -o /dev/null -w '%s' --header \"Authorization: Bearer `%s`\" https://%s$NODE_IP%s:%d/%s", + "%{http_code}", + "cat /var/run/secrets/kubernetes.io/serviceaccount/token", + hostWarpStart, + hostWarpEnd, + ports.KubeletPort, + endpoint)) + + return result }