mirror of
https://github.com/outbackdingo/kubernetes.git
synced 2026-01-27 10:19:35 +00:00
Added podutil.HasAPIObjectReference to deny admission for static pods referencing API objects
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user