• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubescape / opa-utils / 25130735917

29 Apr 2026 07:57PM UTC coverage: 67.949% (-0.07%) from 68.018%
25130735917

push

github

web-flow
Merge pull request #165 from RohanKaran/fix/manual-control-cluster-scoping

feat(exceptions): add cluster-matching and control-ID helpers for external callers

14 of 22 new or added lines in 1 file covered. (63.64%)

3144 of 4627 relevant lines covered (67.95%)

122.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

64.83
/exceptions/exceptionprocessor.go
1
package exceptions
2

3
import (
4
        "strings"
5

6
        "github.com/armosec/armoapi-go/identifiers"
7

8
        "github.com/kubescape/k8s-interface/workloadinterface"
9
        "github.com/kubescape/opa-utils/objectsenvelopes"
10
        "github.com/kubescape/opa-utils/reporthandling"
11

12
        "github.com/armosec/armoapi-go/armotypes"
13
)
14

15
// Processor processes exceptions.
16
type Processor struct {
17
        *comparator
18
        designatorCache *designatorCache
19
}
20

21
func NewProcessor() *Processor {
5✔
22
        return &Processor{
5✔
23
                comparator:      newComparator(),
5✔
24
                designatorCache: newDesignatorCache(),
5✔
25
        }
5✔
26
}
5✔
27

28
// SetFrameworkExceptions add exceptions to framework report
29
func (p *Processor) SetFrameworkExceptions(frameworkReport *reporthandling.FrameworkReport, exceptionsPolicies []armotypes.PostureExceptionPolicy, clusterName string) {
×
30
        for c := range frameworkReport.ControlReports {
×
31
                p.SetControlExceptions(&frameworkReport.ControlReports[c], exceptionsPolicies, clusterName, frameworkReport.Name)
×
32
        }
×
33
}
34

35
// SetControlExceptions add exceptions to control report
36
func (p *Processor) SetControlExceptions(controlReport *reporthandling.ControlReport, exceptionsPolicies []armotypes.PostureExceptionPolicy, clusterName, frameworkName string) {
×
37
        for r := range controlReport.RuleReports {
×
38
                p.SetRuleExceptions(&controlReport.RuleReports[r], exceptionsPolicies, clusterName, frameworkName, controlReport.ControlID)
×
39
        }
×
40
}
41

42
// SetRuleExceptions add exceptions to rule report
43
func (p *Processor) SetRuleExceptions(ruleReport *reporthandling.RuleReport, exceptionsPolicies []armotypes.PostureExceptionPolicy, clusterName, frameworkName, controlID string) {
×
44
        // adding exceptions to the rules
×
45
        ruleExceptions := p.ListRuleExceptions(exceptionsPolicies, frameworkName, controlID, ruleReport.Name)
×
46
        p.SetRuleResponsExceptions(ruleReport.RuleResponses, ruleExceptions, clusterName)
×
47
}
×
48

49
// SetRuleExceptions add exceptions to rule respons structure
50
func (p *Processor) SetRuleResponsExceptions(results []reporthandling.RuleResponse, ruleExceptions []armotypes.PostureExceptionPolicy, clusterName string) {
×
51
        if len(ruleExceptions) == 0 {
×
52
                return
×
53
        }
×
54

55
        for i := range results {
×
56
                workloads := alertObjectToWorkloads(&results[i].AlertObject)
×
57
                if len(workloads) == 0 {
×
58
                        continue
×
59
                }
60

61
                for w := range workloads {
×
62
                        if exceptions := p.GetResourceExceptions(ruleExceptions, workloads[w], clusterName); len(exceptions) > 0 {
×
63
                                results[i].Exception = &exceptions[0]
×
64
                        }
×
65
                }
66

67
                results[i].RuleStatus = results[i].GetStatus()
×
68
        }
69
}
70

71
func (p *Processor) ListRuleExceptions(exceptionPolicies []armotypes.PostureExceptionPolicy, frameworkName, controlID, ruleName string) []armotypes.PostureExceptionPolicy {
11✔
72
        ruleExceptions := make([]armotypes.PostureExceptionPolicy, 0, len(exceptionPolicies))
11✔
73

11✔
74
        for i := range exceptionPolicies {
22✔
75
                if p.ruleHasExceptions(&exceptionPolicies[i], frameworkName, controlID, ruleName) {
19✔
76
                        ruleExceptions = append(ruleExceptions, exceptionPolicies[i])
8✔
77
                }
8✔
78
        }
79

80
        return ruleExceptions[:len(ruleExceptions):len(ruleExceptions)]
11✔
81

82
}
83

