go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/explorer/filters.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package explorer
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"errors"
    10  	"sort"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"go.mondoo.com/cnquery/checksums"
    15  	llx "go.mondoo.com/cnquery/llx"
    16  	"go.mondoo.com/cnquery/utils/multierr"
    17  )
    18  
    19  // NewFilters creates a Filters object from a simple list of MQL snippets
    20  func NewFilters(queries ...string) *Filters {
    21  	res := &Filters{
    22  		Items: map[string]*Mquery{},
    23  	}
    24  
    25  	for i := range queries {
    26  		res.Items[strconv.Itoa(i)] = &Mquery{Mql: queries[i]}
    27  	}
    28  
    29  	return res
    30  }
    31  
    32  // Computes the checksum for the filters and adds it to the aggregate
    33  // execution and content checksums. Filters must have been previously compiled!
    34  // We need it to be ready for checksums and don't want to do the compile
    35  // step here because it's not the primary function.
    36  func (filters *Filters) Checksum() (checksums.Fast, checksums.Fast) {
    37  	content := checksums.New
    38  	execution := checksums.New
    39  
    40  	if filters == nil {
    41  		return content, execution
    42  	}
    43  
    44  	keys := make([]string, len(filters.Items))
    45  	i := 0
    46  	for k := range filters.Items {
    47  		// we add this sanity check since we expose the method, but can't ensure
    48  		// that users have compiled everything beforehand
    49  		if len(k) < 2 {
    50  			panic("internal error processing filter checksums: queries are not compiled")
    51  		}
    52  
    53  		keys[i] = k
    54  		i++
    55  	}
    56  	sort.Strings(keys)
    57  
    58  	for i := range keys {
    59  		filter := filters.Items[keys[i]]
    60  		content = content.Add(filter.Title).Add(filter.Desc)
    61  
    62  		// we add this sanity check since we expose the method, but can't ensure
    63  		// that users have compiled everything beforehand
    64  		if filter.Checksum == "" || filter.CodeId == "" {
    65  			panic("internal error processing filter checksums: query is compiled")
    66  		}
    67  
    68  		content = content.Add(filter.Checksum)
    69  		execution = execution.Add(filter.CodeId)
    70  	}
    71  
    72  	content = content.AddUint(uint64(execution))
    73  
    74  	return content, execution
    75  }
    76  
    77  func (s *Filters) UnmarshalJSON(data []byte) error {
    78  	var str string
    79  	err := json.Unmarshal(data, &str)
    80  	if err == nil {
    81  		s.Items = map[string]*Mquery{}
    82  		s.Items[""] = &Mquery{
    83  			Mql: str,
    84  		}
    85  		return nil
    86  	}
    87  
    88  	// FIXME: DEPRECATED, remove in v9.0 vv
    89  	// This old style of specifying filters is going to be removed, we
    90  	// have an alternative with list and keys
    91  	var arr []string
    92  	err = json.Unmarshal(data, &arr)
    93  	if err == nil {
    94  		s.Items = map[string]*Mquery{}
    95  		for i := range arr {
    96  			s.Items[strconv.Itoa(i)] = &Mquery{Mql: arr[i]}
    97  		}
    98  		return nil
    99  	}
   100  	// ^^
   101  
   102  	var list []*Mquery
   103  	err = json.Unmarshal(data, &list)
   104  	if err == nil {
   105  		s.Items = map[string]*Mquery{}
   106  		for i := range list {
   107  			s.Items[strconv.Itoa(i)] = list[i]
   108  		}
   109  		return nil
   110  	}
   111  
   112  	// prevent recursive calls into UnmarshalJSON with a placeholder type
   113  	type tmp Filters
   114  	return json.Unmarshal(data, (*tmp)(s))
   115  }
   116  
   117  func (s *Filters) Compile(ownerMRN string, schema llx.Schema) error {
   118  	if s == nil || len(s.Items) == 0 {
   119  		return nil
   120  	}
   121  
   122  	res := make(map[string]*Mquery, len(s.Items))
   123  	for _, query := range s.Items {
   124  		query.RefreshAsFilter(ownerMRN, schema)
   125  
   126  		if _, ok := res[query.CodeId]; ok {
   127  			continue
   128  		}
   129  
   130  		res[query.CodeId] = query
   131  	}
   132  
   133  	s.Items = res
   134  	return nil
   135  }
   136  
   137  // AddFilters takes all given filters (or nil) and adds them to the parent.
   138  // Note: The parent must be non-empty and non-nil, or this method will panic.
   139  func (s *Filters) AddFilters(child *Filters) {
   140  	if child == nil {
   141  		return
   142  	}
   143  
   144  	for k, v := range child.Items {
   145  		s.Items[k] = v
   146  	}
   147  }
   148  
   149  var ErrQueryNotFound = errors.New("query not found")
   150  
   151  // AddQueryFilters attempt to take a query (or nil) and register all its filters.
   152  // This includes any variants that the query might have as well.
   153  func (s *Filters) AddQueryFilters(query *Mquery, lookupQueries map[string]*Mquery) error {
   154  	if query == nil {
   155  		return nil
   156  	}
   157  
   158  	return s.AddQueryFiltersFn(context.Background(), query, func(_ context.Context, mrn string) (*Mquery, error) {
   159  		q, ok := lookupQueries[mrn]
   160  		if !ok {
   161  			return nil, ErrQueryNotFound
   162  		}
   163  		return q, nil
   164  	})
   165  }
   166  
   167  // AddQueryFiltersFn attempt to take a query (or nil) and register all its filters.
   168  // This includes any variants that the query might have as well.
   169  func (s *Filters) AddQueryFiltersFn(ctx context.Context, query *Mquery, lookupQuery func(ctx context.Context, mrn string) (*Mquery, error)) error {
   170  	if query == nil {
   171  		return nil
   172  	}
   173  
   174  	s.AddFilters(query.Filters)
   175  
   176  	for i := range query.Variants {
   177  		mrn := query.Variants[i].Mrn
   178  		variant, err := lookupQuery(ctx, mrn)
   179  		if err != nil {
   180  			return multierr.Wrap(err, "cannot find query variant "+mrn)
   181  		}
   182  		s.AddQueryFiltersFn(ctx, variant, lookupQuery)
   183  	}
   184  	return nil
   185  }
   186  
   187  // Checks if the given queries (via CodeIDs) are supported by this set of
   188  // asset filters. Asset filters that are not defined return true.
   189  // If any of the filters is supported, the set returns true.
   190  func (s *Filters) Supports(supported map[string]struct{}) bool {
   191  	if s == nil || len(s.Items) == 0 {
   192  		return true
   193  	}
   194  
   195  	for k := range s.Items {
   196  		if _, ok := supported[k]; ok {
   197  			return true
   198  		}
   199  	}
   200  
   201  	return false
   202  }
   203  
   204  func (s *Filters) Summarize() string {
   205  	if s == nil || len(s.Items) == 0 {
   206  		return ""
   207  	}
   208  
   209  	filters := make([]string, len(s.Items))
   210  	i := 0
   211  	for _, filter := range s.Items {
   212  		if filter.Title != "" {
   213  			filters[i] = filter.Title
   214  		} else {
   215  			filters[i] = filter.Mql
   216  		}
   217  		i++
   218  	}
   219  
   220  	sort.Strings(filters)
   221  	return strings.Join(filters, ", ")
   222  }