Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions exceptions/exceptionprocessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,25 +416,31 @@ func (p *Processor) metadataHasException(workload workloadinterface.IMetadata, a
}
}

if isTypeWorkload(workload) {
allLabels := attributes.GetLabels()
containerName, hasContainerName := allLabels[identifiers.AttributeContainerName]
allLabels := attributes.GetLabels()
containerName, hasContainerName := allLabels[identifiers.AttributeContainerName]

// Build a label map with the attributes that are not Kubernetes labels stripped out,
// so they are not compared as such.
realLabels := stripNonLabelAttributes(allLabels)
// Build a label map with the attributes that are not Kubernetes labels stripped out,
// so they are not compared as such.
realLabels := stripNonLabelAttributes(allLabels)

if len(realLabels) > 0 {
if !p.compareLabels(workload, realLabels) && !p.compareAnnotations(workload, realLabels) {
return false // labels nor annotations do not match
}
}
if !isTypeWorkload(workload) {
// RegoResponseVector, host-sensor and cloud-provider objects have no labels,
// annotations or containers to compare, so a designator that constrains any of
// them cannot match. Skipping the comparison instead would widen a label-scoped
// exception to every such object.
return len(realLabels) == 0 && !hasContainerName
}

if hasContainerName && !p.compareContainerName(workload, containerName, failingContainerNames) {
return false // container name does not match
if len(realLabels) > 0 {
if !p.compareLabels(workload, realLabels) && !p.compareAnnotations(workload, realLabels) {
return false // labels nor annotations do not match
}
}

if hasContainerName && !p.compareContainerName(workload, containerName, failingContainerNames) {
return false // container name does not match
}

return true
}

Expand Down
70 changes: 70 additions & 0 deletions exceptions/exceptionprocessor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2451,3 +2451,73 @@ func TestHasException_ApiGroup_RegoResponseVector(t *testing.T) {
})
}
}

// TestGetResourceExceptions_LabelDesignatorOnNonWorkloads pins that a label in the
// designator is a constraint on every object type. RegoResponseVector, host-sensor and
// cloud-provider objects have no labels to compare, so a label-scoped exception must
// not match them unless a related object of the vector carries the label.
func TestGetResourceExceptions_LabelDesignatorOnNonWorkloads(t *testing.T) {
labelOnly := armotypes.PostureExceptionPolicy{
PortalBase: armotypes.PortalBase{Name: "nginx-only"},
Resources: []identifiers.PortalDesignator{{
DesignatorType: identifiers.DesignatorAttributes,
Attributes: map[string]string{"app": "nginx"},
}},
}
nameOnly := armotypes.PostureExceptionPolicy{
PortalBase: armotypes.PortalBase{Name: "node-1"},
Resources: []identifiers.PortalDesignator{{
DesignatorType: identifiers.DesignatorAttributes,
Attributes: map[string]string{"name": "node-1"},
}},
}

subject := func(bindingLabels map[string]interface{}) workloadinterface.IMetadata {
binding := map[string]interface{}{"name": "alice-admin"}
if bindingLabels != nil {
binding["labels"] = bindingLabels
}
return objectsenvelopes.NewRegoResponseVectorObject(map[string]interface{}{
"apiGroup": "rbac.authorization.k8s.io",
"kind": "User",
"name": "alice",
"relatedObjects": []interface{}{
map[string]interface{}{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "ClusterRoleBinding",
"metadata": binding,
"roleRef": map[string]interface{}{"kind": "ClusterRole", "name": "cluster-admin"},
},
map[string]interface{}{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "ClusterRole",
"metadata": map[string]interface{}{"name": "cluster-admin"},
},
},
})
}
hostSensor := objectsenvelopes.NewObject(map[string]interface{}{
"apiVersion": "hostdata.kubescape.cloud/v1beta0",
"kind": "KubeletInfo",
"metadata": map[string]interface{}{"name": "node-1"},
"data": map[string]interface{}{},
})

tests := []struct {
name string
policy armotypes.PostureExceptionPolicy
workload workloadinterface.IMetadata
expected int
}{
{"label-only exception does not match an RBAC subject without the label", labelOnly, subject(nil), 0},
{"label-only exception matches an RBAC subject whose binding has the label", labelOnly, subject(map[string]interface{}{"app": "nginx"}), 1},
{"label-only exception does not match a host-sensor object", labelOnly, hostSensor, 0},
{"name-only exception still matches a host-sensor object", nameOnly, hostSensor, 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := NewProcessor().GetResourceExceptions([]armotypes.PostureExceptionPolicy{tt.policy}, tt.workload, "")
assert.Len(t, got, tt.expected)
})
}
}
Loading