84
func (p *Processor) ruleHasExceptions(exceptionPolicy *armotypes.PostureExceptionPolicy, frameworkName, controlID, ruleName string) bool {
11✔
85
        if len(exceptionPolicy.PosturePolicies) == 0 {
12✔
86
                return true // empty policy -> apply all
1✔
87
        }
1✔
88

89
        for _, posturePolicy := range exceptionPolicy.PosturePolicies {
20✔
90
                if posturePolicy.FrameworkName == "" && posturePolicy.ControlID == "" && posturePolicy.RuleName == "" {
10✔
91
                        return true // empty policy -> apply all
×
92
                }
×
93
                if posturePolicy.FrameworkName != "" && frameworkName != "" && !(strings.EqualFold(posturePolicy.FrameworkName, frameworkName) || p.regexCompareI(posturePolicy.FrameworkName, frameworkName)) {
12✔
94
                        continue // policy does not match
2✔
95
                }
96
                if posturePolicy.ControlID != "" && controlID != "" && !(strings.EqualFold(posturePolicy.ControlID, controlID) || p.regexCompareI(posturePolicy.ControlID, controlID)) {
8✔
97
                        continue // policy does not match
×
98
                }
99
                if posturePolicy.RuleName != "" && ruleName != "" && !(strings.EqualFold(posturePolicy.RuleName, ruleName) || p.regexCompareI(posturePolicy.RuleName, ruleName)) {
9✔
100
                        continue // policy does not match
1✔
101
                }
102

103
                return true // policies match
7✔
104
        }
105

106
        return false
3✔
107

108
}
109

110
func alertObjectToWorkloads(obj *reporthandling.AlertObject) []workloadinterface.IMetadata {
×
111
        resources := make([]workloadinterface.IMetadata, 0, len(obj.K8SApiObjects)+1)
×
112

×
113
        for i := range obj.K8SApiObjects {
×
114
                r := objectsenvelopes.NewObject(obj.K8SApiObjects[i])
×
115
                if r == nil {
×
116
                        continue
×
117
                }
118

119
                resources = append(resources, r)
×
120
                /*
121
                        ns : = r.GetNamespace()
122
                        if ns != "" {
123
                                // TODO - handle empty namespace
124
                        }
125
                */
126
        }
127

128
        if obj.ExternalObjects != nil {
×
129
                if r := objectsenvelopes.NewObject(obj.ExternalObjects); r != nil {
×
130
                        // TODO - What about linked objects?
×
131
                        resources = append(resources, r)
×
132
                }
×
133
        }
134

135
        return resources[:len(resources):len(resources)]
×
136
}
137

138
// GetResourceException get exceptions of single resource
139
func (p *Processor) GetResourceExceptions(ruleExceptions []armotypes.PostureExceptionPolicy, workload workloadinterface.IMetadata, clusterName string) []armotypes.PostureExceptionPolicy {
9✔
140
        // no pre-allocation since most of the time it's empty or has only one element
9✔
141
        var postureExceptionPolicy []armotypes.PostureExceptionPolicy
9✔
142

9✔
143
        for _, ruleException := range ruleExceptions {
18✔
144
                for _, resourceToPin := range ruleException.Resources {
18✔
145
                        resource := resourceToPin
9✔
146
                        if p.hasException(clusterName, &resource, workload) {
15✔
147
                                postureExceptionPolicy = append(postureExceptionPolicy, ruleException)
6✔
148
                        }
6✔
149
                }
150
        }
151

152
        return postureExceptionPolicy
9✔
153
}
154

155
// RegexCompareControlID reports whether pattern case-insensitively matches target.
NEW
156
func (p *Processor) RegexCompareControlID(pattern, target string) bool {
×
NEW
157
        return p.regexCompareI(pattern, target)
×
NEW
158
}
×
159

