mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 09:22:44 +00:00
Merge pull request #127098 from tosi3k/node-auth
Make Node authorizer's index authoritative for unauthorized calls
This commit is contained in:
commit
0d86c02b26
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -79,7 +80,8 @@ func (e *destinationEdge) DestinationID() int { return e.Destination.ID() }
|
|||||||
// pvc <- pv
|
// pvc <- pv
|
||||||
// pv <- secret
|
// pv <- secret
|
||||||
type Graph struct {
|
type Graph struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
|
// Calling graph.SetEdge is restricted to the addEdgeLocked method of Graph.
|
||||||
graph *simple.DirectedAcyclicGraph
|
graph *simple.DirectedAcyclicGraph
|
||||||
// vertices is a map of type -> namespace -> name -> vertex
|
// vertices is a map of type -> namespace -> name -> vertex
|
||||||
vertices map[vertexType]namespaceVertexMapping
|
vertices map[vertexType]namespaceVertexMapping
|
||||||
@ -138,6 +140,20 @@ var vertexTypes = map[vertexType]string{
|
|||||||
serviceAccountVertexType: "serviceAccount",
|
serviceAccountVertexType: "serviceAccount",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// vertexTypeWithAuthoritativeIndex indicates which types of vertices can hold
|
||||||
|
// a destination edge index that is authoritative i.e. if the index exists,
|
||||||
|
// then it always stores all of the Nodes that are reachable from that vertex
|
||||||
|
// in the graph.
|
||||||
|
var vertexTypeWithAuthoritativeIndex = map[vertexType]bool{
|
||||||
|
configMapVertexType: true,
|
||||||
|
sliceVertexType: true,
|
||||||
|
podVertexType: true,
|
||||||
|
pvcVertexType: true,
|
||||||
|
resourceClaimVertexType: true,
|
||||||
|
vaVertexType: true,
|
||||||
|
serviceAccountVertexType: true,
|
||||||
|
}
|
||||||
|
|
||||||
// must be called under a write lock
|
// must be called under a write lock
|
||||||
func (g *Graph) getOrCreateVertexLocked(vertexType vertexType, namespace, name string) *namedVertex {
|
func (g *Graph) getOrCreateVertexLocked(vertexType vertexType, namespace, name string) *namedVertex {
|
||||||
if vertex, exists := g.getVertexRLocked(vertexType, namespace, name); exists {
|
if vertex, exists := g.getVertexRLocked(vertexType, namespace, name); exists {
|
||||||
@ -348,7 +364,8 @@ func (g *Graph) AddPod(pod *corev1.Pod) {
|
|||||||
g.deleteVertexLocked(podVertexType, pod.Namespace, pod.Name)
|
g.deleteVertexLocked(podVertexType, pod.Namespace, pod.Name)
|
||||||
podVertex := g.getOrCreateVertexLocked(podVertexType, pod.Namespace, pod.Name)
|
podVertex := g.getOrCreateVertexLocked(podVertexType, pod.Namespace, pod.Name)
|
||||||
nodeVertex := g.getOrCreateVertexLocked(nodeVertexType, "", pod.Spec.NodeName)
|
nodeVertex := g.getOrCreateVertexLocked(nodeVertexType, "", pod.Spec.NodeName)
|
||||||
g.graph.SetEdge(newDestinationEdge(podVertex, nodeVertex, nodeVertex))
|
// Edge adds must be handled by addEdgeLocked instead of direct g.graph.SetEdge calls.
|
||||||
|
g.addEdgeLocked(podVertex, nodeVertex, nodeVertex)
|
||||||
|
|
||||||
// Short-circuit adding edges to other resources for mirror pods.
|
// Short-circuit adding edges to other resources for mirror pods.
|
||||||
// A node must never be able to create a pod that grants them permissions on other API objects.
|
// A node must never be able to create a pod that grants them permissions on other API objects.
|
||||||
@ -363,24 +380,21 @@ func (g *Graph) AddPod(pod *corev1.Pod) {
|
|||||||
// ref https://github.com/kubernetes/kubernetes/issues/58790
|
// ref https://github.com/kubernetes/kubernetes/issues/58790
|
||||||
if len(pod.Spec.ServiceAccountName) > 0 {
|
if len(pod.Spec.ServiceAccountName) > 0 {
|
||||||
serviceAccountVertex := g.getOrCreateVertexLocked(serviceAccountVertexType, pod.Namespace, pod.Spec.ServiceAccountName)
|
serviceAccountVertex := g.getOrCreateVertexLocked(serviceAccountVertexType, pod.Namespace, pod.Spec.ServiceAccountName)
|
||||||
e := newDestinationEdge(serviceAccountVertex, podVertex, nodeVertex)
|
// Edge adds must be handled by addEdgeLocked instead of direct g.graph.SetEdge calls.
|
||||||
g.graph.SetEdge(e)
|
g.addEdgeLocked(serviceAccountVertex, podVertex, nodeVertex)
|
||||||
g.addEdgeToDestinationIndexLocked(e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
podutil.VisitPodSecretNames(pod, func(secret string) bool {
|
podutil.VisitPodSecretNames(pod, func(secret string) bool {
|
||||||
secretVertex := g.getOrCreateVertexLocked(secretVertexType, pod.Namespace, secret)
|
secretVertex := g.getOrCreateVertexLocked(secretVertexType, pod.Namespace, secret)
|
||||||
e := newDestinationEdge(secretVertex, podVertex, nodeVertex)
|
// Edge adds must be handled by addEdgeLocked instead of direct g.graph.SetEdge calls.
|
||||||
g.graph.SetEdge(e)
|
g.addEdgeLocked(secretVertex, podVertex, nodeVertex)
|
||||||
g.addEdgeToDestinationIndexLocked(e)
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
podutil.VisitPodConfigmapNames(pod, func(configmap string) bool {
|
podutil.VisitPodConfigmapNames(pod, func(configmap string) bool {
|
||||||
configmapVertex := g.getOrCreateVertexLocked(configMapVertexType, pod.Namespace, configmap)
|
configmapVertex := g.getOrCreateVertexLocked(configMapVertexType, pod.Namespace, configmap)
|
||||||
e := newDestinationEdge(configmapVertex, podVertex, nodeVertex)
|
// Edge adds must be handled by addEdgeLocked instead of direct g.graph.SetEdge calls.
|
||||||
g.graph.SetEdge(e)
|
g.addEdgeLocked(configmapVertex, podVertex, nodeVertex)
|
||||||
g.addEdgeToDestinationIndexLocked(e)
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -393,9 +407,8 @@ func (g *Graph) AddPod(pod *corev1.Pod) {
|
|||||||
}
|
}
|
||||||
if claimName != "" {
|
if claimName != "" {
|
||||||
pvcVertex := g.getOrCreateVertexLocked(pvcVertexType, pod.Namespace, claimName)
|
pvcVertex := g.getOrCreateVertexLocked(pvcVertexType, pod.Namespace, claimName)
|
||||||
e := newDestinationEdge(pvcVertex, podVertex, nodeVertex)
|
// Edge adds must be handled by addEdgeLocked instead of direct g.graph.SetEdge calls.
|
||||||
g.graph.SetEdge(e)
|
g.addEdgeLocked(pvcVertex, podVertex, nodeVertex)
|
||||||
g.addEdgeToDestinationIndexLocked(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,12 +420,34 @@ func (g *Graph) AddPod(pod *corev1.Pod) {
|
|||||||
// was created and never will be because it isn't needed.
|
// was created and never will be because it isn't needed.
|
||||||
if err == nil && claimName != nil {
|
if err == nil && claimName != nil {
|
||||||
claimVertex := g.getOrCreateVertexLocked(resourceClaimVertexType, pod.Namespace, *claimName)
|
claimVertex := g.getOrCreateVertexLocked(resourceClaimVertexType, pod.Namespace, *claimName)
|
||||||
e := newDestinationEdge(claimVertex, podVertex, nodeVertex)
|
// Edge adds must be handled by addEdgeLocked instead of direct g.graph.SetEdge calls.
|
||||||
g.graph.SetEdge(e)
|
g.addEdgeLocked(claimVertex, podVertex, nodeVertex)
|
||||||
g.addEdgeToDestinationIndexLocked(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Must be called under a write lock.
|
||||||
|
// All edge adds must be handled by that method rather than by calling
|
||||||
|
// g.graph.SetEdge directly.
|
||||||
|
// Note: if "from" belongs to vertexTypeWithAuthoritativeIndex, then
|
||||||
|
// "destination" must be non-nil.
|
||||||
|
func (g *Graph) addEdgeLocked(from, to, destination *namedVertex) {
|
||||||
|
if destination != nil {
|
||||||
|
e := newDestinationEdge(from, to, destination)
|
||||||
|
g.graph.SetEdge(e)
|
||||||
|
g.addEdgeToDestinationIndexLocked(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// We must not create edges without a Node label from a vertex that is
|
||||||
|
// supposed to hold authoritative destination edge index only.
|
||||||
|
// Entering this branch would mean there's a bug in the Node authorizer.
|
||||||
|
if vertexTypeWithAuthoritativeIndex[from.vertexType] {
|
||||||
|
panic(fmt.Sprintf("vertex of type %q must have destination edges only", vertexTypes[from.vertexType]))
|
||||||
|
}
|
||||||
|
g.graph.SetEdge(simple.Edge{F: from, T: to})
|
||||||
|
}
|
||||||
|
|
||||||
func (g *Graph) DeletePod(name, namespace string) {
|
func (g *Graph) DeletePod(name, namespace string) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -444,11 +479,13 @@ func (g *Graph) AddPV(pv *corev1.PersistentVolume) {
|
|||||||
pvVertex := g.getOrCreateVertexLocked(pvVertexType, "", pv.Name)
|
pvVertex := g.getOrCreateVertexLocked(pvVertexType, "", pv.Name)
|
||||||
|
|
||||||
// since we don't know the other end of the pvc -> pod -> node chain (or it may not even exist yet), we can't decorate these edges with kubernetes node info
|
// since we don't know the other end of the pvc -> pod -> node chain (or it may not even exist yet), we can't decorate these edges with kubernetes node info
|
||||||
g.graph.SetEdge(simple.Edge{F: pvVertex, T: g.getOrCreateVertexLocked(pvcVertexType, pv.Spec.ClaimRef.Namespace, pv.Spec.ClaimRef.Name)})
|
// Edge adds must be handled by addEdgeLocked instead of direct g.graph.SetEdge calls.
|
||||||
|
g.addEdgeLocked(pvVertex, g.getOrCreateVertexLocked(pvcVertexType, pv.Spec.ClaimRef.Namespace, pv.Spec.ClaimRef.Name), nil)
|
||||||
pvutil.VisitPVSecretNames(pv, func(namespace, secret string, kubeletVisible bool) bool {
|
pvutil.VisitPVSecretNames(pv, func(namespace, secret string, kubeletVisible bool) bool {
|
||||||
// This grants access to the named secret in the same namespace as the bound PVC
|
// This grants access to the named secret in the same namespace as the bound PVC
|
||||||
if kubeletVisible {
|
if kubeletVisible {
|
||||||
g.graph.SetEdge(simple.Edge{F: g.getOrCreateVertexLocked(secretVertexType, namespace, secret), T: pvVertex})
|
// Edge adds must be handled by addEdgeLocked instead of direct g.graph.SetEdge calls.
|
||||||
|
g.addEdgeLocked(g.getOrCreateVertexLocked(secretVertexType, namespace, secret), pvVertex, nil)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@ -482,7 +519,8 @@ func (g *Graph) AddVolumeAttachment(attachmentName, nodeName string) {
|
|||||||
if len(nodeName) > 0 {
|
if len(nodeName) > 0 {
|
||||||
vaVertex := g.getOrCreateVertexLocked(vaVertexType, "", attachmentName)
|
vaVertex := g.getOrCreateVertexLocked(vaVertexType, "", attachmentName)
|
||||||
nodeVertex := g.getOrCreateVertexLocked(nodeVertexType, "", nodeName)
|
nodeVertex := g.getOrCreateVertexLocked(nodeVertexType, "", nodeName)
|
||||||
g.graph.SetEdge(newDestinationEdge(vaVertex, nodeVertex, nodeVertex))
|
// Edge adds must be handled by addEdgeLocked instead of direct g.graph.SetEdge calls.
|
||||||
|
g.addEdgeLocked(vaVertex, nodeVertex, nodeVertex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (g *Graph) DeleteVolumeAttachment(name string) {
|
func (g *Graph) DeleteVolumeAttachment(name string) {
|
||||||
@ -513,7 +551,8 @@ func (g *Graph) AddResourceSlice(sliceName, nodeName string) {
|
|||||||
if len(nodeName) > 0 {
|
if len(nodeName) > 0 {
|
||||||
sliceVertex := g.getOrCreateVertexLocked(sliceVertexType, "", sliceName)
|
sliceVertex := g.getOrCreateVertexLocked(sliceVertexType, "", sliceName)
|
||||||
nodeVertex := g.getOrCreateVertexLocked(nodeVertexType, "", nodeName)
|
nodeVertex := g.getOrCreateVertexLocked(nodeVertexType, "", nodeName)
|
||||||
g.graph.SetEdge(newDestinationEdge(sliceVertex, nodeVertex, nodeVertex))
|
// Edge adds must be handled by addEdgeLocked instead of direct g.graph.SetEdge calls.
|
||||||
|
g.addEdgeLocked(sliceVertex, nodeVertex, nodeVertex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (g *Graph) DeleteResourceSlice(sliceName string) {
|
func (g *Graph) DeleteResourceSlice(sliceName string) {
|
||||||
|
@ -52,7 +52,7 @@ func TestDeleteEdgesLocked(t *testing.T) {
|
|||||||
g.getOrCreateVertexLocked(configMapVertexType, "namespace1", "configmap2")
|
g.getOrCreateVertexLocked(configMapVertexType, "namespace1", "configmap2")
|
||||||
nodeVertex := g.getOrCreateVertexLocked(nodeVertexType, "", "node1")
|
nodeVertex := g.getOrCreateVertexLocked(nodeVertexType, "", "node1")
|
||||||
configmapVertex := g.getOrCreateVertexLocked(configMapVertexType, "namespace1", "configmap1")
|
configmapVertex := g.getOrCreateVertexLocked(configMapVertexType, "namespace1", "configmap1")
|
||||||
g.graph.SetEdge(newDestinationEdge(configmapVertex, nodeVertex, nodeVertex))
|
g.addEdgeLocked(configmapVertex, nodeVertex, nodeVertex)
|
||||||
return g
|
return g
|
||||||
}(),
|
}(),
|
||||||
expect: func() *Graph {
|
expect: func() *Graph {
|
||||||
@ -74,8 +74,8 @@ func TestDeleteEdgesLocked(t *testing.T) {
|
|||||||
nodeVertex1 := g.getOrCreateVertexLocked(nodeVertexType, "", "node1")
|
nodeVertex1 := g.getOrCreateVertexLocked(nodeVertexType, "", "node1")
|
||||||
nodeVertex2 := g.getOrCreateVertexLocked(nodeVertexType, "", "node2")
|
nodeVertex2 := g.getOrCreateVertexLocked(nodeVertexType, "", "node2")
|
||||||
configmapVertex := g.getOrCreateVertexLocked(configMapVertexType, "namespace1", "configmap1")
|
configmapVertex := g.getOrCreateVertexLocked(configMapVertexType, "namespace1", "configmap1")
|
||||||
g.graph.SetEdge(newDestinationEdge(configmapVertex, nodeVertex1, nodeVertex1))
|
g.addEdgeLocked(configmapVertex, nodeVertex1, nodeVertex1)
|
||||||
g.graph.SetEdge(newDestinationEdge(configmapVertex, nodeVertex2, nodeVertex2))
|
g.addEdgeLocked(configmapVertex, nodeVertex2, nodeVertex2)
|
||||||
return g
|
return g
|
||||||
}(),
|
}(),
|
||||||
expect: func() *Graph {
|
expect: func() *Graph {
|
||||||
@ -83,7 +83,7 @@ func TestDeleteEdgesLocked(t *testing.T) {
|
|||||||
nodeVertex1 := g.getOrCreateVertexLocked(nodeVertexType, "", "node1")
|
nodeVertex1 := g.getOrCreateVertexLocked(nodeVertexType, "", "node1")
|
||||||
g.getOrCreateVertexLocked(nodeVertexType, "", "node2")
|
g.getOrCreateVertexLocked(nodeVertexType, "", "node2")
|
||||||
configmapVertex := g.getOrCreateVertexLocked(configMapVertexType, "namespace1", "configmap1")
|
configmapVertex := g.getOrCreateVertexLocked(configMapVertexType, "namespace1", "configmap1")
|
||||||
g.graph.SetEdge(newDestinationEdge(configmapVertex, nodeVertex1, nodeVertex1))
|
g.addEdgeLocked(configmapVertex, nodeVertex1, nodeVertex1)
|
||||||
return g
|
return g
|
||||||
}(),
|
}(),
|
||||||
},
|
},
|
||||||
@ -344,3 +344,558 @@ func TestIndex(t *testing.T) {
|
|||||||
"serviceAccount:ns/sa1": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
"serviceAccount:ns/sa1": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIndex2(t *testing.T) {
|
||||||
|
NewTestGraph := func() *Graph {
|
||||||
|
g := NewGraph()
|
||||||
|
g.destinationEdgeThreshold = 3
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
pod := func(podName, nodeName, saName string, volumes []corev1.Volume, resourceClaims []corev1.PodResourceClaim) *corev1.Pod {
|
||||||
|
p := &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: "ns", UID: types.UID(fmt.Sprintf("pod%suid", podName))},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
NodeName: nodeName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if saName != "" {
|
||||||
|
p.Spec.ServiceAccountName = saName
|
||||||
|
}
|
||||||
|
if volumes != nil {
|
||||||
|
p.Spec.Volumes = volumes
|
||||||
|
}
|
||||||
|
if resourceClaims != nil {
|
||||||
|
p.Spec.ResourceClaims = resourceClaims
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
podWithSAAndCMs := func(podName, nodeName string) *corev1.Pod {
|
||||||
|
cm := func(name string) corev1.Volume {
|
||||||
|
return corev1.Volume{Name: name, VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: name}}}}
|
||||||
|
}
|
||||||
|
return pod(podName, nodeName, "sa1", []corev1.Volume{
|
||||||
|
cm("cm1"),
|
||||||
|
cm("cm2"),
|
||||||
|
cm("cm3"),
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
podWithSecrets := func(podName, nodeName string) *corev1.Pod {
|
||||||
|
secret := func(name string) corev1.Volume {
|
||||||
|
return corev1.Volume{Name: name, VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: name}}}
|
||||||
|
}
|
||||||
|
return pod(podName, nodeName, "", []corev1.Volume{
|
||||||
|
secret("s1"),
|
||||||
|
secret("s2"),
|
||||||
|
secret("s3"),
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
podWithPVCs := func(podName, nodeName string) *corev1.Pod {
|
||||||
|
pvc := func(name string) corev1.Volume {
|
||||||
|
return corev1.Volume{Name: name, VolumeSource: corev1.VolumeSource{PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: name}}}
|
||||||
|
}
|
||||||
|
return pod(podName, nodeName, "", []corev1.Volume{
|
||||||
|
pvc("pvc1"),
|
||||||
|
pvc("pvc2"),
|
||||||
|
pvc("pvc3"),
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
podWithResourceClaims := func(podName, nodeName string) *corev1.Pod {
|
||||||
|
rc := func(name string) corev1.PodResourceClaim {
|
||||||
|
return corev1.PodResourceClaim{ResourceClaimName: &name}
|
||||||
|
}
|
||||||
|
return pod(podName, nodeName, "", nil, []corev1.PodResourceClaim{
|
||||||
|
rc("rc1"),
|
||||||
|
rc("rc2"),
|
||||||
|
rc("rc3"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pv := func(pvName, pvcName, secretName string) *corev1.PersistentVolume {
|
||||||
|
pv := &corev1.PersistentVolume{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: pvName, UID: types.UID(fmt.Sprintf("pv%suid", pvName))},
|
||||||
|
Spec: corev1.PersistentVolumeSpec{
|
||||||
|
ClaimRef: &corev1.ObjectReference{
|
||||||
|
Name: pvcName,
|
||||||
|
Namespace: "ns",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if secretName != "" {
|
||||||
|
pv.Spec.PersistentVolumeSource = corev1.PersistentVolumeSource{
|
||||||
|
CSI: &corev1.CSIPersistentVolumeSource{
|
||||||
|
NodePublishSecretRef: &corev1.SecretReference{
|
||||||
|
Name: secretName,
|
||||||
|
Namespace: "ns",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pv
|
||||||
|
}
|
||||||
|
|
||||||
|
toString := func(g *Graph, id int) string {
|
||||||
|
for _, namespaceName := range g.vertices {
|
||||||
|
for _, nameVertex := range namespaceName {
|
||||||
|
for _, vertex := range nameVertex {
|
||||||
|
if vertex.id == id {
|
||||||
|
return vertex.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
expectGraph := func(t *testing.T, g *Graph, expect map[string][]string) {
|
||||||
|
t.Helper()
|
||||||
|
actual := map[string][]string{}
|
||||||
|
for _, node := range g.graph.Nodes() {
|
||||||
|
sortedTo := []string{}
|
||||||
|
for _, to := range g.graph.From(node) {
|
||||||
|
sortedTo = append(sortedTo, toString(g, to.ID()))
|
||||||
|
}
|
||||||
|
sort.Strings(sortedTo)
|
||||||
|
actual[toString(g, node.ID())] = sortedTo
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expect, actual) {
|
||||||
|
e, _ := json.MarshalIndent(expect, "", " ")
|
||||||
|
a, _ := json.MarshalIndent(actual, "", " ")
|
||||||
|
t.Errorf("expected graph:\n%s\ngot:\n%s", string(e), string(a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expectIndex := func(t *testing.T, g *Graph, expect map[string][]string) {
|
||||||
|
t.Helper()
|
||||||
|
actual := map[string][]string{}
|
||||||
|
for from, to := range g.destinationEdgeIndex {
|
||||||
|
sortedValues := []string{}
|
||||||
|
for member, count := range to.members {
|
||||||
|
sortedValues = append(sortedValues, fmt.Sprintf("%s=%d", toString(g, member), count))
|
||||||
|
}
|
||||||
|
sort.Strings(sortedValues)
|
||||||
|
actual[toString(g, from)] = sortedValues
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expect, actual) {
|
||||||
|
e, _ := json.MarshalIndent(expect, "", " ")
|
||||||
|
a, _ := json.MarshalIndent(actual, "", " ")
|
||||||
|
t.Errorf("expected index:\n%s\ngot:\n%s", string(e), string(a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
desc string
|
||||||
|
startingGraph *Graph
|
||||||
|
graphTransformer func(*Graph)
|
||||||
|
expectedGraph map[string][]string
|
||||||
|
expectedIndex map[string][]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "empty graph",
|
||||||
|
startingGraph: NewTestGraph(),
|
||||||
|
graphTransformer: func(_ *Graph) {},
|
||||||
|
expectedGraph: map[string][]string{},
|
||||||
|
expectedIndex: map[string][]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "outdeg below destination edge index threshold",
|
||||||
|
startingGraph: NewTestGraph(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.AddPod(podWithSAAndCMs("pod1", "node1"))
|
||||||
|
g.AddPod(podWithSAAndCMs("pod2", "node2"))
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"node:node1": {},
|
||||||
|
"node:node2": {},
|
||||||
|
"pod:ns/pod1": {"node:node1"},
|
||||||
|
"pod:ns/pod2": {"node:node2"},
|
||||||
|
"configmap:ns/cm1": {"pod:ns/pod1", "pod:ns/pod2"},
|
||||||
|
"configmap:ns/cm2": {"pod:ns/pod1", "pod:ns/pod2"},
|
||||||
|
"configmap:ns/cm3": {"pod:ns/pod1", "pod:ns/pod2"},
|
||||||
|
"serviceAccount:ns/sa1": {"pod:ns/pod1", "pod:ns/pod2"},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "index built for configmaps and serviceaccounts",
|
||||||
|
startingGraph: func() *Graph {
|
||||||
|
g := NewTestGraph()
|
||||||
|
g.AddPod(podWithSAAndCMs("pod1", "node1"))
|
||||||
|
g.AddPod(podWithSAAndCMs("pod2", "node2"))
|
||||||
|
return g
|
||||||
|
}(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.AddPod(podWithSAAndCMs("pod3", "node3"))
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"node:node1": {},
|
||||||
|
"node:node2": {},
|
||||||
|
"node:node3": {},
|
||||||
|
"pod:ns/pod1": {"node:node1"},
|
||||||
|
"pod:ns/pod2": {"node:node2"},
|
||||||
|
"pod:ns/pod3": {"node:node3"},
|
||||||
|
"configmap:ns/cm1": {"pod:ns/pod1", "pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"configmap:ns/cm2": {"pod:ns/pod1", "pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"configmap:ns/cm3": {"pod:ns/pod1", "pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"serviceAccount:ns/sa1": {"pod:ns/pod1", "pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{
|
||||||
|
"configmap:ns/cm1": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
"configmap:ns/cm2": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
"configmap:ns/cm3": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
"serviceAccount:ns/sa1": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no index for configmaps and serviceaccounts - dropping below threshold",
|
||||||
|
startingGraph: func() *Graph {
|
||||||
|
g := NewTestGraph()
|
||||||
|
g.AddPod(podWithSAAndCMs("pod1", "node1"))
|
||||||
|
g.AddPod(podWithSAAndCMs("pod2", "node2"))
|
||||||
|
g.AddPod(podWithSAAndCMs("pod3", "node3"))
|
||||||
|
return g
|
||||||
|
}(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.DeletePod("pod1", "ns")
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"node:node2": {},
|
||||||
|
"node:node3": {},
|
||||||
|
"pod:ns/pod2": {"node:node2"},
|
||||||
|
"pod:ns/pod3": {"node:node3"},
|
||||||
|
"configmap:ns/cm1": {"pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"configmap:ns/cm2": {"pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"configmap:ns/cm3": {"pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"serviceAccount:ns/sa1": {"pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "removing pod but staying above threshold",
|
||||||
|
startingGraph: func() *Graph {
|
||||||
|
g := NewTestGraph()
|
||||||
|
g.AddPod(podWithSAAndCMs("pod1", "node1"))
|
||||||
|
g.AddPod(podWithSAAndCMs("pod2", "node2"))
|
||||||
|
g.AddPod(podWithSAAndCMs("pod3", "node3"))
|
||||||
|
g.AddPod(podWithSAAndCMs("pod4", "node1"))
|
||||||
|
return g
|
||||||
|
}(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.DeletePod("pod1", "ns")
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"node:node1": {},
|
||||||
|
"node:node2": {},
|
||||||
|
"node:node3": {},
|
||||||
|
"pod:ns/pod2": {"node:node2"},
|
||||||
|
"pod:ns/pod3": {"node:node3"},
|
||||||
|
"pod:ns/pod4": {"node:node1"},
|
||||||
|
"configmap:ns/cm1": {"pod:ns/pod2", "pod:ns/pod3", "pod:ns/pod4"},
|
||||||
|
"configmap:ns/cm2": {"pod:ns/pod2", "pod:ns/pod3", "pod:ns/pod4"},
|
||||||
|
"configmap:ns/cm3": {"pod:ns/pod2", "pod:ns/pod3", "pod:ns/pod4"},
|
||||||
|
"serviceAccount:ns/sa1": {"pod:ns/pod2", "pod:ns/pod3", "pod:ns/pod4"},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{
|
||||||
|
"configmap:ns/cm1": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
"configmap:ns/cm2": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
"configmap:ns/cm3": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
"serviceAccount:ns/sa1": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "index built for secrets",
|
||||||
|
startingGraph: func() *Graph {
|
||||||
|
g := NewTestGraph()
|
||||||
|
g.AddPod(podWithSecrets("pod1", "node1"))
|
||||||
|
g.AddPod(podWithSecrets("pod2", "node2"))
|
||||||
|
return g
|
||||||
|
}(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.AddPod(podWithSecrets("pod3", "node3"))
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"node:node1": {},
|
||||||
|
"node:node2": {},
|
||||||
|
"node:node3": {},
|
||||||
|
"pod:ns/pod1": {"node:node1"},
|
||||||
|
"pod:ns/pod2": {"node:node2"},
|
||||||
|
"pod:ns/pod3": {"node:node3"},
|
||||||
|
"secret:ns/s1": {"pod:ns/pod1", "pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"secret:ns/s2": {"pod:ns/pod1", "pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"secret:ns/s3": {"pod:ns/pod1", "pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{
|
||||||
|
"secret:ns/s1": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
"secret:ns/s2": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
"secret:ns/s3": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no index for secrets - dropping below threshold",
|
||||||
|
startingGraph: func() *Graph {
|
||||||
|
g := NewTestGraph()
|
||||||
|
g.AddPod(podWithSecrets("pod1", "node1"))
|
||||||
|
g.AddPod(podWithSecrets("pod2", "node2"))
|
||||||
|
g.AddPod(podWithSecrets("pod3", "node3"))
|
||||||
|
return g
|
||||||
|
}(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.DeletePod("pod1", "ns")
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"node:node2": {},
|
||||||
|
"node:node3": {},
|
||||||
|
"pod:ns/pod2": {"node:node2"},
|
||||||
|
"pod:ns/pod3": {"node:node3"},
|
||||||
|
"secret:ns/s1": {"pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"secret:ns/s2": {"pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"secret:ns/s3": {"pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "index built for pvcs",
|
||||||
|
startingGraph: func() *Graph {
|
||||||
|
g := NewTestGraph()
|
||||||
|
g.AddPod(podWithPVCs("pod1", "node1"))
|
||||||
|
g.AddPod(podWithPVCs("pod2", "node2"))
|
||||||
|
return g
|
||||||
|
}(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.AddPod(podWithPVCs("pod3", "node3"))
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"node:node1": {},
|
||||||
|
"node:node2": {},
|
||||||
|
"node:node3": {},
|
||||||
|
"pod:ns/pod1": {"node:node1"},
|
||||||
|
"pod:ns/pod2": {"node:node2"},
|
||||||
|
"pod:ns/pod3": {"node:node3"},
|
||||||
|
"pvc:ns/pvc1": {"pod:ns/pod1", "pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"pvc:ns/pvc2": {"pod:ns/pod1", "pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"pvc:ns/pvc3": {"pod:ns/pod1", "pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{
|
||||||
|
"pvc:ns/pvc1": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
"pvc:ns/pvc2": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
"pvc:ns/pvc3": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no index for pvcs - dropping below threshold",
|
||||||
|
startingGraph: func() *Graph {
|
||||||
|
g := NewTestGraph()
|
||||||
|
g.AddPod(podWithPVCs("pod1", "node1"))
|
||||||
|
g.AddPod(podWithPVCs("pod2", "node2"))
|
||||||
|
g.AddPod(podWithPVCs("pod3", "node3"))
|
||||||
|
return g
|
||||||
|
}(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.DeletePod("pod1", "ns")
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"node:node2": {},
|
||||||
|
"node:node3": {},
|
||||||
|
"pod:ns/pod2": {"node:node2"},
|
||||||
|
"pod:ns/pod3": {"node:node3"},
|
||||||
|
"pvc:ns/pvc1": {"pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"pvc:ns/pvc2": {"pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"pvc:ns/pvc3": {"pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "index built for resourceclaims",
|
||||||
|
startingGraph: NewTestGraph(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.AddPod(podWithResourceClaims("pod1", "node1"))
|
||||||
|
g.AddPod(podWithResourceClaims("pod2", "node2"))
|
||||||
|
g.AddPod(podWithResourceClaims("pod3", "node3"))
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"node:node1": {},
|
||||||
|
"node:node2": {},
|
||||||
|
"node:node3": {},
|
||||||
|
"pod:ns/pod1": {"node:node1"},
|
||||||
|
"pod:ns/pod2": {"node:node2"},
|
||||||
|
"pod:ns/pod3": {"node:node3"},
|
||||||
|
"resourceclaim:ns/rc1": {"pod:ns/pod1", "pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"resourceclaim:ns/rc2": {"pod:ns/pod1", "pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"resourceclaim:ns/rc3": {"pod:ns/pod1", "pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{
|
||||||
|
"resourceclaim:ns/rc1": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
"resourceclaim:ns/rc2": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
"resourceclaim:ns/rc3": {"node:node1=1", "node:node2=1", "node:node3=1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no index for resourceclaims - dropping below threshold",
|
||||||
|
startingGraph: func() *Graph {
|
||||||
|
g := NewTestGraph()
|
||||||
|
g.AddPod(podWithResourceClaims("pod1", "node1"))
|
||||||
|
g.AddPod(podWithResourceClaims("pod2", "node2"))
|
||||||
|
g.AddPod(podWithResourceClaims("pod3", "node3"))
|
||||||
|
return g
|
||||||
|
}(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.DeletePod("pod1", "ns")
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"node:node2": {},
|
||||||
|
"node:node3": {},
|
||||||
|
"pod:ns/pod2": {"node:node2"},
|
||||||
|
"pod:ns/pod3": {"node:node3"},
|
||||||
|
"resourceclaim:ns/rc1": {"pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"resourceclaim:ns/rc2": {"pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
"resourceclaim:ns/rc3": {"pod:ns/pod2", "pod:ns/pod3"},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "resourceslices adding",
|
||||||
|
startingGraph: NewTestGraph(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.AddResourceSlice("s1", "node1")
|
||||||
|
g.AddResourceSlice("s2", "node2")
|
||||||
|
g.AddResourceSlice("s3", "node3")
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"node:node1": {},
|
||||||
|
"node:node2": {},
|
||||||
|
"node:node3": {},
|
||||||
|
"resourceslice:s1": {"node:node1"},
|
||||||
|
"resourceslice:s2": {"node:node2"},
|
||||||
|
"resourceslice:s3": {"node:node3"},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "resourceslices deleting",
|
||||||
|
startingGraph: func() *Graph {
|
||||||
|
g := NewTestGraph()
|
||||||
|
g.AddResourceSlice("s1", "node1")
|
||||||
|
g.AddResourceSlice("s2", "node2")
|
||||||
|
g.AddResourceSlice("s3", "node3")
|
||||||
|
return g
|
||||||
|
}(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.DeleteResourceSlice("s1")
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"node:node2": {},
|
||||||
|
"node:node3": {},
|
||||||
|
"resourceslice:s2": {"node:node2"},
|
||||||
|
"resourceslice:s3": {"node:node3"},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "volumeattachments adding",
|
||||||
|
startingGraph: NewTestGraph(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.AddVolumeAttachment("va1", "node1")
|
||||||
|
g.AddVolumeAttachment("va2", "node2")
|
||||||
|
g.AddVolumeAttachment("va3", "node3")
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"node:node1": {},
|
||||||
|
"node:node2": {},
|
||||||
|
"node:node3": {},
|
||||||
|
"volumeattachment:va1": {"node:node1"},
|
||||||
|
"volumeattachment:va2": {"node:node2"},
|
||||||
|
"volumeattachment:va3": {"node:node3"},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "volumeattachments deleting",
|
||||||
|
startingGraph: func() *Graph {
|
||||||
|
g := NewTestGraph()
|
||||||
|
g.AddVolumeAttachment("va1", "node1")
|
||||||
|
g.AddVolumeAttachment("va2", "node2")
|
||||||
|
g.AddVolumeAttachment("va3", "node3")
|
||||||
|
return g
|
||||||
|
}(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.DeleteVolumeAttachment("va1")
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"node:node2": {},
|
||||||
|
"node:node3": {},
|
||||||
|
"volumeattachment:va2": {"node:node2"},
|
||||||
|
"volumeattachment:va3": {"node:node3"},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "persistentvolumes adding",
|
||||||
|
startingGraph: NewTestGraph(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.AddPV(pv("pv1", "pvc1", ""))
|
||||||
|
g.AddPV(pv("pv2", "pvc2", ""))
|
||||||
|
g.AddPV(pv("pv3", "pvc3", ""))
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"pv:pv1": {"pvc:ns/pvc1"},
|
||||||
|
"pv:pv2": {"pvc:ns/pvc2"},
|
||||||
|
"pv:pv3": {"pvc:ns/pvc3"},
|
||||||
|
"pvc:ns/pvc1": {},
|
||||||
|
"pvc:ns/pvc2": {},
|
||||||
|
"pvc:ns/pvc3": {},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "persistentvolumes deleting",
|
||||||
|
startingGraph: func() *Graph {
|
||||||
|
g := NewTestGraph()
|
||||||
|
g.AddPV(pv("pv1", "pvc1", ""))
|
||||||
|
g.AddPV(pv("pv2", "pvc2", ""))
|
||||||
|
g.AddPV(pv("pv3", "pvc3", ""))
|
||||||
|
return g
|
||||||
|
}(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.DeletePV("pv1")
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"pv:pv2": {"pvc:ns/pvc2"},
|
||||||
|
"pv:pv3": {"pvc:ns/pvc3"},
|
||||||
|
"pvc:ns/pvc2": {},
|
||||||
|
"pvc:ns/pvc3": {},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "persistentvolumes with secrets",
|
||||||
|
startingGraph: NewTestGraph(),
|
||||||
|
graphTransformer: func(g *Graph) {
|
||||||
|
g.AddPV(pv("pv1", "pvc1", "s1"))
|
||||||
|
g.AddPV(pv("pv2", "pvc2", "s2"))
|
||||||
|
g.AddPV(pv("pv3", "pvc3", "s3"))
|
||||||
|
},
|
||||||
|
expectedGraph: map[string][]string{
|
||||||
|
"pv:pv1": {"pvc:ns/pvc1"},
|
||||||
|
"pv:pv2": {"pvc:ns/pvc2"},
|
||||||
|
"pv:pv3": {"pvc:ns/pvc3"},
|
||||||
|
"pvc:ns/pvc1": {},
|
||||||
|
"pvc:ns/pvc2": {},
|
||||||
|
"pvc:ns/pvc3": {},
|
||||||
|
"secret:ns/s1": {"pv:pv1"},
|
||||||
|
"secret:ns/s2": {"pv:pv2"},
|
||||||
|
"secret:ns/s3": {"pv:pv3"},
|
||||||
|
},
|
||||||
|
expectedIndex: map[string][]string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
tc.graphTransformer(tc.startingGraph)
|
||||||
|
expectGraph(t, tc.startingGraph, tc.expectedGraph)
|
||||||
|
expectIndex(t, tc.startingGraph, tc.expectedIndex)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -458,9 +458,17 @@ func (r *NodeAuthorizer) hasPathFrom(nodeName string, startingType vertexType, s
|
|||||||
return false, fmt.Errorf("node '%s' cannot get unknown %s %s/%s", nodeName, vertexTypes[startingType], startingNamespace, startingName)
|
return false, fmt.Errorf("node '%s' cannot get unknown %s %s/%s", nodeName, vertexTypes[startingType], startingNamespace, startingName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fast check to see if we know of a destination edge
|
if index, indexExists := r.graph.destinationEdgeIndex[startingVertex.ID()]; indexExists {
|
||||||
if r.graph.destinationEdgeIndex[startingVertex.ID()].has(nodeVertex.ID()) {
|
// Fast check to see if we know of a destination edge
|
||||||
return true, nil
|
if index.has(nodeVertex.ID()) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
// For some types of vertices, the destination edge index is authoritative
|
||||||
|
// (as long as it exists), hence we can fail-fast here instead of running
|
||||||
|
// a potentially costly DFS below.
|
||||||
|
if vertexTypeWithAuthoritativeIndex[startingType] {
|
||||||
|
return false, fmt.Errorf("node '%s' cannot get %s %s/%s, no relationship to this object was found in the node authorizer graph", nodeName, vertexTypes[startingType], startingNamespace, startingName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
|
@ -996,6 +996,50 @@ func BenchmarkWriteIndexMaintenance(b *testing.B) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnauthorizedRequests(b *testing.B) {
|
||||||
|
|
||||||
|
// Run with:
|
||||||
|
// go test ./plugin/pkg/auth/authorizer/node -benchmem -bench BenchmarkUnauthorizedRequests -run None
|
||||||
|
|
||||||
|
opts := &sampleDataOpts{
|
||||||
|
nodes: 5000,
|
||||||
|
namespaces: 1,
|
||||||
|
podsPerNode: 1,
|
||||||
|
sharedConfigMapsPerPod: 1,
|
||||||
|
}
|
||||||
|
nodes, pods, pvs, attachments, slices := generate(opts)
|
||||||
|
|
||||||
|
// Create an additional Node that doesn't have access to a shared ConfigMap
|
||||||
|
// that all the other Nodes are authorized to read.
|
||||||
|
additionalNodeName := "nodeX"
|
||||||
|
nsName := "ns0"
|
||||||
|
additionalNode := &user.DefaultInfo{Name: fmt.Sprintf("system:node:%s", additionalNodeName), Groups: []string{"system:nodes"}}
|
||||||
|
pod := &corev1.Pod{}
|
||||||
|
pod.Name = "pod-X"
|
||||||
|
pod.Namespace = nsName
|
||||||
|
pod.Spec.NodeName = additionalNodeName
|
||||||
|
pod.Spec.ServiceAccountName = "svcacct-X"
|
||||||
|
pods = append(pods, pod)
|
||||||
|
|
||||||
|
g := NewGraph()
|
||||||
|
populate(g, nodes, pods, pvs, attachments, slices)
|
||||||
|
|
||||||
|
identifier := nodeidentifier.NewDefaultNodeIdentifier()
|
||||||
|
authz := NewAuthorizer(g, identifier, bootstrappolicy.NodeRules())
|
||||||
|
|
||||||
|
attrs := authorizer.AttributesRecord{User: additionalNode, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-shared", Namespace: nsName}
|
||||||
|
|
||||||
|
b.SetParallelism(500)
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
decision, _, _ := authz.Authorize(context.Background(), attrs)
|
||||||
|
if decision != authorizer.DecisionNoOpinion {
|
||||||
|
b.Errorf("expected %v, got %v", authorizer.DecisionNoOpinion, decision)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkAuthorization(b *testing.B) {
|
func BenchmarkAuthorization(b *testing.B) {
|
||||||
g := NewGraph()
|
g := NewGraph()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user