k8s.io/kubernetes@v1.29.3/test/e2e/framework/pod/utils.go (about) 1 /* 2 Copyright 2021 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 pod 18 19 import ( 20 "flag" 21 "fmt" 22 23 "github.com/onsi/ginkgo/v2" 24 "github.com/onsi/gomega" 25 26 v1 "k8s.io/api/core/v1" 27 imageutils "k8s.io/kubernetes/test/utils/image" 28 psaapi "k8s.io/pod-security-admission/api" 29 psapolicy "k8s.io/pod-security-admission/policy" 30 "k8s.io/utils/pointer" 31 ) 32 33 // NodeOSDistroIs returns true if the distro is the same as `--node-os-distro` 34 // the package framework/pod can't import the framework package (see #81245) 35 // we need to check if the --node-os-distro=windows is set and the framework package 36 // is the one that's parsing the flags, as a workaround this method is looking for the same flag again 37 // TODO: replace with `framework.NodeOSDistroIs` when #81245 is complete 38 func NodeOSDistroIs(distro string) bool { 39 var nodeOsDistro *flag.Flag = flag.Lookup("node-os-distro") 40 if nodeOsDistro != nil && nodeOsDistro.Value.String() == distro { 41 return true 42 } 43 return false 44 } 45 46 // GenerateScriptCmd generates the corresponding command lines to execute a command. 47 func GenerateScriptCmd(command string) []string { 48 var commands []string 49 commands = []string{"/bin/sh", "-c", command} 50 return commands 51 } 52 53 // GetDefaultTestImage returns the default test image based on OS. 54 // If the node OS is windows, currently we return Agnhost image for Windows node 55 // due to the issue of #https://github.com/kubernetes-sigs/windows-testing/pull/35. 56 // If the node OS is linux, return busybox image 57 func GetDefaultTestImage() string { 58 return imageutils.GetE2EImage(GetDefaultTestImageID()) 59 } 60 61 // GetDefaultTestImageID returns the default test image id based on OS. 62 // If the node OS is windows, currently we return Agnhost image for Windows node 63 // due to the issue of #https://github.com/kubernetes-sigs/windows-testing/pull/35. 64 // If the node OS is linux, return busybox image 65 func GetDefaultTestImageID() imageutils.ImageID { 66 return GetTestImageID(imageutils.BusyBox) 67 } 68 69 // GetTestImage returns the image name with the given input 70 // If the Node OS is windows, currently we return Agnhost image for Windows node 71 // due to the issue of #https://github.com/kubernetes-sigs/windows-testing/pull/35. 72 func GetTestImage(id imageutils.ImageID) string { 73 if NodeOSDistroIs("windows") { 74 return imageutils.GetE2EImage(imageutils.Agnhost) 75 } 76 return imageutils.GetE2EImage(id) 77 } 78 79 // GetTestImageID returns the image id with the given input 80 // If the Node OS is windows, currently we return Agnhost image for Windows node 81 // due to the issue of #https://github.com/kubernetes-sigs/windows-testing/pull/35. 82 func GetTestImageID(id imageutils.ImageID) imageutils.ImageID { 83 if NodeOSDistroIs("windows") { 84 return imageutils.Agnhost 85 } 86 return id 87 } 88 89 // GetDefaultNonRootUser returns default non root user 90 // If the Node OS is windows, we return nill due to issue with invalid permissions set on projected volumes 91 // https://github.com/kubernetes/kubernetes/issues/102849 92 func GetDefaultNonRootUser() *int64 { 93 if NodeOSDistroIs("windows") { 94 return nil 95 } 96 return pointer.Int64(DefaultNonRootUser) 97 } 98 99 // GeneratePodSecurityContext generates the corresponding pod security context with the given inputs 100 // If the Node OS is windows, currently we will ignore the inputs and return nil. 101 // TODO: Will modify it after windows has its own security context 102 func GeneratePodSecurityContext(fsGroup *int64, seLinuxOptions *v1.SELinuxOptions) *v1.PodSecurityContext { 103 if NodeOSDistroIs("windows") { 104 return nil 105 } 106 return &v1.PodSecurityContext{ 107 FSGroup: fsGroup, 108 SELinuxOptions: seLinuxOptions, 109 } 110 } 111 112 // GenerateContainerSecurityContext generates the corresponding container security context with the given inputs 113 // If the Node OS is windows, currently we will ignore the inputs and return nil. 114 // TODO: Will modify it after windows has its own security context 115 func GenerateContainerSecurityContext(level psaapi.Level) *v1.SecurityContext { 116 if NodeOSDistroIs("windows") { 117 return nil 118 } 119 120 switch level { 121 case psaapi.LevelBaseline: 122 return &v1.SecurityContext{ 123 Privileged: pointer.Bool(false), 124 } 125 case psaapi.LevelPrivileged: 126 return &v1.SecurityContext{ 127 Privileged: pointer.Bool(true), 128 } 129 case psaapi.LevelRestricted: 130 return GetRestrictedContainerSecurityContext() 131 default: 132 ginkgo.Fail(fmt.Sprintf("unknown k8s.io/pod-security-admission/policy.Level %q", level)) 133 panic("not reached") 134 } 135 } 136 137 // GetLinuxLabel returns the default SELinuxLabel based on OS. 138 // If the node OS is windows, it will return nil 139 func GetLinuxLabel() *v1.SELinuxOptions { 140 if NodeOSDistroIs("windows") { 141 return nil 142 } 143 return &v1.SELinuxOptions{ 144 Level: "s0:c0,c1"} 145 } 146 147 // DefaultNonRootUser is the default user ID used for running restricted (non-root) containers. 148 const DefaultNonRootUser = 1000 149 150 // DefaultNonRootUserName is the default username in Windows used for running restricted (non-root) containers 151 const DefaultNonRootUserName = "ContainerUser" 152 153 // GetRestrictedPodSecurityContext returns a restricted pod security context. 154 // This includes setting RunAsUser for convenience, to pass the RunAsNonRoot check. 155 // Tests that require a specific user ID should override this. 156 func GetRestrictedPodSecurityContext() *v1.PodSecurityContext { 157 psc := &v1.PodSecurityContext{ 158 RunAsNonRoot: pointer.Bool(true), 159 RunAsUser: GetDefaultNonRootUser(), 160 SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}, 161 } 162 163 if NodeOSDistroIs("windows") { 164 psc.WindowsOptions = &v1.WindowsSecurityContextOptions{} 165 psc.WindowsOptions.RunAsUserName = pointer.String(DefaultNonRootUserName) 166 } 167 168 return psc 169 } 170 171 // GetRestrictedContainerSecurityContext returns a minimal restricted container security context. 172 func GetRestrictedContainerSecurityContext() *v1.SecurityContext { 173 return &v1.SecurityContext{ 174 AllowPrivilegeEscalation: pointer.Bool(false), 175 Capabilities: &v1.Capabilities{Drop: []v1.Capability{"ALL"}}, 176 } 177 } 178 179 var psaEvaluator, _ = psapolicy.NewEvaluator(psapolicy.DefaultChecks()) 180 181 // MustMixinRestrictedPodSecurity makes the given pod compliant with the restricted pod security level. 182 // If doing so would overwrite existing non-conformant configuration, a test failure is triggered. 183 func MustMixinRestrictedPodSecurity(pod *v1.Pod) *v1.Pod { 184 err := MixinRestrictedPodSecurity(pod) 185 gomega.ExpectWithOffset(1, err).NotTo(gomega.HaveOccurred()) 186 return pod 187 } 188 189 // MixinRestrictedPodSecurity makes the given pod compliant with the restricted pod security level. 190 // If doing so would overwrite existing non-conformant configuration, an error is returned. 191 // Note that this sets a default RunAsUser. See GetRestrictedPodSecurityContext. 192 // TODO(#105919): Handle PodOS for windows pods. 193 func MixinRestrictedPodSecurity(pod *v1.Pod) error { 194 if pod.Spec.SecurityContext == nil { 195 pod.Spec.SecurityContext = GetRestrictedPodSecurityContext() 196 } else { 197 if pod.Spec.SecurityContext.RunAsNonRoot == nil { 198 pod.Spec.SecurityContext.RunAsNonRoot = pointer.Bool(true) 199 } 200 if pod.Spec.SecurityContext.RunAsUser == nil { 201 pod.Spec.SecurityContext.RunAsUser = GetDefaultNonRootUser() 202 } 203 if pod.Spec.SecurityContext.SeccompProfile == nil { 204 pod.Spec.SecurityContext.SeccompProfile = &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault} 205 } 206 if NodeOSDistroIs("windows") && pod.Spec.SecurityContext.WindowsOptions == nil { 207 pod.Spec.SecurityContext.WindowsOptions = &v1.WindowsSecurityContextOptions{} 208 pod.Spec.SecurityContext.WindowsOptions.RunAsUserName = pointer.String(DefaultNonRootUserName) 209 } 210 } 211 for i := range pod.Spec.Containers { 212 mixinRestrictedContainerSecurityContext(&pod.Spec.Containers[i]) 213 } 214 for i := range pod.Spec.InitContainers { 215 mixinRestrictedContainerSecurityContext(&pod.Spec.InitContainers[i]) 216 } 217 218 // Validate the resulting pod against the restricted profile. 219 restricted := psaapi.LevelVersion{ 220 Level: psaapi.LevelRestricted, 221 Version: psaapi.LatestVersion(), 222 } 223 if agg := psapolicy.AggregateCheckResults(psaEvaluator.EvaluatePod(restricted, &pod.ObjectMeta, &pod.Spec)); !agg.Allowed { 224 return fmt.Errorf("failed to make pod %s restricted: %s", pod.Name, agg.ForbiddenDetail()) 225 } 226 227 return nil 228 } 229 230 // mixinRestrictedContainerSecurityContext adds the required container security context options to 231 // be compliant with the restricted pod security level. Non-conformance checking is handled by the 232 // caller. 233 func mixinRestrictedContainerSecurityContext(container *v1.Container) { 234 if container.SecurityContext == nil { 235 container.SecurityContext = GetRestrictedContainerSecurityContext() 236 } else { 237 if container.SecurityContext.AllowPrivilegeEscalation == nil { 238 container.SecurityContext.AllowPrivilegeEscalation = pointer.Bool(false) 239 } 240 if container.SecurityContext.Capabilities == nil { 241 container.SecurityContext.Capabilities = &v1.Capabilities{} 242 } 243 if len(container.SecurityContext.Capabilities.Drop) == 0 { 244 container.SecurityContext.Capabilities.Drop = []v1.Capability{"ALL"} 245 } 246 } 247 } 248 249 // FindPodConditionByType loops through all pod conditions in pod status and returns the specified condition. 250 func FindPodConditionByType(podStatus *v1.PodStatus, conditionType v1.PodConditionType) *v1.PodCondition { 251 for _, cond := range podStatus.Conditions { 252 if cond.Type == conditionType { 253 return &cond 254 } 255 } 256 return nil 257 } 258 259 // FindContainerStatusInPod finds a container status by its name in the provided pod 260 func FindContainerStatusInPod(pod *v1.Pod, containerName string) *v1.ContainerStatus { 261 for _, containerStatus := range pod.Status.InitContainerStatuses { 262 if containerStatus.Name == containerName { 263 return &containerStatus 264 } 265 } 266 for _, containerStatus := range pod.Status.ContainerStatuses { 267 if containerStatus.Name == containerName { 268 return &containerStatus 269 } 270 } 271 for _, containerStatus := range pod.Status.EphemeralContainerStatuses { 272 if containerStatus.Name == containerName { 273 return &containerStatus 274 } 275 } 276 return nil 277 }