Make NodeRestriction admission allow evictions for bounded pods

parent 088141ca
...@@ -15,6 +15,7 @@ go_library( ...@@ -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( ...@@ -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 ( ...@@ -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 { ...@@ -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 { ...@@ -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 ...@@ -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()
......
...@@ -113,6 +113,9 @@ func NodeRules() []rbac.PolicyRule { ...@@ -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
......
...@@ -689,6 +689,12 @@ items: ...@@ -689,6 +689,12 @@ items:
- apiGroups: - apiGroups:
- "" - ""
resources: resources:
- pods/eviction
verbs:
- create
- apiGroups:
- ""
resources:
- configmaps - configmaps
- secrets - secrets
verbs: verbs:
......
...@@ -27,6 +27,7 @@ go_test( ...@@ -27,6 +27,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 ( ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment