From 12db1e28dced08dbdacdad8793fb7cf1dfedfa59 Mon Sep 17 00:00:00 2001 From: HirazawaUi <695097494plus@gmail.com> Date: Sun, 21 Sep 2025 00:27:07 +0800 Subject: [PATCH] remove unused file --- cmd/kubeadm/app/util/staticpod/utils.go | 17 - cmd/kubeadm/app/util/staticpod/utils_linux.go | 195 ------ .../app/util/staticpod/utils_linux_test.go | 168 ----- .../app/util/staticpod/utils_others.go | 32 - cmd/kubeadm/app/util/users/users_linux.go | 658 ------------------ .../app/util/users/users_linux_test.go | 593 ---------------- cmd/kubeadm/app/util/users/users_other.go | 56 -- 7 files changed, 1719 deletions(-) delete mode 100644 cmd/kubeadm/app/util/staticpod/utils_linux.go delete mode 100644 cmd/kubeadm/app/util/staticpod/utils_linux_test.go delete mode 100644 cmd/kubeadm/app/util/staticpod/utils_others.go delete mode 100644 cmd/kubeadm/app/util/users/users_linux.go delete mode 100644 cmd/kubeadm/app/util/users/users_linux_test.go delete mode 100644 cmd/kubeadm/app/util/users/users_other.go diff --git a/cmd/kubeadm/app/util/staticpod/utils.go b/cmd/kubeadm/app/util/staticpod/utils.go index 5b70cb89c35..21604b46eda 100644 --- a/cmd/kubeadm/app/util/staticpod/utils.go +++ b/cmd/kubeadm/app/util/staticpod/utils.go @@ -27,7 +27,6 @@ import ( "os" "sort" "strings" - "sync" "github.com/pmezard/go-difflib/difflib" @@ -43,7 +42,6 @@ import ( kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/errors" "k8s.io/kubernetes/cmd/kubeadm/app/util/patches" - "k8s.io/kubernetes/cmd/kubeadm/app/util/users" ) const ( @@ -54,11 +52,6 @@ const ( kubeSchedulerBindAddressArg = "bind-address" ) -var ( - usersAndGroups *users.UsersAndGroups - usersAndGroupsOnce sync.Once -) - // ComponentPod returns a Pod object from the container, volume and annotations specifications func ComponentPod(container v1.Container, volumes map[string]v1.Volume, annotations map[string]string) v1.Pod { // priority value for system-node-critical class @@ -418,16 +411,6 @@ func getProbeAddress(addr string) string { return addr } -// GetUsersAndGroups returns the local usersAndGroups, but first creates it -// in a thread safe way once. -func GetUsersAndGroups() (*users.UsersAndGroups, error) { - var err error - usersAndGroupsOnce.Do(func() { - usersAndGroups, err = users.AddUsersAndGroups() - }) - return usersAndGroups, err -} - // DeepHashObject writes specified object to hash using the spew library // which follows pointers and prints actual values of the nested objects // ensuring the hash does not change when a pointer changes. diff --git a/cmd/kubeadm/app/util/staticpod/utils_linux.go b/cmd/kubeadm/app/util/staticpod/utils_linux.go deleted file mode 100644 index cdd371f1e74..00000000000 --- a/cmd/kubeadm/app/util/staticpod/utils_linux.go +++ /dev/null @@ -1,195 +0,0 @@ -//go:build linux -// +build linux - -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package staticpod - -import ( - "fmt" - "path/filepath" - - v1 "k8s.io/api/core/v1" - "k8s.io/utils/ptr" - - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" - kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" - certphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" - "k8s.io/kubernetes/cmd/kubeadm/app/util/errors" - "k8s.io/kubernetes/cmd/kubeadm/app/util/users" -) - -type pathOwnerAndPermissionsUpdaterFunc func(path string, uid, gid int64, perms uint32) error -type pathOwnerUpdaterFunc func(path string, uid, gid int64) error - -// RunComponentAsNonRoot updates the pod manifest and the hostVolume permissions to run as non root. -func RunComponentAsNonRoot(componentName string, pod *v1.Pod, usersAndGroups *users.UsersAndGroups, cfg *kubeadmapi.ClusterConfiguration) error { - switch componentName { - case kubeadmconstants.KubeAPIServer: - return runKubeAPIServerAsNonRoot( - pod, - usersAndGroups.Users.ID(kubeadmconstants.KubeAPIServerUserName), - usersAndGroups.Groups.ID(kubeadmconstants.KubeAPIServerUserName), - usersAndGroups.Groups.ID(kubeadmconstants.ServiceAccountKeyReadersGroupName), - users.UpdatePathOwnerAndPermissions, - cfg, - ) - case kubeadmconstants.KubeControllerManager: - return runKubeControllerManagerAsNonRoot( - pod, - usersAndGroups.Users.ID(kubeadmconstants.KubeControllerManagerUserName), - usersAndGroups.Groups.ID(kubeadmconstants.KubeControllerManagerUserName), - usersAndGroups.Groups.ID(kubeadmconstants.ServiceAccountKeyReadersGroupName), - users.UpdatePathOwnerAndPermissions, - cfg, - ) - case kubeadmconstants.KubeScheduler: - return runKubeSchedulerAsNonRoot( - pod, - usersAndGroups.Users.ID(kubeadmconstants.KubeSchedulerUserName), - usersAndGroups.Groups.ID(kubeadmconstants.KubeSchedulerUserName), - users.UpdatePathOwnerAndPermissions, - ) - case kubeadmconstants.Etcd: - return runEtcdAsNonRoot( - pod, - usersAndGroups.Users.ID(kubeadmconstants.EtcdUserName), - usersAndGroups.Groups.ID(kubeadmconstants.EtcdUserName), - users.UpdatePathOwnerAndPermissions, - users.UpdatePathOwner, - cfg, - ) - } - return errors.New(fmt.Sprintf("component name %q is not valid", componentName)) -} - -// runKubeAPIServerAsNonRoot updates the pod manifest and the hostVolume permissions to run kube-apiserver as non root. -func runKubeAPIServerAsNonRoot(pod *v1.Pod, runAsUser, runAsGroup, supplementalGroup *int64, updatePathOwnerAndPermissions pathOwnerAndPermissionsUpdaterFunc, cfg *kubeadmapi.ClusterConfiguration) error { - saPublicKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName) - if err := updatePathOwnerAndPermissions(saPublicKeyFile, *runAsUser, *runAsGroup, 0600); err != nil { - return err - } - saPrivateKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName) - if err := updatePathOwnerAndPermissions(saPrivateKeyFile, 0, *supplementalGroup, 0640); err != nil { - return err - } - apiServerKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName) - if err := updatePathOwnerAndPermissions(apiServerKeyFile, *runAsUser, *runAsGroup, 0600); err != nil { - return err - } - apiServerKubeletClientKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName) - if err := updatePathOwnerAndPermissions(apiServerKubeletClientKeyFile, *runAsUser, *runAsGroup, 0600); err != nil { - return err - } - frontProxyClientKeyName := filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyClientKeyName) - if err := updatePathOwnerAndPermissions(frontProxyClientKeyName, *runAsUser, *runAsGroup, 0600); err != nil { - return err - } - if cfg.Etcd.External == nil { - apiServerEtcdClientKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerEtcdClientKeyName) - if err := updatePathOwnerAndPermissions(apiServerEtcdClientKeyFile, *runAsUser, *runAsGroup, 0600); err != nil { - return err - } - } - pod.Spec.Containers[0].SecurityContext = &v1.SecurityContext{ - Capabilities: &v1.Capabilities{ - // We drop all capabilities that are added by default. - Drop: []v1.Capability{"ALL"}, - // kube-apiserver binary has the file capability cap_net_bind_service applied to it. - // This means that we must add this capability when running as non-root even if the - // capability is not required. - Add: []v1.Capability{"NET_BIND_SERVICE"}, - }, - } - pod.Spec.SecurityContext.RunAsGroup = runAsGroup - pod.Spec.SecurityContext.RunAsUser = runAsUser - pod.Spec.SecurityContext.SupplementalGroups = []int64{*supplementalGroup} - return nil -} - -// runKubeControllerManagerAsNonRoot updates the pod manifest and the hostVolume permissions to run kube-controller-manager as non root. -func runKubeControllerManagerAsNonRoot(pod *v1.Pod, runAsUser, runAsGroup, supplementalGroup *int64, updatePathOwnerAndPermissions pathOwnerAndPermissionsUpdaterFunc, cfg *kubeadmapi.ClusterConfiguration) error { - kubeconfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ControllerManagerKubeConfigFileName) - if err := updatePathOwnerAndPermissions(kubeconfigFile, *runAsUser, *runAsGroup, 0600); err != nil { - return err - } - saPrivateKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName) - if err := updatePathOwnerAndPermissions(saPrivateKeyFile, 0, *supplementalGroup, 0640); err != nil { - return err - } - if res, _ := certphase.UsingExternalCA(cfg); !res { - caKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName) - err := updatePathOwnerAndPermissions(caKeyFile, *runAsUser, *runAsGroup, 0600) - if err != nil { - return err - } - } - pod.Spec.Containers[0].SecurityContext = &v1.SecurityContext{ - AllowPrivilegeEscalation: ptr.To(false), - Capabilities: &v1.Capabilities{ - // We drop all capabilities that are added by default. - Drop: []v1.Capability{"ALL"}, - }, - } - pod.Spec.SecurityContext.RunAsUser = runAsUser - pod.Spec.SecurityContext.RunAsGroup = runAsGroup - pod.Spec.SecurityContext.SupplementalGroups = []int64{*supplementalGroup} - return nil -} - -// runKubeSchedulerAsNonRoot updates the pod manifest and the hostVolume permissions to run kube-scheduler as non root. -func runKubeSchedulerAsNonRoot(pod *v1.Pod, runAsUser, runAsGroup *int64, updatePathOwnerAndPermissions pathOwnerAndPermissionsUpdaterFunc) error { - kubeconfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.SchedulerKubeConfigFileName) - if err := updatePathOwnerAndPermissions(kubeconfigFile, *runAsUser, *runAsGroup, 0600); err != nil { - return err - } - pod.Spec.Containers[0].SecurityContext = &v1.SecurityContext{ - AllowPrivilegeEscalation: ptr.To(false), - // We drop all capabilities that are added by default. - Capabilities: &v1.Capabilities{ - Drop: []v1.Capability{"ALL"}, - }, - } - pod.Spec.SecurityContext.RunAsUser = runAsUser - pod.Spec.SecurityContext.RunAsGroup = runAsGroup - return nil -} - -// runEtcdAsNonRoot updates the pod manifest and the hostVolume permissions to run etcd as non root. -func runEtcdAsNonRoot(pod *v1.Pod, runAsUser, runAsGroup *int64, updatePathOwnerAndPermissions pathOwnerAndPermissionsUpdaterFunc, updatePathOwner pathOwnerUpdaterFunc, cfg *kubeadmapi.ClusterConfiguration) error { - if err := updatePathOwner(cfg.Etcd.Local.DataDir, *runAsUser, *runAsGroup); err != nil { - return err - } - etcdServerKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdServerKeyName) - if err := updatePathOwnerAndPermissions(etcdServerKeyFile, *runAsUser, *runAsGroup, 0600); err != nil { - return err - } - etcdPeerKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdPeerKeyName) - if err := updatePathOwnerAndPermissions(etcdPeerKeyFile, *runAsUser, *runAsGroup, 0600); err != nil { - return err - } - pod.Spec.Containers[0].SecurityContext = &v1.SecurityContext{ - AllowPrivilegeEscalation: ptr.To(false), - // We drop all capabilities that are added by default. - Capabilities: &v1.Capabilities{ - Drop: []v1.Capability{"ALL"}, - }, - } - pod.Spec.SecurityContext.RunAsUser = runAsUser - pod.Spec.SecurityContext.RunAsGroup = runAsGroup - return nil -} diff --git a/cmd/kubeadm/app/util/staticpod/utils_linux_test.go b/cmd/kubeadm/app/util/staticpod/utils_linux_test.go deleted file mode 100644 index 29a3e675e1e..00000000000 --- a/cmd/kubeadm/app/util/staticpod/utils_linux_test.go +++ /dev/null @@ -1,168 +0,0 @@ -//go:build linux -// +build linux - -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package staticpod - -import ( - "path/filepath" - "reflect" - "testing" - - v1 "k8s.io/api/core/v1" - "k8s.io/utils/ptr" - - "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" - kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" -) - -type ownerAndPermissions struct { - uid int64 - gid int64 - permissions uint32 -} - -func verifyPodSecurityContext(t *testing.T, pod *v1.Pod, wantRunAsUser, wantRunAsGroup int64, wantSupGroup []int64) { - t.Helper() - wantPodSecurityContext := &v1.PodSecurityContext{ - RunAsUser: ptr.To(wantRunAsUser), - RunAsGroup: ptr.To(wantRunAsGroup), - SupplementalGroups: wantSupGroup, - SeccompProfile: &v1.SeccompProfile{ - Type: v1.SeccompProfileTypeRuntimeDefault, - }, - } - if !reflect.DeepEqual(wantPodSecurityContext, pod.Spec.SecurityContext) { - t.Errorf("unexpected diff in PodSecurityContext, want: %+v, got: %+v", wantPodSecurityContext, pod.Spec.SecurityContext) - } -} - -func verifyContainerSecurityContext(t *testing.T, container v1.Container, addCaps, dropCaps []v1.Capability, allowPrivielege *bool) { - t.Helper() - wantContainerSecurityContext := &v1.SecurityContext{ - AllowPrivilegeEscalation: allowPrivielege, - Capabilities: &v1.Capabilities{ - Add: addCaps, - Drop: dropCaps, - }, - } - if !reflect.DeepEqual(wantContainerSecurityContext, container.SecurityContext) { - t.Errorf("unexpected diff in container SecurityContext, want: %+v, got: %+v", wantContainerSecurityContext, container.SecurityContext) - } -} - -func verifyFilePermissions(t *testing.T, updatedFiles, wantFiles map[string]ownerAndPermissions) { - t.Helper() - if !reflect.DeepEqual(updatedFiles, wantFiles) { - t.Errorf("unexpected diff in file owners and permissions want: %+v, got: %+v", wantFiles, updatedFiles) - } -} - -func TestRunKubeAPIServerAsNonRoot(t *testing.T) { - cfg := &kubeadm.ClusterConfiguration{} - pod := ComponentPod(v1.Container{Name: "kube-apiserver"}, nil, nil) - var runAsUser, runAsGroup, supGroup int64 = 1000, 1001, 1002 - updatedFiles := map[string]ownerAndPermissions{} - if err := runKubeAPIServerAsNonRoot(&pod, &runAsUser, &runAsGroup, &supGroup, func(path string, uid, gid int64, perms uint32) error { - updatedFiles[path] = ownerAndPermissions{uid: uid, gid: gid, permissions: perms} - return nil - }, cfg); err != nil { - t.Fatal(err) - } - verifyPodSecurityContext(t, &pod, runAsUser, runAsGroup, []int64{supGroup}) - verifyContainerSecurityContext(t, pod.Spec.Containers[0], []v1.Capability{"NET_BIND_SERVICE"}, []v1.Capability{"ALL"}, nil) - wantUpdateFiles := map[string]ownerAndPermissions{ - filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName): {uid: runAsUser, gid: runAsGroup, permissions: 0600}, - filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName): {uid: 0, gid: supGroup, permissions: 0640}, - filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName): {uid: runAsUser, gid: runAsGroup, permissions: 0600}, - filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName): {uid: runAsUser, gid: runAsGroup, permissions: 0600}, - filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyClientKeyName): {uid: runAsUser, gid: runAsGroup, permissions: 0600}, - filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerEtcdClientKeyName): {uid: runAsUser, gid: runAsGroup, permissions: 0600}, - } - verifyFilePermissions(t, updatedFiles, wantUpdateFiles) -} - -func TestRunKubeControllerManagerAsNonRoot(t *testing.T) { - cfg := &kubeadm.ClusterConfiguration{} - pod := ComponentPod(v1.Container{Name: "kube-controller-manager"}, nil, nil) - var runAsUser, runAsGroup, supGroup int64 = 1000, 1001, 1002 - updatedFiles := map[string]ownerAndPermissions{} - if err := runKubeControllerManagerAsNonRoot(&pod, &runAsUser, &runAsGroup, &supGroup, func(path string, uid, gid int64, perms uint32) error { - updatedFiles[path] = ownerAndPermissions{uid: uid, gid: gid, permissions: perms} - return nil - }, cfg); err != nil { - t.Fatal(err) - } - verifyPodSecurityContext(t, &pod, runAsUser, runAsGroup, []int64{supGroup}) - verifyContainerSecurityContext(t, pod.Spec.Containers[0], nil, []v1.Capability{"ALL"}, ptr.To(false)) - wantUpdateFiles := map[string]ownerAndPermissions{ - filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ControllerManagerKubeConfigFileName): {uid: runAsUser, gid: runAsGroup, permissions: 0600}, - filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName): {uid: 0, gid: supGroup, permissions: 0640}, - filepath.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName): {uid: runAsUser, gid: runAsGroup, permissions: 0600}, - } - verifyFilePermissions(t, updatedFiles, wantUpdateFiles) -} - -func TestRunKubeSchedulerAsNonRoot(t *testing.T) { - pod := ComponentPod(v1.Container{Name: "kube-scheduler"}, nil, nil) - var runAsUser, runAsGroup int64 = 1000, 1001 - updatedFiles := map[string]ownerAndPermissions{} - if err := runKubeSchedulerAsNonRoot(&pod, &runAsUser, &runAsGroup, func(path string, uid, gid int64, perms uint32) error { - updatedFiles[path] = ownerAndPermissions{uid: uid, gid: gid, permissions: perms} - return nil - }); err != nil { - t.Fatal(err) - } - verifyPodSecurityContext(t, &pod, runAsUser, runAsGroup, nil) - verifyContainerSecurityContext(t, pod.Spec.Containers[0], nil, []v1.Capability{"ALL"}, ptr.To(false)) - wantUpdateFiles := map[string]ownerAndPermissions{ - filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.SchedulerKubeConfigFileName): {uid: runAsUser, gid: runAsGroup, permissions: 0600}, - } - verifyFilePermissions(t, updatedFiles, wantUpdateFiles) -} - -func TestRunEtcdAsNonRoot(t *testing.T) { - cfg := &kubeadm.ClusterConfiguration{ - Etcd: kubeadm.Etcd{ - Local: &kubeadm.LocalEtcd{ - DataDir: "/var/lib/etcd/data", - }, - }, - } - pod := ComponentPod(v1.Container{Name: "etcd"}, nil, nil) - var runAsUser, runAsGroup int64 = 1000, 1001 - updatedFiles := map[string]ownerAndPermissions{} - if err := runEtcdAsNonRoot(&pod, &runAsUser, &runAsGroup, func(path string, uid, gid int64, perms uint32) error { - updatedFiles[path] = ownerAndPermissions{uid: uid, gid: gid, permissions: perms} - return nil - }, - func(path string, uid, gid int64) error { - updatedFiles[path] = ownerAndPermissions{uid: uid, gid: gid, permissions: 0700} - return nil - }, cfg); err != nil { - t.Fatal(err) - } - verifyPodSecurityContext(t, &pod, runAsUser, runAsGroup, nil) - verifyContainerSecurityContext(t, pod.Spec.Containers[0], nil, []v1.Capability{"ALL"}, ptr.To(false)) - wantUpdateFiles := map[string]ownerAndPermissions{ - cfg.Etcd.Local.DataDir: {uid: runAsUser, gid: runAsGroup, permissions: 0700}, - filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdServerKeyName): {uid: runAsUser, gid: runAsGroup, permissions: 0600}, - filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdPeerKeyName): {uid: runAsUser, gid: runAsGroup, permissions: 0600}, - } - verifyFilePermissions(t, updatedFiles, wantUpdateFiles) -} diff --git a/cmd/kubeadm/app/util/staticpod/utils_others.go b/cmd/kubeadm/app/util/staticpod/utils_others.go deleted file mode 100644 index c259f48b3ec..00000000000 --- a/cmd/kubeadm/app/util/staticpod/utils_others.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build !linux -// +build !linux - -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package staticpod - -import ( - v1 "k8s.io/api/core/v1" - - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" - "k8s.io/kubernetes/cmd/kubeadm/app/util/users" -) - -// RunComponentAsNonRoot is a NO-OP on non linux. -func RunComponentAsNonRoot(componentName string, pod *v1.Pod, usersAndGroups *users.UsersAndGroups, cfg *kubeadmapi.ClusterConfiguration) error { - return nil -} diff --git a/cmd/kubeadm/app/util/users/users_linux.go b/cmd/kubeadm/app/util/users/users_linux.go deleted file mode 100644 index 09308f98d83..00000000000 --- a/cmd/kubeadm/app/util/users/users_linux.go +++ /dev/null @@ -1,658 +0,0 @@ -//go:build linux -// +build linux - -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package users - -import ( - "bytes" - "fmt" - "io" - "os" - "path/filepath" - "sort" - "strconv" - "strings" - "syscall" - "time" - - "k8s.io/klog/v2" - - "k8s.io/kubernetes/cmd/kubeadm/app/constants" - "k8s.io/kubernetes/cmd/kubeadm/app/util/errors" -) - -// EntryMap holds a map of user or group entries. -type EntryMap struct { - entries map[string]*entry -} - -// UsersAndGroups is a structure that holds entry maps of users and groups. -// It is returned by AddUsersAndGroups. -type UsersAndGroups struct { - // Users is an entry map of users. - Users *EntryMap - // Groups is an entry map of groups. - Groups *EntryMap -} - -// entry is a structure that holds information about a UNIX user or group. -// It partially conforms parsing of both users from /etc/passwd and groups from /etc/group. -type entry struct { - name string - id int64 - gid int64 - userNames []string - shell string -} - -// limits is used to hold information about the minimum and maximum system ranges for UID and GID. -type limits struct { - minUID, maxUID, minGID, maxGID int64 -} - -const ( - // These are constants used when parsing /etc/passwd or /etc/group in terms of how many - // fields and entry has. - totalFieldsGroup = 4 - totalFieldsUser = 7 - - // klogLevel holds the klog level to use for output. - klogLevel = 5 - - // noshell holds a path to a binary to disable shell login. - noshell = "/bin/false" - - // These are constants for the default system paths on Linux. - fileEtcLoginDefs = "/etc/login.defs" - fileEtcPasswd = "/etc/passwd" - fileEtcGroup = "/etc/group" -) - -var ( - // these entries hold the users and groups to create as defined in: - // https://git.k8s.io/enhancements/keps/sig-cluster-lifecycle/kubeadm/2568-kubeadm-non-root-control-plane - usersToCreateSpec = []*entry{ - {name: constants.EtcdUserName}, - {name: constants.KubeAPIServerUserName}, - {name: constants.KubeControllerManagerUserName}, - {name: constants.KubeSchedulerUserName}, - } - groupsToCreateSpec = []*entry{ - {name: constants.EtcdUserName, userNames: []string{constants.EtcdUserName}}, - {name: constants.KubeAPIServerUserName, userNames: []string{constants.KubeAPIServerUserName}}, - {name: constants.KubeControllerManagerUserName, userNames: []string{constants.KubeControllerManagerUserName}}, - {name: constants.KubeSchedulerUserName, userNames: []string{constants.KubeSchedulerUserName}}, - {name: constants.ServiceAccountKeyReadersGroupName, userNames: []string{constants.KubeAPIServerUserName, constants.KubeControllerManagerUserName}}, - } - - // defaultLimits holds the default limits in case values are missing in /etc/login.defs - defaultLimits = &limits{minUID: 100, maxUID: 999, minGID: 100, maxGID: 999} -) - -// ID returns the ID for an entry based on the entry name. -// In case of a user entry it returns the user UID. -// In case of a group entry it returns the group GID. -// It returns nil if no such entry exists. -func (u *EntryMap) ID(name string) *int64 { - entry, ok := u.entries[name] - if !ok { - return nil - } - id := entry.id - return &id -} - -// String converts an EntryMap object to a readable string. -func (u *EntryMap) String() string { - lines := make([]string, 0, len(u.entries)) - for k, e := range u.entries { - lines = append(lines, fmt.Sprintf("%s{%d,%d};", k, e.id, e.gid)) - } - sort.Strings(lines) - return strings.Join(lines, "") -} - -// AddUsersAndGroups is a public wrapper around addUsersAndGroupsImpl with default system file paths. -func AddUsersAndGroups() (*UsersAndGroups, error) { - return addUsersAndGroupsImpl(fileEtcLoginDefs, fileEtcPasswd, fileEtcGroup) -} - -// addUsersAndGroupsImpl adds the managed users and groups to the files specified -// by pathUsers and pathGroups. It uses the file specified with pathLoginDef to -// determine limits for UID and GID. If managed users and groups exist in these files -// validation is performed on them. The function returns a pointer to a Users object -// that can be used to return UID and GID of managed users. -func addUsersAndGroupsImpl(pathLoginDef, pathUsers, pathGroups string) (*UsersAndGroups, error) { - klog.V(1).Info("Adding managed users and groups") - klog.V(klogLevel).Infof("Parsing %q", pathLoginDef) - - // Read and parse /etc/login.def. Some distributions might be missing this file, which makes - // them non-standard. If an error occurs fallback to defaults by passing an empty string - // to parseLoginDefs(). - var loginDef string - f, close, err := openFileWithLock(pathLoginDef) - if err != nil { - klog.V(1).Infof("Could not open %q, using default system limits: %v", pathLoginDef, err) - } else { - loginDef, err = readFile(f) - if err != nil { - klog.V(1).Infof("Could not read %q, using default system limits: %v", pathLoginDef, err) - } - close() - } - limits, err := parseLoginDefs(loginDef) - if err != nil { - return nil, err - } - - klog.V(klogLevel).Infof("Using system UID/GID limits: %+v", limits) - klog.V(klogLevel).Infof("Parsing %q and %q", pathUsers, pathGroups) - - // Open /etc/passwd and /etc/group with locks. - fUsers, close, err := openFileWithLock(pathUsers) - if err != nil { - return nil, err - } - defer close() - fGroups, close, err := openFileWithLock(pathGroups) - if err != nil { - return nil, err - } - defer close() - - // Read the files. - fileUsers, err := readFile(fUsers) - if err != nil { - return nil, err - } - fileGroups, err := readFile(fGroups) - if err != nil { - return nil, err - } - - // Parse the files. - users, err := parseEntries(fileUsers, totalFieldsUser) - if err != nil { - return nil, errors.Wrapf(err, "could not parse %q", pathUsers) - } - groups, err := parseEntries(fileGroups, totalFieldsGroup) - if err != nil { - return nil, errors.Wrapf(err, "could not parse %q", pathGroups) - } - - klog.V(klogLevel).Info("Validating existing users and groups") - - // Validate for existing tracked entries based on limits. - usersToCreate, groupsToCreate, err := validateEntries(users, groups, limits) - if err != nil { - return nil, errors.Wrap(err, "error validating existing users and groups") - } - - // Allocate and assign IDs to users / groups. - allocUIDs, err := allocateIDs(users, limits.minUID, limits.maxUID, len(usersToCreate)) - if err != nil { - return nil, err - } - allocGIDs, err := allocateIDs(groups, limits.minGID, limits.maxGID, len(groupsToCreate)) - if err != nil { - return nil, err - } - if err := assignUserAndGroupIDs(groups, usersToCreate, groupsToCreate, allocUIDs, allocGIDs); err != nil { - return nil, err - } - - if len(usersToCreate) > 0 { - klog.V(klogLevel).Infof("Adding users: %s", entriesToString(usersToCreate)) - } - if len(groupsToCreate) > 0 { - klog.V(klogLevel).Infof("Adding groups: %s", entriesToString(groupsToCreate)) - } - - // Add users and groups. - fileUsers = addEntries(fileUsers, usersToCreate, createUser) - fileGroups = addEntries(fileGroups, groupsToCreate, createGroup) - - // Write the files. - klog.V(klogLevel).Infof("Writing %q and %q", pathUsers, pathGroups) - if err := writeFile(fUsers, fileUsers); err != nil { - return nil, err - } - if err := writeFile(fGroups, fileGroups); err != nil { - return nil, err - } - - // Prepare the maps of users and groups. - usersConcat := append(users, usersToCreate...) - mapUsers, err := entriesToEntryMap(usersConcat, usersToCreateSpec) - if err != nil { - return nil, err - } - groupsConcat := append(groups, groupsToCreate...) - mapGroups, err := entriesToEntryMap(groupsConcat, groupsToCreateSpec) - if err != nil { - return nil, err - } - return &UsersAndGroups{Users: mapUsers, Groups: mapGroups}, nil -} - -// RemoveUsersAndGroups is a public wrapper around removeUsersAndGroupsImpl with -// default system file paths. -func RemoveUsersAndGroups() error { - return removeUsersAndGroupsImpl(fileEtcPasswd, fileEtcGroup) -} - -// removeUsersAndGroupsImpl removes the managed users and groups from the files specified -// by pathUsers and pathGroups. -func removeUsersAndGroupsImpl(pathUsers, pathGroups string) error { - klog.V(1).Info("Removing managed users and groups") - klog.V(klogLevel).Infof("Opening %q and %q", pathUsers, pathGroups) - - // Open /etc/passwd and /etc/group. - fUsers, close, err := openFileWithLock(pathUsers) - if err != nil { - return err - } - defer close() - fGroups, close, err := openFileWithLock(pathGroups) - if err != nil { - return err - } - defer close() - - // Read the files. - fileUsers, err := readFile(fUsers) - if err != nil { - return err - } - fileGroups, err := readFile(fGroups) - if err != nil { - return err - } - - klog.V(klogLevel).Infof("Removing users: %s", entriesToString(usersToCreateSpec)) - klog.V(klogLevel).Infof("Removing groups: %s", entriesToString(groupsToCreateSpec)) - - // Delete users / groups. - fileUsers, _ = removeEntries(fileUsers, usersToCreateSpec) - fileGroups, _ = removeEntries(fileGroups, groupsToCreateSpec) - - klog.V(klogLevel).Infof("Writing %q and %q", pathUsers, pathGroups) - - // Write the files. - if err := writeFile(fUsers, fileUsers); err != nil { - return err - } - if err := writeFile(fGroups, fileGroups); err != nil { - return err - } - - return nil -} - -// parseLoginDefs can be used to parse an /etc/login.defs file and obtain system ranges for UID and GID. -// Passing an empty string will return the defaults. The defaults are 100-999 for both UID and GID. -func parseLoginDefs(file string) (*limits, error) { - l := *defaultLimits - if len(file) == 0 { - return &l, nil - } - var mapping = map[string]*int64{ - "SYS_UID_MIN": &l.minUID, - "SYS_UID_MAX": &l.maxUID, - "SYS_GID_MIN": &l.minGID, - "SYS_GID_MAX": &l.maxGID, - } - lines := strings.Split(file, "\n") - for i, line := range lines { - for k, v := range mapping { - // A line must start with one of the definitions - if !strings.HasPrefix(line, k) { - continue - } - line = strings.TrimPrefix(line, k) - line = strings.TrimSpace(line) - val, err := strconv.ParseInt(line, 10, 64) - if err != nil { - return nil, errors.Wrapf(err, "could not parse value for %s at line %d", k, i) - } - *v = val - } - } - return &l, nil -} - -// parseEntries can be used to parse an /etc/passwd or /etc/group file as their format is similar. -// It returns a slice of entries obtained from the file. -// https://www.cyberciti.biz/faq/understanding-etcpasswd-file-format/ -// https://www.cyberciti.biz/faq/understanding-etcgroup-file/ -func parseEntries(file string, totalFields int) ([]*entry, error) { - if totalFields != totalFieldsUser && totalFields != totalFieldsGroup { - return nil, errors.Errorf("unsupported total fields for entry parsing: %d", totalFields) - } - lines := strings.Split(file, "\n") - entries := []*entry{} - for i, line := range lines { - line = strings.TrimSpace(line) - if len(line) == 0 { - continue - } - fields := strings.Split(line, ":") - if len(fields) != totalFields { - return nil, errors.Errorf("entry must have %d fields separated by ':', "+ - "got %d at line %d: %s", totalFields, len(fields), i, line) - } - id, err := strconv.ParseInt(fields[2], 10, 64) - if err != nil { - return nil, errors.Wrapf(err, "error parsing id at line %d", i) - } - entry := &entry{name: fields[0], id: id} - if totalFields == totalFieldsGroup { - entry.userNames = strings.Split(fields[3], ",") - } else { - gid, err := strconv.ParseInt(fields[3], 10, 64) - if err != nil { - return nil, errors.Wrapf(err, "error parsing GID at line %d", i) - } - entry.gid = gid - entry.shell = fields[6] - } - entries = append(entries, entry) - } - return entries, nil -} - -// validateEntries takes user and group entries and validates if these entries are valid based on limits, -// mapping between users and groups and specs. Returns slices of missing user and group entries that must be created. -// Returns an error if existing users and groups do not match requirements. -func validateEntries(users, groups []*entry, limits *limits) ([]*entry, []*entry, error) { - u := []*entry{} - g := []*entry{} - // Validate users - for _, uc := range usersToCreateSpec { - for _, user := range users { - if uc.name != user.name { - continue - } - // Found existing user - if user.id < limits.minUID || user.id > limits.maxUID { - return nil, nil, errors.Errorf("UID %d for user %q is outside the system UID range: %d - %d", - user.id, user.name, limits.minUID, limits.maxUID) - } - if user.shell != noshell { - return nil, nil, errors.Errorf("user %q has unexpected shell %q; expected %q", - user.name, user.shell, noshell) - } - for _, g := range groups { - if g.id != user.gid { - continue - } - // Found matching group GID for user GID - if g.name != uc.name { - return nil, nil, errors.Errorf("user %q has GID %d but the group with that GID is not named %q", - uc.name, g.id, uc.name) - } - goto skipUser // Valid group GID and name; skip - } - return nil, nil, errors.Errorf("could not find group with GID %d for user %q", user.gid, user.name) - } - u = append(u, uc) - skipUser: - } - // validate groups - for _, gc := range groupsToCreateSpec { - for _, group := range groups { - if gc.name != group.name { - continue - } - if group.id < limits.minGID || group.id > limits.maxGID { - return nil, nil, errors.Errorf("GID %d for user %q is outside the system UID range: %d - %d", - group.id, group.name, limits.minGID, limits.maxGID) - } - u1 := strings.Join(gc.userNames, ",") - u2 := strings.Join(group.userNames, ",") - if u1 != u2 { - return nil, nil, errors.Errorf("expected users %q for group %q; got %q", - u1, gc.name, u2) - } - goto skipGroup // group has valid users; skip - } - g = append(g, gc) - skipGroup: - } - return u, g, nil -} - -// allocateIDs takes a list of entries and based on minimum and maximum ID allocates a "total" of IDs. -func allocateIDs(entries []*entry, min, max int64, total int) ([]int64, error) { - if total == 0 { - return []int64{}, nil - } - ids := make([]int64, 0, total) - for i := min; i < max+1; i++ { - i64 := int64(i) - for _, e := range entries { - if i64 == e.id { - goto continueLoop - } - } - ids = append(ids, i64) - if len(ids) == total { - return ids, nil - } - continueLoop: - } - return nil, errors.Errorf("could not allocate %d IDs based on existing entries in the range: %d - %d", - total, min, max) -} - -// addEntries takes /etc/passwd or /etc/group file content and appends entries to it based -// on a createEntry function. Returns the updated contents of the file. -func addEntries(file string, entries []*entry, createEntry func(*entry) string) string { - out := file - newLines := make([]string, 0, len(entries)) - for _, e := range entries { - newLines = append(newLines, createEntry(e)) - } - newLinesStr := "" - if len(newLines) > 0 { - if !strings.HasSuffix(out, "\n") { // Append a new line if its missing. - newLinesStr = "\n" - } - newLinesStr += strings.Join(newLines, "\n") + "\n" - } - return out + newLinesStr -} - -// removeEntries takes /etc/passwd or /etc/group file content and deletes entries from them -// by name matching. Returns the updated contents of the file and the number of entries removed. -func removeEntries(file string, entries []*entry) (string, int) { - lines := strings.Split(file, "\n") - total := len(lines) - len(entries) - if total < 0 { - total = 0 - } - newLines := make([]string, 0, total) - removed := 0 - for _, line := range lines { - for _, entry := range entries { - if strings.HasPrefix(line, entry.name+":") { - removed++ - goto continueLoop - } - } - newLines = append(newLines, line) - continueLoop: - } - return strings.Join(newLines, "\n"), removed -} - -// assignUserAndGroupIDs takes the list of existing groups, the users and groups to be created, -// and assigns UIDs and GIDs to the users and groups to be created based on a list of provided UIDs and GIDs. -// Returns an error if not enough UIDs or GIDs are passed. It does not perform any other validation. -func assignUserAndGroupIDs(groups, usersToCreate, groupsToCreate []*entry, uids, gids []int64) error { - if len(gids) < len(groupsToCreate) { - return errors.Errorf("not enough GIDs to assign to groups: have %d, want %d", len(gids), len(groupsToCreate)) - } - if len(uids) < len(usersToCreate) { - return errors.Errorf("not enough UIDs to assign to users: have %d, want %d", len(uids), len(usersToCreate)) - } - for i := range groupsToCreate { - groupsToCreate[i].id = gids[i] - } - // Concat the list of old and new groups to find a matching GID. - groupsConcat := append([]*entry{}, groups...) - groupsConcat = append(groupsConcat, groupsToCreate...) - for i := range usersToCreate { - usersToCreate[i].id = uids[i] - for _, g := range groupsConcat { - if usersToCreate[i].name == g.name { - usersToCreate[i].gid = g.id - break - } - } - } - return nil -} - -// createGroup is a helper function to produce a group from entry. -func createGroup(e *entry) string { - return fmt.Sprintf("%s:x:%d:%s", e.name, e.id, strings.Join(e.userNames, ",")) -} - -// createUser is a helper function to produce a user from entry. -func createUser(e *entry) string { - return fmt.Sprintf("%s:x:%d:%d:::/bin/false", e.name, e.id, e.gid) -} - -// entriesToEntryMap takes a list of entries and prepares an EntryMap object. -func entriesToEntryMap(entries, spec []*entry) (*EntryMap, error) { - m := map[string]*entry{} - for _, spec := range spec { - for _, e := range entries { - if spec.name == e.name { - entry := *e - m[e.name] = &entry - goto continueLoop - } - } - return nil, errors.Errorf("could not find entry %q in the list", spec.name) - continueLoop: - } - return &EntryMap{entries: m}, nil -} - -// entriesToString is a utility to convert a list of entries to string. -func entriesToString(entries []*entry) string { - lines := make([]string, 0, len(entries)) - for _, e := range entries { - lines = append(lines, e.name) - } - sort.Strings(lines) - return strings.Join(lines, ",") -} - -// openFileWithLock opens the file at path by acquiring an exclusive write lock. -// The returned close() function should be called to release the lock and close the file. -// If a lock cannot be obtained the function fails after a period of time. -func openFileWithLock(path string) (f *os.File, close func(), err error) { - f, err = os.OpenFile(path, os.O_RDWR, os.ModePerm) - if err != nil { - return nil, nil, err - } - deadline := time.Now().Add(time.Second * 5) - for { - // If another process is holding a write lock, this call will exit - // with an error. F_SETLK is used instead of F_SETLKW to avoid - // the case where a runaway process grabs the exclusive lock and - // blocks this call indefinitely. - // https://man7.org/linux/man-pages/man2/fcntl.2.html - lock := syscall.Flock_t{Type: syscall.F_WRLCK} - if err = syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &lock); err == nil { - break - } - time.Sleep(200 * time.Millisecond) - if time.Now().After(deadline) { - err = errors.Wrapf(err, "timeout attempting to obtain lock on file %q", path) - break - } - } - if err != nil { - f.Close() - return nil, nil, err - } - close = func() { - // This function should be called once operations with the file are finished. - // It unlocks the file and closes it. - unlock := syscall.Flock_t{Type: syscall.F_UNLCK} - syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &unlock) - f.Close() - } - return f, close, nil -} - -// readFile reads a File into a string. -func readFile(f *os.File) (string, error) { - buf := bytes.NewBuffer(nil) - if _, err := f.Seek(0, io.SeekStart); err != nil { - return "", err - } - if _, err := io.Copy(buf, f); err != nil { - return "", err - } - return buf.String(), nil -} - -// writeFile writes a string to a File. -func writeFile(f *os.File, str string) error { - if _, err := f.Seek(0, io.SeekStart); err != nil { - return err - } - if _, err := f.Write([]byte(str)); err != nil { - return err - } - if err := f.Truncate(int64(len(str))); err != nil { - return err - } - return nil -} - -// UpdatePathOwnerAndPermissions updates the owner and permissions of the given path. -// If the path is a directory it is not recursively updated. -func UpdatePathOwnerAndPermissions(path string, uid, gid int64, perms uint32) error { - if err := os.Chown(path, int(uid), int(gid)); err != nil { - return errors.Wrapf(err, "failed to update owner of %q to uid: %d and gid: %d", path, uid, gid) - } - fm := os.FileMode(perms) - if err := os.Chmod(path, fm); err != nil { - return errors.Wrapf(err, "failed to update permissions of %q to %s", path, fm.String()) - } - return nil -} - -// UpdatePathOwner recursively updates the owners of a directory. -// It is equivalent to calling `chown -R uid:gid /path/to/dir`. -func UpdatePathOwner(dirPath string, uid, gid int64) error { - err := filepath.WalkDir(dirPath, func(path string, d os.DirEntry, err error) error { - if err := os.Chown(path, int(uid), int(gid)); err != nil { - return errors.Wrapf(err, "failed to update owner of %q to uid: %d and gid: %d", path, uid, gid) - } - return nil - }) - return err -} diff --git a/cmd/kubeadm/app/util/users/users_linux_test.go b/cmd/kubeadm/app/util/users/users_linux_test.go deleted file mode 100644 index e0c1368ba05..00000000000 --- a/cmd/kubeadm/app/util/users/users_linux_test.go +++ /dev/null @@ -1,593 +0,0 @@ -//go:build linux -// +build linux - -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package users - -import ( - "os" - "reflect" - "testing" -) - -func TestParseLoginDef(t *testing.T) { - testCases := []struct { - name string - input string - expectedLimits *limits - expectedError bool - }{ - { - name: "non number value for tracked limit", - input: "SYS_UID_MIN foo\n", - expectedError: true, - }, - { - name: "empty string must return defaults", - expectedLimits: defaultLimits, - }, - { - name: "no tracked limits in file must return defaults", - input: "# some comment\n", - expectedLimits: defaultLimits, - }, - { - name: "must parse all valid tracked limits", - input: "SYS_UID_MIN 101\nSYS_UID_MAX 998\nSYS_GID_MIN 102\nSYS_GID_MAX 999\n", - expectedLimits: &limits{minUID: 101, maxUID: 998, minGID: 102, maxGID: 999}, - }, - { - name: "must return defaults for missing limits", - input: "SYS_UID_MIN 101\n#SYS_UID_MAX 998\nSYS_GID_MIN 102\n#SYS_GID_MAX 999\n", - expectedLimits: &limits{minUID: 101, maxUID: defaultLimits.maxUID, minGID: 102, maxGID: defaultLimits.maxGID}, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - got, err := parseLoginDefs(tc.input) - if err != nil != tc.expectedError { - t.Fatalf("expected error: %v, got: %v, error: %v", tc.expectedError, err != nil, err) - } - if err == nil && *tc.expectedLimits != *got { - t.Fatalf("expected limits %+v, got %+v", tc.expectedLimits, got) - } - }) - } -} - -func TestParseEntries(t *testing.T) { - testCases := []struct { - name string - file string - expectedEntries []*entry - totalFields int - expectedError bool - }{ - { - name: "totalFields must be a known value", - expectedError: true, - }, - { - name: "unexpected number of fields", - file: "foo:x:100::::::", - totalFields: totalFieldsUser, - expectedError: true, - }, - { - name: "cannot parse 'bar' as UID", - file: "foo:x:bar:101:::\n", - totalFields: totalFieldsUser, - expectedError: true, - }, - { - name: "cannot parse 'bar' as GID", - file: "foo:x:101:bar:::\n", - totalFields: totalFieldsUser, - expectedError: true, - }, - { - name: "valid file for users", - file: "\nfoo:x:100:101:foo:/home/foo:/bin/bash\n\nbar:x:102:103:bar::\n", - totalFields: totalFieldsUser, - expectedEntries: []*entry{ - {name: "foo", id: 100, gid: 101, shell: "/bin/bash"}, - {name: "bar", id: 102, gid: 103}, - }, - }, - { - name: "valid file for groups", - file: "\nfoo:x:100:bar,baz\n\nbar:x:101:baz\n", - totalFields: totalFieldsGroup, - expectedEntries: []*entry{ - {name: "foo", id: 100, userNames: []string{"bar", "baz"}}, - {name: "bar", id: 101, userNames: []string{"baz"}}, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - got, err := parseEntries(tc.file, tc.totalFields) - if err != nil != tc.expectedError { - t.Fatalf("expected error: %v, got: %v, error: %v", tc.expectedError, err != nil, err) - } - if err != nil { - return - } - if len(tc.expectedEntries) != len(got) { - t.Fatalf("expected entries %d, got %d", len(tc.expectedEntries), len(got)) - } - for i := range got { - if !reflect.DeepEqual(tc.expectedEntries[i], got[i]) { - t.Fatalf("expected entry at position %d: %+v, got: %+v", i, tc.expectedEntries[i], got[i]) - } - } - }) - } -} - -func TestValidateEntries(t *testing.T) { - testCases := []struct { - name string - users []*entry - groups []*entry - expectedUsers []*entry - expectedGroups []*entry - expectedError bool - }{ - { - name: "UID for user is outside of system limits", - users: []*entry{ - {name: "kubeadm-etcd", id: 2000, gid: 102, shell: noshell}, - }, - groups: []*entry{}, - expectedError: true, - }, - { - name: "user has unexpected shell", - users: []*entry{ - {name: "kubeadm-etcd", id: 102, gid: 102, shell: "foo"}, - }, - groups: []*entry{}, - expectedError: true, - }, - { - name: "user is mapped to unknown group", - users: []*entry{ - {name: "kubeadm-etcd", id: 102, gid: 102, shell: noshell}, - }, - groups: []*entry{}, - expectedError: true, - }, - { - name: "user and group names do not match", - users: []*entry{ - {name: "kubeadm-etcd", id: 102, gid: 102, shell: noshell}, - }, - groups: []*entry{ - {name: "foo", id: 102}, - }, - expectedError: true, - }, - { - name: "GID is outside system limits", - users: []*entry{}, - groups: []*entry{ - {name: "kubeadm-etcd", id: 2000}, - }, - expectedError: true, - }, - { - name: "group is missing users", - users: []*entry{}, - groups: []*entry{ - {name: "kubeadm-etcd", id: 100}, - }, - expectedError: true, - }, - { - name: "empty input must return default users and groups", - users: []*entry{}, - groups: []*entry{}, - expectedUsers: usersToCreateSpec, - expectedGroups: groupsToCreateSpec, - }, - { - name: "existing valid users mapped to groups", - users: []*entry{ - {name: "kubeadm-etcd", id: 100, gid: 102, shell: noshell}, - {name: "kubeadm-kas", id: 101, gid: 103, shell: noshell}, - }, - groups: []*entry{ - {name: "kubeadm-etcd", id: 102, userNames: []string{"kubeadm-etcd"}}, - {name: "kubeadm-kas", id: 103, userNames: []string{"kubeadm-kas"}}, - {name: "kubeadm-sa-key-readers", id: 104, userNames: []string{"kubeadm-kas", "kubeadm-kcm"}}, - }, - expectedUsers: []*entry{ - {name: "kubeadm-kcm"}, - {name: "kubeadm-ks"}, - }, - expectedGroups: []*entry{ - {name: "kubeadm-kcm", userNames: []string{"kubeadm-kcm"}}, - {name: "kubeadm-ks", userNames: []string{"kubeadm-ks"}}, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - users, groups, err := validateEntries(tc.users, tc.groups, defaultLimits) - if err != nil != tc.expectedError { - t.Fatalf("expected error: %v, got: %v, error: %v", tc.expectedError, err != nil, err) - } - if err != nil { - return - } - if len(tc.expectedUsers) != len(users) { - t.Fatalf("expected users %d, got %d", len(tc.expectedUsers), len(users)) - } - for i := range users { - if !reflect.DeepEqual(tc.expectedUsers[i], users[i]) { - t.Fatalf("expected user at position %d: %+v, got: %+v", i, tc.expectedUsers[i], users[i]) - } - } - if len(tc.expectedGroups) != len(groups) { - t.Fatalf("expected groups %d, got %d", len(tc.expectedGroups), len(groups)) - } - for i := range groups { - if !reflect.DeepEqual(tc.expectedGroups[i], groups[i]) { - t.Fatalf("expected group at position %d: %+v, got: %+v", i, tc.expectedGroups[i], groups[i]) - } - } - }) - } -} - -func TestAllocateIDs(t *testing.T) { - testCases := []struct { - name string - entries []*entry - min int64 - max int64 - total int - expectedIDs []int64 - expectedError bool - }{ - { - name: "zero total ids returns empty slice", - expectedIDs: []int64{}, - }, - { - name: "not enough free ids in range", - entries: []*entry{ - {name: "foo", id: 101}, - {name: "bar", id: 103}, - {name: "baz", id: 105}, - }, - min: 100, - max: 105, - total: 4, - expectedError: true, - }, - { - name: "successfully allocate ids", - entries: []*entry{ - {name: "foo", id: 101}, - {name: "bar", id: 103}, - {name: "baz", id: 105}, - }, - min: 100, - max: 110, - total: 4, - expectedIDs: []int64{100, 102, 104, 106}, - expectedError: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - got, err := allocateIDs(tc.entries, tc.min, tc.max, tc.total) - if err != nil != tc.expectedError { - t.Fatalf("expected error: %v, got: %v, error: %v", tc.expectedError, err != nil, err) - } - if err != nil { - return - } - if len(tc.expectedIDs) != len(got) { - t.Fatalf("expected id %d, got %d", len(tc.expectedIDs), len(got)) - } - for i := range got { - if !reflect.DeepEqual(tc.expectedIDs[i], got[i]) { - t.Fatalf("expected id at position %d: %+v, got: %+v", i, tc.expectedIDs[i], got[i]) - } - } - }) - } -} - -func TestAddEntries(t *testing.T) { - testCases := []struct { - name string - file string - entries []*entry - createEntry func(*entry) string - expectedOutput string - }{ - { - name: "user entries are added", - file: "foo:x:101:101:::/bin/false\n", - entries: []*entry{ - {name: "bar", id: 102, gid: 102}, - {name: "baz", id: 103, gid: 103}, - }, - expectedOutput: "foo:x:101:101:::/bin/false\nbar:x:102:102:::/bin/false\nbaz:x:103:103:::/bin/false\n", - createEntry: createUser, - }, - { - name: "user entries are added (new line is appended)", - file: "foo:x:101:101:::/bin/false", - entries: []*entry{ - {name: "bar", id: 102, gid: 102}, - }, - expectedOutput: "foo:x:101:101:::/bin/false\nbar:x:102:102:::/bin/false\n", - createEntry: createUser, - }, - { - name: "group entries are added", - file: "foo:x:101:foo\n", - entries: []*entry{ - {name: "bar", id: 102, userNames: []string{"bar"}}, - {name: "baz", id: 103, userNames: []string{"baz"}}, - }, - expectedOutput: "foo:x:101:foo\nbar:x:102:bar\nbaz:x:103:baz\n", - createEntry: createGroup, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - got := addEntries(tc.file, tc.entries, tc.createEntry) - if tc.expectedOutput != got { - t.Fatalf("expected output:\n%s\ngot:\n%s\n", tc.expectedOutput, got) - } - }) - } -} - -func TestRemoveEntries(t *testing.T) { - testCases := []struct { - name string - file string - entries []*entry - expectedRemoved int - expectedOutput string - }{ - { - name: "entries that are missing do not cause an error", - file: "foo:x:102:102:::/bin/false\nbar:x:103:103:::/bin/false\n", - entries: []*entry{}, - expectedRemoved: 0, - expectedOutput: "foo:x:102:102:::/bin/false\nbar:x:103:103:::/bin/false\n", - }, - { - name: "user entry is removed", - file: "foo:x:102:102:::/bin/false\nbar:x:103:103:::/bin/false\n", - entries: []*entry{ - {name: "bar"}, - }, - expectedRemoved: 1, - expectedOutput: "foo:x:102:102:::/bin/false\n", - }, - { - name: "group entry is removed", - file: "foo:x:102:foo\nbar:x:102:bar\n", - entries: []*entry{ - {name: "bar"}, - }, - expectedRemoved: 1, - expectedOutput: "foo:x:102:foo\n", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - got, removed := removeEntries(tc.file, tc.entries) - if tc.expectedRemoved != removed { - t.Fatalf("expected entries to be removed: %v, got: %v", tc.expectedRemoved, removed) - } - if tc.expectedOutput != got { - t.Fatalf("expected output:\n%s\ngot:\n%s\n", tc.expectedOutput, got) - } - }) - } -} - -func TestAssignUserAndGroupIDs(t *testing.T) { - testCases := []struct { - name string - users []*entry - groups []*entry - usersToCreate []*entry - groupsToCreate []*entry - uids []int64 - gids []int64 - expectedUsers []*entry - expectedGroups []*entry - expectedError bool - }{ - { - name: "not enough UIDs", - usersToCreate: []*entry{ - {name: "foo"}, - {name: "bar"}, - }, - uids: []int64{100}, - expectedError: true, - }, - { - name: "not enough GIDs", - groupsToCreate: []*entry{ - {name: "foo"}, - {name: "bar"}, - }, - gids: []int64{100}, - expectedError: true, - }, - { - name: "valid UIDs and GIDs are assigned to input", - groups: []*entry{ - {name: "foo", id: 110}, - {name: "bar", id: 111}, - }, - usersToCreate: []*entry{ - {name: "foo"}, - {name: "bar"}, - {name: "baz"}, - }, - groupsToCreate: []*entry{ - {name: "baz"}, - }, - uids: []int64{100, 101, 102}, - gids: []int64{112}, - expectedUsers: []*entry{ - {name: "foo", id: 100, gid: 110}, - {name: "bar", id: 101, gid: 111}, - {name: "baz", id: 102, gid: 112}, - }, - expectedGroups: []*entry{ - {name: "baz", id: 112}, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - err := assignUserAndGroupIDs(tc.groups, tc.usersToCreate, tc.groupsToCreate, tc.uids, tc.gids) - if err != nil != tc.expectedError { - t.Fatalf("expected error: %v, got: %v, error: %v", tc.expectedError, err != nil, err) - } - if err != nil { - return - } - if len(tc.expectedUsers) != len(tc.usersToCreate) { - t.Fatalf("expected users %d, got %d", len(tc.expectedUsers), len(tc.usersToCreate)) - } - for i := range tc.usersToCreate { - if !reflect.DeepEqual(tc.expectedUsers[i], tc.usersToCreate[i]) { - t.Fatalf("expected user at position %d: %+v, got: %+v", i, tc.expectedUsers[i], tc.usersToCreate[i]) - } - } - if len(tc.expectedGroups) != len(tc.groupsToCreate) { - t.Fatalf("expected groups %d, got %d", len(tc.expectedGroups), len(tc.groupsToCreate)) - } - for i := range tc.groupsToCreate { - if !reflect.DeepEqual(tc.expectedGroups[i], tc.groupsToCreate[i]) { - t.Fatalf("expected group at position %d: %+v, got: %+v", i, tc.expectedGroups[i], tc.groupsToCreate[i]) - } - } - }) - } -} - -func TestID(t *testing.T) { - e := &entry{name: "foo", id: 101} - m := &EntryMap{entries: map[string]*entry{ - "foo": e, - }} - id := m.ID("foo") - if *id != 101 { - t.Fatalf("expected: id=%d; got: id=%d", 101, *id) - } - id = m.ID("bar") - if id != nil { - t.Fatalf("expected nil for unknown entry") - } -} - -func TestAddUsersAndGroupsImpl(t *testing.T) { - const ( - loginDef = "SYS_UID_MIN 101\nSYS_UID_MAX 998\nSYS_GID_MIN 102\nSYS_GID_MAX 999\n" - passwd = "root:x:0:0:::/bin/bash\nkubeadm-etcd:x:101:102:::/bin/false\n" - group = "root:x:0:root\nkubeadm-etcd:x:102:kubeadm-etcd\n" - expectedUsers = "kubeadm-etcd{101,102};kubeadm-kas{102,103};kubeadm-kcm{103,104};kubeadm-ks{104,105};" - expectedGroups = "kubeadm-etcd{102,0};kubeadm-kas{103,0};kubeadm-kcm{104,0};kubeadm-ks{105,0};kubeadm-sa-key-readers{106,0};" - ) - fileLoginDef, close := writeTempFile(t, loginDef) - defer close() - filePasswd, close := writeTempFile(t, passwd) - defer close() - fileGroup, close := writeTempFile(t, group) - defer close() - got, err := addUsersAndGroupsImpl(fileLoginDef, filePasswd, fileGroup) - if err != nil { - t.Fatalf("AddUsersAndGroups failed: %v", err) - } - if expectedUsers != got.Users.String() { - t.Fatalf("expected users: %q, got: %q", expectedUsers, got.Users.String()) - } - if expectedGroups != got.Groups.String() { - t.Fatalf("expected groups: %q, got: %q", expectedGroups, got.Groups.String()) - } -} - -func TestRemoveUsersAndGroups(t *testing.T) { - const ( - passwd = "root:x:0:0:::/bin/bash\nkubeadm-etcd:x:101:102:::/bin/false\n" - group = "root:x:0:root\nkubeadm-etcd:x:102:kubeadm-etcd\n" - expectedPasswd = "root:x:0:0:::/bin/bash\n" - expectedGroup = "root:x:0:root\n" - ) - filePasswd, close := writeTempFile(t, passwd) - defer close() - fileGroup, close := writeTempFile(t, group) - defer close() - if err := removeUsersAndGroupsImpl(filePasswd, fileGroup); err != nil { - t.Fatalf("RemoveUsersAndGroups failed: %v", err) - } - contentsPasswd := readTempFile(t, filePasswd) - if expectedPasswd != contentsPasswd { - t.Fatalf("expected passwd:\n%s\ngot:\n%s\n", expectedPasswd, contentsPasswd) - } - contentsGroup := readTempFile(t, fileGroup) - if expectedGroup != contentsGroup { - t.Fatalf("expected passwd:\n%s\ngot:\n%s\n", expectedGroup, contentsGroup) - } -} - -func writeTempFile(t *testing.T, contents string) (string, func()) { - file, err := os.CreateTemp("", "") - if err != nil { - t.Fatalf("could not create file: %v", err) - } - if err := os.WriteFile(file.Name(), []byte(contents), os.ModePerm); err != nil { - t.Fatalf("could not write file: %v", err) - } - close := func() { - os.Remove(file.Name()) - } - return file.Name(), close -} - -func readTempFile(t *testing.T, path string) string { - b, err := os.ReadFile(path) - if err != nil { - t.Fatalf("could not read file: %v", err) - } - return string(b) -} diff --git a/cmd/kubeadm/app/util/users/users_other.go b/cmd/kubeadm/app/util/users/users_other.go deleted file mode 100644 index ba79ebfbb06..00000000000 --- a/cmd/kubeadm/app/util/users/users_other.go +++ /dev/null @@ -1,56 +0,0 @@ -//go:build !linux -// +build !linux - -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package users - -// EntryMap is empty on non-Linux. -type EntryMap struct{} - -// UsersAndGroups is empty on non-Linux. -type UsersAndGroups struct{} - -// ID is a NO-OP on non-Linux. -func (*EntryMap) ID(string) *int64 { - return nil -} - -// String is NO-OP on non-Linux. -func (*EntryMap) String() string { - return "" -} - -// AddUsersAndGroups is a NO-OP on non-Linux. -func AddUsersAndGroups() (*UsersAndGroups, error) { - return nil, nil -} - -// RemoveUsersAndGroups is a NO-OP on non-Linux. -func RemoveUsersAndGroups() error { - return nil -} - -// UpdatePathOwnerAndPermissions is a NO-OP on non-Linux. -func UpdatePathOwnerAndPermissions(path string, uid, gid int64, perms uint32) error { - return nil -} - -// UpdatePathOwner is a NO-OP on non-Linux. -func UpdatePathOwner(dirPath string, uid, gid int64) error { - return nil -}