66 "strings"
77 "time"
88
9+ "github.com/pkg/errors"
910 collectionDataStore "github.com/stackrox/rox/central/resourcecollection/datastore"
1011 v1 "github.com/stackrox/rox/generated/api/v1"
1112 "github.com/stackrox/rox/generated/storage"
@@ -27,17 +28,20 @@ type queryBuilder struct {
2728 collection * storage.ResourceCollection
2829 collectionQueryResolver collectionDataStore.QueryResolver
2930 dataStartTime time.Time
31+ entityScope * storage.EntityScope
3032}
3133
3234// NewVulnReportQueryBuilder builds a query builder to build scope and cve filtering queries for vuln reporting
33- func NewVulnReportQueryBuilder (collection * storage.ResourceCollection , vulnFilters * storage.VulnerabilityReportFilters ,
35+ func NewVulnReportQueryBuilder (collection * storage.ResourceCollection , entityScope * storage. EntityScope , vulnFilters * storage.VulnerabilityReportFilters ,
3436 collectionQueryRes collectionDataStore.QueryResolver , dataStartTime time.Time ) * queryBuilder {
3537 return & queryBuilder {
36- vulnFilters : vulnFilters ,
3738 collection : collection ,
39+ entityScope : entityScope ,
40+ vulnFilters : vulnFilters ,
3841 collectionQueryResolver : collectionQueryRes ,
3942 dataStartTime : dataStartTime ,
4043 }
44+
4145}
4246
4347// BuildQuery builds scope and cve filtering queries for vuln reporting
@@ -46,7 +50,13 @@ func (q *queryBuilder) BuildQuery(
4650 clusters []effectiveaccessscope.Cluster ,
4751 namespaces []effectiveaccessscope.Namespace ,
4852) (* ReportQuery , error ) {
49- deploymentsQuery , err := q .collectionQueryResolver .ResolveCollectionQuery (ctx , q .collection )
53+ deploymentsQuery := search .EmptyQuery ()
54+ var err error
55+ if q .collection != nil {
56+ deploymentsQuery , err = q .collectionQueryResolver .ResolveCollectionQuery (ctx , q .collection )
57+ } else if q .entityScope != nil {
58+ deploymentsQuery , err = q .buildEntityScopeQuery ()
59+ }
5060 if err != nil {
5161 return nil , err
5262 }
@@ -66,7 +76,9 @@ func (q *queryBuilder) BuildQuery(
6676 }, nil
6777}
6878
69- func (q * queryBuilder ) buildCVEAttributesQuery () (string , error ) {
79+ // addSeverityFixabilityFiltersCollectionScopedReports adds severity, fixability filters for collection scoped reports
80+ func (q * queryBuilder ) addSeverityFixabilityFiltersCollectionScopedReports () []string {
81+
7082 vulnReportFilters := q .vulnFilters
7183 var conjuncts []string
7284
@@ -86,13 +98,26 @@ func (q *queryBuilder) buildCVEAttributesQuery() (string, error) {
8698 if len (severities ) > 0 {
8799 conjuncts = append (conjuncts , search .NewQueryBuilder ().AddExactMatches (search .Severity , severities ... ).Query ())
88100 }
101+ return conjuncts
102+ }
103+
104+ func (q * queryBuilder ) buildCVEAttributesQuery () (string , error ) {
89105
106+ vulnReportFilters := q .vulnFilters
107+ var conjuncts []string
108+
109+ if q .collection != nil {
110+ // for collections only add fixability, severity filters for CVE
111+ conjuncts = q .addSeverityFixabilityFiltersCollectionScopedReports ()
112+ } else if q .entityScope != nil {
113+ // for entity scoped reports add all the search filters from query string
114+ conjuncts = append (conjuncts , q .vulnFilters .GetQuery ())
115+ }
90116 if filterVulnsByFirstOccurrenceTime (vulnReportFilters ) {
91117 startTimeStr := fmt .Sprintf (">=%s" , q .dataStartTime .Format ("01/02/2006 3:04:05 PM MST" ))
92118 tsQ := search .NewQueryBuilder ().AddStrings (search .FirstImageOccurrenceTimestamp , startTimeStr ).Query ()
93119 conjuncts = append (conjuncts , tsQ )
94120 }
95-
96121 return strings .Join (conjuncts , "+" ), nil
97122}
98123
@@ -129,6 +154,100 @@ func (q *queryBuilder) buildAccessScopeQuery(
129154 return scopeQuery , nil
130155}
131156
157+ // buildEntityScopeQuery uses entity scope object to build v1 query
158+ func (q * queryBuilder ) buildEntityScopeQuery () (* v1.Query , error ) {
159+ rules := q .entityScope .GetRules ()
160+ if len (rules ) == 0 {
161+ return search .EmptyQuery (), nil
162+ }
163+
164+ var conjuncts []* v1.Query
165+ for _ , rule := range rules {
166+ fieldLabel , err := entityScopeRuleToFieldLabel (rule )
167+ if err != nil {
168+ return nil , err
169+ }
170+ isMapField := fieldLabel == search .DeploymentLabel ||
171+ fieldLabel == search .NamespaceLabel ||
172+ fieldLabel == search .ClusterLabel ||
173+ fieldLabel == search .DeploymentAnnotation ||
174+ fieldLabel == search .NamespaceAnnotation
175+
176+ values := make ([]string , 0 , len (rule .GetValues ()))
177+ for _ , rv := range rule .GetValues () {
178+ val := rv .GetValue ()
179+ if rv .GetMatchType () == storage .MatchType_REGEX {
180+ val = search .RegexPrefix + val
181+ }
182+ values = append (values , val )
183+ }
184+
185+ if len (values ) == 0 {
186+ continue
187+ }
188+
189+ if isMapField {
190+ for _ , v := range values {
191+ key , value := splitLabelValue (v )
192+ conjuncts = append (conjuncts ,
193+ search .NewQueryBuilder ().AddMapQuery (fieldLabel , key , value ).ProtoQuery ())
194+ }
195+ } else if rule .GetValues ()[0 ].GetMatchType () == storage .MatchType_REGEX {
196+ conjuncts = append (conjuncts ,
197+ search .NewQueryBuilder ().AddStrings (fieldLabel , values ... ).ProtoQuery ())
198+ } else {
199+ conjuncts = append (conjuncts ,
200+ search .NewQueryBuilder ().AddExactMatches (fieldLabel , values ... ).ProtoQuery ())
201+ }
202+ }
203+
204+ if len (conjuncts ) == 0 {
205+ return search .EmptyQuery (), nil
206+ }
207+ return search .ConjunctionQuery (conjuncts ... ), nil
208+ }
209+
210+ // entityScopeRuleToFieldLabel returns search filter for given entity field pair
211+ func entityScopeRuleToFieldLabel (rule * storage.EntityScopeRule ) (search.FieldLabel , error ) {
212+ switch rule .GetEntity () {
213+ case storage .EntityType_ENTITY_TYPE_DEPLOYMENT :
214+ switch rule .GetField () {
215+ case storage .EntityField_FIELD_NAME :
216+ return search .DeploymentName , nil
217+ case storage .EntityField_FIELD_LABEL :
218+ return search .DeploymentLabel , nil
219+ case storage .EntityField_FIELD_ANNOTATION :
220+ return search .DeploymentAnnotation , nil
221+ }
222+ case storage .EntityType_ENTITY_TYPE_NAMESPACE :
223+ switch rule .GetField () {
224+ case storage .EntityField_FIELD_NAME :
225+ return search .Namespace , nil
226+ case storage .EntityField_FIELD_LABEL :
227+ return search .NamespaceLabel , nil
228+ case storage .EntityField_FIELD_ANNOTATION :
229+ return search .NamespaceAnnotation , nil
230+ }
231+ case storage .EntityType_ENTITY_TYPE_CLUSTER :
232+ switch rule .GetField () {
233+ case storage .EntityField_FIELD_NAME :
234+ return search .Cluster , nil
235+ case storage .EntityField_FIELD_LABEL :
236+ return search .ClusterLabel , nil
237+ }
238+ }
239+ return "" , errors .Errorf ("Unsupported entity/field combination %s/%s" , rule .GetEntity (), rule .GetField ())
240+ }
241+
132242func filterVulnsByFirstOccurrenceTime (vulnReportFilters * storage.VulnerabilityReportFilters ) bool {
133243 return vulnReportFilters .GetSinceLastSentScheduledReport () || vulnReportFilters .GetSinceStartDate () != nil
134244}
245+
246+ // split map field values like namespace labels(key=val) to key,val pair
247+ func splitLabelValue (labelVal string ) (string , string ) {
248+ parts := strings .SplitN (labelVal , "=" , 2 )
249+ if len (parts ) == 2 {
250+ return fmt .Sprintf ("%q" , parts [0 ]), fmt .Sprintf ("%q" , parts [1 ])
251+ }
252+ return fmt .Sprintf ("%q" , labelVal ), fmt .Sprintf ("%q" , "" )
253+ }
0 commit comments