mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-08 11:38:15 +00:00
Merge pull request #116254 from pohly/dra-node-authorizer
node authorizer: limit kubelet access to ResourceClaim objects
This commit is contained in:
commit
f55f2785e2
@ -288,6 +288,9 @@ func (p *Plugin) admitPodStatus(nodeName string, a admission.Attributes) error {
|
|||||||
if !labels.Equals(oldPod.Labels, newPod.Labels) {
|
if !labels.Equals(oldPod.Labels, newPod.Labels) {
|
||||||
return admission.NewForbidden(a, fmt.Errorf("node %q cannot update labels through pod status", nodeName))
|
return admission.NewForbidden(a, fmt.Errorf("node %q cannot update labels through pod status", nodeName))
|
||||||
}
|
}
|
||||||
|
if !resourceClaimStatusesEqual(oldPod.Status.ResourceClaimStatuses, newPod.Status.ResourceClaimStatuses) {
|
||||||
|
return admission.NewForbidden(a, fmt.Errorf("node %q cannot update resource claim statues", nodeName))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -295,6 +298,29 @@ func (p *Plugin) admitPodStatus(nodeName string, a admission.Attributes) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resourceClaimStatusesEqual(statusA, statusB []api.PodResourceClaimStatus) bool {
|
||||||
|
if len(statusA) != len(statusB) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// In most cases, status entries only get added once and not modified.
|
||||||
|
// But this cannot be guaranteed, so for the sake of correctness in all
|
||||||
|
// cases this code here has to check.
|
||||||
|
for i := range statusA {
|
||||||
|
if statusA[i].Name != statusB[i].Name {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
claimNameA := statusA[i].ResourceClaimName
|
||||||
|
claimNameB := statusB[i].ResourceClaimName
|
||||||
|
if (claimNameA == nil) != (claimNameB == nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if claimNameA != nil && *claimNameA != *claimNameB {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// admitPodEviction allows to evict a pod if it is assigned to the current node.
|
// admitPodEviction allows to evict a pod if it is assigned to the current node.
|
||||||
func (p *Plugin) admitPodEviction(nodeName string, a admission.Attributes) error {
|
func (p *Plugin) admitPodEviction(nodeName string, a admission.Attributes) error {
|
||||||
switch a.GetOperation() {
|
switch a.GetOperation() {
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/component-helpers/storage/ephemeral"
|
"k8s.io/component-helpers/storage/ephemeral"
|
||||||
|
"k8s.io/dynamic-resource-allocation/resourceclaim"
|
||||||
pvutil "k8s.io/kubernetes/pkg/api/v1/persistentvolume"
|
pvutil "k8s.io/kubernetes/pkg/api/v1/persistentvolume"
|
||||||
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
||||||
"k8s.io/kubernetes/third_party/forked/gonum/graph"
|
"k8s.io/kubernetes/third_party/forked/gonum/graph"
|
||||||
@ -117,6 +118,7 @@ const (
|
|||||||
podVertexType
|
podVertexType
|
||||||
pvcVertexType
|
pvcVertexType
|
||||||
pvVertexType
|
pvVertexType
|
||||||
|
resourceClaimVertexType
|
||||||
secretVertexType
|
secretVertexType
|
||||||
vaVertexType
|
vaVertexType
|
||||||
serviceAccountVertexType
|
serviceAccountVertexType
|
||||||
@ -128,6 +130,7 @@ var vertexTypes = map[vertexType]string{
|
|||||||
podVertexType: "pod",
|
podVertexType: "pod",
|
||||||
pvcVertexType: "pvc",
|
pvcVertexType: "pvc",
|
||||||
pvVertexType: "pv",
|
pvVertexType: "pv",
|
||||||
|
resourceClaimVertexType: "resourceclaim",
|
||||||
secretVertexType: "secret",
|
secretVertexType: "secret",
|
||||||
vaVertexType: "volumeattachment",
|
vaVertexType: "volumeattachment",
|
||||||
serviceAccountVertexType: "serviceAccount",
|
serviceAccountVertexType: "serviceAccount",
|
||||||
@ -393,6 +396,20 @@ func (g *Graph) AddPod(pod *corev1.Pod) {
|
|||||||
g.addEdgeToDestinationIndex_locked(e)
|
g.addEdgeToDestinationIndex_locked(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, podResourceClaim := range pod.Spec.ResourceClaims {
|
||||||
|
claimName, _, err := resourceclaim.Name(pod, &podResourceClaim)
|
||||||
|
// Do we have a valid claim name? If yes, add an edge that grants
|
||||||
|
// kubelet access to that claim. An error indicates that a claim
|
||||||
|
// still needs to be created, nil that intentionally no claim
|
||||||
|
// was created and never will be because it isn't needed.
|
||||||
|
if err == nil && claimName != nil {
|
||||||
|
claimVertex := g.getOrCreateVertex_locked(resourceClaimVertexType, pod.Namespace, *claimName)
|
||||||
|
e := newDestinationEdge(claimVertex, podVertex, nodeVertex)
|
||||||
|
g.graph.SetEdge(e)
|
||||||
|
g.addEdgeToDestinationIndex_locked(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
func (g *Graph) DeletePod(name, namespace string) {
|
func (g *Graph) DeletePod(name, namespace string) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
@ -78,8 +78,9 @@ func (g *graphPopulator) updatePod(oldObj, obj interface{}) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if oldPod, ok := oldObj.(*corev1.Pod); ok && oldPod != nil {
|
if oldPod, ok := oldObj.(*corev1.Pod); ok && oldPod != nil {
|
||||||
if (pod.Spec.NodeName == oldPod.Spec.NodeName) && (pod.UID == oldPod.UID) {
|
if (pod.Spec.NodeName == oldPod.Spec.NodeName) && (pod.UID == oldPod.UID) &&
|
||||||
// Node and uid are unchanged, all object references in the pod spec are immutable
|
resourceClaimStatusesEqual(oldPod.Status.ResourceClaimStatuses, pod.Status.ResourceClaimStatuses) {
|
||||||
|
// Node and uid are unchanged, all object references in the pod spec are immutable respectively unmodified (claim statuses).
|
||||||
klog.V(5).Infof("updatePod %s/%s, node unchanged", pod.Namespace, pod.Name)
|
klog.V(5).Infof("updatePod %s/%s, node unchanged", pod.Namespace, pod.Name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -91,6 +92,29 @@ func (g *graphPopulator) updatePod(oldObj, obj interface{}) {
|
|||||||
klog.V(5).Infof("updatePod %s/%s for node %s completed in %v", pod.Namespace, pod.Name, pod.Spec.NodeName, time.Since(startTime))
|
klog.V(5).Infof("updatePod %s/%s for node %s completed in %v", pod.Namespace, pod.Name, pod.Spec.NodeName, time.Since(startTime))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resourceClaimStatusesEqual(statusA, statusB []corev1.PodResourceClaimStatus) bool {
|
||||||
|
if len(statusA) != len(statusB) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// In most cases, status entries only get added once and not modified.
|
||||||
|
// But this cannot be guaranteed, so for the sake of correctness in all
|
||||||
|
// cases this code here has to check.
|
||||||
|
for i := range statusA {
|
||||||
|
if statusA[i].Name != statusB[i].Name {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
claimNameA := statusA[i].ResourceClaimName
|
||||||
|
claimNameB := statusB[i].ResourceClaimName
|
||||||
|
if (claimNameA == nil) != (claimNameB == nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if claimNameA != nil && *claimNameA != *claimNameB {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (g *graphPopulator) deletePod(obj interface{}) {
|
func (g *graphPopulator) deletePod(obj interface{}) {
|
||||||
if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok {
|
if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok {
|
||||||
obj = tombstone.Obj
|
obj = tombstone.Obj
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
"k8s.io/component-base/featuregate"
|
"k8s.io/component-base/featuregate"
|
||||||
coordapi "k8s.io/kubernetes/pkg/apis/coordination"
|
coordapi "k8s.io/kubernetes/pkg/apis/coordination"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
resourceapi "k8s.io/kubernetes/pkg/apis/resource"
|
||||||
storageapi "k8s.io/kubernetes/pkg/apis/storage"
|
storageapi "k8s.io/kubernetes/pkg/apis/storage"
|
||||||
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
|
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
|
||||||
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
|
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
|
||||||
@ -40,7 +41,7 @@ import (
|
|||||||
// NodeAuthorizer authorizes requests from kubelets, with the following logic:
|
// NodeAuthorizer authorizes requests from kubelets, with the following logic:
|
||||||
// 1. If a request is not from a node (NodeIdentity() returns isNode=false), reject
|
// 1. If a request is not from a node (NodeIdentity() returns isNode=false), reject
|
||||||
// 2. If a specific node cannot be identified (NodeIdentity() returns nodeName=""), reject
|
// 2. If a specific node cannot be identified (NodeIdentity() returns nodeName=""), reject
|
||||||
// 3. If a request is for a secret, configmap, persistent volume or persistent volume claim, reject unless the verb is get, and the requested object is related to the requesting node:
|
// 3. If a request is for a secret, configmap, persistent volume, resource claim, or persistent volume claim, reject unless the verb is get, and the requested object is related to the requesting node:
|
||||||
// node <- configmap
|
// node <- configmap
|
||||||
// node <- pod
|
// node <- pod
|
||||||
// node <- pod <- secret
|
// node <- pod <- secret
|
||||||
@ -48,6 +49,7 @@ import (
|
|||||||
// node <- pod <- pvc
|
// node <- pod <- pvc
|
||||||
// node <- pod <- pvc <- pv
|
// node <- pod <- pvc <- pv
|
||||||
// node <- pod <- pvc <- pv <- secret
|
// node <- pod <- pvc <- pv <- secret
|
||||||
|
// node <- pod <- ResourceClaim
|
||||||
// 4. For other resources, authorize all nodes uniformly using statically defined rules
|
// 4. For other resources, authorize all nodes uniformly using statically defined rules
|
||||||
type NodeAuthorizer struct {
|
type NodeAuthorizer struct {
|
||||||
graph *Graph
|
graph *Graph
|
||||||
@ -72,14 +74,15 @@ func NewAuthorizer(graph *Graph, identifier nodeidentifier.NodeIdentifier, rules
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
configMapResource = api.Resource("configmaps")
|
configMapResource = api.Resource("configmaps")
|
||||||
secretResource = api.Resource("secrets")
|
secretResource = api.Resource("secrets")
|
||||||
pvcResource = api.Resource("persistentvolumeclaims")
|
pvcResource = api.Resource("persistentvolumeclaims")
|
||||||
pvResource = api.Resource("persistentvolumes")
|
pvResource = api.Resource("persistentvolumes")
|
||||||
vaResource = storageapi.Resource("volumeattachments")
|
resourceClaimResource = resourceapi.Resource("resourceclaims")
|
||||||
svcAcctResource = api.Resource("serviceaccounts")
|
vaResource = storageapi.Resource("volumeattachments")
|
||||||
leaseResource = coordapi.Resource("leases")
|
svcAcctResource = api.Resource("serviceaccounts")
|
||||||
csiNodeResource = storageapi.Resource("csinodes")
|
leaseResource = coordapi.Resource("leases")
|
||||||
|
csiNodeResource = storageapi.Resource("csinodes")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *NodeAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
|
func (r *NodeAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
|
||||||
@ -117,6 +120,8 @@ func (r *NodeAuthorizer) Authorize(ctx context.Context, attrs authorizer.Attribu
|
|||||||
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)
|
||||||
|
case resourceClaimResource:
|
||||||
|
return r.authorizeGet(nodeName, resourceClaimVertexType, attrs)
|
||||||
case vaResource:
|
case vaResource:
|
||||||
return r.authorizeGet(nodeName, vaVertexType, attrs)
|
return r.authorizeGet(nodeName, vaVertexType, attrs)
|
||||||
case svcAcctResource:
|
case svcAcctResource:
|
||||||
|
@ -42,16 +42,19 @@ func TestAuthorizer(t *testing.T) {
|
|||||||
g := NewGraph()
|
g := NewGraph()
|
||||||
|
|
||||||
opts := &sampleDataOpts{
|
opts := &sampleDataOpts{
|
||||||
nodes: 2,
|
nodes: 2,
|
||||||
namespaces: 2,
|
namespaces: 2,
|
||||||
podsPerNode: 2,
|
podsPerNode: 2,
|
||||||
attachmentsPerNode: 1,
|
attachmentsPerNode: 1,
|
||||||
sharedConfigMapsPerPod: 0,
|
sharedConfigMapsPerPod: 0,
|
||||||
uniqueConfigMapsPerPod: 1,
|
uniqueConfigMapsPerPod: 1,
|
||||||
sharedSecretsPerPod: 1,
|
sharedSecretsPerPod: 1,
|
||||||
uniqueSecretsPerPod: 1,
|
uniqueSecretsPerPod: 1,
|
||||||
sharedPVCsPerPod: 0,
|
sharedPVCsPerPod: 0,
|
||||||
uniquePVCsPerPod: 1,
|
uniquePVCsPerPod: 1,
|
||||||
|
uniqueResourceClaimsPerPod: 1,
|
||||||
|
uniqueResourceClaimTemplatesPerPod: 1,
|
||||||
|
uniqueResourceClaimTemplatesWithClaimPerPod: 1,
|
||||||
}
|
}
|
||||||
nodes, pods, pvs, attachments := generate(opts)
|
nodes, pods, pvs, attachments := generate(opts)
|
||||||
populate(g, nodes, pods, pvs, attachments)
|
populate(g, nodes, pods, pvs, attachments)
|
||||||
@ -117,6 +120,16 @@ func TestAuthorizer(t *testing.T) {
|
|||||||
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node0", Namespace: "ns0"},
|
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node0", Namespace: "ns0"},
|
||||||
expect: authorizer.DecisionAllow,
|
expect: authorizer.DecisionAllow,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "allowed resource claim",
|
||||||
|
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceclaims", APIGroup: "resource.k8s.io", Name: "claim0-pod0-node0-ns0", Namespace: "ns0"},
|
||||||
|
expect: authorizer.DecisionAllow,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allowed resource claim with template",
|
||||||
|
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceclaims", APIGroup: "resource.k8s.io", Name: "generated-claim-pod0-node0-ns0-0", Namespace: "ns0"},
|
||||||
|
expect: authorizer.DecisionAllow,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "allowed pv",
|
name: "allowed pv",
|
||||||
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node0-ns0", Namespace: ""},
|
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node0-ns0", Namespace: ""},
|
||||||
@ -142,6 +155,16 @@ func TestAuthorizer(t *testing.T) {
|
|||||||
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node1", Namespace: "ns0"},
|
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node1", Namespace: "ns0"},
|
||||||
expect: authorizer.DecisionNoOpinion,
|
expect: authorizer.DecisionNoOpinion,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "disallowed resource claim, other node",
|
||||||
|
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceclaims", APIGroup: "resource.k8s.io", Name: "claim0-pod0-node1-ns0", Namespace: "ns0"},
|
||||||
|
expect: authorizer.DecisionNoOpinion,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "disallowed resource claim with template",
|
||||||
|
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceclaims", APIGroup: "resource.k8s.io", Name: "pod0-node1-claimtemplate0", Namespace: "ns0"},
|
||||||
|
expect: authorizer.DecisionNoOpinion,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "disallowed pv",
|
name: "disallowed pv",
|
||||||
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node1-ns0", Namespace: ""},
|
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node1-ns0", Namespace: ""},
|
||||||
@ -468,9 +491,12 @@ type sampleDataOpts struct {
|
|||||||
sharedSecretsPerPod int
|
sharedSecretsPerPod int
|
||||||
sharedPVCsPerPod int
|
sharedPVCsPerPod int
|
||||||
|
|
||||||
uniqueSecretsPerPod int
|
uniqueSecretsPerPod int
|
||||||
uniqueConfigMapsPerPod int
|
uniqueConfigMapsPerPod int
|
||||||
uniquePVCsPerPod int
|
uniquePVCsPerPod int
|
||||||
|
uniqueResourceClaimsPerPod int
|
||||||
|
uniqueResourceClaimTemplatesPerPod int
|
||||||
|
uniqueResourceClaimTemplatesWithClaimPerPod int
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkPopulationAllocation(b *testing.B) {
|
func BenchmarkPopulationAllocation(b *testing.B) {
|
||||||
@ -845,6 +871,40 @@ func generatePod(name, namespace, nodeName, svcAccountName string, opts *sampleD
|
|||||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: pv.Spec.ClaimRef.Name},
|
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: pv.Spec.ClaimRef.Name},
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
|
for i := 0; i < opts.uniqueResourceClaimsPerPod; i++ {
|
||||||
|
claimName := fmt.Sprintf("claim%d-%s-%s", i, pod.Name, pod.Namespace)
|
||||||
|
pod.Spec.ResourceClaims = append(pod.Spec.ResourceClaims, corev1.PodResourceClaim{
|
||||||
|
Name: fmt.Sprintf("claim%d", i),
|
||||||
|
Source: corev1.ClaimSource{
|
||||||
|
ResourceClaimName: &claimName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for i := 0; i < opts.uniqueResourceClaimTemplatesPerPod; i++ {
|
||||||
|
claimTemplateName := fmt.Sprintf("claimtemplate%d-%s-%s", i, pod.Name, pod.Namespace)
|
||||||
|
podClaimName := fmt.Sprintf("claimtemplate%d", i)
|
||||||
|
pod.Spec.ResourceClaims = append(pod.Spec.ResourceClaims, corev1.PodResourceClaim{
|
||||||
|
Name: podClaimName,
|
||||||
|
Source: corev1.ClaimSource{
|
||||||
|
ResourceClaimTemplateName: &claimTemplateName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for i := 0; i < opts.uniqueResourceClaimTemplatesWithClaimPerPod; i++ {
|
||||||
|
claimTemplateName := fmt.Sprintf("claimtemplate%d-%s-%s", i, pod.Name, pod.Namespace)
|
||||||
|
podClaimName := fmt.Sprintf("claimtemplate-with-claim%d", i)
|
||||||
|
claimName := fmt.Sprintf("generated-claim-%s-%s-%d", pod.Name, pod.Namespace, i)
|
||||||
|
pod.Spec.ResourceClaims = append(pod.Spec.ResourceClaims, corev1.PodResourceClaim{
|
||||||
|
Name: podClaimName,
|
||||||
|
Source: corev1.ClaimSource{
|
||||||
|
ResourceClaimTemplateName: &claimTemplateName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
pod.Status.ResourceClaimStatuses = append(pod.Status.ResourceClaimStatuses, corev1.PodResourceClaimStatus{
|
||||||
|
Name: podClaimName,
|
||||||
|
ResourceClaimName: &claimName,
|
||||||
|
})
|
||||||
|
}
|
||||||
// Choose shared pvcs randomly from shared pvcs in a namespace.
|
// Choose shared pvcs randomly from shared pvcs in a namespace.
|
||||||
subset = randomSubset(opts.sharedPVCsPerPod, opts.sharedPVCsPerNamespace)
|
subset = randomSubset(opts.sharedPVCsPerPod, opts.sharedPVCsPerNamespace)
|
||||||
for _, i := range subset {
|
for _, i := range subset {
|
||||||
|
@ -27,14 +27,18 @@ import (
|
|||||||
coordination "k8s.io/api/coordination/v1"
|
coordination "k8s.io/api/coordination/v1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
policy "k8s.io/api/policy/v1"
|
policy "k8s.io/api/policy/v1"
|
||||||
|
"k8s.io/api/resource/v1alpha2"
|
||||||
storagev1 "k8s.io/api/storage/v1"
|
storagev1 "k8s.io/api/storage/v1"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "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/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/test/integration/framework"
|
"k8s.io/kubernetes/test/integration/framework"
|
||||||
"k8s.io/utils/pointer"
|
"k8s.io/utils/pointer"
|
||||||
)
|
)
|
||||||
@ -61,7 +65,10 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||||||
}, "\n"))
|
}, "\n"))
|
||||||
tokenFile.Close()
|
tokenFile.Close()
|
||||||
|
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DynamicResourceAllocation, true)()
|
||||||
|
|
||||||
server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{
|
server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{
|
||||||
|
"--runtime-config=api/all=true",
|
||||||
"--authorization-mode", "Node,RBAC",
|
"--authorization-mode", "Node,RBAC",
|
||||||
"--token-auth-file", tokenFile.Name(),
|
"--token-auth-file", tokenFile.Name(),
|
||||||
"--enable-admission-plugins", "NodeRestriction",
|
"--enable-admission-plugins", "NodeRestriction",
|
||||||
@ -100,6 +107,13 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||||||
if _, err := superuserClient.CoreV1().ConfigMaps("ns").Create(context.TODO(), &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "myconfigmap"}}, metav1.CreateOptions{}); err != nil {
|
if _, err := superuserClient.CoreV1().ConfigMaps("ns").Create(context.TODO(), &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "myconfigmap"}}, metav1.CreateOptions{}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if _, err := superuserClient.ResourceV1alpha2().ResourceClaims("ns").Create(context.TODO(), &v1alpha2.ResourceClaim{ObjectMeta: metav1.ObjectMeta{Name: "mynamedresourceclaim"}, Spec: v1alpha2.ResourceClaimSpec{ResourceClassName: "example.com"}}, metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := superuserClient.ResourceV1alpha2().ResourceClaims("ns").Create(context.TODO(), &v1alpha2.ResourceClaim{ObjectMeta: metav1.ObjectMeta{Name: "mytemplatizedresourceclaim"}, Spec: v1alpha2.ResourceClaimSpec{ResourceClassName: "example.com"}}, metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
pvName := "mypv"
|
pvName := "mypv"
|
||||||
if _, err := superuserClientExternal.StorageV1().VolumeAttachments().Create(context.TODO(), &storagev1.VolumeAttachment{
|
if _, err := superuserClientExternal.StorageV1().VolumeAttachments().Create(context.TODO(), &storagev1.VolumeAttachment{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "myattachment"},
|
ObjectMeta: metav1.ObjectMeta{Name: "myattachment"},
|
||||||
@ -169,6 +183,34 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
getResourceClaim := func(client clientset.Interface) func() error {
|
||||||
|
return func() error {
|
||||||
|
_, err := client.ResourceV1alpha2().ResourceClaims("ns").Get(context.TODO(), "mynamedresourceclaim", metav1.GetOptions{})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getResourceClaimTemplate := func(client clientset.Interface) func() error {
|
||||||
|
return func() error {
|
||||||
|
_, err := client.ResourceV1alpha2().ResourceClaims("ns").Get(context.TODO(), "mytemplatizedresourceclaim", metav1.GetOptions{})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addResourceClaimTemplateReference := func(client clientset.Interface) func() error {
|
||||||
|
return func() error {
|
||||||
|
_, err := client.CoreV1().Pods("ns").Patch(context.TODO(), "node2normalpod", types.MergePatchType,
|
||||||
|
[]byte(`{"status":{"resourceClaimStatuses":[{"name":"templateclaim","resourceClaimName":"mytemplatizedresourceclaim"}]}}`),
|
||||||
|
metav1.PatchOptions{}, "status")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removeResourceClaimReference := func(client clientset.Interface) func() error {
|
||||||
|
return func() error {
|
||||||
|
_, err := client.CoreV1().Pods("ns").Patch(context.TODO(), "node2normalpod", types.MergePatchType,
|
||||||
|
[]byte(`{"status":{"resourceClaimStatuses":null}}`),
|
||||||
|
metav1.PatchOptions{}, "status")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createNode2NormalPod := func(client clientset.Interface) func() error {
|
createNode2NormalPod := func(client clientset.Interface) func() error {
|
||||||
return func() error {
|
return func() error {
|
||||||
@ -182,6 +224,10 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||||||
{Name: "cm", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: "myconfigmap"}}}},
|
{Name: "cm", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: "myconfigmap"}}}},
|
||||||
{Name: "pvc", VolumeSource: corev1.VolumeSource{PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: "mypvc"}}},
|
{Name: "pvc", VolumeSource: corev1.VolumeSource{PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: "mypvc"}}},
|
||||||
},
|
},
|
||||||
|
ResourceClaims: []corev1.PodResourceClaim{
|
||||||
|
{Name: "namedclaim", Source: corev1.ClaimSource{ResourceClaimName: pointer.String("mynamedresourceclaim")}},
|
||||||
|
{Name: "templateclaim", Source: corev1.ClaimSource{ResourceClaimTemplateName: pointer.String("myresourceclaimtemplate")}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, metav1.CreateOptions{})
|
}, metav1.CreateOptions{})
|
||||||
return err
|
return err
|
||||||
@ -428,6 +474,8 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||||||
expectForbidden(t, getConfigMap(nodeanonClient))
|
expectForbidden(t, getConfigMap(nodeanonClient))
|
||||||
expectForbidden(t, getPVC(nodeanonClient))
|
expectForbidden(t, getPVC(nodeanonClient))
|
||||||
expectForbidden(t, getPV(nodeanonClient))
|
expectForbidden(t, getPV(nodeanonClient))
|
||||||
|
expectForbidden(t, getResourceClaim(nodeanonClient))
|
||||||
|
expectForbidden(t, getResourceClaimTemplate(nodeanonClient))
|
||||||
expectForbidden(t, createNode2NormalPod(nodeanonClient))
|
expectForbidden(t, createNode2NormalPod(nodeanonClient))
|
||||||
expectForbidden(t, deleteNode2NormalPod(nodeanonClient))
|
expectForbidden(t, deleteNode2NormalPod(nodeanonClient))
|
||||||
expectForbidden(t, createNode2MirrorPodEviction(nodeanonClient))
|
expectForbidden(t, createNode2MirrorPodEviction(nodeanonClient))
|
||||||
@ -440,6 +488,8 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||||||
expectForbidden(t, getConfigMap(node1Client))
|
expectForbidden(t, getConfigMap(node1Client))
|
||||||
expectForbidden(t, getPVC(node1Client))
|
expectForbidden(t, getPVC(node1Client))
|
||||||
expectForbidden(t, getPV(node1Client))
|
expectForbidden(t, getPV(node1Client))
|
||||||
|
expectForbidden(t, getResourceClaim(node1Client))
|
||||||
|
expectForbidden(t, getResourceClaimTemplate(node1Client))
|
||||||
expectForbidden(t, createNode2NormalPod(nodeanonClient))
|
expectForbidden(t, createNode2NormalPod(nodeanonClient))
|
||||||
expectNotFound(t, createNode2MirrorPodEviction(node1Client))
|
expectNotFound(t, createNode2MirrorPodEviction(node1Client))
|
||||||
expectForbidden(t, createNode2(node1Client))
|
expectForbidden(t, createNode2(node1Client))
|
||||||
@ -452,6 +502,8 @@ 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, getResourceClaim(node2Client))
|
||||||
|
expectForbidden(t, getResourceClaimTemplate(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
|
||||||
@ -479,6 +531,8 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||||||
expectForbidden(t, getConfigMap(nodeanonClient))
|
expectForbidden(t, getConfigMap(nodeanonClient))
|
||||||
expectForbidden(t, getPVC(nodeanonClient))
|
expectForbidden(t, getPVC(nodeanonClient))
|
||||||
expectForbidden(t, getPV(nodeanonClient))
|
expectForbidden(t, getPV(nodeanonClient))
|
||||||
|
expectForbidden(t, getResourceClaim(nodeanonClient))
|
||||||
|
expectForbidden(t, getResourceClaimTemplate(nodeanonClient))
|
||||||
expectForbidden(t, createNode2NormalPod(nodeanonClient))
|
expectForbidden(t, createNode2NormalPod(nodeanonClient))
|
||||||
expectForbidden(t, updateNode2NormalPodStatus(nodeanonClient))
|
expectForbidden(t, updateNode2NormalPodStatus(nodeanonClient))
|
||||||
expectForbidden(t, deleteNode2NormalPod(nodeanonClient))
|
expectForbidden(t, deleteNode2NormalPod(nodeanonClient))
|
||||||
@ -492,6 +546,8 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||||||
expectForbidden(t, getConfigMap(node1Client))
|
expectForbidden(t, getConfigMap(node1Client))
|
||||||
expectForbidden(t, getPVC(node1Client))
|
expectForbidden(t, getPVC(node1Client))
|
||||||
expectForbidden(t, getPV(node1Client))
|
expectForbidden(t, getPV(node1Client))
|
||||||
|
expectForbidden(t, getResourceClaim(node1Client))
|
||||||
|
expectForbidden(t, getResourceClaimTemplate(node1Client))
|
||||||
expectForbidden(t, createNode2NormalPod(node1Client))
|
expectForbidden(t, createNode2NormalPod(node1Client))
|
||||||
expectForbidden(t, updateNode2NormalPodStatus(node1Client))
|
expectForbidden(t, updateNode2NormalPodStatus(node1Client))
|
||||||
expectForbidden(t, deleteNode2NormalPod(node1Client))
|
expectForbidden(t, deleteNode2NormalPod(node1Client))
|
||||||
@ -507,6 +563,26 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||||||
expectAllowed(t, getPVC(node2Client))
|
expectAllowed(t, getPVC(node2Client))
|
||||||
expectAllowed(t, getPV(node2Client))
|
expectAllowed(t, getPV(node2Client))
|
||||||
|
|
||||||
|
// node2 can only get direct claim references
|
||||||
|
expectAllowed(t, getResourceClaim(node2Client))
|
||||||
|
expectForbidden(t, getResourceClaimTemplate(node2Client))
|
||||||
|
|
||||||
|
// node cannot add a claim reference
|
||||||
|
expectForbidden(t, addResourceClaimTemplateReference(node2Client))
|
||||||
|
// superuser can add a claim reference
|
||||||
|
expectAllowed(t, addResourceClaimTemplateReference(superuserClient))
|
||||||
|
// node can get direct and template claim references
|
||||||
|
expectAllowed(t, getResourceClaim(node2Client))
|
||||||
|
expectAllowed(t, getResourceClaimTemplate(node2Client))
|
||||||
|
|
||||||
|
// node cannot remove a claim reference
|
||||||
|
expectForbidden(t, removeResourceClaimReference(node2Client))
|
||||||
|
// superuser can remove a claim reference
|
||||||
|
expectAllowed(t, removeResourceClaimReference(superuserClient))
|
||||||
|
// node2 can only get direct claim references
|
||||||
|
expectAllowed(t, getResourceClaim(node2Client))
|
||||||
|
expectForbidden(t, getResourceClaimTemplate(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))
|
||||||
|
Loading…
Reference in New Issue
Block a user