Added podutil.HasAPIObjectReference to deny admission for static pods referencing API objects

This commit is contained in:
sreeram-venkitesh
2025-06-25 23:59:26 +05:30
parent 7e94ec9eac
commit 5390f75360
5 changed files with 127 additions and 40 deletions

View File

@@ -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
}

View File

@@ -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},
},

View File

@@ -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 {

View File

@@ -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

View File

@@ -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