k8s.io/kubernetes@v1.29.3/test/e2e/auth/node_authz.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package auth
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    25  
    26  	v1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/util/wait"
    29  	clientset "k8s.io/client-go/kubernetes"
    30  	restclient "k8s.io/client-go/rest"
    31  	"k8s.io/kubernetes/test/e2e/feature"
    32  	"k8s.io/kubernetes/test/e2e/framework"
    33  	imageutils "k8s.io/kubernetes/test/utils/image"
    34  	admissionapi "k8s.io/pod-security-admission/api"
    35  
    36  	"github.com/onsi/ginkgo/v2"
    37  	"github.com/onsi/gomega"
    38  )
    39  
    40  const (
    41  	nodesGroup     = "system:nodes"
    42  	nodeNamePrefix = "system:node:"
    43  )
    44  
    45  var _ = SIGDescribe(feature.NodeAuthorizer, func() {
    46  
    47  	f := framework.NewDefaultFramework("node-authz")
    48  	f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
    49  	// client that will impersonate a node
    50  	var c clientset.Interface
    51  	var ns string
    52  	var asUser string
    53  	var nodeName string
    54  	ginkgo.BeforeEach(func(ctx context.Context) {
    55  		ns = f.Namespace.Name
    56  
    57  		nodeList, err := f.ClientSet.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
    58  		framework.ExpectNoError(err, "failed to list nodes in namespace: %s", ns)
    59  		gomega.Expect(nodeList.Items).NotTo(gomega.BeEmpty())
    60  		nodeName = nodeList.Items[0].Name
    61  		asUser = nodeNamePrefix + nodeName
    62  		ginkgo.By("Creating a kubernetes client that impersonates a node")
    63  		config, err := framework.LoadConfig()
    64  		framework.ExpectNoError(err, "failed to load kubernetes client config")
    65  		config.Impersonate = restclient.ImpersonationConfig{
    66  			UserName: asUser,
    67  			Groups:   []string{nodesGroup},
    68  		}
    69  		c, err = clientset.NewForConfig(config)
    70  		framework.ExpectNoError(err, "failed to create Clientset for the given config: %+v", *config)
    71  
    72  	})
    73  	ginkgo.It("Getting a non-existent secret should exit with the Forbidden error, not a NotFound error", func(ctx context.Context) {
    74  		_, err := c.CoreV1().Secrets(ns).Get(ctx, "foo", metav1.GetOptions{})
    75  		if !apierrors.IsForbidden(err) {
    76  			framework.Failf("should be a forbidden error, got %#v", err)
    77  		}
    78  	})
    79  
    80  	ginkgo.It("Getting an existing secret should exit with the Forbidden error", func(ctx context.Context) {
    81  		ginkgo.By("Create a secret for testing")
    82  		secret := &v1.Secret{
    83  			ObjectMeta: metav1.ObjectMeta{
    84  				Namespace: ns,
    85  				Name:      "node-auth-secret",
    86  			},
    87  			StringData: map[string]string{},
    88  		}
    89  		_, err := f.ClientSet.CoreV1().Secrets(ns).Create(ctx, secret, metav1.CreateOptions{})
    90  		framework.ExpectNoError(err, "failed to create secret (%s:%s) %+v", ns, secret.Name, *secret)
    91  		_, err = c.CoreV1().Secrets(ns).Get(ctx, secret.Name, metav1.GetOptions{})
    92  		if !apierrors.IsForbidden(err) {
    93  			framework.Failf("should be a forbidden error, got %#v", err)
    94  		}
    95  	})
    96  
    97  	ginkgo.It("Getting a non-existent configmap should exit with the Forbidden error, not a NotFound error", func(ctx context.Context) {
    98  		_, err := c.CoreV1().ConfigMaps(ns).Get(ctx, "foo", metav1.GetOptions{})
    99  		if !apierrors.IsForbidden(err) {
   100  			framework.Failf("should be a forbidden error, got %#v", err)
   101  		}
   102  	})
   103  
   104  	ginkgo.It("Getting an existing configmap should exit with the Forbidden error", func(ctx context.Context) {
   105  		ginkgo.By("Create a configmap for testing")
   106  		configmap := &v1.ConfigMap{
   107  			ObjectMeta: metav1.ObjectMeta{
   108  				Namespace: ns,
   109  				Name:      "node-auth-configmap",
   110  			},
   111  			Data: map[string]string{
   112  				"data": "content",
   113  			},
   114  		}
   115  		_, err := f.ClientSet.CoreV1().ConfigMaps(ns).Create(ctx, configmap, metav1.CreateOptions{})
   116  		framework.ExpectNoError(err, "failed to create configmap (%s:%s) %+v", ns, configmap.Name, *configmap)
   117  		_, err = c.CoreV1().ConfigMaps(ns).Get(ctx, configmap.Name, metav1.GetOptions{})
   118  		if !apierrors.IsForbidden(err) {
   119  			framework.Failf("should be a forbidden error, got %#v", err)
   120  		}
   121  	})
   122  
   123  	ginkgo.It("Getting a secret for a workload the node has access to should succeed", func(ctx context.Context) {
   124  		ginkgo.By("Create a secret for testing")
   125  		secret := &v1.Secret{
   126  			ObjectMeta: metav1.ObjectMeta{
   127  				Namespace: ns,
   128  				Name:      "node-auth-secret",
   129  			},
   130  			Data: map[string][]byte{
   131  				"data": []byte("keep it secret"),
   132  			},
   133  		}
   134  		_, err := f.ClientSet.CoreV1().Secrets(ns).Create(ctx, secret, metav1.CreateOptions{})
   135  		framework.ExpectNoError(err, "failed to create secret (%s:%s)", ns, secret.Name)
   136  
   137  		ginkgo.By("Node should not get the secret")
   138  		_, err = c.CoreV1().Secrets(ns).Get(ctx, secret.Name, metav1.GetOptions{})
   139  		if !apierrors.IsForbidden(err) {
   140  			framework.Failf("should be a forbidden error, got %#v", err)
   141  		}
   142  
   143  		ginkgo.By("Create a pod that use the secret")
   144  		pod := &v1.Pod{
   145  			ObjectMeta: metav1.ObjectMeta{
   146  				Name: "pause",
   147  			},
   148  			Spec: v1.PodSpec{
   149  				Containers: []v1.Container{
   150  					{
   151  						Name:  "pause",
   152  						Image: imageutils.GetPauseImageName(),
   153  					},
   154  				},
   155  				NodeName: nodeName,
   156  				Volumes: []v1.Volume{
   157  					{
   158  						Name: "node-auth-secret",
   159  						VolumeSource: v1.VolumeSource{
   160  							Secret: &v1.SecretVolumeSource{
   161  								SecretName: secret.Name,
   162  							},
   163  						},
   164  					},
   165  				},
   166  			},
   167  		}
   168  
   169  		_, err = f.ClientSet.CoreV1().Pods(ns).Create(ctx, pod, metav1.CreateOptions{})
   170  		framework.ExpectNoError(err, "failed to create pod (%s:%s)", ns, pod.Name)
   171  
   172  		ginkgo.By("The node should able to access the secret")
   173  		itv := framework.Poll
   174  		dur := 1 * time.Minute
   175  		err = wait.Poll(itv, dur, func() (bool, error) {
   176  			_, err = c.CoreV1().Secrets(ns).Get(ctx, secret.Name, metav1.GetOptions{})
   177  			if err != nil {
   178  				framework.Logf("Failed to get secret %v, err: %v", secret.Name, err)
   179  				return false, nil
   180  			}
   181  			return true, nil
   182  		})
   183  		framework.ExpectNoError(err, "failed to get secret after trying every %v for %v (%s:%s)", itv, dur, ns, secret.Name)
   184  	})
   185  
   186  	ginkgo.It("A node shouldn't be able to create another node", func(ctx context.Context) {
   187  		node := &v1.Node{
   188  			ObjectMeta: metav1.ObjectMeta{Name: "foo"},
   189  			TypeMeta: metav1.TypeMeta{
   190  				Kind:       "Node",
   191  				APIVersion: "v1",
   192  			},
   193  		}
   194  		ginkgo.By(fmt.Sprintf("Create node foo by user: %v", asUser))
   195  		_, err := c.CoreV1().Nodes().Create(ctx, node, metav1.CreateOptions{})
   196  
   197  		// NOTE: If the test fails and a new node IS created, we need to delete it. If we don't, we'd have
   198  		// a zombie node in a NotReady state which will delay further tests since we're waiting for all
   199  		// tests to be in the Ready state.
   200  		ginkgo.DeferCleanup(framework.IgnoreNotFound(f.ClientSet.CoreV1().Nodes().Delete), node.Name, metav1.DeleteOptions{})
   201  
   202  		if !apierrors.IsForbidden(err) {
   203  			framework.Failf("should be a forbidden error, got %#v", err)
   204  		}
   205  	})
   206  
   207  	ginkgo.It("A node shouldn't be able to delete another node", func(ctx context.Context) {
   208  		ginkgo.By(fmt.Sprintf("Create node foo by user: %v", asUser))
   209  		err := c.CoreV1().Nodes().Delete(ctx, "foo", metav1.DeleteOptions{})
   210  		if !apierrors.IsForbidden(err) {
   211  			framework.Failf("should be a forbidden error, got %#v", err)
   212  		}
   213  	})
   214  })