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

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package explorer
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"sort"
    10  	"strings"
    11  
    12  	"github.com/rs/zerolog/log"
    13  	llx "go.mondoo.com/cnquery/llx"
    14  	"go.mondoo.com/cnquery/mrn"
    15  	"go.mondoo.com/cnquery/utils/multierr"
    16  	"go.mondoo.com/ranger-rpc/codes"
    17  	"go.mondoo.com/ranger-rpc/status"
    18  	"google.golang.org/genproto/googleapis/rpc/errdetails"
    19  	"google.golang.org/protobuf/proto"
    20  )
    21  
    22  func (s *LocalServices) Assign(ctx context.Context, assignment *Assignment) (*Empty, error) {
    23  	if len(assignment.PackMrns) == 0 {
    24  		return nil, status.Error(codes.InvalidArgument, "no query pack MRNs were provided")
    25  	}
    26  
    27  	// all remote, call upstream
    28  	if s.Upstream != nil && !s.Incognito {
    29  		return s.Upstream.QueryConductor.Assign(ctx, assignment)
    30  	}
    31  
    32  	// cache everything from upstream
    33  	if s.Upstream != nil && s.Incognito {
    34  		// NOTE: we request the packs to cache them
    35  		for i := range assignment.PackMrns {
    36  			mrn := assignment.PackMrns[i]
    37  			_, err := s.GetQueryPack(ctx, &Mrn{
    38  				Mrn: mrn,
    39  			})
    40  			if err != nil {
    41  				return nil, err
    42  			}
    43  		}
    44  	}
    45  
    46  	// assign a query pack locally
    47  	deltas := map[string]*AssignmentDelta{}
    48  	for i := range assignment.PackMrns {
    49  		packMrn := assignment.PackMrns[i]
    50  		deltas[packMrn] = &AssignmentDelta{
    51  			Mrn:    packMrn,
    52  			Action: AssignmentDelta_ADD,
    53  		}
    54  	}
    55  
    56  	s.DataLake.EnsureAsset(ctx, assignment.AssetMrn)
    57  
    58  	_, err := s.DataLake.MutateBundle(ctx, &BundleMutationDelta{
    59  		OwnerMrn: assignment.AssetMrn,
    60  		Deltas:   deltas,
    61  	}, true)
    62  	return globalEmpty, err
    63  }
    64  
    65  func (s *LocalServices) Unassign(ctx context.Context, assignment *Assignment) (*Empty, error) {
    66  	if len(assignment.PackMrns) == 0 {
    67  		return nil, status.Error(codes.InvalidArgument, "no query pack MRNs were provided")
    68  	}
    69  
    70  	// all remote, call upstream
    71  	if s.Upstream != nil && !s.Incognito {
    72  		return s.Upstream.QueryConductor.Unassign(ctx, assignment)
    73  	}
    74  
    75  	deltas := map[string]*AssignmentDelta{}
    76  	for i := range assignment.PackMrns {
    77  		packMrn := assignment.PackMrns[i]
    78  		deltas[packMrn] = &AssignmentDelta{
    79  			Mrn:    packMrn,
    80  			Action: AssignmentDelta_DELETE,
    81  		}
    82  	}
    83  
    84  	_, err := s.DataLake.MutateBundle(ctx, &BundleMutationDelta{
    85  		OwnerMrn: assignment.AssetMrn,
    86  		Deltas:   deltas,
    87  	}, true)
    88  	return globalEmpty, err
    89  }
    90  
    91  func (s *LocalServices) SetProps(ctx context.Context, req *PropsReq) (*Empty, error) {
    92  	// validate that the queries compile and fill in checksums
    93  	for i := range req.Props {
    94  		prop := req.Props[i]
    95  		code, err := prop.RefreshChecksumAndType(s.runtime.Schema())
    96  		if err != nil {
    97  			return nil, err
    98  		}
    99  		prop.CodeId = code.CodeV2.Id
   100  	}
   101  
   102  	return globalEmpty, s.DataLake.SetProps(ctx, req)
   103  }
   104  
   105  // Resolve executable bits for an asset (via asset filters)
   106  func (s *LocalServices) Resolve(ctx context.Context, req *ResolveReq) (*ResolvedPack, error) {
   107  	if s.Upstream != nil && !s.Incognito {
   108  		res, err := s.Upstream.Resolve(ctx, req)
   109  		if err != nil {
   110  			return nil, err
   111  		}
   112  
   113  		err = s.DataLake.SetResolvedPack(req.EntityMrn, res.FiltersChecksum, res)
   114  		if err != nil {
   115  			return nil, err
   116  		}
   117  
   118  		err = s.DataLake.SetAssetResolvedPack(ctx, req.EntityMrn, res, V2Code)
   119  		return res, err
   120  	}
   121  
   122  	bundle, err := s.DataLake.GetBundle(ctx, req.EntityMrn)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	bundleMap := bundle.ToMap()
   128  
   129  	filtersChecksum, err := MatchFilters(req.EntityMrn, req.AssetFilters, bundle.Packs, s.runtime.Schema())
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	supportedFilters := make(map[string]struct{}, len(req.AssetFilters))
   135  	for i := range req.AssetFilters {
   136  		f := req.AssetFilters[i]
   137  		supportedFilters[f.CodeId] = struct{}{}
   138  	}
   139  
   140  	job := ExecutionJob{
   141  		Queries:    make(map[string]*ExecutionQuery),
   142  		Datapoints: make(map[string]*DataQueryInfo),
   143  	}
   144  	for i := range bundle.Packs {
   145  		pack := bundle.Packs[i]
   146  
   147  		if !pack.Filters.Supports(supportedFilters) {
   148  			continue
   149  		}
   150  
   151  		props := NewPropsCache()
   152  		props.Add(bundle.Props...)
   153  
   154  		for i := range pack.Queries {
   155  			err := s.addQueryToJob(ctx, pack.Queries[i], &job, props, supportedFilters, bundleMap)
   156  			if err != nil {
   157  				return nil, err
   158  			}
   159  		}
   160  
   161  		for i := range pack.Groups {
   162  			group := pack.Groups[i]
   163  
   164  			if !group.Filters.Supports(supportedFilters) {
   165  				continue
   166  			}
   167  
   168  			for i := range group.Queries {
   169  				err := s.addQueryToJob(ctx, group.Queries[i], &job, props, supportedFilters, bundleMap)
   170  				if err != nil {
   171  					return nil, err
   172  				}
   173  			}
   174  		}
   175  	}
   176  
   177  	res := &ResolvedPack{
   178  		ExecutionJob: &job,
   179  	}
   180  
   181  	err = s.DataLake.SetResolvedPack(req.EntityMrn, filtersChecksum, res)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	err = s.DataLake.SetAssetResolvedPack(ctx, req.EntityMrn, res, V2Code)
   187  	return res, err
   188  }
   189  
   190  func (s *LocalServices) addQueryToJob(ctx context.Context, query *Mquery, job *ExecutionJob, propsCache PropsCache, supportedFilters map[string]struct{}, bundle *BundleMap) error {
   191  	if !query.Filters.Supports(supportedFilters) {
   192  		return nil
   193  	}
   194  
   195  	var props map[string]*llx.Primitive
   196  	var propRefs map[string]string
   197  	if len(query.Props) != 0 {
   198  		props = map[string]*llx.Primitive{}
   199  		propRefs = map[string]string{}
   200  
   201  		for i := range query.Props {
   202  			prop := query.Props[i]
   203  
   204  			override, name, _ := propsCache.Get(ctx, prop.Mrn)
   205  			if override != nil {
   206  				prop = override
   207  			}
   208  			if name == "" {
   209  				var err error
   210  				name, err = mrn.GetResource(prop.Mrn, MRN_RESOURCE_QUERY)
   211  				if err != nil {
   212  					return errors.New("failed to get property name")
   213  				}
   214  			}
   215  
   216  			props[name] = &llx.Primitive{Type: prop.Type}
   217  			propRefs[name] = prop.CodeId
   218  
   219  			if _, ok := job.Queries[prop.CodeId]; ok {
   220  				continue
   221  			}
   222  
   223  			code, err := prop.Compile(nil, s.runtime.Schema())
   224  			if err != nil {
   225  				return multierr.Wrap(err, "failed to compile property for query "+query.Mrn)
   226  			}
   227  			job.Queries[prop.CodeId] = &ExecutionQuery{
   228  				Query:    prop.Mql,
   229  				Checksum: prop.Checksum,
   230  				Code:     code,
   231  			}
   232  		}
   233  	}
   234  
   235  	if len(query.Variants) != 0 {
   236  		for i := range query.Variants {
   237  			ref := query.Variants[i].Mrn
   238  			err := s.addQueryToJob(ctx, bundle.Queries[ref], job, propsCache, supportedFilters, bundle)
   239  			if err != nil {
   240  				return err
   241  			}
   242  		}
   243  		return nil
   244  	}
   245  
   246  	codeBundle, err := query.Compile(props, s.runtime.Schema())
   247  	if err != nil {
   248  		return err
   249  	}
   250  
   251  	equery := &ExecutionQuery{
   252  		Query:      query.Mql,
   253  		Checksum:   query.Checksum,
   254  		Code:       codeBundle,
   255  		Properties: propRefs,
   256  	}
   257  
   258  	code := equery.Code.CodeV2
   259  	refs := append(code.Datapoints(), code.Entrypoints()...)
   260  
   261  	job.Queries[query.CodeId] = equery
   262  	for i := range refs {
   263  		ref := refs[i]
   264  		checksum := code.Checksums[ref]
   265  		typ := code.Chunk(ref).DereferencedTypeV2(code)
   266  
   267  		job.Datapoints[checksum] = &DataQueryInfo{
   268  			Type: string(typ),
   269  		}
   270  	}
   271  
   272  	return nil
   273  }
   274  
   275  // MatchFilters will take the list of filters and only return the ones
   276  // that are supported by the given querypacks.
   277  func MatchFilters(entityMrn string, filters []*Mquery, packs []*QueryPack, schema llx.Schema) (string, error) {
   278  	supported := map[string]*Mquery{}
   279  	for i := range packs {
   280  		pack := packs[i]
   281  		if pack.ComputedFilters == nil {
   282  			continue
   283  		}
   284  
   285  		for k, v := range pack.ComputedFilters.Items {
   286  			supported[k] = v
   287  		}
   288  	}
   289  
   290  	matching := []*Mquery{}
   291  	for i := range filters {
   292  		cur := filters[i]
   293  
   294  		if _, ok := supported[cur.CodeId]; ok {
   295  			curCopy := proto.Clone(cur).(*Mquery)
   296  			curCopy.Mrn = entityMrn + "/assetfilter/" + cur.CodeId
   297  			curCopy.Title = curCopy.Query
   298  			matching = append(matching, curCopy)
   299  		}
   300  	}
   301  
   302  	if len(matching) == 0 {
   303  		return "", NewAssetMatchError(entityMrn, "querypacks", "no-matching-packs", filters, &Filters{Items: supported})
   304  	}
   305  
   306  	sum, err := ChecksumFilters(matching, schema)
   307  	if err != nil {
   308  		return "", err
   309  	}
   310  
   311  	return sum, nil
   312  }
   313  
   314  func NewAssetMatchError(mrn string, objectType string, errorReason string, assetFilters []*Mquery, supported *Filters) error {
   315  	if len(assetFilters) == 0 {
   316  		// send a proto error with details, so that the agent can render it properly
   317  		msg := "asset doesn't support any " + objectType
   318  		st := status.New(codes.InvalidArgument, msg)
   319  
   320  		std, err := st.WithDetails(&errdetails.ErrorInfo{
   321  			Domain: SERVICE_NAME,
   322  			Reason: errorReason,
   323  			Metadata: map[string]string{
   324  				"mrn":       mrn,
   325  				"errorCode": NotApplicable.String(),
   326  			},
   327  		})
   328  		if err != nil {
   329  			log.Error().Err(err).Msg("could not send status with additional information")
   330  			return st.Err()
   331  		}
   332  		return std.Err()
   333  	}
   334  
   335  	supportedSummary := supported.Summarize()
   336  	var supportedPrefix string
   337  	if supportedSummary == "" {
   338  		supportedPrefix = objectType + " didn't provide any filters"
   339  	} else {
   340  		supportedPrefix = objectType + " support: "
   341  	}
   342  
   343  	filters := make([]string, len(assetFilters))
   344  	for i := range assetFilters {
   345  		filters[i] = strings.TrimSpace(assetFilters[i].Mql)
   346  	}
   347  	sort.Strings(filters)
   348  	foundSummary := strings.Join(filters, ", ")
   349  	foundPrefix := "asset supports: "
   350  
   351  	msg := "asset isn't supported by any " + objectType + "\n" +
   352  		supportedPrefix + supportedSummary + "\n" +
   353  		foundPrefix + foundSummary + "\n"
   354  	return status.Error(codes.InvalidArgument, msg)
   355  }
   356  
   357  func (s *LocalServices) StoreResults(ctx context.Context, req *StoreResultsReq) (*Empty, error) {
   358  	_, err := s.DataLake.UpdateData(ctx, req.AssetMrn, req.Data)
   359  	if err != nil {
   360  		return globalEmpty, err
   361  	}
   362  
   363  	if s.Upstream != nil && !s.Incognito {
   364  		_, err := s.Upstream.QueryConductor.StoreResults(ctx, req)
   365  		if err != nil {
   366  			return globalEmpty, err
   367  		}
   368  	}
   369  
   370  	return globalEmpty, nil
   371  }
   372  
   373  func (s *LocalServices) GetReport(ctx context.Context, req *EntityDataRequest) (*Report, error) {
   374  	return s.DataLake.GetReport(ctx, req.EntityMrn, req.DataMrn)
   375  }
   376  
   377  func (s *LocalServices) SynchronizeAssets(context.Context, *SynchronizeAssetsReq) (*SynchronizeAssetsResp, error) {
   378  	return nil, status.Error(codes.Unimplemented, "not implemented")
   379  }