Allow node to update PVC's status

Implement node policy feature gates
Add tests for node policy update
This commit is contained in:
Hemant Kumar 2017-11-20 12:57:24 -05:00
parent b2a233b6d4
commit 1b76b0b2ff
7 changed files with 153 additions and 6 deletions

View File

@ -17,11 +17,14 @@ go_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",
"//pkg/features:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library", "//pkg/kubeapiserver/admission:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
], ],
) )

View File

@ -23,13 +23,16 @@ import (
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
utilfeature "k8s.io/apiserver/pkg/util/feature"
podutil "k8s.io/kubernetes/pkg/api/pod" podutil "k8s.io/kubernetes/pkg/api/pod"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy" "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"
"k8s.io/kubernetes/pkg/features"
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission" kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
) )
@ -82,6 +85,7 @@ func (p *nodePlugin) ValidateInitialization() error {
var ( var (
podResource = api.Resource("pods") podResource = api.Resource("pods")
nodeResource = api.Resource("nodes") nodeResource = api.Resource("nodes")
pvcResource = api.Resource("persistentvolumeclaims")
) )
func (c *nodePlugin) Admit(a admission.Attributes) error { func (c *nodePlugin) Admit(a admission.Attributes) error {
@ -113,6 +117,14 @@ func (c *nodePlugin) Admit(a admission.Attributes) error {
case nodeResource: case nodeResource:
return c.admitNode(nodeName, a) return c.admitNode(nodeName, a)
case pvcResource:
switch a.GetSubresource() {
case "status":
return c.admitPVCStatus(nodeName, a)
default:
return admission.NewForbidden(a, fmt.Errorf("may only update PVC status"))
}
default: default:
return nil return nil
} }
@ -189,7 +201,7 @@ func (c *nodePlugin) admitPodStatus(nodeName string, a admission.Attributes) err
// require an existing pod // require an existing pod
pod, ok := a.GetOldObject().(*api.Pod) pod, ok := a.GetOldObject().(*api.Pod)
if !ok { if !ok {
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject())) return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetOldObject()))
} }
// only allow a node to update status of a pod bound to itself // only allow a node to update status of a pod bound to itself
if pod.Spec.NodeName != nodeName { if pod.Spec.NodeName != nodeName {
@ -241,6 +253,50 @@ func (c *nodePlugin) admitPodEviction(nodeName string, a admission.Attributes) e
} }
} }
func (c *nodePlugin) admitPVCStatus(nodeName string, a admission.Attributes) error {
switch a.GetOperation() {
case admission.Update:
if !utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
return admission.NewForbidden(a, fmt.Errorf("node %q may not update persistentvolumeclaim metadata", nodeName))
}
oldPVC, ok := a.GetOldObject().(*api.PersistentVolumeClaim)
if !ok {
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetOldObject()))
}
newPVC, ok := a.GetObject().(*api.PersistentVolumeClaim)
if !ok {
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
}
// make copies for comparison
oldPVC = oldPVC.DeepCopy()
newPVC = newPVC.DeepCopy()
// zero out resourceVersion to avoid comparing differences,
// since the new object could leave it empty to indicate an unconditional update
oldPVC.ObjectMeta.ResourceVersion = ""
newPVC.ObjectMeta.ResourceVersion = ""
oldPVC.Status.Capacity = nil
newPVC.Status.Capacity = nil
oldPVC.Status.Conditions = nil
newPVC.Status.Conditions = nil
// ensure no metadata changed. nodes should not be able to relabel, add finalizers/owners, etc
if !apiequality.Semantic.DeepEqual(oldPVC, newPVC) {
return admission.NewForbidden(a, fmt.Errorf("node %q may not update fields other than status.capacity and status.conditions: %v", nodeName, diff.ObjectReflectDiff(oldPVC, newPVC)))
}
return nil
default:
return admission.NewForbidden(a, fmt.Errorf("unexpected operation %q", 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()
if a.GetOperation() == admission.Create { if a.GetOperation() == admission.Create {

View File

@ -36,6 +36,7 @@ go_library(
"//pkg/apis/rbac:go_default_library", "//pkg/apis/rbac:go_default_library",
"//pkg/auth/nodeidentifier:go_default_library", "//pkg/auth/nodeidentifier:go_default_library",
"//pkg/client/informers/informers_generated/internalversion/core/internalversion:go_default_library", "//pkg/client/informers/informers_generated/internalversion/core/internalversion:go_default_library",
"//pkg/features:go_default_library",
"//plugin/pkg/auth/authorizer/rbac:go_default_library", "//plugin/pkg/auth/authorizer/rbac:go_default_library",
"//third_party/forked/gonum/graph:go_default_library", "//third_party/forked/gonum/graph:go_default_library",
"//third_party/forked/gonum/graph/simple:go_default_library", "//third_party/forked/gonum/graph/simple:go_default_library",
@ -43,6 +44,7 @@ go_library(
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library",
], ],
) )

View File

@ -23,9 +23,11 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizer"
utilfeature "k8s.io/apiserver/pkg/util/feature"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
rbacapi "k8s.io/kubernetes/pkg/apis/rbac" rbacapi "k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/auth/nodeidentifier" "k8s.io/kubernetes/pkg/auth/nodeidentifier"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac" "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
"k8s.io/kubernetes/third_party/forked/gonum/graph" "k8s.io/kubernetes/third_party/forked/gonum/graph"
"k8s.io/kubernetes/third_party/forked/gonum/graph/traverse" "k8s.io/kubernetes/third_party/forked/gonum/graph/traverse"
@ -85,6 +87,11 @@ func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (authorizer.Deci
case configMapResource: case configMapResource:
return r.authorizeGet(nodeName, configMapVertexType, attrs) return r.authorizeGet(nodeName, configMapVertexType, attrs)
case pvcResource: case pvcResource:
if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
if attrs.GetSubresource() == "status" {
return r.authorizeStatusUpdate(nodeName, pvcVertexType, attrs)
}
}
return r.authorizeGet(nodeName, pvcVertexType, attrs) return r.authorizeGet(nodeName, pvcVertexType, attrs)
case pvResource: case pvResource:
return r.authorizeGet(nodeName, pvVertexType, attrs) return r.authorizeGet(nodeName, pvVertexType, attrs)
@ -98,17 +105,42 @@ func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (authorizer.Deci
return authorizer.DecisionNoOpinion, "", nil return authorizer.DecisionNoOpinion, "", nil
} }
// authorizeStatusUpdate authorizes get/update/patch requests to status subresources of the specified type if they are related to the specified node
func (r *NodeAuthorizer) authorizeStatusUpdate(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
switch attrs.GetVerb() {
case "update", "patch":
// ok
default:
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
return authorizer.DecisionNoOpinion, "can only get/update/patch this type", nil
}
if attrs.GetSubresource() != "status" {
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
return authorizer.DecisionNoOpinion, "can only update status subresource", nil
}
return r.authorize(nodeName, startingType, attrs)
}
// authorizeGet authorizes "get" requests to objects of the specified type if they are related to the specified node // authorizeGet authorizes "get" requests to objects of the specified type if they are related to the specified node
func (r *NodeAuthorizer) authorizeGet(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) { func (r *NodeAuthorizer) authorizeGet(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
if attrs.GetVerb() != "get" || len(attrs.GetName()) == 0 { if attrs.GetVerb() != "get" {
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
return authorizer.DecisionNoOpinion, "can only get individual resources of this type", nil return authorizer.DecisionNoOpinion, "can only get individual resources of this type", nil
} }
if len(attrs.GetSubresource()) > 0 { if len(attrs.GetSubresource()) > 0 {
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
return authorizer.DecisionNoOpinion, "cannot get subresource", nil return authorizer.DecisionNoOpinion, "cannot get subresource", nil
} }
return r.authorize(nodeName, startingType, attrs)
}
func (r *NodeAuthorizer) authorize(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
if len(attrs.GetName()) == 0 {
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
return authorizer.DecisionNoOpinion, "No Object name found", nil
}
ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName()) ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName())
if err != nil { if err != nil {

View File

@ -91,7 +91,7 @@ func addClusterRoleBindingLabel(rolebindings []rbac.ClusterRoleBinding) {
} }
func NodeRules() []rbac.PolicyRule { func NodeRules() []rbac.PolicyRule {
return []rbac.PolicyRule{ nodePolicyRules := []rbac.PolicyRule{
// Needed to check API access. These creates are non-mutating // Needed to check API access. These creates are non-mutating
rbac.NewRule("create").Groups(authenticationGroup).Resources("tokenreviews").RuleOrDie(), rbac.NewRule("create").Groups(authenticationGroup).Resources("tokenreviews").RuleOrDie(),
rbac.NewRule("create").Groups(authorizationGroup).Resources("subjectaccessreviews", "localsubjectaccessreviews").RuleOrDie(), rbac.NewRule("create").Groups(authorizationGroup).Resources("subjectaccessreviews", "localsubjectaccessreviews").RuleOrDie(),
@ -123,11 +123,12 @@ func NodeRules() []rbac.PolicyRule {
// 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
// Use the NodeRestriction admission plugin to limit a node to get secrets/configmaps referenced by pods bound to itself. // Use the Node authorization mode to limit a node to get secrets/configmaps referenced by pods bound to itself.
rbac.NewRule("get").Groups(legacyGroup).Resources("secrets", "configmaps").RuleOrDie(), rbac.NewRule("get").Groups(legacyGroup).Resources("secrets", "configmaps").RuleOrDie(),
// Needed for persistent volumes // Needed for persistent volumes
// Use the NodeRestriction admission plugin to limit a node to get pv/pvc objects referenced by pods bound to itself. // Use the Node authorization mode to limit a node to get pv/pvc objects referenced by pods bound to itself.
rbac.NewRule("get").Groups(legacyGroup).Resources("persistentvolumeclaims", "persistentvolumes").RuleOrDie(), rbac.NewRule("get").Groups(legacyGroup).Resources("persistentvolumeclaims", "persistentvolumes").RuleOrDie(),
// TODO: add to the Node authorizer and restrict to endpoints referenced by pods or PVs bound to the node // TODO: add to the Node authorizer and restrict to endpoints referenced by pods or PVs bound to the node
// Needed for glusterfs volumes // Needed for glusterfs volumes
rbac.NewRule("get").Groups(legacyGroup).Resources("endpoints").RuleOrDie(), rbac.NewRule("get").Groups(legacyGroup).Resources("endpoints").RuleOrDie(),
@ -135,6 +136,14 @@ func NodeRules() []rbac.PolicyRule {
// for it to be signed. This allows the kubelet to rotate it's own certificate. // for it to be signed. This allows the kubelet to rotate it's own certificate.
rbac.NewRule("create", "get", "list", "watch").Groups(certificatesGroup).Resources("certificatesigningrequests").RuleOrDie(), rbac.NewRule("create", "get", "list", "watch").Groups(certificatesGroup).Resources("certificatesigningrequests").RuleOrDie(),
} }
if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
// Use the Node authorization mode to limit a node to update status of pvc objects referenced by pods bound to itself.
// Use the NodeRestriction admission plugin to limit a node to just update the status stanza.
pvcStatusPolicyRule := rbac.NewRule("get", "update", "patch").Groups(legacyGroup).Resources("persistentvolumeclaims/status").RuleOrDie()
nodePolicyRules = append(nodePolicyRules, pvcStatusPolicyRule)
}
return nodePolicyRules
} }
// ClusterRoles returns the cluster roles to bootstrap an API server with // ClusterRoles returns the cluster roles to bootstrap an API server with

View File

@ -32,6 +32,7 @@ go_test(
"//pkg/bootstrap/api:go_default_library", "//pkg/bootstrap/api:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/features:go_default_library",
"//pkg/kubeapiserver/authorizer:go_default_library", "//pkg/kubeapiserver/authorizer:go_default_library",
"//pkg/master:go_default_library", "//pkg/master:go_default_library",
"//pkg/registry/rbac/clusterrole:go_default_library", "//pkg/registry/rbac/clusterrole:go_default_library",
@ -56,6 +57,7 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/group:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/group:go_default_library",
@ -66,6 +68,8 @@ go_test(
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature/testing:go_default_library",
"//vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/tokentest:go_default_library", "//vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/tokentest:go_default_library",
"//vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/webhook:go_default_library", "//vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/webhook:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library",

View File

@ -17,6 +17,7 @@ limitations under the License.
package auth package auth
import ( import (
"fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"path/filepath" "path/filepath"
@ -27,9 +28,12 @@ import (
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/authentication/request/bearertoken" "k8s.io/apiserver/pkg/authentication/request/bearertoken"
"k8s.io/apiserver/pkg/authentication/token/tokenfile" "k8s.io/apiserver/pkg/authentication/token/tokenfile"
"k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authentication/user"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
@ -37,6 +41,7 @@ import (
"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"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubeapiserver/authorizer" "k8s.io/kubernetes/pkg/kubeapiserver/authorizer"
"k8s.io/kubernetes/plugin/pkg/admission/noderestriction" "k8s.io/kubernetes/plugin/pkg/admission/noderestriction"
"k8s.io/kubernetes/test/integration/framework" "k8s.io/kubernetes/test/integration/framework"
@ -131,6 +136,7 @@ func TestNodeAuthorizer(t *testing.T) {
}); err != nil { }); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err := superuserClient.Core().PersistentVolumes().Create(&api.PersistentVolume{ if _, err := superuserClient.Core().PersistentVolumes().Create(&api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "mypv"}, ObjectMeta: metav1.ObjectMeta{Name: "mypv"},
Spec: api.PersistentVolumeSpec{ Spec: api.PersistentVolumeSpec{
@ -249,6 +255,21 @@ func TestNodeAuthorizer(t *testing.T) {
}) })
} }
capacity := 50
updatePVCCapacity := func(client clientset.Interface) error {
capacity++
statusString := fmt.Sprintf("{\"status\": {\"capacity\": {\"storage\": \"%dG\"}}}", capacity)
patchBytes := []byte(statusString)
_, err := client.Core().PersistentVolumeClaims("ns").Patch("mypvc", types.StrategicMergePatchType, patchBytes, "status")
return err
}
updatePVCPhase := func(client clientset.Interface) error {
patchBytes := []byte(`{"status":{"phase": "Bound"}}`)
_, err := client.Core().PersistentVolumeClaims("ns").Patch("mypvc", types.StrategicMergePatchType, patchBytes, "status")
return err
}
nodeanonClient := clientsetForToken(tokenNodeUnknown, clientConfig) nodeanonClient := clientsetForToken(tokenNodeUnknown, clientConfig)
node1Client := clientsetForToken(tokenNode1, clientConfig) node1Client := clientsetForToken(tokenNode1, clientConfig)
node2Client := clientsetForToken(tokenNode2, clientConfig) node2Client := clientsetForToken(tokenNode2, clientConfig)
@ -287,6 +308,7 @@ func TestNodeAuthorizer(t *testing.T) {
expectForbidden(t, getConfigMap(node2Client)) expectForbidden(t, getConfigMap(node2Client))
expectForbidden(t, getPVC(node2Client)) expectForbidden(t, getPVC(node2Client))
expectForbidden(t, getPV(node2Client)) expectForbidden(t, getPV(node2Client))
expectForbidden(t, createNode2NormalPod(nodeanonClient)) expectForbidden(t, createNode2NormalPod(nodeanonClient))
// mirror pod and self node lifecycle is allowed // mirror pod and self node lifecycle is allowed
expectAllowed(t, createNode2MirrorPod(node2Client)) expectAllowed(t, createNode2MirrorPod(node2Client))
@ -333,6 +355,7 @@ func TestNodeAuthorizer(t *testing.T) {
expectAllowed(t, getConfigMap(node2Client)) expectAllowed(t, getConfigMap(node2Client))
expectAllowed(t, getPVC(node2Client)) expectAllowed(t, getPVC(node2Client))
expectAllowed(t, getPV(node2Client)) expectAllowed(t, getPV(node2Client))
expectForbidden(t, createNode2NormalPod(node2Client)) expectForbidden(t, createNode2NormalPod(node2Client))
expectAllowed(t, updateNode2NormalPodStatus(node2Client)) expectAllowed(t, updateNode2NormalPodStatus(node2Client))
expectAllowed(t, deleteNode2NormalPod(node2Client)) expectAllowed(t, deleteNode2NormalPod(node2Client))
@ -343,6 +366,24 @@ func TestNodeAuthorizer(t *testing.T) {
expectAllowed(t, createNode2MirrorPod(superuserClient)) expectAllowed(t, createNode2MirrorPod(superuserClient))
expectAllowed(t, createNode2NormalPodEviction(node2Client)) expectAllowed(t, createNode2NormalPodEviction(node2Client))
expectAllowed(t, createNode2MirrorPodEviction(node2Client)) expectAllowed(t, createNode2MirrorPodEviction(node2Client))
// re-create a pod as an admin to add object references
expectAllowed(t, createNode2NormalPod(superuserClient))
// With ExpandPersistentVolumes feature disabled
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExpandPersistentVolumes, false)()
// node->pvc relationship not established
expectForbidden(t, updatePVCCapacity(node1Client))
// node->pvc relationship established but feature is disabled
expectForbidden(t, updatePVCCapacity(node2Client))
//Enabled ExpandPersistentVolumes feature
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExpandPersistentVolumes, true)()
// Node->pvc relationship not established
expectForbidden(t, updatePVCCapacity(node1Client))
// node->pvc relationship established and feature is enabled
expectAllowed(t, updatePVCCapacity(node2Client))
// node->pvc relationship established but updating phase
expectForbidden(t, updatePVCPhase(node2Client))
} }
func expectForbidden(t *testing.T, err error) { func expectForbidden(t *testing.T, err error) {