mirror of
https://github.com/optim-enterprises-bv/kubernetes.git
synced 2025-10-29 17:32:47 +00:00
2347 lines
106 KiB
Go
2347 lines
106 KiB
Go
/*
|
|
Copyright 2017 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package noderestriction
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
storagev1 "k8s.io/api/storage/v1"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
"k8s.io/apiserver/pkg/admission"
|
|
"k8s.io/apiserver/pkg/authentication/user"
|
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
|
"k8s.io/apiserver/pkg/util/feature"
|
|
corev1lister "k8s.io/client-go/listers/core/v1"
|
|
storagelisters "k8s.io/client-go/listers/storage/v1"
|
|
"k8s.io/client-go/tools/cache"
|
|
"k8s.io/component-base/featuregate"
|
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
|
kubeletapis "k8s.io/kubelet/pkg/apis"
|
|
authenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
|
|
certificatesapi "k8s.io/kubernetes/pkg/apis/certificates"
|
|
"k8s.io/kubernetes/pkg/apis/coordination"
|
|
api "k8s.io/kubernetes/pkg/apis/core"
|
|
"k8s.io/kubernetes/pkg/apis/policy"
|
|
resourceapi "k8s.io/kubernetes/pkg/apis/resource"
|
|
"k8s.io/kubernetes/pkg/apis/storage"
|
|
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
|
|
"k8s.io/kubernetes/pkg/features"
|
|
"k8s.io/utils/pointer"
|
|
)
|
|
|
|
func makeTestPod(namespace, name, node string, mirror bool) (*api.Pod, *corev1.Pod) {
|
|
corePod := &api.Pod{}
|
|
corePod.Namespace = namespace
|
|
corePod.UID = types.UID("pod-uid")
|
|
corePod.Name = name
|
|
corePod.Spec.NodeName = node
|
|
v1Pod := &corev1.Pod{}
|
|
v1Pod.Namespace = namespace
|
|
v1Pod.UID = types.UID("pod-uid")
|
|
v1Pod.Name = name
|
|
v1Pod.Spec.NodeName = node
|
|
if mirror {
|
|
corePod.Annotations = map[string]string{api.MirrorPodAnnotationKey: "true"}
|
|
v1Pod.Annotations = map[string]string{api.MirrorPodAnnotationKey: "true"}
|
|
|
|
// Insert a valid owner reference by default.
|
|
controller := true
|
|
owner := metav1.OwnerReference{
|
|
APIVersion: "v1",
|
|
Kind: "Node",
|
|
Name: node,
|
|
UID: types.UID(node + "-uid"),
|
|
Controller: &controller,
|
|
}
|
|
corePod.OwnerReferences = []metav1.OwnerReference{owner}
|
|
v1Pod.OwnerReferences = []metav1.OwnerReference{owner}
|
|
}
|
|
return corePod, v1Pod
|
|
}
|
|
|
|
func withLabels(pod *api.Pod, labels map[string]string) *api.Pod {
|
|
labeledPod := pod.DeepCopy()
|
|
if labels == nil {
|
|
labeledPod.Labels = nil
|
|
return labeledPod
|
|
}
|
|
// Clone.
|
|
labeledPod.Labels = map[string]string{}
|
|
for key, value := range labels {
|
|
labeledPod.Labels[key] = value
|
|
}
|
|
return labeledPod
|
|
}
|
|
|
|
func makeTestPodEviction(name string) *policy.Eviction {
|
|
eviction := &policy.Eviction{}
|
|
eviction.Name = name
|
|
eviction.Namespace = "ns"
|
|
return eviction
|
|
}
|
|
|
|
func makeTokenRequest(podname string, poduid types.UID, audiences []string) *authenticationapi.TokenRequest {
|
|
tr := &authenticationapi.TokenRequest{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "ns",
|
|
},
|
|
Spec: authenticationapi.TokenRequestSpec{
|
|
Audiences: audiences,
|
|
},
|
|
}
|
|
if podname != "" {
|
|
tr.Spec.BoundObjectRef = &authenticationapi.BoundObjectReference{
|
|
Kind: "Pod",
|
|
APIVersion: "v1",
|
|
Name: podname,
|
|
UID: poduid,
|
|
}
|
|
}
|
|
return tr
|
|
}
|
|
|
|
func setAllLabels(node *api.Node, value string) *api.Node {
|
|
node = setAllowedCreateLabels(node, value)
|
|
node = setAllowedUpdateLabels(node, value)
|
|
node = setForbiddenCreateLabels(node, value)
|
|
node = setForbiddenUpdateLabels(node, value)
|
|
return node
|
|
}
|
|
|
|
func setAllowedCreateLabels(node *api.Node, value string) *api.Node {
|
|
node = setAllowedUpdateLabels(node, value)
|
|
return node
|
|
}
|
|
|
|
func setAllowedUpdateLabels(node *api.Node, value string) *api.Node {
|
|
node = node.DeepCopy()
|
|
if node.Labels == nil {
|
|
node.Labels = map[string]string{}
|
|
}
|
|
if value == "" {
|
|
value = "value"
|
|
}
|
|
// non-kube labels
|
|
node.Labels["foo"] = value
|
|
node.Labels["example.com/foo"] = value
|
|
|
|
// kubelet labels
|
|
node.Labels["kubernetes.io/hostname"] = value
|
|
node.Labels["failure-domain.beta.kubernetes.io/zone"] = value
|
|
node.Labels["failure-domain.beta.kubernetes.io/region"] = value
|
|
node.Labels["topology.kubernetes.io/zone"] = value
|
|
node.Labels["topology.kubernetes.io/region"] = value
|
|
node.Labels["beta.kubernetes.io/instance-type"] = value
|
|
node.Labels["node.kubernetes.io/instance-type"] = value
|
|
node.Labels["beta.kubernetes.io/os"] = value
|
|
node.Labels["beta.kubernetes.io/arch"] = value
|
|
node.Labels["kubernetes.io/os"] = value
|
|
node.Labels["kubernetes.io/arch"] = value
|
|
|
|
// kubelet label prefixes
|
|
node.Labels["kubelet.kubernetes.io/foo"] = value
|
|
node.Labels["foo.kubelet.kubernetes.io/foo"] = value
|
|
node.Labels["node.kubernetes.io/foo"] = value
|
|
node.Labels["foo.node.kubernetes.io/foo"] = value
|
|
|
|
// test all explicitly allowed labels and prefixes
|
|
for _, key := range kubeletapis.KubeletLabels() {
|
|
node.Labels[key] = value
|
|
}
|
|
for _, namespace := range kubeletapis.KubeletLabelNamespaces() {
|
|
node.Labels[namespace+"/foo"] = value
|
|
node.Labels["foo."+namespace+"/foo"] = value
|
|
}
|
|
|
|
return node
|
|
}
|
|
|
|
func setForbiddenCreateLabels(node *api.Node, value string) *api.Node {
|
|
node = node.DeepCopy()
|
|
if node.Labels == nil {
|
|
node.Labels = map[string]string{}
|
|
}
|
|
if value == "" {
|
|
value = "value"
|
|
}
|
|
// node restriction labels are forbidden
|
|
node.Labels["node-restriction.kubernetes.io/foo"] = value
|
|
node.Labels["foo.node-restriction.kubernetes.io/foo"] = value
|
|
node.Labels["other.kubernetes.io/foo"] = value
|
|
node.Labels["other.k8s.io/foo"] = value
|
|
return node
|
|
}
|
|
|
|
func setForbiddenUpdateLabels(node *api.Node, value string) *api.Node {
|
|
node = node.DeepCopy()
|
|
if node.Labels == nil {
|
|
node.Labels = map[string]string{}
|
|
}
|
|
if value == "" {
|
|
value = "value"
|
|
}
|
|
// node restriction labels are forbidden
|
|
node.Labels["node-restriction.kubernetes.io/foo"] = value
|
|
node.Labels["foo.node-restriction.kubernetes.io/foo"] = value
|
|
// arbitrary kubernetes labels are forbidden on update
|
|
node.Labels["other.kubernetes.io/foo"] = value
|
|
node.Labels["other.k8s.io/foo"] = value
|
|
return node
|
|
}
|
|
|
|
type admitTestCase struct {
|
|
name string
|
|
podsGetter corev1lister.PodLister
|
|
nodesGetter corev1lister.NodeLister
|
|
csiDriverGetter storagelisters.CSIDriverLister
|
|
pvcGetter corev1lister.PersistentVolumeClaimLister
|
|
pvGetter corev1lister.PersistentVolumeLister
|
|
attributes admission.Attributes
|
|
features featuregate.FeatureGate
|
|
setupFunc func(t *testing.T)
|
|
err string
|
|
authz authorizer.Authorizer
|
|
}
|
|
|
|
func (a *admitTestCase) run(t *testing.T) {
|
|
t.Run(a.name, func(t *testing.T) {
|
|
if a.setupFunc != nil {
|
|
a.setupFunc(t)
|
|
}
|
|
c := NewPlugin(nodeidentifier.NewDefaultNodeIdentifier())
|
|
if a.features != nil {
|
|
c.InspectFeatureGates(a.features)
|
|
}
|
|
c.podsGetter = a.podsGetter
|
|
c.nodesGetter = a.nodesGetter
|
|
c.csiDriverGetter = a.csiDriverGetter
|
|
c.pvcGetter = a.pvcGetter
|
|
c.pvGetter = a.pvGetter
|
|
c.authz = a.authz
|
|
err := c.Admit(context.TODO(), a.attributes, nil)
|
|
if (err == nil) != (len(a.err) == 0) {
|
|
t.Errorf("nodePlugin.Admit() error = %v, expected %v", err, a.err)
|
|
return
|
|
}
|
|
if len(a.err) > 0 && !strings.Contains(err.Error(), a.err) {
|
|
t.Errorf("nodePlugin.Admit() error = %v, expected %v", err, a.err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test_nodePlugin_Admit(t *testing.T) {
|
|
var (
|
|
mynode = &user.DefaultInfo{Name: "system:node:mynode", Groups: []string{"system:nodes"}}
|
|
bob = &user.DefaultInfo{Name: "bob"}
|
|
|
|
mynodeObjMeta = metav1.ObjectMeta{Name: "mynode", UID: "mynode-uid"}
|
|
mynodeObj = &api.Node{ObjectMeta: mynodeObjMeta}
|
|
mynodeObjConfigA = &api.Node{ObjectMeta: mynodeObjMeta, Spec: api.NodeSpec{ConfigSource: &api.NodeConfigSource{
|
|
ConfigMap: &api.ConfigMapNodeConfigSource{
|
|
Name: "foo",
|
|
Namespace: "bar",
|
|
UID: "fooUID",
|
|
KubeletConfigKey: "kubelet",
|
|
}}}}
|
|
mynodeObjConfigB = &api.Node{ObjectMeta: mynodeObjMeta, Spec: api.NodeSpec{ConfigSource: &api.NodeConfigSource{
|
|
ConfigMap: &api.ConfigMapNodeConfigSource{
|
|
Name: "qux",
|
|
Namespace: "bar",
|
|
UID: "quxUID",
|
|
KubeletConfigKey: "kubelet",
|
|
}}}}
|
|
|
|
mynodeObjTaintA = &api.Node{ObjectMeta: mynodeObjMeta, Spec: api.NodeSpec{Taints: []api.Taint{{Key: "mykey", Value: "A"}}}}
|
|
mynodeObjTaintB = &api.Node{ObjectMeta: mynodeObjMeta, Spec: api.NodeSpec{Taints: []api.Taint{{Key: "mykey", Value: "B"}}}}
|
|
othernodeObj = &api.Node{ObjectMeta: metav1.ObjectMeta{Name: "othernode"}}
|
|
|
|
coremymirrorpod, v1mymirrorpod = makeTestPod("ns", "mymirrorpod", "mynode", true)
|
|
coreothermirrorpod, v1othermirrorpod = makeTestPod("ns", "othermirrorpod", "othernode", true)
|
|
coreunboundmirrorpod, v1unboundmirrorpod = makeTestPod("ns", "unboundmirrorpod", "", true)
|
|
coremypod, v1mypod = makeTestPod("ns", "mypod", "mynode", false)
|
|
coreotherpod, v1otherpod = makeTestPod("ns", "otherpod", "othernode", false)
|
|
coreunboundpod, v1unboundpod = makeTestPod("ns", "unboundpod", "", false)
|
|
coreunnamedpod, _ = makeTestPod("ns", "", "mynode", false)
|
|
|
|
mymirrorpodEviction = makeTestPodEviction("mymirrorpod")
|
|
othermirrorpodEviction = makeTestPodEviction("othermirrorpod")
|
|
unboundmirrorpodEviction = makeTestPodEviction("unboundmirrorpod")
|
|
mypodEviction = makeTestPodEviction("mypod")
|
|
otherpodEviction = makeTestPodEviction("otherpod")
|
|
unboundpodEviction = makeTestPodEviction("unboundpod")
|
|
unnamedEviction = makeTestPodEviction("")
|
|
|
|
configmapResource = api.Resource("configmap").WithVersion("v1")
|
|
configmapKind = api.Kind("ConfigMap").WithVersion("v1")
|
|
|
|
podResource = api.Resource("pods").WithVersion("v1")
|
|
podKind = api.Kind("Pod").WithVersion("v1")
|
|
evictionKind = policy.Kind("Eviction").WithVersion("v1beta1")
|
|
|
|
nodeResource = api.Resource("nodes").WithVersion("v1")
|
|
nodeKind = api.Kind("Node").WithVersion("v1")
|
|
|
|
svcacctResource = api.Resource("serviceaccounts").WithVersion("v1")
|
|
tokenrequestKind = api.Kind("TokenRequest").WithVersion("v1")
|
|
|
|
leaseResource = coordination.Resource("leases").WithVersion("v1beta1")
|
|
leaseKind = coordination.Kind("Lease").WithVersion("v1beta1")
|
|
lease = &coordination.Lease{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "mynode",
|
|
Namespace: api.NamespaceNodeLease,
|
|
},
|
|
Spec: coordination.LeaseSpec{
|
|
HolderIdentity: pointer.String("mynode"),
|
|
LeaseDurationSeconds: pointer.Int32(40),
|
|
RenewTime: &metav1.MicroTime{Time: time.Now()},
|
|
},
|
|
}
|
|
leaseWrongNS = &coordination.Lease{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "mynode",
|
|
Namespace: "foo",
|
|
},
|
|
Spec: coordination.LeaseSpec{
|
|
HolderIdentity: pointer.String("mynode"),
|
|
LeaseDurationSeconds: pointer.Int32(40),
|
|
RenewTime: &metav1.MicroTime{Time: time.Now()},
|
|
},
|
|
}
|
|
leaseWrongName = &coordination.Lease{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo",
|
|
Namespace: api.NamespaceNodeLease,
|
|
},
|
|
Spec: coordination.LeaseSpec{
|
|
HolderIdentity: pointer.String("mynode"),
|
|
LeaseDurationSeconds: pointer.Int32(40),
|
|
RenewTime: &metav1.MicroTime{Time: time.Now()},
|
|
},
|
|
}
|
|
|
|
csiNodeResource = storage.Resource("csinodes").WithVersion("v1")
|
|
csiNodeKind = schema.GroupVersionKind{Group: "storage.k8s.io", Version: "v1", Kind: "CSINode"}
|
|
nodeInfo = &storage.CSINode{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "mynode",
|
|
},
|
|
Spec: storage.CSINodeSpec{
|
|
Drivers: []storage.CSINodeDriver{
|
|
{
|
|
Name: "com.example.csi/mydriver",
|
|
NodeID: "com.example.csi/mynode",
|
|
TopologyKeys: []string{"com.example.csi/zone"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
nodeInfoWrongName = &storage.CSINode{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo",
|
|
},
|
|
Spec: storage.CSINodeSpec{
|
|
Drivers: []storage.CSINodeDriver{
|
|
{
|
|
Name: "com.example.csi/mydriver",
|
|
NodeID: "com.example.csi/foo",
|
|
TopologyKeys: []string{"com.example.csi/zone"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
existingNodesIndex = cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
|
existingNodes = corev1lister.NewNodeLister(existingNodesIndex)
|
|
|
|
noExistingPodsIndex = cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
|
noExistingPods = corev1lister.NewPodLister(noExistingPodsIndex)
|
|
|
|
existingPodsIndex = cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
|
existingPods = corev1lister.NewPodLister(existingPodsIndex)
|
|
|
|
labelsA = map[string]string{
|
|
"label-a": "value-a",
|
|
}
|
|
labelsAB = map[string]string{
|
|
"label-a": "value-a",
|
|
"label-b": "value-b",
|
|
}
|
|
aLabeledPod = withLabels(coremypod, labelsA)
|
|
abLabeledPod = withLabels(coremypod, labelsAB)
|
|
|
|
privKey, _ = rsa.GenerateKey(rand.Reader, 2048)
|
|
|
|
csiDriverWithAudience = &storagev1.CSIDriver{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "com.example.csi.mydriver",
|
|
},
|
|
Spec: storagev1.CSIDriverSpec{
|
|
TokenRequests: []storagev1.TokenRequest{
|
|
{
|
|
Audience: "foo",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
azureFileCSIDriver = &storagev1.CSIDriver{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "file.csi.azure.com",
|
|
},
|
|
Spec: storagev1.CSIDriverSpec{
|
|
TokenRequests: []storagev1.TokenRequest{
|
|
{
|
|
Audience: "foo",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
csiDriverIndex = cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
|
csiDriverLister = storagelisters.NewCSIDriverLister(csiDriverIndex)
|
|
|
|
noexistingCSIDriverIndex = cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
|
noexistingCSIDriverLister = storagelisters.NewCSIDriverLister(noexistingCSIDriverIndex)
|
|
|
|
pvcWithCSIDriver = &corev1.PersistentVolumeClaim{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pvclaim",
|
|
Namespace: "ns",
|
|
},
|
|
Spec: corev1.PersistentVolumeClaimSpec{
|
|
VolumeName: "pvname",
|
|
},
|
|
}
|
|
|
|
pvcWithIntreeAzureFile = &corev1.PersistentVolumeClaim{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pvclaim-azurefile",
|
|
Namespace: "ns",
|
|
},
|
|
Spec: corev1.PersistentVolumeClaimSpec{
|
|
VolumeName: "pvname-azurefile",
|
|
},
|
|
}
|
|
|
|
ephemeralVolumePVCWithCSIDriver = &corev1.PersistentVolumeClaim{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "myephemeralpod-myvol",
|
|
Namespace: "ns",
|
|
},
|
|
Spec: corev1.PersistentVolumeClaimSpec{
|
|
VolumeName: "pvname",
|
|
},
|
|
}
|
|
|
|
pvcIndex = cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
|
pvcLister = corev1lister.NewPersistentVolumeClaimLister(pvcIndex)
|
|
|
|
noexistingPVCIndex = cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
|
noexistingPVCLister = corev1lister.NewPersistentVolumeClaimLister(noexistingPVCIndex)
|
|
|
|
pvWithCSIDriver = &corev1.PersistentVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pvname",
|
|
},
|
|
Spec: corev1.PersistentVolumeSpec{
|
|
PersistentVolumeSource: corev1.PersistentVolumeSource{
|
|
CSI: &corev1.CSIPersistentVolumeSource{Driver: "com.example.csi.mydriver"},
|
|
},
|
|
},
|
|
}
|
|
|
|
pvWithIntreeAzureFile = &corev1.PersistentVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pvname-azurefile",
|
|
},
|
|
Spec: corev1.PersistentVolumeSpec{
|
|
ClaimRef: &corev1.ObjectReference{
|
|
Namespace: "ns",
|
|
},
|
|
PersistentVolumeSource: corev1.PersistentVolumeSource{
|
|
AzureFile: &corev1.AzureFilePersistentVolumeSource{ShareName: "default", SecretName: "secret"},
|
|
},
|
|
},
|
|
}
|
|
|
|
pvIndex = cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
|
pvLister = corev1lister.NewPersistentVolumeLister(pvIndex)
|
|
|
|
noexistingPVIndex = cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
|
noexistingPVLister = corev1lister.NewPersistentVolumeLister(noexistingPVIndex)
|
|
)
|
|
|
|
// create pods for validating the service account node audience restriction
|
|
projectedVolumeSourceEmptyAudience := &corev1.ProjectedVolumeSource{Sources: []corev1.VolumeProjection{{ServiceAccountToken: &corev1.ServiceAccountTokenProjection{Audience: ""}}}}
|
|
projectedVolumeSource := &corev1.ProjectedVolumeSource{Sources: []corev1.VolumeProjection{{ServiceAccountToken: &corev1.ServiceAccountTokenProjection{Audience: "foo"}}}}
|
|
csiDriverVolumeSource := &corev1.CSIVolumeSource{Driver: "com.example.csi.mydriver"}
|
|
persistentVolumeClaimVolumeSource := &corev1.PersistentVolumeClaimVolumeSource{ClaimName: "pvclaim"}
|
|
persistentVolumeClaimVolumeSourceAzureFile := &corev1.PersistentVolumeClaimVolumeSource{ClaimName: "pvclaim-azurefile"}
|
|
ephemeralVolumeSource := &corev1.EphemeralVolumeSource{VolumeClaimTemplate: &corev1.PersistentVolumeClaimTemplate{}}
|
|
|
|
coremypodWithProjectedServiceAccountEmptyAudience, v1mypodWithProjectedServiceAccountEmptyAudience := makeTestPod("ns", "mysapod", "mynode", false)
|
|
v1mypodWithProjectedServiceAccountEmptyAudience.Spec.Volumes = []corev1.Volume{{VolumeSource: corev1.VolumeSource{Projected: projectedVolumeSourceEmptyAudience}}}
|
|
|
|
coremypodWithProjectedServiceAccount, v1mypodWithProjectedServiceAccount := makeTestPod("ns", "mysapod", "mynode", false)
|
|
v1mypodWithProjectedServiceAccount.Spec.Volumes = []corev1.Volume{{VolumeSource: corev1.VolumeSource{Projected: projectedVolumeSource}}}
|
|
|
|
coremypodWithCSI, v1mypodWithCSI := makeTestPod("ns", "mycsipod", "mynode", false)
|
|
v1mypodWithCSI.Spec.Volumes = []corev1.Volume{{VolumeSource: corev1.VolumeSource{CSI: csiDriverVolumeSource}}}
|
|
|
|
coremypodWithPVCRefCSI, v1mypodWithPVCRefCSI := makeTestPod("ns", "mypvcpod", "mynode", false)
|
|
v1mypodWithPVCRefCSI.Spec.Volumes = []corev1.Volume{{VolumeSource: corev1.VolumeSource{PersistentVolumeClaim: persistentVolumeClaimVolumeSource}}}
|
|
|
|
coremypodWithEphemeralVolume, v1mypodWithEphemeralVolume := makeTestPod("ns", "myephemeralpod", "mynode", false)
|
|
v1mypodWithEphemeralVolume.Spec.Volumes = []corev1.Volume{{Name: "myvol", VolumeSource: corev1.VolumeSource{Ephemeral: ephemeralVolumeSource}}}
|
|
|
|
coremypodWithPVCAndCSI, v1mypodWithPVCAndCSI := makeTestPod("ns", "mypvcandcsipod", "mynode", false)
|
|
v1mypodWithPVCAndCSI.Spec.Volumes = []corev1.Volume{{VolumeSource: corev1.VolumeSource{PersistentVolumeClaim: persistentVolumeClaimVolumeSource}}, {VolumeSource: corev1.VolumeSource{CSI: csiDriverVolumeSource}}}
|
|
|
|
coremypodIntreeInlineVolToCSI, v1mypodIntreeInlineVolToCSI := makeTestPod("ns", "myintreeinlinevoltocsipod", "mynode", false)
|
|
v1mypodIntreeInlineVolToCSI.Spec.Volumes = []corev1.Volume{{VolumeSource: corev1.VolumeSource{AzureFile: &corev1.AzureFileVolumeSource{ShareName: "default", SecretName: "secret"}}}}
|
|
|
|
coremypodIntreePVToCSI, v1mypodIntreePVToCSI := makeTestPod("ns", "myintreepvtocsipod", "mynode", false)
|
|
v1mypodIntreePVToCSI.Spec.Volumes = []corev1.Volume{{VolumeSource: corev1.VolumeSource{PersistentVolumeClaim: persistentVolumeClaimVolumeSourceAzureFile}}}
|
|
|
|
checkNilError(t, csiDriverIndex.Add(csiDriverWithAudience))
|
|
checkNilError(t, csiDriverIndex.Add(azureFileCSIDriver))
|
|
checkNilError(t, pvcIndex.Add(pvcWithCSIDriver))
|
|
checkNilError(t, pvcIndex.Add(pvcWithIntreeAzureFile))
|
|
checkNilError(t, pvcIndex.Add(ephemeralVolumePVCWithCSIDriver))
|
|
checkNilError(t, pvIndex.Add(pvWithCSIDriver))
|
|
checkNilError(t, pvIndex.Add(pvWithIntreeAzureFile))
|
|
|
|
existingPodsIndex.Add(v1mymirrorpod)
|
|
existingPodsIndex.Add(v1othermirrorpod)
|
|
existingPodsIndex.Add(v1unboundmirrorpod)
|
|
existingPodsIndex.Add(v1mypod)
|
|
existingPodsIndex.Add(v1otherpod)
|
|
existingPodsIndex.Add(v1unboundpod)
|
|
|
|
checkNilError(t, existingPodsIndex.Add(v1mypodWithProjectedServiceAccountEmptyAudience))
|
|
checkNilError(t, existingPodsIndex.Add(v1mypodWithProjectedServiceAccount))
|
|
checkNilError(t, existingPodsIndex.Add(v1mypodWithCSI))
|
|
checkNilError(t, existingPodsIndex.Add(v1mypodWithPVCRefCSI))
|
|
checkNilError(t, existingPodsIndex.Add(v1mypodWithEphemeralVolume))
|
|
checkNilError(t, existingPodsIndex.Add(v1mypodWithPVCAndCSI))
|
|
checkNilError(t, existingPodsIndex.Add(v1mypodIntreePVToCSI))
|
|
checkNilError(t, existingPodsIndex.Add(v1mypodIntreeInlineVolToCSI))
|
|
|
|
existingNodesIndex.Add(&corev1.Node{ObjectMeta: mynodeObjMeta})
|
|
|
|
sapod, _ := makeTestPod("ns", "mysapod", "mynode", true)
|
|
sapod.Spec.ServiceAccountName = "foo"
|
|
|
|
secretpod, _ := makeTestPod("ns", "mysecretpod", "mynode", true)
|
|
secretpod.Spec.Volumes = []api.Volume{{VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "foo"}}}}
|
|
|
|
configmappod, _ := makeTestPod("ns", "myconfigmappod", "mynode", true)
|
|
configmappod.Spec.Volumes = []api.Volume{{VolumeSource: api.VolumeSource{ConfigMap: &api.ConfigMapVolumeSource{LocalObjectReference: api.LocalObjectReference{Name: "foo"}}}}}
|
|
|
|
ctbpod, _ := makeTestPod("ns", "myctbpod", "mynode", true)
|
|
ctbpod.Spec.Volumes = []api.Volume{{VolumeSource: api.VolumeSource{Projected: &api.ProjectedVolumeSource{Sources: []api.VolumeProjection{{ClusterTrustBundle: &api.ClusterTrustBundleProjection{Name: pointer.String("foo")}}}}}}}
|
|
|
|
pvcpod, _ := makeTestPod("ns", "mypvcpod", "mynode", true)
|
|
pvcpod.Spec.Volumes = []api.Volume{{VolumeSource: api.VolumeSource{PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ClaimName: "foo"}}}}
|
|
|
|
tests := []admitTestCase{
|
|
// Mirror pods bound to us
|
|
{
|
|
name: "allow creating a mirror pod bound to self",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(coremymirrorpod, nil, podKind, coremymirrorpod.Namespace, coremymirrorpod.Name, podResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "forbid update of mirror pod bound to self",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(coremymirrorpod, coremymirrorpod, podKind, coremymirrorpod.Namespace, coremymirrorpod.Name, podResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "allow delete of mirror pod bound to self",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, coremymirrorpod.Namespace, coremymirrorpod.Name, podResource, "", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "forbid create of mirror pod status bound to self",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(coremymirrorpod, nil, podKind, coremymirrorpod.Namespace, coremymirrorpod.Name, podResource, "status", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "allow update of mirror pod status bound to self",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(coremymirrorpod, coremymirrorpod, podKind, coremymirrorpod.Namespace, coremymirrorpod.Name, podResource, "status", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "forbid delete of mirror pod status bound to self",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, coremymirrorpod.Namespace, coremymirrorpod.Name, podResource, "status", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "allow create of eviction for mirror pod bound to self",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mymirrorpodEviction, nil, evictionKind, coremymirrorpod.Namespace, coremymirrorpod.Name, podResource, "eviction", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "forbid update of eviction for mirror pod bound to self",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mymirrorpodEviction, nil, evictionKind, coremymirrorpod.Namespace, coremymirrorpod.Name, podResource, "eviction", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid delete of eviction for mirror pod bound to self",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mymirrorpodEviction, nil, evictionKind, coremymirrorpod.Namespace, coremymirrorpod.Name, podResource, "eviction", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "allow create of unnamed eviction for mirror pod bound to self",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, coremymirrorpod.Namespace, coremymirrorpod.Name, podResource, "eviction", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
|
|
// Mirror pods bound to another node
|
|
{
|
|
name: "forbid creating a mirror pod bound to another",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(coreothermirrorpod, nil, podKind, coreothermirrorpod.Namespace, coreothermirrorpod.Name, podResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
{
|
|
name: "forbid update of mirror pod bound to another",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(coreothermirrorpod, coreothermirrorpod, podKind, coreothermirrorpod.Namespace, coreothermirrorpod.Name, podResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid delete of mirror pod bound to another",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, coreothermirrorpod.Namespace, coreothermirrorpod.Name, podResource, "", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
{
|
|
name: "forbid create of mirror pod status bound to another",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(coreothermirrorpod, nil, podKind, coreothermirrorpod.Namespace, coreothermirrorpod.Name, podResource, "status", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid update of mirror pod status bound to another",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(coreothermirrorpod, coreothermirrorpod, podKind, coreothermirrorpod.Namespace, coreothermirrorpod.Name, podResource, "status", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
{
|
|
name: "forbid delete of mirror pod status bound to another",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, coreothermirrorpod.Namespace, coreothermirrorpod.Name, podResource, "status", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid create of eviction for mirror pod bound to another",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(othermirrorpodEviction, nil, evictionKind, coreothermirrorpod.Namespace, coreothermirrorpod.Name, podResource, "eviction", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
{
|
|
name: "forbid update of eviction for mirror pod bound to another",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(othermirrorpodEviction, nil, evictionKind, coreothermirrorpod.Namespace, coreothermirrorpod.Name, podResource, "eviction", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid delete of eviction for mirror pod bound to another",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(othermirrorpodEviction, nil, evictionKind, coreothermirrorpod.Namespace, coreothermirrorpod.Name, podResource, "eviction", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid create of unnamed eviction for mirror pod to another",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, coreothermirrorpod.Namespace, coreothermirrorpod.Name, podResource, "eviction", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
|
|
// Mirror pods not bound to any node
|
|
{
|
|
name: "forbid creating a mirror pod unbound",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(coreunboundmirrorpod, nil, podKind, coreunboundmirrorpod.Namespace, coreunboundmirrorpod.Name, podResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
{
|
|
name: "forbid update of mirror pod unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(coreunboundmirrorpod, coreunboundmirrorpod, podKind, coreunboundmirrorpod.Namespace, coreunboundmirrorpod.Name, podResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid delete of mirror pod unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, coreunboundmirrorpod.Namespace, coreunboundmirrorpod.Name, podResource, "", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
{
|
|
name: "forbid create of mirror pod status unbound",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(coreunboundmirrorpod, nil, podKind, coreunboundmirrorpod.Namespace, coreunboundmirrorpod.Name, podResource, "status", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid update of mirror pod status unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(coreunboundmirrorpod, coreunboundmirrorpod, podKind, coreunboundmirrorpod.Namespace, coreunboundmirrorpod.Name, podResource, "status", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
{
|
|
name: "forbid delete of mirror pod status unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, coreunboundmirrorpod.Namespace, coreunboundmirrorpod.Name, podResource, "status", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid create of eviction for mirror pod unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(unboundmirrorpodEviction, nil, evictionKind, coreunboundmirrorpod.Namespace, coreunboundmirrorpod.Name, podResource, "eviction", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
{
|
|
name: "forbid update of eviction for mirror pod unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(unboundmirrorpodEviction, nil, evictionKind, coreunboundmirrorpod.Namespace, coreunboundmirrorpod.Name, podResource, "eviction", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid delete of eviction for mirror pod unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(unboundmirrorpodEviction, nil, evictionKind, coreunboundmirrorpod.Namespace, coreunboundmirrorpod.Name, podResource, "eviction", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid create of unnamed eviction for mirror pod unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, coreunboundmirrorpod.Namespace, coreunboundmirrorpod.Name, podResource, "eviction", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
|
|
// Normal pods bound to us
|
|
{
|
|
name: "forbid creating a normal pod bound to self",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(coremypod, nil, podKind, coremypod.Namespace, coremypod.Name, podResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "can only create mirror pods",
|
|
},
|
|
{
|
|
name: "forbid update of normal pod bound to self",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(coremypod, coremypod, podKind, coremypod.Namespace, coremypod.Name, podResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "allow delete of normal pod bound to self",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, coremypod.Namespace, coremypod.Name, podResource, "", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "forbid create of normal pod status bound to self",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(coremypod, nil, podKind, coremypod.Namespace, coremypod.Name, podResource, "status", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "allow update of normal pod status bound to self",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(coremypod, coremypod, podKind, coremypod.Namespace, coremypod.Name, podResource, "status", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "forbid delete of normal pod status bound to self",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, coremypod.Namespace, coremypod.Name, podResource, "status", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid addition of pod status preexisting labels",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(abLabeledPod, aLabeledPod, podKind, coremypod.Namespace, coremypod.Name, podResource, "status", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "cannot update labels through pod status",
|
|
},
|
|
{
|
|
name: "forbid deletion of pod status preexisting labels",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(aLabeledPod, abLabeledPod, podKind, coremypod.Namespace, coremypod.Name, podResource, "status", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "cannot update labels through pod status",
|
|
},
|
|
{
|
|
name: "forbid deletion of all pod status preexisting labels",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(aLabeledPod, coremypod, podKind, coremypod.Namespace, coremypod.Name, podResource, "status", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "cannot update labels through pod status",
|
|
},
|
|
{
|
|
name: "forbid addition of pod status labels",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(coremypod, aLabeledPod, podKind, coremypod.Namespace, coremypod.Name, podResource, "status", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "cannot update labels through pod status",
|
|
},
|
|
{
|
|
name: "forbid update of eviction for normal pod bound to self",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, coremypod.Namespace, coremypod.Name, podResource, "eviction", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid delete of eviction for normal pod bound to self",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, coremypod.Namespace, coremypod.Name, podResource, "eviction", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "allow create of unnamed eviction for normal pod bound to self",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, coremypod.Namespace, coremypod.Name, podResource, "eviction", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
|
|
// Normal pods bound to another
|
|
{
|
|
name: "forbid creating a normal pod bound to another",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(coreotherpod, nil, podKind, coreotherpod.Namespace, coreotherpod.Name, podResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "can only create mirror pods",
|
|
},
|
|
{
|
|
name: "forbid update of normal pod bound to another",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(coreotherpod, coreotherpod, podKind, coreotherpod.Namespace, coreotherpod.Name, podResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid delete of normal pod bound to another",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, coreotherpod.Namespace, coreotherpod.Name, podResource, "", admission.Delete, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
{
|
|
name: "forbid create of normal pod status bound to another",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(coreotherpod, nil, podKind, coreotherpod.Namespace, coreotherpod.Name, podResource, "status", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid update of normal pod status bound to another",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(coreotherpod, coreotherpod, podKind, coreotherpod.Namespace, coreotherpod.Name, podResource, "status", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
{
|
|
name: "forbid delete of normal pod status bound to another",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, coreotherpod.Namespace, coreotherpod.Name, podResource, "status", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid create of eviction for normal pod bound to another",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(otherpodEviction, nil, evictionKind, otherpodEviction.Namespace, otherpodEviction.Name, podResource, "eviction", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
{
|
|
name: "forbid update of eviction for normal pod bound to another",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(otherpodEviction, nil, evictionKind, otherpodEviction.Namespace, otherpodEviction.Name, podResource, "eviction", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid delete of eviction for normal pod bound to another",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(otherpodEviction, nil, evictionKind, otherpodEviction.Namespace, otherpodEviction.Name, podResource, "eviction", admission.Delete, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid create of unnamed eviction for normal pod bound to another",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, coreotherpod.Namespace, coreotherpod.Name, podResource, "eviction", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
|
|
// Normal pods not bound to any node
|
|
{
|
|
name: "forbid creating a normal pod unbound",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(coreunboundpod, nil, podKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "can only create mirror pods",
|
|
},
|
|
{
|
|
name: "forbid update of normal pod unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(coreunboundpod, coreunboundpod, podKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid delete of normal pod unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
{
|
|
name: "forbid create of normal pod status unbound",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(coreunboundpod, nil, podKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "status", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid update of normal pod status unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(coreunboundpod, coreunboundpod, podKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "status", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
{
|
|
name: "forbid delete of normal pod status unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "status", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid create of eviction for normal pod unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(unboundpodEviction, nil, evictionKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "eviction", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
{
|
|
name: "forbid update of eviction for normal pod unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(unboundpodEviction, nil, evictionKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "eviction", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid delete of eviction for normal pod unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(unboundpodEviction, nil, evictionKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "eviction", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid create of unnamed eviction for normal unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "eviction", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "spec.nodeName set to itself",
|
|
},
|
|
|
|
// Missing pod
|
|
{
|
|
name: "forbid delete of unknown pod",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "not found",
|
|
},
|
|
{
|
|
name: "forbid create of eviction for unknown pod",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, coremypod.Namespace, coremypod.Name, podResource, "eviction", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "not found",
|
|
},
|
|
{
|
|
name: "forbid update of eviction for unknown pod",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, coremypod.Namespace, coremypod.Name, podResource, "eviction", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid delete of eviction for unknown pod",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, coremypod.Namespace, coremypod.Name, podResource, "eviction", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid create of unnamed eviction for unknown pod",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, coremypod.Namespace, coremypod.Name, podResource, "eviction", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "not found",
|
|
},
|
|
|
|
// Eviction for unnamed pod
|
|
{
|
|
name: "allow create of eviction for unnamed pod",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, coreunnamedpod.Namespace, coreunnamedpod.Name, podResource, "eviction", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
// use the submitted eviction resource name as the pod name
|
|
err: "",
|
|
},
|
|
{
|
|
name: "forbid update of eviction for unnamed pod",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, coreunnamedpod.Namespace, coreunnamedpod.Name, podResource, "eviction", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid delete of eviction for unnamed pod",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, coreunnamedpod.Namespace, coreunnamedpod.Name, podResource, "eviction", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "forbidden: unexpected operation",
|
|
},
|
|
{
|
|
name: "forbid create of unnamed eviction for unnamed pod",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, coreunnamedpod.Namespace, coreunnamedpod.Name, podResource, "eviction", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "could not determine pod from request data",
|
|
},
|
|
|
|
// Resource pods
|
|
{
|
|
name: "forbid create of pod referencing service account",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(sapod, nil, podKind, sapod.Namespace, sapod.Name, podResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "reference a service account",
|
|
},
|
|
{
|
|
name: "forbid create of pod referencing secret",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(secretpod, nil, podKind, secretpod.Namespace, secretpod.Name, podResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "reference secrets",
|
|
},
|
|
{
|
|
name: "forbid create of pod referencing configmap",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(configmappod, nil, podKind, configmappod.Namespace, configmappod.Name, podResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "reference configmaps",
|
|
},
|
|
{
|
|
name: "forbid create of pod referencing clustertrustbundle",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(ctbpod, nil, podKind, ctbpod.Namespace, ctbpod.Name, podResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "reference clustertrustbundles",
|
|
},
|
|
{
|
|
name: "forbid create of pod referencing persistentvolumeclaim",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(pvcpod, nil, podKind, pvcpod.Namespace, pvcpod.Name, podResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "reference persistentvolumeclaims",
|
|
},
|
|
|
|
// My node object
|
|
{
|
|
name: "allow create of my node",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(mynodeObj, nil, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow create of my node pulling name from object",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(mynodeObj, nil, nodeKind, mynodeObj.Namespace, "mynode", nodeResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow create of my node with taints",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(mynodeObjTaintA, nil, nodeKind, mynodeObj.Namespace, "mynode", nodeResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow create of my node with labels",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(setAllowedCreateLabels(mynodeObj, ""), nil, nodeKind, mynodeObj.Namespace, "mynode", nodeResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "forbid create of my node with forbidden labels",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(setForbiddenCreateLabels(mynodeObj, ""), nil, nodeKind, mynodeObj.Namespace, "mynode", nodeResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: `is not allowed to set the following labels: foo.node-restriction.kubernetes.io/foo, node-restriction.kubernetes.io/foo, other.k8s.io/foo, other.kubernetes.io/foo`,
|
|
},
|
|
{
|
|
name: "allow update of my node",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mynodeObj, mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow delete of my node",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow update of my node status",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mynodeObj, mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "status", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "forbid create of my node with non-nil configSource",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(mynodeObjConfigA, nil, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "is not allowed to create pods with a non-nil configSource",
|
|
},
|
|
{
|
|
name: "forbid update of my node: nil configSource to new non-nil configSource",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mynodeObjConfigA, mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "update configSource to a new non-nil configSource",
|
|
},
|
|
{
|
|
name: "forbid update of my node: non-nil configSource to new non-nil configSource",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mynodeObjConfigB, mynodeObjConfigA, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "update configSource to a new non-nil configSource",
|
|
},
|
|
{
|
|
name: "allow update of my node: non-nil configSource unchanged",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mynodeObjConfigA, mynodeObjConfigA, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow update of my node: non-nil configSource to nil configSource",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mynodeObj, mynodeObjConfigA, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow update of my node: no change to taints",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mynodeObjTaintA, mynodeObjTaintA, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow update of my node: add allowed labels",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(setAllowedUpdateLabels(mynodeObj, ""), mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow update of my node: remove allowed labels",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mynodeObj, setAllowedUpdateLabels(mynodeObj, ""), nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow update of my node: modify allowed labels",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(setAllowedUpdateLabels(mynodeObj, "b"), setAllowedUpdateLabels(mynodeObj, "a"), nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow update of my node: no change to labels",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(setAllLabels(mynodeObj, ""), setAllLabels(mynodeObj, ""), nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow update of my node: add allowed labels while forbidden labels exist unmodified",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(setAllLabels(mynodeObj, ""), setForbiddenUpdateLabels(mynodeObj, ""), nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow update of my node: remove allowed labels while forbidden labels exist unmodified",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(setForbiddenUpdateLabels(mynodeObj, ""), setAllLabels(mynodeObj, ""), nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "forbid update of my node: add taints",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mynodeObjTaintA, mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "is not allowed to modify taints",
|
|
},
|
|
{
|
|
name: "forbid update of my node: remove taints",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mynodeObj, mynodeObjTaintA, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "is not allowed to modify taints",
|
|
},
|
|
{
|
|
name: "forbid update of my node: change taints",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mynodeObjTaintA, mynodeObjTaintB, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "is not allowed to modify taints",
|
|
},
|
|
{
|
|
name: "forbid update of my node: add labels",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(setForbiddenUpdateLabels(mynodeObj, ""), mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: `is not allowed to modify labels: foo.node-restriction.kubernetes.io/foo, node-restriction.kubernetes.io/foo, other.k8s.io/foo, other.kubernetes.io/foo`,
|
|
},
|
|
{
|
|
name: "forbid update of my node: remove labels",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(mynodeObj, setForbiddenUpdateLabels(mynodeObj, ""), nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: `is not allowed to modify labels: foo.node-restriction.kubernetes.io/foo, node-restriction.kubernetes.io/foo, other.k8s.io/foo, other.kubernetes.io/foo`,
|
|
},
|
|
{
|
|
name: "forbid update of my node: change labels",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(setForbiddenUpdateLabels(mynodeObj, "new"), setForbiddenUpdateLabels(mynodeObj, "old"), nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: `is not allowed to modify labels: foo.node-restriction.kubernetes.io/foo, node-restriction.kubernetes.io/foo, other.k8s.io/foo, other.kubernetes.io/foo`,
|
|
},
|
|
|
|
// Other node object
|
|
{
|
|
name: "forbid create of other node",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(othernodeObj, nil, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "is not allowed to modify node",
|
|
},
|
|
{
|
|
name: "forbid create of other node pulling name from object",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(othernodeObj, nil, nodeKind, othernodeObj.Namespace, "", nodeResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "is not allowed to modify node",
|
|
},
|
|
{
|
|
name: "forbid update of other node",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(othernodeObj, othernodeObj, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "is not allowed to modify node",
|
|
},
|
|
{
|
|
name: "forbid delete of other node",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "is not allowed to modify node",
|
|
},
|
|
{
|
|
name: "forbid update of other node status",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(othernodeObj, othernodeObj, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "status", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "is not allowed to modify node",
|
|
},
|
|
|
|
// Service accounts
|
|
{
|
|
name: "forbid create of unbound token",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest("", "", []string{"foo"}), nil, tokenrequestKind, "ns", "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "not bound to a pod",
|
|
},
|
|
{
|
|
name: "forbid create of token bound to nonexistant pod",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest("nopod", "someuid", []string{"foo"}), nil, tokenrequestKind, "ns", "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "not found",
|
|
},
|
|
{
|
|
name: "forbid create of token bound to pod without uid",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypod.Name, "", []string{"foo"}), nil, tokenrequestKind, "ns", "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "pod binding without a uid",
|
|
},
|
|
{
|
|
name: "forbid create of token bound to pod scheduled on another node",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coreotherpod.Name, coreotherpod.UID, []string{"foo"}), nil, tokenrequestKind, coreotherpod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "pod scheduled on a different node",
|
|
},
|
|
{
|
|
name: "allow create of token bound to pod scheduled this node",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypod.Name, coremypod.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
},
|
|
// Service accounts tests with token audience restrictions
|
|
{
|
|
name: "allow create of token when audience in PSAT volume and ServiceAccountNodeAudienceRestriction is enabled, empty audience",
|
|
podsGetter: existingPods,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodWithProjectedServiceAccountEmptyAudience.Name, coremypodWithProjectedServiceAccountEmptyAudience.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
},
|
|
{
|
|
name: "allow create of token when audience in PSAT volume and ServiceAccountNodeAudienceRestriction is enabled, single audience",
|
|
podsGetter: existingPods,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodWithProjectedServiceAccount.Name, coremypodWithProjectedServiceAccount.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
},
|
|
{
|
|
name: "forbid create of token with multiple audiences in token request and ServiceAccountNodeAudienceRestriction is enabled",
|
|
podsGetter: existingPods,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodWithProjectedServiceAccount.Name, coremypodWithProjectedServiceAccount.UID, []string{"foo", "bar"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "node may only request 0 or 1 audiences",
|
|
},
|
|
{
|
|
name: "forbid create of token when audience not in pod spec and ServiceAccountNodeAudienceRestriction is enabled",
|
|
podsGetter: existingPods,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypod.Name, coremypod.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: `serviceaccounts "mysa" is forbidden: audience "foo" not found in pod spec volume, system:node:mynode is not authorized to request tokens for this audience`,
|
|
authz: fakeAuthorizer{
|
|
t: t,
|
|
serviceAccountName: "mysa",
|
|
namespace: coremypod.Namespace,
|
|
requestAudience: "foo",
|
|
decision: authorizer.DecisionDeny,
|
|
},
|
|
},
|
|
{
|
|
name: "allow create of token when audience in pod --> csi --> driver --> tokenrequest with audience and ServiceAccountNodeAudienceRestriction is enabled",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: csiDriverLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodWithCSI.Name, v1mypodWithCSI.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
},
|
|
{
|
|
name: "forbid create of token when audience in pod --> csi --> driver --> tokenrequest does not have audience and ServiceAccountNodeAudienceRestriction is enabled",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: csiDriverLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodWithCSI.Name, v1mypodWithCSI.UID, []string{"bar"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: `audience "bar" not found in pod spec volume, system:node:mynode is not authorized to request tokens for this audience`,
|
|
authz: fakeAuthorizer{
|
|
t: t,
|
|
serviceAccountName: "mysa",
|
|
namespace: coremypod.Namespace,
|
|
requestAudience: "bar",
|
|
decision: authorizer.DecisionDeny,
|
|
},
|
|
},
|
|
{
|
|
name: "forbid create of token when audience in pod --> csi --> driver --> tokenrequest with audience and ServiceAccountNodeAudienceRestriction is enabled, csidriver not found",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: noexistingCSIDriverLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodWithCSI.Name, v1mypodWithCSI.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: `error validating audience "foo": csidriver.storage.k8s.io "com.example.csi.mydriver" not found`,
|
|
authz: fakeAuthorizer{},
|
|
},
|
|
{
|
|
name: "allow create of token when audience in pod --> pvc --> pv --> csi --> driver --> tokenrequest with audience and ServiceAccountNodeAudienceRestriction is enabled",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: csiDriverLister,
|
|
pvcGetter: pvcLister,
|
|
pvGetter: pvLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodWithPVCRefCSI.Name, v1mypodWithPVCRefCSI.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
},
|
|
{
|
|
name: "forbid create of token when audience in pod --> pvc --> pv --> csi --> driver --> tokenrequest does not have audience and ServiceAccountNodeAudienceRestriction is enabled",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: csiDriverLister,
|
|
pvcGetter: pvcLister,
|
|
pvGetter: pvLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodWithPVCRefCSI.Name, v1mypodWithPVCRefCSI.UID, []string{"bar"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: `audience "bar" not found in pod spec volume, system:node:mynode is not authorized to request tokens for this audience`,
|
|
authz: fakeAuthorizer{
|
|
t: t,
|
|
serviceAccountName: "mysa",
|
|
namespace: coremypod.Namespace,
|
|
requestAudience: "bar",
|
|
decision: authorizer.DecisionDeny,
|
|
},
|
|
},
|
|
{
|
|
name: "forbid create of token when audience in pod --> pvc --> pv --> csi --> driver --> tokenrequest with audience and ServiceAccountNodeAudienceRestriction is enabled, pvc not found",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: csiDriverLister,
|
|
pvcGetter: noexistingPVCLister,
|
|
pvGetter: pvLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodWithPVCRefCSI.Name, v1mypodWithPVCRefCSI.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: `error validating audience "foo": persistentvolumeclaim "pvclaim" not found`,
|
|
authz: fakeAuthorizer{},
|
|
},
|
|
{
|
|
name: "forbid create of token when audience in pod --> pvc --> pv --> csi --> driver --> tokenrequest with audience and ServiceAccountNodeAudienceRestriction is enabled, pv not found",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: csiDriverLister,
|
|
pvcGetter: pvcLister,
|
|
pvGetter: noexistingPVLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodWithPVCRefCSI.Name, v1mypodWithPVCRefCSI.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: `error validating audience "foo": persistentvolume "pvname" not found`,
|
|
authz: fakeAuthorizer{},
|
|
},
|
|
{
|
|
name: "allow create of token when audience in pod --> ephemeral --> pvc --> pv --> csi --> driver --> tokenrequest with audience and ServiceAccountNodeAudienceRestriction is enabled",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: csiDriverLister,
|
|
pvcGetter: pvcLister,
|
|
pvGetter: pvLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodWithEphemeralVolume.Name, v1mypodWithEphemeralVolume.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
},
|
|
{
|
|
name: "forbid create of token when audience in pod --> ephemeral --> pvc --> pv --> csi --> driver --> tokenrequest does not have audience and ServiceAccountNodeAudienceRestriction is enabled",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: csiDriverLister,
|
|
pvcGetter: pvcLister,
|
|
pvGetter: pvLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodWithEphemeralVolume.Name, v1mypodWithEphemeralVolume.UID, []string{"bar"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: `audience "bar" not found in pod spec volume, system:node:mynode is not authorized to request tokens for this audience`,
|
|
authz: fakeAuthorizer{
|
|
t: t,
|
|
serviceAccountName: "mysa",
|
|
namespace: coremypod.Namespace,
|
|
requestAudience: "bar",
|
|
decision: authorizer.DecisionDeny,
|
|
},
|
|
},
|
|
{
|
|
name: "allow create of token when ServiceAccountNodeAudienceRestriction is disabled, pvc not found should not be checked",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: csiDriverLister,
|
|
pvcGetter: noexistingPVCLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodWithPVCAndCSI.Name, v1mypodWithPVCAndCSI.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
},
|
|
{
|
|
name: "allow create of token when audience in pod --> csi --> driver --> tokenrequest with audience and ServiceAccountNodeAudienceRestriction is enabled, pv not found",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: csiDriverLister,
|
|
pvcGetter: pvcLister,
|
|
pvGetter: noexistingPVLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodWithPVCAndCSI.Name, v1mypodWithPVCAndCSI.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
},
|
|
{
|
|
name: "allow create of token when ServiceAccountNodeAudienceRestriction is disabled, pv not found should not be checked",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: csiDriverLister,
|
|
pvcGetter: pvcLister,
|
|
pvGetter: noexistingPVLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, false)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodWithPVCAndCSI.Name, v1mypodWithPVCAndCSI.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
},
|
|
{
|
|
name: "allow create of token when ServiceAccountNodeAudienceRestriction is disabled, csidriver not found should not be checked",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: noexistingCSIDriverLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, false)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodWithCSI.Name, v1mypodWithCSI.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
},
|
|
{
|
|
name: "intree pv to csi, allow create of token when audience in pod --> csi --> driver --> tokenrequest with audience, ServiceAccountNodeAudienceRestriction=true",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: csiDriverLister,
|
|
pvcGetter: pvcLister,
|
|
pvGetter: pvLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodIntreePVToCSI.Name, v1mypodIntreePVToCSI.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
},
|
|
{
|
|
name: "intree inline vol to csi, allow create of token when audience in pod --> csi --> driver --> tokenrequest with audience, ServiceAccountNodeAudienceRestriction=true",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: csiDriverLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypodIntreeInlineVolToCSI.Name, v1mypodIntreeInlineVolToCSI.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
},
|
|
{
|
|
name: "allow create of token when ServiceAccountNodeAudienceRestriction is enabled, clusterrole and clusterrolebinding are configured",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: noexistingCSIDriverLister,
|
|
pvcGetter: noexistingPVCLister,
|
|
pvGetter: noexistingPVLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypod.Name, v1mypod.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
authz: fakeAuthorizer{
|
|
t: t,
|
|
serviceAccountName: "mysa",
|
|
namespace: coremypod.Namespace,
|
|
requestAudience: "foo",
|
|
decision: authorizer.DecisionAllow,
|
|
},
|
|
},
|
|
{
|
|
name: "forbid create of token when ServiceAccountNodeAudienceRestriction is enabled, clusterrole and clusterrolebinding for audience not configured",
|
|
podsGetter: existingPods,
|
|
csiDriverGetter: noexistingCSIDriverLister,
|
|
pvcGetter: noexistingPVCLister,
|
|
pvGetter: noexistingPVLister,
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ServiceAccountNodeAudienceRestriction, true)
|
|
},
|
|
attributes: admission.NewAttributesRecord(makeTokenRequest(coremypod.Name, v1mypod.UID, []string{"foo"}), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
authz: fakeAuthorizer{
|
|
t: t,
|
|
serviceAccountName: "mysa",
|
|
namespace: coremypod.Namespace,
|
|
requestAudience: "foo",
|
|
decision: authorizer.DecisionDeny,
|
|
},
|
|
err: `serviceaccounts "mysa" is forbidden: audience "foo" not found in pod spec volume, system:node:mynode is not authorized to request tokens for this audience`,
|
|
},
|
|
|
|
// Unrelated objects
|
|
{
|
|
name: "allow create of unrelated object",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(&api.ConfigMap{}, nil, configmapKind, "myns", "mycm", configmapResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow update of unrelated object",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(&api.ConfigMap{}, &api.ConfigMap{}, configmapKind, "myns", "mycm", configmapResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow delete of unrelated object",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, configmapKind, "myns", "mycm", configmapResource, "", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
|
|
// Unrelated user
|
|
{
|
|
name: "allow unrelated user creating a normal pod unbound",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(coreunboundpod, nil, podKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "", admission.Create, &metav1.CreateOptions{}, false, bob),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow unrelated user update of normal pod unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(coreunboundpod, coreunboundpod, podKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "", admission.Update, &metav1.UpdateOptions{}, false, bob),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow unrelated user delete of normal pod unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "", admission.Delete, &metav1.DeleteOptions{}, false, bob),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow unrelated user create of normal pod status unbound",
|
|
podsGetter: noExistingPods,
|
|
attributes: admission.NewAttributesRecord(coreunboundpod, nil, podKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "status", admission.Create, &metav1.CreateOptions{}, false, bob),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow unrelated user update of normal pod status unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(coreunboundpod, coreunboundpod, podKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "status", admission.Update, &metav1.UpdateOptions{}, false, bob),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allow unrelated user delete of normal pod status unbound",
|
|
podsGetter: existingPods,
|
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, coreunboundpod.Namespace, coreunboundpod.Name, podResource, "status", admission.Delete, &metav1.DeleteOptions{}, false, bob),
|
|
err: "",
|
|
},
|
|
// Node leases
|
|
{
|
|
name: "disallowed create lease in namespace other than kube-node-lease - feature enabled",
|
|
attributes: admission.NewAttributesRecord(leaseWrongNS, nil, leaseKind, leaseWrongNS.Namespace, leaseWrongNS.Name, leaseResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "forbidden: ",
|
|
},
|
|
{
|
|
name: "disallowed update lease in namespace other than kube-node-lease - feature enabled",
|
|
attributes: admission.NewAttributesRecord(leaseWrongNS, leaseWrongNS, leaseKind, leaseWrongNS.Namespace, leaseWrongNS.Name, leaseResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: ",
|
|
},
|
|
{
|
|
name: "disallowed delete lease in namespace other than kube-node-lease - feature enabled",
|
|
attributes: admission.NewAttributesRecord(nil, nil, leaseKind, leaseWrongNS.Namespace, leaseWrongNS.Name, leaseResource, "", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "forbidden: ",
|
|
},
|
|
{
|
|
name: "disallowed create another node's lease - feature enabled",
|
|
attributes: admission.NewAttributesRecord(leaseWrongName, nil, leaseKind, leaseWrongName.Namespace, leaseWrongName.Name, leaseResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "forbidden: ",
|
|
},
|
|
{
|
|
name: "disallowed update another node's lease - feature enabled",
|
|
attributes: admission.NewAttributesRecord(leaseWrongName, leaseWrongName, leaseKind, leaseWrongName.Namespace, leaseWrongName.Name, leaseResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: ",
|
|
},
|
|
{
|
|
name: "disallowed delete another node's lease - feature enabled",
|
|
attributes: admission.NewAttributesRecord(nil, nil, leaseKind, leaseWrongName.Namespace, leaseWrongName.Name, leaseResource, "", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "forbidden: ",
|
|
},
|
|
{
|
|
name: "allowed create node lease - feature enabled",
|
|
attributes: admission.NewAttributesRecord(lease, nil, leaseKind, lease.Namespace, lease.Name, leaseResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allowed update node lease - feature enabled",
|
|
attributes: admission.NewAttributesRecord(lease, lease, leaseKind, lease.Namespace, lease.Name, leaseResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allowed delete node lease - feature enabled",
|
|
attributes: admission.NewAttributesRecord(nil, nil, leaseKind, lease.Namespace, lease.Name, leaseResource, "", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
// CSINode
|
|
{
|
|
name: "disallowed create another node's CSINode",
|
|
attributes: admission.NewAttributesRecord(nodeInfoWrongName, nil, csiNodeKind, nodeInfoWrongName.Namespace, nodeInfoWrongName.Name, csiNodeResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "forbidden: ",
|
|
},
|
|
{
|
|
name: "disallowed update another node's CSINode",
|
|
attributes: admission.NewAttributesRecord(nodeInfoWrongName, nodeInfoWrongName, csiNodeKind, nodeInfoWrongName.Namespace, nodeInfoWrongName.Name, csiNodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "forbidden: ",
|
|
},
|
|
{
|
|
name: "disallowed delete another node's CSINode",
|
|
attributes: admission.NewAttributesRecord(nil, nil, csiNodeKind, nodeInfoWrongName.Namespace, nodeInfoWrongName.Name, csiNodeResource, "", admission.Delete, &metav1.DeleteOptions{}, false, mynode),
|
|
err: "forbidden: ",
|
|
},
|
|
{
|
|
name: "allowed create node CSINode",
|
|
attributes: admission.NewAttributesRecord(nodeInfo, nil, csiNodeKind, nodeInfo.Namespace, nodeInfo.Name, csiNodeResource, "", admission.Create, &metav1.CreateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allowed update node CSINode",
|
|
attributes: admission.NewAttributesRecord(nodeInfo, nodeInfo, csiNodeKind, nodeInfo.Namespace, nodeInfo.Name, csiNodeResource, "", admission.Update, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allowed delete node CSINode",
|
|
attributes: admission.NewAttributesRecord(nil, nil, csiNodeKind, nodeInfo.Namespace, nodeInfo.Name, csiNodeResource, "", admission.Delete, &metav1.UpdateOptions{}, false, mynode),
|
|
err: "",
|
|
},
|
|
// CSR
|
|
{
|
|
name: "allowed CSR create correct node serving",
|
|
attributes: createCSRAttributes("system:node:mynode", certificatesapi.KubeletServingSignerName, true, privKey, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allowed CSR create correct node client",
|
|
attributes: createCSRAttributes("system:node:mynode", certificatesapi.KubeAPIServerClientKubeletSignerName, true, privKey, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "allowed CSR create non-node CSR",
|
|
attributes: createCSRAttributes("some-other-identity", certificatesapi.KubeAPIServerClientSignerName, true, privKey, mynode),
|
|
err: "",
|
|
},
|
|
{
|
|
name: "deny CSR create incorrect node",
|
|
attributes: createCSRAttributes("system:node:othernode", certificatesapi.KubeletServingSignerName, true, privKey, mynode),
|
|
err: "forbidden: can only create a node CSR with CN=system:node:mynode",
|
|
},
|
|
{
|
|
name: "allow CSR create incorrect node with feature gate disabled",
|
|
attributes: createCSRAttributes("system:node:othernode", certificatesapi.KubeletServingSignerName, true, privKey, mynode),
|
|
err: "",
|
|
features: feature.DefaultFeatureGate,
|
|
setupFunc: func(t *testing.T) {
|
|
t.Helper()
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.AllowInsecureKubeletCertificateSigningRequests, true)
|
|
},
|
|
},
|
|
{
|
|
name: "deny CSR create invalid",
|
|
attributes: createCSRAttributes("system:node:mynode", certificatesapi.KubeletServingSignerName, false, privKey, mynode),
|
|
err: "unable to parse csr: asn1: syntax error: sequence truncated",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tt.nodesGetter = existingNodes
|
|
tt.run(t)
|
|
}
|
|
}
|
|
|
|
func Test_nodePlugin_Admit_OwnerReference(t *testing.T) {
|
|
expectedNodeIndex := cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
|
expectedNodeIndex.Add(&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "mynode", UID: "mynode-uid"}})
|
|
expectedNode := corev1lister.NewNodeLister(expectedNodeIndex)
|
|
|
|
unexpectedNodeIndex := cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
|
unexpectedNodeIndex.Add(&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "mynode", UID: "mynode-unexpected-uid"}})
|
|
unexpectedNode := corev1lister.NewNodeLister(unexpectedNodeIndex)
|
|
|
|
noNodesIndex := cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
|
noNodes := corev1lister.NewNodeLister(noNodesIndex)
|
|
|
|
noExistingPodsIndex := cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
|
noExistingPods := corev1lister.NewPodLister(noExistingPodsIndex)
|
|
|
|
mynode := &user.DefaultInfo{Name: "system:node:mynode", Groups: []string{"system:nodes"}}
|
|
validOwner := metav1.OwnerReference{
|
|
APIVersion: "v1",
|
|
Kind: "Node",
|
|
Name: "mynode",
|
|
UID: "mynode-uid",
|
|
Controller: pointer.BoolPtr(true),
|
|
}
|
|
invalidName := validOwner
|
|
invalidName.Name = "other"
|
|
invalidKind := validOwner
|
|
invalidKind.Kind = "Pod"
|
|
invalidAPI := validOwner
|
|
invalidAPI.APIVersion = "v2"
|
|
invalidControllerNil := validOwner
|
|
invalidControllerNil.Controller = nil
|
|
invalidControllerFalse := validOwner
|
|
invalidControllerFalse.Controller = pointer.BoolPtr(false)
|
|
invalidBlockDeletion := validOwner
|
|
invalidBlockDeletion.BlockOwnerDeletion = pointer.BoolPtr(true)
|
|
|
|
tests := []struct {
|
|
name string
|
|
owners []metav1.OwnerReference
|
|
nodesGetter corev1lister.NodeLister
|
|
expectErr string
|
|
}{
|
|
{
|
|
name: "no owner",
|
|
owners: nil,
|
|
expectErr: "pods \"test\" is forbidden: node \"mynode\" can only create pods with an owner reference set to itself",
|
|
},
|
|
{
|
|
name: "valid owner",
|
|
owners: []metav1.OwnerReference{validOwner},
|
|
},
|
|
{
|
|
name: "duplicate owner",
|
|
owners: []metav1.OwnerReference{validOwner, validOwner},
|
|
expectErr: "can only create pods with a single owner reference set to itself",
|
|
},
|
|
{
|
|
name: "invalid name",
|
|
owners: []metav1.OwnerReference{invalidName},
|
|
expectErr: "can only create pods with an owner reference set to itself",
|
|
},
|
|
{
|
|
name: "invalid UID",
|
|
owners: []metav1.OwnerReference{validOwner},
|
|
nodesGetter: unexpectedNode,
|
|
expectErr: "UID mismatch",
|
|
},
|
|
{
|
|
name: "node not found",
|
|
owners: []metav1.OwnerReference{validOwner},
|
|
nodesGetter: noNodes,
|
|
expectErr: "not found",
|
|
},
|
|
{
|
|
name: "invalid API version",
|
|
owners: []metav1.OwnerReference{invalidAPI},
|
|
expectErr: "can only create pods with an owner reference set to itself",
|
|
},
|
|
{
|
|
name: "invalid kind",
|
|
owners: []metav1.OwnerReference{invalidKind},
|
|
expectErr: "can only create pods with an owner reference set to itself",
|
|
},
|
|
{
|
|
name: "nil controller",
|
|
owners: []metav1.OwnerReference{invalidControllerNil},
|
|
expectErr: "can only create pods with a controller owner reference set to itself",
|
|
},
|
|
{
|
|
name: "false controller",
|
|
owners: []metav1.OwnerReference{invalidControllerFalse},
|
|
expectErr: "can only create pods with a controller owner reference set to itself",
|
|
},
|
|
{
|
|
name: "invalid blockOwnerDeletion",
|
|
owners: []metav1.OwnerReference{invalidBlockDeletion},
|
|
expectErr: "must not set blockOwnerDeletion on an owner reference",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.nodesGetter == nil {
|
|
test.nodesGetter = expectedNode
|
|
}
|
|
|
|
pod, _ := makeTestPod("ns", "test", "mynode", true)
|
|
pod.OwnerReferences = test.owners
|
|
a := &admitTestCase{
|
|
name: test.name,
|
|
podsGetter: noExistingPods,
|
|
nodesGetter: test.nodesGetter,
|
|
attributes: createPodAttributes(pod, mynode),
|
|
err: test.expectErr,
|
|
}
|
|
a.run(t)
|
|
}
|
|
}
|
|
|
|
func Test_getModifiedLabels(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
a map[string]string
|
|
b map[string]string
|
|
want sets.String
|
|
}{
|
|
{
|
|
name: "empty",
|
|
a: nil,
|
|
b: nil,
|
|
want: sets.NewString(),
|
|
},
|
|
{
|
|
name: "no change",
|
|
a: map[string]string{"x": "1", "y": "2", "z": "3"},
|
|
b: map[string]string{"x": "1", "y": "2", "z": "3"},
|
|
want: sets.NewString(),
|
|
},
|
|
{
|
|
name: "added",
|
|
a: map[string]string{},
|
|
b: map[string]string{"a": "0"},
|
|
want: sets.NewString("a"),
|
|
},
|
|
{
|
|
name: "removed",
|
|
a: map[string]string{"z": "3"},
|
|
b: map[string]string{},
|
|
want: sets.NewString("z"),
|
|
},
|
|
{
|
|
name: "changed",
|
|
a: map[string]string{"z": "3"},
|
|
b: map[string]string{"z": "4"},
|
|
want: sets.NewString("z"),
|
|
},
|
|
{
|
|
name: "added empty",
|
|
a: map[string]string{},
|
|
b: map[string]string{"a": ""},
|
|
want: sets.NewString("a"),
|
|
},
|
|
{
|
|
name: "removed empty",
|
|
a: map[string]string{"z": ""},
|
|
b: map[string]string{},
|
|
want: sets.NewString("z"),
|
|
},
|
|
{
|
|
name: "changed to empty",
|
|
a: map[string]string{"z": "3"},
|
|
b: map[string]string{"z": ""},
|
|
want: sets.NewString("z"),
|
|
},
|
|
{
|
|
name: "changed from empty",
|
|
a: map[string]string{"z": ""},
|
|
b: map[string]string{"z": "3"},
|
|
want: sets.NewString("z"),
|
|
},
|
|
{
|
|
name: "added, removed, and changed",
|
|
a: map[string]string{"a": "1", "b": "2"},
|
|
b: map[string]string{"a": "2", "c": "3"},
|
|
want: sets.NewString("a", "b", "c"),
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := getModifiedLabels(tt.a, tt.b); !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("getModifiedLabels() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAdmitPVCStatus(t *testing.T) {
|
|
expectedNodeIndex := cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
|
expectedNodeIndex.Add(&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "mynode", UID: "mynode-uid"}})
|
|
expectedNode := corev1lister.NewNodeLister(expectedNodeIndex)
|
|
noExistingPodsIndex := cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
|
noExistingPods := corev1lister.NewPodLister(noExistingPodsIndex)
|
|
mynode := &user.DefaultInfo{Name: "system:node:mynode", Groups: []string{"system:nodes"}}
|
|
|
|
nodeExpansionFailed := api.PersistentVolumeClaimNodeResizeInfeasible
|
|
|
|
tests := []struct {
|
|
name string
|
|
resource schema.GroupVersionResource
|
|
subresource string
|
|
newObj runtime.Object
|
|
oldObj runtime.Object
|
|
expansionFeatureEnabled bool
|
|
recoveryFeatureEnabled bool
|
|
expectError string
|
|
}{
|
|
{
|
|
name: "should not allow full pvc update from nodes",
|
|
oldObj: makeTestPVC(
|
|
api.PersistentVolumeClaimResizing,
|
|
"10G", nil,
|
|
),
|
|
subresource: "",
|
|
newObj: makeTestPVC(
|
|
"", "10G", nil,
|
|
),
|
|
expectError: "is forbidden: may only update PVC status",
|
|
},
|
|
{
|
|
name: "should allow capacity and condition updates, if expansion is enabled",
|
|
oldObj: makeTestPVC(
|
|
api.PersistentVolumeClaimResizing,
|
|
"10G", nil,
|
|
),
|
|
expansionFeatureEnabled: true,
|
|
subresource: "status",
|
|
newObj: makeTestPVC(
|
|
api.PersistentVolumeClaimFileSystemResizePending,
|
|
"10G", nil,
|
|
),
|
|
expectError: "",
|
|
},
|
|
{
|
|
name: "should not allow updates to allocatedResources with just expansion enabled",
|
|
oldObj: makeTestPVC(
|
|
api.PersistentVolumeClaimResizing,
|
|
"10G", nil,
|
|
),
|
|
subresource: "status",
|
|
expansionFeatureEnabled: true,
|
|
newObj: makeTestPVC(
|
|
api.PersistentVolumeClaimFileSystemResizePending,
|
|
"15G", nil,
|
|
),
|
|
expectError: "is not allowed to update fields other than",
|
|
},
|
|
{
|
|
name: "should allow updates to allocatedResources with expansion and recovery enabled",
|
|
oldObj: makeTestPVC(
|
|
api.PersistentVolumeClaimResizing,
|
|
"10G", nil,
|
|
),
|
|
subresource: "status",
|
|
expansionFeatureEnabled: true,
|
|
recoveryFeatureEnabled: true,
|
|
newObj: makeTestPVC(
|
|
api.PersistentVolumeClaimFileSystemResizePending,
|
|
"15G", nil,
|
|
),
|
|
expectError: "",
|
|
},
|
|
{
|
|
name: "should allow updates to resizeStatus with expansion and recovery enabled",
|
|
oldObj: makeTestPVC(
|
|
api.PersistentVolumeClaimResizing,
|
|
"10G", nil,
|
|
),
|
|
subresource: "status",
|
|
expansionFeatureEnabled: true,
|
|
recoveryFeatureEnabled: true,
|
|
newObj: makeTestPVC(
|
|
api.PersistentVolumeClaimResizing,
|
|
"10G", &nodeExpansionFailed,
|
|
),
|
|
expectError: "",
|
|
},
|
|
}
|
|
|
|
for i := range tests {
|
|
test := tests[i]
|
|
t.Run(test.name, func(t *testing.T) {
|
|
operation := admission.Update
|
|
apiResource := api.SchemeGroupVersion.WithResource("persistentvolumeclaims")
|
|
attributes := admission.NewAttributesRecord(
|
|
test.newObj, test.oldObj, schema.GroupVersionKind{},
|
|
metav1.NamespaceDefault, "foo", apiResource, test.subresource, operation, &metav1.CreateOptions{}, false, mynode)
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, test.recoveryFeatureEnabled)
|
|
a := &admitTestCase{
|
|
name: test.name,
|
|
podsGetter: noExistingPods,
|
|
nodesGetter: expectedNode,
|
|
attributes: attributes,
|
|
features: feature.DefaultFeatureGate,
|
|
err: test.expectError,
|
|
}
|
|
a.run(t)
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
func makeTestPVC(
|
|
condition api.PersistentVolumeClaimConditionType,
|
|
allocatedResources string,
|
|
resizeStatus *api.ClaimResourceStatus) *api.PersistentVolumeClaim {
|
|
pvc := &api.PersistentVolumeClaim{
|
|
Spec: api.PersistentVolumeClaimSpec{
|
|
VolumeName: "volume1",
|
|
Resources: api.VolumeResourceRequirements{
|
|
Requests: api.ResourceList{
|
|
api.ResourceStorage: resource.MustParse("10G"),
|
|
},
|
|
},
|
|
},
|
|
Status: api.PersistentVolumeClaimStatus{
|
|
Capacity: api.ResourceList{
|
|
api.ResourceStorage: resource.MustParse(allocatedResources),
|
|
},
|
|
Phase: api.ClaimBound,
|
|
AllocatedResources: api.ResourceList{
|
|
api.ResourceStorage: resource.MustParse(allocatedResources),
|
|
},
|
|
},
|
|
}
|
|
if resizeStatus != nil {
|
|
claimStatusMap := map[api.ResourceName]api.ClaimResourceStatus{
|
|
api.ResourceStorage: *resizeStatus,
|
|
}
|
|
pvc.Status.AllocatedResourceStatuses = claimStatusMap
|
|
}
|
|
|
|
if len(condition) > 0 {
|
|
pvc.Status.Conditions = []api.PersistentVolumeClaimCondition{
|
|
{
|
|
Type: condition,
|
|
Status: api.ConditionTrue,
|
|
},
|
|
}
|
|
}
|
|
|
|
return pvc
|
|
}
|
|
|
|
func createPodAttributes(pod *api.Pod, user user.Info) admission.Attributes {
|
|
podResource := api.Resource("pods").WithVersion("v1")
|
|
podKind := api.Kind("Pod").WithVersion("v1")
|
|
return admission.NewAttributesRecord(pod, nil, podKind, pod.Namespace, pod.Name, podResource, "", admission.Create, &metav1.CreateOptions{}, false, user)
|
|
}
|
|
|
|
func createCSRAttributes(cn, signer string, validCsr bool, key any, user user.Info) admission.Attributes {
|
|
csrResource := certificatesapi.Resource("certificatesigningrequests").WithVersion("v1")
|
|
csrKind := certificatesapi.Kind("CertificateSigningRequest").WithVersion("v1")
|
|
|
|
csrPem := []byte("-----BEGIN CERTIFICATE REQUEST-----\n-----END CERTIFICATE REQUEST-----")
|
|
if validCsr {
|
|
structuredCsr := x509.CertificateRequest{
|
|
Subject: pkix.Name{
|
|
CommonName: cn,
|
|
},
|
|
}
|
|
csrDer, _ := x509.CreateCertificateRequest(rand.Reader, &structuredCsr, key)
|
|
csrPem = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrDer})
|
|
}
|
|
|
|
csreq := &certificatesapi.CertificateSigningRequest{
|
|
Spec: certificatesapi.CertificateSigningRequestSpec{
|
|
Request: csrPem,
|
|
SignerName: signer,
|
|
},
|
|
}
|
|
return admission.NewAttributesRecord(csreq, nil, csrKind, "", "", csrResource, "", admission.Create, &metav1.CreateOptions{}, false, user)
|
|
|
|
}
|
|
|
|
func TestAdmitResourceSlice(t *testing.T) {
|
|
apiResource := resourceapi.SchemeGroupVersion.WithResource("resourceslices")
|
|
nodename := "mynode"
|
|
mynode := &user.DefaultInfo{Name: "system:node:" + nodename, Groups: []string{"system:nodes"}}
|
|
createErr := "can only create ResourceSlice with the same NodeName as the requesting node"
|
|
deleteErr := "can only delete ResourceSlice with the same NodeName as the requesting node"
|
|
|
|
sliceNode := &resourceapi.ResourceSlice{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "something",
|
|
},
|
|
Spec: resourceapi.ResourceSliceSpec{
|
|
NodeName: nodename,
|
|
},
|
|
}
|
|
sliceOtherNode := &resourceapi.ResourceSlice{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "something",
|
|
},
|
|
Spec: resourceapi.ResourceSliceSpec{
|
|
NodeName: nodename + "-other",
|
|
},
|
|
}
|
|
sliceNoNode := &resourceapi.ResourceSlice{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "something",
|
|
},
|
|
Spec: resourceapi.ResourceSliceSpec{
|
|
NodeName: "",
|
|
},
|
|
}
|
|
|
|
tests := map[string]struct {
|
|
operation admission.Operation
|
|
options runtime.Object
|
|
obj, oldObj runtime.Object
|
|
featureEnabled bool
|
|
expectError string
|
|
}{
|
|
"create allowed, enabled": {
|
|
operation: admission.Create,
|
|
options: &metav1.CreateOptions{},
|
|
obj: sliceNode,
|
|
featureEnabled: true,
|
|
expectError: "",
|
|
},
|
|
"create disallowed, enabled": {
|
|
operation: admission.Create,
|
|
options: &metav1.CreateOptions{},
|
|
obj: sliceOtherNode,
|
|
featureEnabled: true,
|
|
expectError: createErr,
|
|
},
|
|
"create disallowed, no node name, enabled": {
|
|
operation: admission.Create,
|
|
options: &metav1.CreateOptions{},
|
|
obj: sliceNoNode,
|
|
featureEnabled: true,
|
|
expectError: createErr,
|
|
},
|
|
"create allowed, disabled": {
|
|
operation: admission.Create,
|
|
options: &metav1.CreateOptions{},
|
|
obj: sliceNode,
|
|
featureEnabled: false,
|
|
expectError: "",
|
|
},
|
|
"create disallowed, disabled": {
|
|
operation: admission.Create,
|
|
options: &metav1.CreateOptions{},
|
|
obj: sliceOtherNode,
|
|
featureEnabled: false,
|
|
expectError: createErr,
|
|
},
|
|
"create disallowed, no node name, disabled": {
|
|
operation: admission.Create,
|
|
options: &metav1.CreateOptions{},
|
|
obj: sliceNoNode,
|
|
featureEnabled: false,
|
|
expectError: createErr,
|
|
},
|
|
"update allowed, same node": {
|
|
operation: admission.Update,
|
|
options: &metav1.UpdateOptions{},
|
|
obj: sliceNode,
|
|
featureEnabled: true,
|
|
expectError: "",
|
|
},
|
|
"update allowed, other node": {
|
|
operation: admission.Update,
|
|
options: &metav1.UpdateOptions{},
|
|
obj: sliceOtherNode,
|
|
featureEnabled: true,
|
|
expectError: "",
|
|
},
|
|
"update allowed, no node": {
|
|
operation: admission.Update,
|
|
options: &metav1.UpdateOptions{},
|
|
obj: sliceNoNode,
|
|
featureEnabled: true,
|
|
expectError: "",
|
|
},
|
|
"delete allowed, enabled": {
|
|
operation: admission.Delete,
|
|
options: &metav1.DeleteOptions{},
|
|
oldObj: sliceNode,
|
|
featureEnabled: true,
|
|
expectError: "",
|
|
},
|
|
"delete disallowed, enabled": {
|
|
operation: admission.Delete,
|
|
options: &metav1.DeleteOptions{},
|
|
oldObj: sliceOtherNode,
|
|
featureEnabled: true,
|
|
expectError: deleteErr,
|
|
},
|
|
"delete disallowed, no node name, enabled": {
|
|
operation: admission.Delete,
|
|
options: &metav1.DeleteOptions{},
|
|
oldObj: sliceNoNode,
|
|
featureEnabled: true,
|
|
expectError: deleteErr,
|
|
},
|
|
"delete allowed, disabled": {
|
|
operation: admission.Delete,
|
|
options: &metav1.DeleteOptions{},
|
|
oldObj: sliceNode,
|
|
featureEnabled: false,
|
|
expectError: "",
|
|
},
|
|
"delete disallowed, disabled": {
|
|
operation: admission.Delete,
|
|
options: &metav1.DeleteOptions{},
|
|
oldObj: sliceOtherNode,
|
|
featureEnabled: false,
|
|
expectError: deleteErr,
|
|
},
|
|
"delete disallowed, no node name, disabled": {
|
|
operation: admission.Delete,
|
|
options: &metav1.DeleteOptions{},
|
|
oldObj: sliceNoNode,
|
|
featureEnabled: false,
|
|
expectError: deleteErr,
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
attributes := admission.NewAttributesRecord(
|
|
test.obj, test.oldObj, schema.GroupVersionKind{},
|
|
"", "foo", apiResource, "", test.operation, test.options, false, mynode)
|
|
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.DynamicResourceAllocation, test.featureEnabled)
|
|
a := &admitTestCase{
|
|
name: name,
|
|
attributes: attributes,
|
|
features: feature.DefaultFeatureGate,
|
|
err: test.expectError,
|
|
}
|
|
a.run(t)
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
func checkNilError(t *testing.T, err error) {
|
|
t.Helper()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
type fakeAuthorizer struct {
|
|
t *testing.T
|
|
serviceAccountName string
|
|
namespace string
|
|
requestAudience string
|
|
decision authorizer.Decision
|
|
err error
|
|
}
|
|
|
|
func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
|
if f.err != nil {
|
|
return f.decision, "forced error", f.err
|
|
}
|
|
|
|
expectedAttrs := authorizer.AttributesRecord{
|
|
User: &user.DefaultInfo{Name: "system:node:mynode", Groups: []string{"system:nodes"}},
|
|
Verb: "request-serviceaccounts-token-audience",
|
|
Namespace: f.namespace,
|
|
APIGroup: "",
|
|
APIVersion: "v1",
|
|
Resource: f.requestAudience,
|
|
Name: f.serviceAccountName,
|
|
ResourceRequest: true,
|
|
}
|
|
|
|
if !reflect.DeepEqual(a, expectedAttrs) {
|
|
f.t.Errorf("expected attributes: %v, got: %v", expectedAttrs, a)
|
|
}
|
|
|
|
return f.decision, "", nil
|
|
}
|