diff --git a/pkg/api/pod/util.go b/pkg/api/pod/util.go index a49416c8ed9..a5b0776fd8d 100644 --- a/pkg/api/pod/util.go +++ b/pkg/api/pod/util.go @@ -17,6 +17,7 @@ limitations under the License. package pod import ( + "fmt" "iter" "strings" @@ -1488,3 +1489,75 @@ func hasRestartableInitContainerResizePolicy(podSpec *api.PodSpec) bool { } return false } + +// HasAPIObjectReference returns true if a reference to an API object is found in the pod spec, +// along with the plural resource of the referenced API type, or an error if an unknown field is encountered. +func HasAPIObjectReference(pod *api.Pod) (bool, string, error) { + if pod.Spec.ServiceAccountName != "" { + return true, "serviceaccounts", nil + } + + hasSecrets := false + VisitPodSecretNames(pod, func(name string) (shouldContinue bool) { hasSecrets = true; return false }, AllContainers) + if hasSecrets { + return true, "secrets", nil + } + + hasConfigMaps := false + VisitPodConfigmapNames(pod, func(name string) (shouldContinue bool) { hasConfigMaps = true; return false }, AllContainers) + if hasConfigMaps { + return true, "configmaps", nil + } + + if len(pod.Spec.ResourceClaims) > 0 { + return true, "resourceclaims", nil + } + + for _, v := range pod.Spec.Volumes { + switch { + case v.AWSElasticBlockStore != nil, v.AzureDisk != nil, v.CephFS != nil, v.Cinder != nil, + v.DownwardAPI != nil, v.EmptyDir != nil, v.FC != nil, v.FlexVolume != nil, v.Flocker != nil, v.GCEPersistentDisk != nil, + v.GitRepo != nil, v.HostPath != nil, v.Image != nil, v.ISCSI != nil, v.NFS != nil, v.PhotonPersistentDisk != nil, + v.PortworxVolume != nil, v.Quobyte != nil, v.RBD != nil, v.ScaleIO != nil, v.StorageOS != nil, v.VsphereVolume != nil: + continue + case v.ConfigMap != nil: + return true, "configmaps (via configmap volumes)", nil + case v.Secret != nil: + return true, "secrets (via secret volumes)", nil + case v.CSI != nil: + return true, "csidrivers (via CSI volumes)", nil + case v.Glusterfs != nil: + return true, "endpoints (via glusterFS volumes)", nil + case v.PersistentVolumeClaim != nil: + return true, "persistentvolumeclaims", nil + case v.Ephemeral != nil: + return true, "persistentvolumeclaims (via ephemeral volumes)", nil + case v.AzureFile != nil: + return true, "secrets (via azureFile volumes)", nil + case v.Projected != nil: + for _, s := range v.Projected.Sources { + // Reject projected volume sources that require the Kubernetes API + switch { + case s.ConfigMap != nil: + return true, "configmaps (via projected volumes)", nil + case s.Secret != nil: + return true, "secrets (via projected volumes)", nil + case s.ServiceAccountToken != nil: + return true, "serviceaccounts (via projected volumes)", nil + case s.ClusterTrustBundle != nil: + return true, "clustertrustbundles", nil + case s.DownwardAPI != nil: + // Allow projected volume sources that don't require the Kubernetes API + continue + default: + // Reject unknown volume types + return true, "", fmt.Errorf("unknown source for projected volume %q", v.Name) + } + } + default: + return true, "", fmt.Errorf("unknown volume type for volume %q", v.Name) + } + } + + return false, "", nil +} diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index fea80412c9e..a1a46da62cc 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -918,6 +918,11 @@ const ( // Enables the image volume source. ImageVolume featuregate.Feature = "ImageVolume" + // owner: @sreeram-venkitesh + // + // Denies pod admission if static pods reference other API objects. + PreventStaticPodAPIReferences featuregate.Feature = "PreventStaticPodAPIReferences" + // owner: @zhifei92 // // Enables the systemd watchdog for the kubelet. When enabled, the kubelet will @@ -1755,6 +1760,10 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // GA in 1.32, remove in 1.35 }, + PreventStaticPodAPIReferences: { + {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.Beta}, + }, + StorageCapacityScoring: { {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Alpha}, }, diff --git a/pkg/kubelet/config/common.go b/pkg/kubelet/config/common.go index a73d6372a47..48f6ba77ada 100644 --- a/pkg/kubelet/config/common.go +++ b/pkg/kubelet/config/common.go @@ -28,8 +28,11 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utilyaml "k8s.io/apimachinery/pkg/util/yaml" + utilfeature "k8s.io/apiserver/pkg/util/feature" + podutil "k8s.io/kubernetes/pkg/api/pod" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/core/helper" + "k8s.io/kubernetes/pkg/features" // TODO: remove this import if // api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String() is changed @@ -144,19 +147,31 @@ func tryDecodeSinglePod(data []byte, defaultFn defaultFunc) (parsed bool, pod *v return true, nil, err } - for _, v := range v1Pod.Spec.Volumes { - if v.Projected == nil { - continue + if utilfeature.DefaultFeatureGate.Enabled(features.PreventStaticPodAPIReferences) { + // Check if pod has references to API objects + _, resource, err := podutil.HasAPIObjectReference(newPod) + if err != nil { + return true, nil, err } + if resource != "" { + return true, nil, fmt.Errorf("static pods may not reference %s", resource) + } + } else { + // TODO: Remove this else block once the PreventStaticPodAPIReferences gate is GA + for _, v := range v1Pod.Spec.Volumes { + if v.Projected == nil { + continue + } - for _, s := range v.Projected.Sources { - if s.ClusterTrustBundle != nil { - return true, nil, ErrStaticPodTriedToUseClusterTrustBundle + for _, s := range v.Projected.Sources { + if s.ClusterTrustBundle != nil { + return true, nil, ErrStaticPodTriedToUseClusterTrustBundle + } } } - } - if len(v1Pod.Spec.ResourceClaims) > 0 { - return true, nil, ErrStaticPodTriedToUseResourceClaims + if len(v1Pod.Spec.ResourceClaims) > 0 { + return true, nil, ErrStaticPodTriedToUseResourceClaims + } } return true, v1Pod, nil @@ -188,6 +203,16 @@ func tryDecodePodList(data []byte, defaultFn defaultFunc) (parsed bool, pods v1. err = fmt.Errorf("invalid pod: %v", errs) return true, pods, err } + if utilfeature.DefaultFeatureGate.Enabled(features.PreventStaticPodAPIReferences) { + // Check if pod has references to API objects + _, resource, err := podutil.HasAPIObjectReference(newPod) + if err != nil { + return true, pods, err + } + if resource != "" { + return true, pods, fmt.Errorf("static pods may not reference %s", resource) + } + } } v1Pods := &v1.PodList{} if err := k8s_api_v1.Convert_core_PodList_To_v1_PodList(newPods, v1Pods, nil); err != nil { diff --git a/plugin/pkg/admission/noderestriction/admission.go b/plugin/pkg/admission/noderestriction/admission.go index b85989dbd28..89ed7b4057d 100644 --- a/plugin/pkg/admission/noderestriction/admission.go +++ b/plugin/pkg/admission/noderestriction/admission.go @@ -305,38 +305,12 @@ func (p *Plugin) admitPodCreate(nodeName string, a admission.Attributes) error { } // don't allow a node to create a pod that references any other API objects - if pod.Spec.ServiceAccountName != "" { - return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference a service account", nodeName)) + isPodReferencingAPIObjects, resource, err := podutil.HasAPIObjectReference(pod) + if err != nil { + return admission.NewForbidden(a, fmt.Errorf("error checking mirror pod for API references: %w", err)) } - hasSecrets := false - podutil.VisitPodSecretNames(pod, func(name string) (shouldContinue bool) { hasSecrets = true; return false }, podutil.AllContainers) - if hasSecrets { - return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference secrets", nodeName)) - } - hasConfigMaps := false - podutil.VisitPodConfigmapNames(pod, func(name string) (shouldContinue bool) { hasConfigMaps = true; return false }, podutil.AllContainers) - if hasConfigMaps { - return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference configmaps", nodeName)) - } - - for _, vol := range pod.Spec.Volumes { - if vol.VolumeSource.Projected != nil { - for _, src := range vol.VolumeSource.Projected.Sources { - if src.ClusterTrustBundle != nil { - return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference clustertrustbundles", nodeName)) - } - } - } - } - - for _, v := range pod.Spec.Volumes { - if v.PersistentVolumeClaim != nil { - return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference persistentvolumeclaims", nodeName)) - } - } - - if len(pod.Spec.ResourceClaims) > 0 { - return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference resourceclaims", nodeName)) + if isPodReferencingAPIObjects { + return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference %s", nodeName, resource)) } return nil diff --git a/test/compatibility_lifecycle/reference/versioned_feature_list.yaml b/test/compatibility_lifecycle/reference/versioned_feature_list.yaml index 29a9ccd105e..8f2b77c5576 100644 --- a/test/compatibility_lifecycle/reference/versioned_feature_list.yaml +++ b/test/compatibility_lifecycle/reference/versioned_feature_list.yaml @@ -1107,6 +1107,12 @@ lockToDefault: false preRelease: Beta version: "1.34" +- name: PreventStaticPodAPIReferences + versionedSpecs: + - default: true + lockToDefault: false + preRelease: Beta + version: "1.34" - name: ProcMountType versionedSpecs: - default: false