1// +build linux 2 3/* 4Copyright 2021 The Kubernetes Authors. 5 6Licensed under the Apache License, Version 2.0 (the "License"); 7you may not use this file except in compliance with the License. 8You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12Unless required by applicable law or agreed to in writing, software 13distributed under the License is distributed on an "AS IS" BASIS, 14WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15See the License for the specific language governing permissions and 16limitations under the License. 17*/ 18 19package staticpod 20 21import ( 22 "fmt" 23 "path/filepath" 24 25 kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" 26 kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" 27 certphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" 28 "k8s.io/kubernetes/cmd/kubeadm/app/util/users" 29 30 v1 "k8s.io/api/core/v1" 31 "k8s.io/utils/pointer" 32 33 "github.com/pkg/errors" 34) 35 36type pathOwnerAndPermissionsUpdaterFunc func(path string, uid, gid int64, perms uint32) error 37type pathOwnerUpdaterFunc func(path string, uid, gid int64) error 38 39// RunComponentAsNonRoot updates the pod manifest and the hostVolume permissions to run as non root. 40func RunComponentAsNonRoot(componentName string, pod *v1.Pod, usersAndGroups *users.UsersAndGroups, cfg *kubeadmapi.ClusterConfiguration) error { 41 switch componentName { 42 case kubeadmconstants.KubeAPIServer: 43 return runKubeAPIServerAsNonRoot( 44 pod, 45 usersAndGroups.Users.ID(kubeadmconstants.KubeAPIServerUserName), 46 usersAndGroups.Groups.ID(kubeadmconstants.KubeAPIServerUserName), 47 usersAndGroups.Groups.ID(kubeadmconstants.ServiceAccountKeyReadersGroupName), 48 users.UpdatePathOwnerAndPermissions, 49 cfg, 50 ) 51 case kubeadmconstants.KubeControllerManager: 52 return runKubeControllerManagerAsNonRoot( 53 pod, 54 usersAndGroups.Users.ID(kubeadmconstants.KubeControllerManagerUserName), 55 usersAndGroups.Groups.ID(kubeadmconstants.KubeControllerManagerUserName), 56 usersAndGroups.Groups.ID(kubeadmconstants.ServiceAccountKeyReadersGroupName), 57 users.UpdatePathOwnerAndPermissions, 58 cfg, 59 ) 60 case kubeadmconstants.KubeScheduler: 61 return runKubeSchedulerAsNonRoot( 62 pod, 63 usersAndGroups.Users.ID(kubeadmconstants.KubeSchedulerUserName), 64 usersAndGroups.Groups.ID(kubeadmconstants.KubeSchedulerUserName), 65 users.UpdatePathOwnerAndPermissions, 66 ) 67 case kubeadmconstants.Etcd: 68 return runEtcdAsNonRoot( 69 pod, 70 usersAndGroups.Users.ID(kubeadmconstants.EtcdUserName), 71 usersAndGroups.Groups.ID(kubeadmconstants.EtcdUserName), 72 users.UpdatePathOwnerAndPermissions, 73 users.UpdatePathOwner, 74 cfg, 75 ) 76 } 77 return errors.New(fmt.Sprintf("component name %q is not valid", componentName)) 78} 79 80// runKubeAPIServerAsNonRoot updates the pod manifest and the hostVolume permissions to run kube-apiserver as non root. 81func runKubeAPIServerAsNonRoot(pod *v1.Pod, runAsUser, runAsGroup, supplementalGroup *int64, updatePathOwnerAndPermissions pathOwnerAndPermissionsUpdaterFunc, cfg *kubeadmapi.ClusterConfiguration) error { 82 saPublicKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName) 83 if err := updatePathOwnerAndPermissions(saPublicKeyFile, *runAsUser, *runAsGroup, 0600); err != nil { 84 return err 85 } 86 saPrivateKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName) 87 if err := updatePathOwnerAndPermissions(saPrivateKeyFile, 0, *supplementalGroup, 0640); err != nil { 88 return err 89 } 90 apiServerKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName) 91 if err := updatePathOwnerAndPermissions(apiServerKeyFile, *runAsUser, *runAsGroup, 0600); err != nil { 92 return err 93 } 94 apiServerKubeletClientKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName) 95 if err := updatePathOwnerAndPermissions(apiServerKubeletClientKeyFile, *runAsUser, *runAsGroup, 0600); err != nil { 96 return err 97 } 98 frontProxyClientKeyName := filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyClientKeyName) 99 if err := updatePathOwnerAndPermissions(frontProxyClientKeyName, *runAsUser, *runAsGroup, 0600); err != nil { 100 return err 101 } 102 if cfg.Etcd.External == nil { 103 apiServerEtcdClientKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerEtcdClientKeyName) 104 if err := updatePathOwnerAndPermissions(apiServerEtcdClientKeyFile, *runAsUser, *runAsGroup, 0600); err != nil { 105 return err 106 } 107 } 108 pod.Spec.Containers[0].SecurityContext = &v1.SecurityContext{ 109 Capabilities: &v1.Capabilities{ 110 // We drop all capabilities that are added by default. 111 Drop: []v1.Capability{"ALL"}, 112 // kube-apiserver binary has the file capability cap_net_bind_service applied to it. 113 // This means that we must add this capability when running as non-root even if the 114 // capability is not required. 115 Add: []v1.Capability{"NET_BIND_SERVICE"}, 116 }, 117 } 118 pod.Spec.SecurityContext.RunAsGroup = runAsGroup 119 pod.Spec.SecurityContext.RunAsUser = runAsUser 120 pod.Spec.SecurityContext.SupplementalGroups = []int64{*supplementalGroup} 121 return nil 122} 123 124// runKubeControllerManagerAsNonRoot updates the pod manifest and the hostVolume permissions to run kube-controller-manager as non root. 125func runKubeControllerManagerAsNonRoot(pod *v1.Pod, runAsUser, runAsGroup, supplementalGroup *int64, updatePathOwnerAndPermissions pathOwnerAndPermissionsUpdaterFunc, cfg *kubeadmapi.ClusterConfiguration) error { 126 kubeconfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ControllerManagerKubeConfigFileName) 127 if err := updatePathOwnerAndPermissions(kubeconfigFile, *runAsUser, *runAsGroup, 0600); err != nil { 128 return err 129 } 130 saPrivateKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName) 131 if err := updatePathOwnerAndPermissions(saPrivateKeyFile, 0, *supplementalGroup, 0640); err != nil { 132 return err 133 } 134 if res, _ := certphase.UsingExternalCA(cfg); !res { 135 caKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName) 136 err := updatePathOwnerAndPermissions(caKeyFile, *runAsUser, *runAsGroup, 0600) 137 if err != nil { 138 return err 139 } 140 } 141 pod.Spec.Containers[0].SecurityContext = &v1.SecurityContext{ 142 AllowPrivilegeEscalation: pointer.Bool(false), 143 Capabilities: &v1.Capabilities{ 144 // We drop all capabilities that are added by default. 145 Drop: []v1.Capability{"ALL"}, 146 }, 147 } 148 pod.Spec.SecurityContext.RunAsUser = runAsUser 149 pod.Spec.SecurityContext.RunAsGroup = runAsGroup 150 pod.Spec.SecurityContext.SupplementalGroups = []int64{*supplementalGroup} 151 return nil 152} 153 154// runKubeSchedulerAsNonRoot updates the pod manifest and the hostVolume permissions to run kube-scheduler as non root. 155func runKubeSchedulerAsNonRoot(pod *v1.Pod, runAsUser, runAsGroup *int64, updatePathOwnerAndPermissions pathOwnerAndPermissionsUpdaterFunc) error { 156 kubeconfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.SchedulerKubeConfigFileName) 157 if err := updatePathOwnerAndPermissions(kubeconfigFile, *runAsUser, *runAsGroup, 0600); err != nil { 158 return err 159 } 160 pod.Spec.Containers[0].SecurityContext = &v1.SecurityContext{ 161 AllowPrivilegeEscalation: pointer.Bool(false), 162 // We drop all capabilities that are added by default. 163 Capabilities: &v1.Capabilities{ 164 Drop: []v1.Capability{"ALL"}, 165 }, 166 } 167 pod.Spec.SecurityContext.RunAsUser = runAsUser 168 pod.Spec.SecurityContext.RunAsGroup = runAsGroup 169 return nil 170} 171 172// runEtcdAsNonRoot updates the pod manifest and the hostVolume permissions to run etcd as non root. 173func runEtcdAsNonRoot(pod *v1.Pod, runAsUser, runAsGroup *int64, updatePathOwnerAndPermissions pathOwnerAndPermissionsUpdaterFunc, updatePathOwner pathOwnerUpdaterFunc, cfg *kubeadmapi.ClusterConfiguration) error { 174 if err := updatePathOwner(cfg.Etcd.Local.DataDir, *runAsUser, *runAsGroup); err != nil { 175 return err 176 } 177 etcdServerKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdServerKeyName) 178 if err := updatePathOwnerAndPermissions(etcdServerKeyFile, *runAsUser, *runAsGroup, 0600); err != nil { 179 return err 180 } 181 etcdPeerKeyFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdPeerKeyName) 182 if err := updatePathOwnerAndPermissions(etcdPeerKeyFile, *runAsUser, *runAsGroup, 0600); err != nil { 183 return err 184 } 185 pod.Spec.Containers[0].SecurityContext = &v1.SecurityContext{ 186 AllowPrivilegeEscalation: pointer.Bool(false), 187 // We drop all capabilities that are added by default. 188 Capabilities: &v1.Capabilities{ 189 Drop: []v1.Capability{"ALL"}, 190 }, 191 } 192 pod.Spec.SecurityContext.RunAsUser = runAsUser 193 pod.Spec.SecurityContext.RunAsGroup = runAsGroup 194 return nil 195} 196