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