kube-public中的资源可以被所有用户读取,本文探索了其实现方式和当前的现状

kube-public命名空间工作原理

在开发中,我们使用到了kube-public命名空间中的cluster-info configmap。按照官方文档的说法,kube-public是任何用户(包括未认证用户)都可以访问的,官网的说明如下:

kube-public This namespace is created automatically and is readable by all users (including those not authenticated). This namespace is mostly reserved for cluster usage, in case that some resources should be visible and readable publicly throughout the whole cluster. The public aspect of this namespace is only a convention, not a requirement.

而我们在实际测试当中,却发现使用kubeadm创建的集群中,匿名用户仅能实现对kube-publiccluster-info对象的get操作,而对该命名空间的其余动作或者操作其余资源均会返回403Forbidden,这显然与官网文档说法有出入。那么kube-public命名空间到底怎么实现的呢?我们进行了一番探索。

首先,从kubernetes源码当中,进行一番搜索,找到两处可疑的代码: plugin/pkg/auth/authorizer/rbac/bootstrappolicy/namespace_policy.go#L140

    addNamespaceRole(metav1.NamespacePublic, rbacv1.Role{
    		// role for the bootstrap signer to be able to write its configmap
    		ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "bootstrap-signer"},
    		Rules: []rbacv1.PolicyRule{
    			rbacv1helpers.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("configmaps").RuleOrDie(),
    			rbacv1helpers.NewRule("update").Groups(legacyGroup).Resources("configmaps").Names("cluster-info").RuleOrDie(),
    			eventsRule(),
    		},
    	})
    	addNamespaceRoleBinding(metav1.NamespacePublic,
    		rbacv1helpers.NewRoleBinding(saRolePrefix+"bootstrap-signer", metav1.NamespacePublic).SAs(metav1.NamespaceSystem, "bootstrap-signer").BindingOrDie())

此处创建了一个角色saRolePrefix + "bootstrap-signer",并和名为bootstrap-signersa进行绑定。看起来和匿名访问关系不大。

cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo.go#L85

    // CreateClusterInfoRBACRules creates the RBAC rules for exposing the cluster-info ConfigMap in the kube-public namespace to unauthenticated users
    func CreateClusterInfoRBACRules(client clientset.Interface) error {
    	klog.V(1).Infoln("creating the RBAC rules for exposing the cluster-info ConfigMap in the kube-public namespace")
    	err := apiclient.CreateOrUpdateRole(client, &rbac.Role{
    		ObjectMeta: metav1.ObjectMeta{
    			Name:      BootstrapSignerClusterRoleName,
    			Namespace: metav1.NamespacePublic,
    		},
    		Rules: []rbac.PolicyRule{
    			{
    				Verbs:         []string{"get"},
    				APIGroups:     []string{""},
    				Resources:     []string{"configmaps"},
    				ResourceNames: []string{bootstrapapi.ConfigMapClusterInfo},
    			},
    		},
    	})
    	if err != nil {
    		return err
    	}
    
    	return apiclient.CreateOrUpdateRoleBinding(client, &rbac.RoleBinding{
    		ObjectMeta: metav1.ObjectMeta{
    			Name:      BootstrapSignerClusterRoleName,
    			Namespace: metav1.NamespacePublic,
    		},
    		RoleRef: rbac.RoleRef{
    			APIGroup: rbac.GroupName,
    			Kind:     "Role",
    			Name:     BootstrapSignerClusterRoleName,
    		},
    		Subjects: []rbac.Subject{
    			{
    				Kind: rbac.UserKind,
    				Name: user.Anonymous,
    			},
    		},
    	})
    }

这段代码首先创建了名为BootstrapSignerClusterRoleName也就是kubeadm:bootstrap-signer-clusterinfo的角色,该角色对kube-public中的cluster-info configmap具有get权限。然后创建了一个绑定,将上面的角色与user.Anonymous绑定起来,这样实现任意用户对cluster-infoget操作授权。同时也说明了为什么未授权用户不能读取其余自定义的资源。

一切都没有魔法,只是kubeadm初始化集群时进行了权限配置。再次看一遍官方的文档,其中有一句:is only a convention。这仅仅是一个约定,系统不会帮你实现这个约定,而是需要用户自己遵守并实现。