mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 07:20:13 +00:00
Merge pull request #48707 from danielfm/node-restriction-pod-eviction-subresource
Automatic merge from submit-queue Allow nodes to create evictions for its own pods in NodeRestriction admission controller **What this PR does / why we need it**: This PR adds support for `pods/eviction` sub-resource to the NodeRestriction admission controller so it allows a node to evict pods bound to itself. **Which issue this PR fixes**: fixes #48666 **Special notes for your reviewer**: The NodeRestriction already allows nodes to delete pods bound to itself, so allowing nodes to also delete pods via the Eviction API probably makes sense. ```release-note NodeRestriction allows a node to evict pods bound to itself ```
This commit is contained in:
commit
4d2a721223
@ -15,6 +15,7 @@ go_library(
|
|||||||
deps = [
|
deps = [
|
||||||
"//pkg/api:go_default_library",
|
"//pkg/api:go_default_library",
|
||||||
"//pkg/api/pod:go_default_library",
|
"//pkg/api/pod:go_default_library",
|
||||||
|
"//pkg/apis/policy:go_default_library",
|
||||||
"//pkg/auth/nodeidentifier:go_default_library",
|
"//pkg/auth/nodeidentifier:go_default_library",
|
||||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||||
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
|
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
|
||||||
@ -32,6 +33,7 @@ go_test(
|
|||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/api:go_default_library",
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/apis/policy:go_default_library",
|
||||||
"//pkg/auth/nodeidentifier:go_default_library",
|
"//pkg/auth/nodeidentifier:go_default_library",
|
||||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||||
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
|
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/policy"
|
||||||
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
|
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
|
||||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
coreinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
coreinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
||||||
@ -102,6 +103,8 @@ func (c *nodePlugin) Admit(a admission.Attributes) error {
|
|||||||
return c.admitPod(nodeName, a)
|
return c.admitPod(nodeName, a)
|
||||||
case "status":
|
case "status":
|
||||||
return c.admitPodStatus(nodeName, a)
|
return c.admitPodStatus(nodeName, a)
|
||||||
|
case "eviction":
|
||||||
|
return c.admitPodEviction(nodeName, a)
|
||||||
default:
|
default:
|
||||||
return admission.NewForbidden(a, fmt.Errorf("unexpected pod subresource %q", a.GetSubresource()))
|
return admission.NewForbidden(a, fmt.Errorf("unexpected pod subresource %q", a.GetSubresource()))
|
||||||
}
|
}
|
||||||
@ -161,6 +164,9 @@ func (c *nodePlugin) admitPod(nodeName string, a admission.Attributes) error {
|
|||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
// wasn't found in the server cache, do a live lookup before forbidding
|
// wasn't found in the server cache, do a live lookup before forbidding
|
||||||
existingPod, err = c.podsGetter.Pods(a.GetNamespace()).Get(a.GetName(), v1.GetOptions{})
|
existingPod, err = c.podsGetter.Pods(a.GetNamespace()).Get(a.GetName(), v1.GetOptions{})
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return admission.NewForbidden(a, err)
|
return admission.NewForbidden(a, err)
|
||||||
@ -195,6 +201,45 @@ func (c *nodePlugin) admitPodStatus(nodeName string, a admission.Attributes) err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *nodePlugin) admitPodEviction(nodeName string, a admission.Attributes) error {
|
||||||
|
switch a.GetOperation() {
|
||||||
|
case admission.Create:
|
||||||
|
// require eviction to an existing pod object
|
||||||
|
eviction, ok := a.GetObject().(*policy.Eviction)
|
||||||
|
if !ok {
|
||||||
|
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
|
||||||
|
}
|
||||||
|
// use pod name from the admission attributes, if set, rather than from the submitted Eviction object
|
||||||
|
podName := a.GetName()
|
||||||
|
if len(podName) == 0 {
|
||||||
|
if len(eviction.Name) == 0 {
|
||||||
|
return admission.NewForbidden(a, fmt.Errorf("could not determine pod from request data"))
|
||||||
|
}
|
||||||
|
podName = eviction.Name
|
||||||
|
}
|
||||||
|
// get the existing pod from the server cache
|
||||||
|
existingPod, err := c.podsGetter.Pods(a.GetNamespace()).Get(podName, v1.GetOptions{ResourceVersion: "0"})
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
// wasn't found in the server cache, do a live lookup before forbidding
|
||||||
|
existingPod, err = c.podsGetter.Pods(a.GetNamespace()).Get(podName, v1.GetOptions{})
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return admission.NewForbidden(a, err)
|
||||||
|
}
|
||||||
|
// only allow a node to evict a pod bound to itself
|
||||||
|
if existingPod.Spec.NodeName != nodeName {
|
||||||
|
return admission.NewForbidden(a, fmt.Errorf("node %s can only evict pods with spec.nodeName set to itself", nodeName))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return admission.NewForbidden(a, fmt.Errorf("unexpected operation %s", a.GetOperation()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *nodePlugin) admitNode(nodeName string, a admission.Attributes) error {
|
func (c *nodePlugin) admitNode(nodeName string, a admission.Attributes) error {
|
||||||
requestedName := a.GetName()
|
requestedName := a.GetName()
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/policy"
|
||||||
|
policyapi "k8s.io/kubernetes/pkg/apis/policy"
|
||||||
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
|
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
|
||||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||||
coreinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
coreinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
||||||
@ -40,6 +42,12 @@ func makeTestPod(namespace, name, node string, mirror bool) *api.Pod {
|
|||||||
return pod
|
return pod
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeTestPodEviction(name string) *policy.Eviction {
|
||||||
|
eviction := &policy.Eviction{}
|
||||||
|
eviction.Name = name
|
||||||
|
return eviction
|
||||||
|
}
|
||||||
|
|
||||||
func Test_nodePlugin_Admit(t *testing.T) {
|
func Test_nodePlugin_Admit(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
mynode = &user.DefaultInfo{Name: "system:node:mynode", Groups: []string{"system:nodes"}}
|
mynode = &user.DefaultInfo{Name: "system:node:mynode", Groups: []string{"system:nodes"}}
|
||||||
@ -54,12 +62,22 @@ func Test_nodePlugin_Admit(t *testing.T) {
|
|||||||
mypod = makeTestPod("ns", "mypod", "mynode", false)
|
mypod = makeTestPod("ns", "mypod", "mynode", false)
|
||||||
otherpod = makeTestPod("ns", "otherpod", "othernode", false)
|
otherpod = makeTestPod("ns", "otherpod", "othernode", false)
|
||||||
unboundpod = makeTestPod("ns", "unboundpod", "", false)
|
unboundpod = makeTestPod("ns", "unboundpod", "", false)
|
||||||
|
unnamedpod = 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")
|
configmapResource = api.Resource("configmap").WithVersion("v1")
|
||||||
configmapKind = api.Kind("ConfigMap").WithVersion("v1")
|
configmapKind = api.Kind("ConfigMap").WithVersion("v1")
|
||||||
|
|
||||||
podResource = api.Resource("pods").WithVersion("v1")
|
podResource = api.Resource("pods").WithVersion("v1")
|
||||||
podKind = api.Kind("Pod").WithVersion("v1")
|
podKind = api.Kind("Pod").WithVersion("v1")
|
||||||
|
evictionKind = policyapi.Kind("Eviction").WithVersion("v1beta1")
|
||||||
|
|
||||||
nodeResource = api.Resource("nodes").WithVersion("v1")
|
nodeResource = api.Resource("nodes").WithVersion("v1")
|
||||||
nodeKind = api.Kind("Node").WithVersion("v1")
|
nodeKind = api.Kind("Node").WithVersion("v1")
|
||||||
@ -123,6 +141,30 @@ func Test_nodePlugin_Admit(t *testing.T) {
|
|||||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "status", admission.Delete, mynode),
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "status", admission.Delete, mynode),
|
||||||
err: "forbidden: unexpected operation",
|
err: "forbidden: unexpected operation",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "allow create of eviction for mirror pod bound to self",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(mymirrorpodEviction, nil, evictionKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||||
|
err: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid update of eviction for mirror pod bound to self",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(mymirrorpodEviction, nil, evictionKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "eviction", admission.Update, mynode),
|
||||||
|
err: "forbidden: unexpected operation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid delete of eviction for mirror pod bound to self",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(mymirrorpodEviction, nil, evictionKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "eviction", admission.Delete, 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, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||||
|
err: "",
|
||||||
|
},
|
||||||
|
|
||||||
// Mirror pods bound to another node
|
// Mirror pods bound to another node
|
||||||
{
|
{
|
||||||
@ -161,6 +203,30 @@ func Test_nodePlugin_Admit(t *testing.T) {
|
|||||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "status", admission.Delete, mynode),
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "status", admission.Delete, mynode),
|
||||||
err: "forbidden: unexpected operation",
|
err: "forbidden: unexpected operation",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "forbid create of eviction for mirror pod bound to another",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(othermirrorpodEviction, nil, evictionKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "eviction", admission.Create, 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, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "eviction", admission.Update, mynode),
|
||||||
|
err: "forbidden: unexpected operation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid delete of eviction for mirror pod bound to another",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(othermirrorpodEviction, nil, evictionKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||||
|
err: "forbidden: unexpected operation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid create of unnamed eviction for mirror pod to another",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||||
|
err: "spec.nodeName set to itself",
|
||||||
|
},
|
||||||
|
|
||||||
// Mirror pods not bound to any node
|
// Mirror pods not bound to any node
|
||||||
{
|
{
|
||||||
@ -199,6 +265,30 @@ func Test_nodePlugin_Admit(t *testing.T) {
|
|||||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "status", admission.Delete, mynode),
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "status", admission.Delete, mynode),
|
||||||
err: "forbidden: unexpected operation",
|
err: "forbidden: unexpected operation",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "forbid create of eviction for mirror pod unbound",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(unboundmirrorpodEviction, nil, evictionKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||||
|
err: "spec.nodeName set to itself",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid update of eviction for mirror pod unbound",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(unboundmirrorpodEviction, nil, evictionKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "eviction", admission.Update, mynode),
|
||||||
|
err: "forbidden: unexpected operation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid delete of eviction for mirror pod unbound",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(unboundmirrorpodEviction, nil, evictionKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||||
|
err: "forbidden: unexpected operation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid create of unnamed eviction for mirror pod unbound",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||||
|
err: "spec.nodeName set to itself",
|
||||||
|
},
|
||||||
|
|
||||||
// Normal pods bound to us
|
// Normal pods bound to us
|
||||||
{
|
{
|
||||||
@ -237,6 +327,24 @@ func Test_nodePlugin_Admit(t *testing.T) {
|
|||||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, mypod.Namespace, mypod.Name, podResource, "status", admission.Delete, mynode),
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, mypod.Namespace, mypod.Name, podResource, "status", admission.Delete, mynode),
|
||||||
err: "forbidden: unexpected operation",
|
err: "forbidden: unexpected operation",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "forbid update of eviction for normal pod bound to self",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Update, mynode),
|
||||||
|
err: "forbidden: unexpected operation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid delete of eviction for normal pod bound to self",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Delete, 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, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Create, mynode),
|
||||||
|
err: "",
|
||||||
|
},
|
||||||
|
|
||||||
// Normal pods bound to another
|
// Normal pods bound to another
|
||||||
{
|
{
|
||||||
@ -275,6 +383,30 @@ func Test_nodePlugin_Admit(t *testing.T) {
|
|||||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, otherpod.Namespace, otherpod.Name, podResource, "status", admission.Delete, mynode),
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, otherpod.Namespace, otherpod.Name, podResource, "status", admission.Delete, mynode),
|
||||||
err: "forbidden: unexpected operation",
|
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, 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, 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, mynode),
|
||||||
|
err: "forbidden: unexpected operation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid create of eviction for normal pod bound to another",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, otherpod.Namespace, otherpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||||
|
err: "spec.nodeName set to itself",
|
||||||
|
},
|
||||||
|
|
||||||
// Normal pods not bound to any node
|
// Normal pods not bound to any node
|
||||||
{
|
{
|
||||||
@ -313,6 +445,30 @@ func Test_nodePlugin_Admit(t *testing.T) {
|
|||||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Delete, mynode),
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Delete, mynode),
|
||||||
err: "forbidden: unexpected operation",
|
err: "forbidden: unexpected operation",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "forbid create of eviction for normal pod unbound",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(unboundpodEviction, nil, evictionKind, unboundpod.Namespace, unboundpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||||
|
err: "spec.nodeName set to itself",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid update of eviction for normal pod unbound",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(unboundpodEviction, nil, evictionKind, unboundpod.Namespace, unboundpod.Name, podResource, "eviction", admission.Update, mynode),
|
||||||
|
err: "forbidden: unexpected operation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid delete of eviction for normal pod unbound",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(unboundpodEviction, nil, evictionKind, unboundpod.Namespace, unboundpod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||||
|
err: "forbidden: unexpected operation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid create of unnamed eviction for normal unbound",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, unboundpod.Namespace, unboundpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||||
|
err: "spec.nodeName set to itself",
|
||||||
|
},
|
||||||
|
|
||||||
// Missing pod
|
// Missing pod
|
||||||
{
|
{
|
||||||
@ -321,6 +477,57 @@ func Test_nodePlugin_Admit(t *testing.T) {
|
|||||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Delete, mynode),
|
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Delete, mynode),
|
||||||
err: "not found",
|
err: "not found",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "forbid create of eviction for unknown pod",
|
||||||
|
podsGetter: noExistingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Create, mynode),
|
||||||
|
err: "not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid update of eviction for unknown pod",
|
||||||
|
podsGetter: noExistingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Update, mynode),
|
||||||
|
err: "forbidden: unexpected operation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid delete of eviction for unknown pod",
|
||||||
|
podsGetter: noExistingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||||
|
err: "forbidden: unexpected operation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid create of unnamed eviction for unknown pod",
|
||||||
|
podsGetter: noExistingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Create, mynode),
|
||||||
|
err: "not found",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Eviction for unnamed pod
|
||||||
|
{
|
||||||
|
name: "allow create of eviction for unnamed pod",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, unnamedpod.Namespace, unnamedpod.Name, podResource, "eviction", admission.Create, 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, unnamedpod.Namespace, unnamedpod.Name, podResource, "eviction", admission.Update, mynode),
|
||||||
|
err: "forbidden: unexpected operation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid delete of eviction for unnamed pod",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, unnamedpod.Namespace, unnamedpod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||||
|
err: "forbidden: unexpected operation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbid create of unnamed eviction for unnamed pod",
|
||||||
|
podsGetter: existingPods,
|
||||||
|
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, unnamedpod.Namespace, unnamedpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||||
|
err: "could not determine pod from request data",
|
||||||
|
},
|
||||||
|
|
||||||
// Resource pods
|
// Resource pods
|
||||||
{
|
{
|
||||||
|
@ -113,6 +113,9 @@ func NodeRules() []rbac.PolicyRule {
|
|||||||
// Needed for the node to report status of pods it is running.
|
// Needed for the node to report status of pods it is running.
|
||||||
// Use the NodeRestriction admission plugin to limit a node to updating status of pods bound to itself.
|
// Use the NodeRestriction admission plugin to limit a node to updating status of pods bound to itself.
|
||||||
rbac.NewRule("update").Groups(legacyGroup).Resources("pods/status").RuleOrDie(),
|
rbac.NewRule("update").Groups(legacyGroup).Resources("pods/status").RuleOrDie(),
|
||||||
|
// Needed for the node to create pod evictions.
|
||||||
|
// Use the NodeRestriction admission plugin to limit a node to creating evictions for pods bound to itself.
|
||||||
|
rbac.NewRule("create").Groups(legacyGroup).Resources("pods/eviction").RuleOrDie(),
|
||||||
|
|
||||||
// Needed for imagepullsecrets, rbd/ceph and secret volumes, and secrets in envs
|
// Needed for imagepullsecrets, rbd/ceph and secret volumes, and secrets in envs
|
||||||
// Needed for configmap volume and envs
|
// Needed for configmap volume and envs
|
||||||
|
@ -686,6 +686,12 @@ items:
|
|||||||
- pods/status
|
- pods/status
|
||||||
verbs:
|
verbs:
|
||||||
- update
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- pods/eviction
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- ""
|
- ""
|
||||||
resources:
|
resources:
|
||||||
|
@ -28,6 +28,7 @@ go_test(
|
|||||||
"//pkg/apis/authorization:go_default_library",
|
"//pkg/apis/authorization:go_default_library",
|
||||||
"//pkg/apis/autoscaling:go_default_library",
|
"//pkg/apis/autoscaling:go_default_library",
|
||||||
"//pkg/apis/extensions:go_default_library",
|
"//pkg/apis/extensions:go_default_library",
|
||||||
|
"//pkg/apis/policy:go_default_library",
|
||||||
"//pkg/apis/rbac:go_default_library",
|
"//pkg/apis/rbac:go_default_library",
|
||||||
"//pkg/auth/authorizer/abac:go_default_library",
|
"//pkg/auth/authorizer/abac:go_default_library",
|
||||||
"//pkg/auth/nodeidentifier:go_default_library",
|
"//pkg/auth/nodeidentifier:go_default_library",
|
||||||
|
@ -32,6 +32,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/policy"
|
||||||
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
|
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
|
||||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||||
@ -224,6 +225,30 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||||||
deleteNode2 := func(client clientset.Interface) error {
|
deleteNode2 := func(client clientset.Interface) error {
|
||||||
return client.Core().Nodes().Delete("node2", nil)
|
return client.Core().Nodes().Delete("node2", nil)
|
||||||
}
|
}
|
||||||
|
createNode2NormalPodEviction := func(client clientset.Interface) error {
|
||||||
|
return client.Policy().Evictions("ns").Evict(&policy.Eviction{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "policy/v1beta1",
|
||||||
|
Kind: "Eviction",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "node2normalpod",
|
||||||
|
Namespace: "ns",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
createNode2MirrorPodEviction := func(client clientset.Interface) error {
|
||||||
|
return client.Policy().Evictions("ns").Evict(&policy.Eviction{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "policy/v1beta1",
|
||||||
|
Kind: "Eviction",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "node2mirrorpod",
|
||||||
|
Namespace: "ns",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
nodeanonClient := clientsetForToken(tokenNodeUnknown, clientConfig)
|
nodeanonClient := clientsetForToken(tokenNodeUnknown, clientConfig)
|
||||||
node1Client := clientsetForToken(tokenNode1, clientConfig)
|
node1Client := clientsetForToken(tokenNode1, clientConfig)
|
||||||
@ -237,7 +262,9 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||||||
expectForbidden(t, getPV(nodeanonClient))
|
expectForbidden(t, getPV(nodeanonClient))
|
||||||
expectForbidden(t, createNode2NormalPod(nodeanonClient))
|
expectForbidden(t, createNode2NormalPod(nodeanonClient))
|
||||||
expectForbidden(t, createNode2MirrorPod(nodeanonClient))
|
expectForbidden(t, createNode2MirrorPod(nodeanonClient))
|
||||||
|
expectForbidden(t, deleteNode2NormalPod(nodeanonClient))
|
||||||
expectForbidden(t, deleteNode2MirrorPod(nodeanonClient))
|
expectForbidden(t, deleteNode2MirrorPod(nodeanonClient))
|
||||||
|
expectForbidden(t, createNode2MirrorPodEviction(nodeanonClient))
|
||||||
expectForbidden(t, createNode2(nodeanonClient))
|
expectForbidden(t, createNode2(nodeanonClient))
|
||||||
expectForbidden(t, updateNode2Status(nodeanonClient))
|
expectForbidden(t, updateNode2Status(nodeanonClient))
|
||||||
expectForbidden(t, deleteNode2(nodeanonClient))
|
expectForbidden(t, deleteNode2(nodeanonClient))
|
||||||
@ -249,7 +276,8 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||||||
expectForbidden(t, getPV(node1Client))
|
expectForbidden(t, getPV(node1Client))
|
||||||
expectForbidden(t, createNode2NormalPod(nodeanonClient))
|
expectForbidden(t, createNode2NormalPod(nodeanonClient))
|
||||||
expectForbidden(t, createNode2MirrorPod(node1Client))
|
expectForbidden(t, createNode2MirrorPod(node1Client))
|
||||||
expectForbidden(t, deleteNode2MirrorPod(node1Client))
|
expectNotFound(t, deleteNode2MirrorPod(node1Client))
|
||||||
|
expectNotFound(t, createNode2MirrorPodEviction(node1Client))
|
||||||
expectForbidden(t, createNode2(node1Client))
|
expectForbidden(t, createNode2(node1Client))
|
||||||
expectForbidden(t, updateNode2Status(node1Client))
|
expectForbidden(t, updateNode2Status(node1Client))
|
||||||
expectForbidden(t, deleteNode2(node1Client))
|
expectForbidden(t, deleteNode2(node1Client))
|
||||||
@ -264,6 +292,8 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||||||
// mirror pod and self node lifecycle is allowed
|
// mirror pod and self node lifecycle is allowed
|
||||||
expectAllowed(t, createNode2MirrorPod(node2Client))
|
expectAllowed(t, createNode2MirrorPod(node2Client))
|
||||||
expectAllowed(t, deleteNode2MirrorPod(node2Client))
|
expectAllowed(t, deleteNode2MirrorPod(node2Client))
|
||||||
|
expectAllowed(t, createNode2MirrorPod(node2Client))
|
||||||
|
expectAllowed(t, createNode2MirrorPodEviction(node2Client))
|
||||||
expectAllowed(t, createNode2(node2Client))
|
expectAllowed(t, createNode2(node2Client))
|
||||||
expectAllowed(t, updateNode2Status(node2Client))
|
expectAllowed(t, updateNode2Status(node2Client))
|
||||||
expectAllowed(t, deleteNode2(node2Client))
|
expectAllowed(t, deleteNode2(node2Client))
|
||||||
@ -280,8 +310,10 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||||||
expectForbidden(t, createNode2NormalPod(nodeanonClient))
|
expectForbidden(t, createNode2NormalPod(nodeanonClient))
|
||||||
expectForbidden(t, updateNode2NormalPodStatus(nodeanonClient))
|
expectForbidden(t, updateNode2NormalPodStatus(nodeanonClient))
|
||||||
expectForbidden(t, deleteNode2NormalPod(nodeanonClient))
|
expectForbidden(t, deleteNode2NormalPod(nodeanonClient))
|
||||||
|
expectForbidden(t, createNode2NormalPodEviction(nodeanonClient))
|
||||||
expectForbidden(t, createNode2MirrorPod(nodeanonClient))
|
expectForbidden(t, createNode2MirrorPod(nodeanonClient))
|
||||||
expectForbidden(t, deleteNode2MirrorPod(nodeanonClient))
|
expectForbidden(t, deleteNode2MirrorPod(nodeanonClient))
|
||||||
|
expectForbidden(t, createNode2MirrorPodEviction(nodeanonClient))
|
||||||
|
|
||||||
expectForbidden(t, getSecret(node1Client))
|
expectForbidden(t, getSecret(node1Client))
|
||||||
expectForbidden(t, getPVSecret(node1Client))
|
expectForbidden(t, getPVSecret(node1Client))
|
||||||
@ -291,8 +323,10 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||||||
expectForbidden(t, createNode2NormalPod(node1Client))
|
expectForbidden(t, createNode2NormalPod(node1Client))
|
||||||
expectForbidden(t, updateNode2NormalPodStatus(node1Client))
|
expectForbidden(t, updateNode2NormalPodStatus(node1Client))
|
||||||
expectForbidden(t, deleteNode2NormalPod(node1Client))
|
expectForbidden(t, deleteNode2NormalPod(node1Client))
|
||||||
|
expectForbidden(t, createNode2NormalPodEviction(node1Client))
|
||||||
expectForbidden(t, createNode2MirrorPod(node1Client))
|
expectForbidden(t, createNode2MirrorPod(node1Client))
|
||||||
expectForbidden(t, deleteNode2MirrorPod(node1Client))
|
expectNotFound(t, deleteNode2MirrorPod(node1Client))
|
||||||
|
expectNotFound(t, createNode2MirrorPodEviction(node1Client))
|
||||||
|
|
||||||
// node2 can get referenced objects now
|
// node2 can get referenced objects now
|
||||||
expectAllowed(t, getSecret(node2Client))
|
expectAllowed(t, getSecret(node2Client))
|
||||||
@ -305,6 +339,11 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||||||
expectAllowed(t, deleteNode2NormalPod(node2Client))
|
expectAllowed(t, deleteNode2NormalPod(node2Client))
|
||||||
expectAllowed(t, createNode2MirrorPod(node2Client))
|
expectAllowed(t, createNode2MirrorPod(node2Client))
|
||||||
expectAllowed(t, deleteNode2MirrorPod(node2Client))
|
expectAllowed(t, deleteNode2MirrorPod(node2Client))
|
||||||
|
// recreate as an admin to test eviction
|
||||||
|
expectAllowed(t, createNode2NormalPod(superuserClient))
|
||||||
|
expectAllowed(t, createNode2MirrorPod(superuserClient))
|
||||||
|
expectAllowed(t, createNode2NormalPodEviction(node2Client))
|
||||||
|
expectAllowed(t, createNode2MirrorPodEviction(node2Client))
|
||||||
}
|
}
|
||||||
|
|
||||||
func expectForbidden(t *testing.T, err error) {
|
func expectForbidden(t *testing.T, err error) {
|
||||||
@ -314,6 +353,13 @@ func expectForbidden(t *testing.T, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func expectNotFound(t *testing.T, err error) {
|
||||||
|
if !errors.IsNotFound(err) {
|
||||||
|
_, file, line, _ := runtime.Caller(1)
|
||||||
|
t.Errorf("%s:%d: Expected notfound error, got %v", filepath.Base(file), line, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func expectAllowed(t *testing.T, err error) {
|
func expectAllowed(t *testing.T, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, file, line, _ := runtime.Caller(1)
|
_, file, line, _ := runtime.Caller(1)
|
||||||
|
Loading…
Reference in New Issue
Block a user