160
// MatchesCluster reports whether the designator's cluster constraint matches clusterName.
161
// A nil designator or empty cluster field matches any cluster.
NEW
162
func (p *Processor) MatchesCluster(designator *identifiers.PortalDesignator, clusterName string) bool {
×
NEW
163
        if designator == nil {
×
NEW
164
                return true
×
NEW
165
        }
×
NEW
166
        return p.matchesCluster(p.getAttributes(designator), clusterName)
×
167
}
168

169
// getAttributes returns digested attributes, using the cache when available.
170
func (p *Processor) getAttributes(designator *identifiers.PortalDesignator) identifiers.AttributesDesignators {
40✔
171
        if attrs, ok := p.designatorCache.Get(designator); ok {
54✔
172
                return attrs
14✔
173
        }
14✔
174
        attrs := designator.DigestPortalDesignator()
26✔
175
        p.designatorCache.Set(designator, attrs)
26✔
176
        return attrs
26✔
177
}
178

179
// matchesCluster checks the cluster constraint against pre-digested attributes.
180
func (p *Processor) matchesCluster(attributes identifiers.AttributesDesignators, clusterName string) bool {
39✔
181
        cluster := attributes.GetCluster()
39✔
182
        if cluster == "" {
72✔
183
                return true
33✔
184
        }
33✔
185
        return p.compareCluster(cluster, clusterName)
6✔
186
}
187

188
// compareMetadata - compare namespace and kind
189
func (p *Processor) hasException(clusterName string, designator *identifiers.PortalDesignator, workload workloadinterface.IMetadata) bool {
40✔
190
        attributes := p.getAttributes(designator)
40✔
191

40✔
192
        if attributes.GetCluster() == "" && attributes.GetNamespace() == "" && attributes.GetKind() == "" && attributes.GetName() == "" && attributes.GetResourceID() == "" && attributes.GetPath() == "" && len(attributes.GetLabels()) == 0 {
41✔
193
                return false // if designators are empty
1✔
194
        }
1✔
195

196
        if !p.matchesCluster(attributes, clusterName) {
40✔
197
                return false // cluster name does not match
1✔
198
        }
1✔
199

200
        if isTypeRegoResponseVector(workload) {
43✔
201
                if p.iterateRegoResponseVector(workload, attributes) {
6✔
202
                        return true
1✔
203
                }
1✔
204
                // otherwise, continue to check the base object
205
        }
206
        return p.metadataHasException(workload, attributes)
37✔
207

208
}
209

210
func (p *Processor) metadataHasException(workload workloadinterface.IMetadata, attributes identifiers.AttributesDesignators) bool {
56✔
211

56✔
212
        if attributes.GetNamespace() != "" && !p.compareNamespace(workload, attributes.GetNamespace()) {
57✔
213
                return false // namespaces do not match
1✔
214
        }
1✔
215

216
        if attributes.GetKind() != "" && !p.compareKind(workload, attributes.GetKind()) {
61✔
217
                return false // kinds do not match
6✔
218
        }
6✔
219

220
        if attributes.GetName() != "" && !p.compareName(workload, attributes.GetName()) {
55✔
221
                return false // names do not match
6✔
222
        }
6✔
223

224
        if attributes.GetResourceID() != "" && !p.compareResourceID(workload, attributes.GetResourceID()) {
44✔
225
                return false // names do not match
1✔
226
        }
1✔
227

228
        if attributes.GetPath() != "" && !p.comparePath(workload, attributes.GetPath()) {
43✔
229
                return false // paths do not match
1✔
230
        }
1✔
231

232
        if isTypeWorkload(workload) && len(attributes.GetLabels()) > 0 {
68✔
233
                if !p.compareLabels(workload, attributes.GetLabels()) && !p.compareAnnotations(workload, attributes.GetLabels()) {
41✔
234
                        return false // labels nor annotations do not match
14✔
235
                }
14✔
236
        }
237
        return true
27✔
238
}
239

240
func (p *Processor) iterateRegoResponseVector(workload workloadinterface.IMetadata, attributes identifiers.AttributesDesignators) bool {
11✔
241
        v := objectsenvelopes.NewRegoResponseVectorObject(workload.GetObject())
11✔
242
        for _, r := range v.GetRelatedObjects() {
30✔
243
                if p.metadataHasException(r, attributes) {
24✔
244
                        return true
5✔
245
                }
5✔
246
        }
247
        return false
6✔
248
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc