go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/explorer/query_hub.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  	"os"
    10  	"path"
    11  
    12  	"go.mondoo.com/cnquery/mrn"
    13  
    14  	"go.mondoo.com/ranger-rpc"
    15  
    16  	"go.mondoo.com/cnquery/logger"
    17  	"go.mondoo.com/ranger-rpc/codes"
    18  	"go.mondoo.com/ranger-rpc/status"
    19  	"go.opentelemetry.io/otel"
    20  )
    21  
    22  const (
    23  	defaultRegistryUrl     = "https://registry.api.mondoo.com"
    24  	RegistryServiceName    = "registry.mondoo.com"
    25  	CollectionIDNamespace  = "namespace"
    26  	CollectionIDQueryPacks = "querypacks"
    27  )
    28  
    29  var tracer = otel.Tracer("go.mondoo.com/cnquery/explorer")
    30  
    31  func NewQueryPackMrn(namespace string, uid string) string {
    32  	m := &mrn.MRN{
    33  		ServiceName:          RegistryServiceName,
    34  		RelativeResourceName: path.Join(CollectionIDNamespace, namespace, CollectionIDQueryPacks, uid),
    35  	}
    36  	return m.String()
    37  }
    38  
    39  // ValidateBundle and check queries, relationships, MRNs, and versions
    40  func (s *LocalServices) ValidateBundle(ctx context.Context, bundle *Bundle) (*Empty, error) {
    41  	_, err := bundle.Compile(ctx, s.runtime.Schema())
    42  	return globalEmpty, err
    43  }
    44  
    45  // SetBundle stores a bundle of query packs and queries in this marketplace
    46  func (s *LocalServices) SetBundle(ctx context.Context, bundle *Bundle) (*Empty, error) {
    47  	bundlemap, err := bundle.Compile(ctx, s.runtime.Schema())
    48  	if err != nil {
    49  		return globalEmpty, err
    50  	}
    51  
    52  	if err := s.setBundleFromMap(ctx, bundlemap); err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	return globalEmpty, nil
    57  }
    58  
    59  // preparePack takes a query pack and an optional bundle and gets it
    60  // ready to be saved in the DB, including asset filters.
    61  func (s *LocalServices) preparePack(ctx context.Context, querypack *QueryPack) (*QueryPack, []*Mquery, error) {
    62  	logCtx := logger.FromContext(ctx)
    63  
    64  	if querypack == nil || len(querypack.Mrn) == 0 {
    65  		return nil, nil, status.Error(codes.InvalidArgument, "mrn is required")
    66  	}
    67  
    68  	if querypack.LocalExecutionChecksum == "" || querypack.LocalContentChecksum == "" {
    69  		logCtx.Trace().Str("querypack", querypack.Mrn).Msg("hub> update checksum")
    70  		if err := querypack.UpdateChecksums(); err != nil {
    71  			return nil, nil, err
    72  		}
    73  	}
    74  
    75  	filters, err := querypack.ComputeFilters(ctx, querypack.Mrn)
    76  	return querypack, filters, err
    77  }
    78  
    79  func (s *LocalServices) setPack(ctx context.Context, querypack *QueryPack) error {
    80  	querypack, filters, err := s.preparePack(ctx, querypack)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	err = s.DataLake.SetQueryPack(ctx, querypack, filters)
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	return nil
    91  }
    92  
    93  func (s *LocalServices) setBundleFromMap(ctx context.Context, bundle *BundleMap) error {
    94  	logCtx := logger.FromContext(ctx)
    95  
    96  	var err error
    97  	for i := range bundle.Queries {
    98  		query := bundle.Queries[i]
    99  		logCtx.Debug().Str("mrn", query.Mrn).Msg("store query")
   100  
   101  		if err := s.setQuery(ctx, query.Mrn, query); err != nil {
   102  			return err
   103  		}
   104  	}
   105  
   106  	for i := range bundle.Props {
   107  		query := bundle.Props[i]
   108  		logCtx.Debug().Str("mrn", query.Mrn).Msg("store prop")
   109  
   110  		if err := s.setQuery(ctx, query.Mrn, query); err != nil {
   111  			return err
   112  		}
   113  	}
   114  
   115  	for i := range bundle.Packs {
   116  		querypack := bundle.Packs[i]
   117  		logCtx.Debug().Str("owner", querypack.OwnerMrn).Str("uid", querypack.Uid).Str("mrn", querypack.Mrn).Msg("store query pack")
   118  		querypack.OwnerMrn = bundle.OwnerMrn
   119  
   120  		if err = s.setPack(ctx, querypack); err != nil {
   121  			return err
   122  		}
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  func (s *LocalServices) setQuery(ctx context.Context, mrn string, query *Mquery) error {
   129  	if query == nil {
   130  		return errors.New("cannot set query '" + mrn + "' as it is not defined")
   131  	}
   132  
   133  	if query.Title == "" {
   134  		query.Title = query.Query
   135  	}
   136  
   137  	return s.DataLake.SetQuery(ctx, mrn, query)
   138  }
   139  
   140  // GetQueryPack for a given MRN
   141  func (s *LocalServices) GetQueryPack(ctx context.Context, in *Mrn) (*QueryPack, error) {
   142  	logCtx := logger.FromContext(ctx)
   143  
   144  	if in == nil || len(in.Mrn) == 0 {
   145  		return nil, status.Error(codes.InvalidArgument, "mrn is required")
   146  	}
   147  
   148  	b, err := s.DataLake.GetQueryPack(ctx, in.Mrn)
   149  	if err == nil {
   150  		logCtx.Debug().Str("querypack", in.Mrn).Err(err).Msg("query.hub> get query pack from db")
   151  		return b, nil
   152  	}
   153  	if s.Upstream == nil {
   154  		return nil, err
   155  	}
   156  
   157  	// try upstream; once it's cached, try again
   158  	_, err = s.cacheUpstreamQueryPackBundle(ctx, in.Mrn)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  	return s.DataLake.GetQueryPack(ctx, in.Mrn)
   163  }
   164  
   165  // GetQueryPack for a given MRN
   166  func (s *LocalServices) GetBundle(ctx context.Context, in *Mrn) (*Bundle, error) {
   167  	if in == nil || len(in.Mrn) == 0 {
   168  		return nil, status.Error(codes.InvalidArgument, "mrn is required")
   169  	}
   170  
   171  	b, err := s.DataLake.GetBundle(ctx, in.Mrn)
   172  	if err == nil {
   173  		return b, nil
   174  	}
   175  	if s.Upstream == nil {
   176  		return nil, err
   177  	}
   178  	// try upstream
   179  	return s.cacheUpstreamQueryPackBundle(ctx, in.Mrn)
   180  }
   181  
   182  // GetFilters retrieves the asset filter queries for a given query pack
   183  func (s *LocalServices) GetFilters(ctx context.Context, mrn *Mrn) (*Mqueries, error) {
   184  	if mrn == nil || len(mrn.Mrn) == 0 {
   185  		return nil, status.Error(codes.InvalidArgument, "mrn is required")
   186  	}
   187  
   188  	filters, err := s.DataLake.GetQueryPackFilters(ctx, mrn.Mrn)
   189  	if err != nil {
   190  		return nil, errors.New("failed to get filters: " + err.Error())
   191  	}
   192  
   193  	return &Mqueries{Items: filters}, nil
   194  }
   195  
   196  // List all query packs for a given owner
   197  func (s *LocalServices) List(ctx context.Context, filter *ListReq) (*QueryPacks, error) {
   198  	if filter == nil {
   199  		return nil, status.Error(codes.InvalidArgument, "need to provide a filter object for list")
   200  	}
   201  
   202  	if len(filter.OwnerMrn) == 0 {
   203  		return nil, status.Error(codes.InvalidArgument, "a MRN for the owner is required")
   204  	}
   205  
   206  	res, err := s.DataLake.ListQueryPacks(ctx, filter.OwnerMrn, filter.Name)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	if res == nil {
   211  		res = []*QueryPack{}
   212  	}
   213  
   214  	return &QueryPacks{
   215  		Items: res,
   216  	}, nil
   217  }
   218  
   219  // DeleteQueryPack removes a query pack via its given MRN
   220  func (s *LocalServices) DeleteQueryPack(ctx context.Context, in *Mrn) (*Empty, error) {
   221  	if in == nil || len(in.Mrn) == 0 {
   222  		return nil, status.Error(codes.InvalidArgument, "mrn is required")
   223  	}
   224  
   225  	return globalEmpty, s.DataLake.DeleteQueryPack(ctx, in.Mrn)
   226  }
   227  
   228  // DefaultPacks retrieves a list of default packs for a given asset
   229  func (s *LocalServices) DefaultPacks(ctx context.Context, req *DefaultPacksReq) (*URLs, error) {
   230  	if req == nil {
   231  		return nil, status.Error(codes.InvalidArgument, "no filters provided")
   232  	}
   233  
   234  	if s.Upstream != nil {
   235  		// since upstream is initialized with http client, it uses proxy config
   236  		return s.Upstream.DefaultPacks(ctx, req)
   237  	}
   238  
   239  	registryEndpoint := os.Getenv("REGISTRY_URL")
   240  	if registryEndpoint == "" {
   241  		registryEndpoint = defaultRegistryUrl
   242  	}
   243  
   244  	// Note, this does not use the proxy config override from the mondoo.yml since we only get here when
   245  	// it is used without upstream config
   246  	client, err := NewQueryHubClient(registryEndpoint, ranger.DefaultHttpClient())
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  	return client.DefaultPacks(ctx, req)
   251  }
   252  
   253  // HELPER METHODS
   254  // =================
   255  
   256  // cacheUpstreamQueryPackBundle by storing a copy of the upstream pack in this db
   257  // Note: upstream has to be defined
   258  func (s *LocalServices) cacheUpstreamQueryPackBundle(ctx context.Context, mrn string) (*Bundle, error) {
   259  	logCtx := logger.FromContext(ctx)
   260  	if s.Upstream == nil {
   261  		return nil, errors.New("failed to retrieve upstream query pack " + mrn + " since upstream is not defined")
   262  	}
   263  
   264  	logCtx.Debug().Str("querypack", mrn).Msg("query.hub> fetch query pack from upstream")
   265  	bundle, err := s.Upstream.GetBundle(ctx, &Mrn{Mrn: mrn})
   266  	if err != nil {
   267  		logCtx.Error().Err(err).Str("querypack", mrn).Msg("query.hub> failed to retrieve query pack from upstream")
   268  		return nil, errors.New("failed to retrieve upstream query pack " + mrn + ": " + err.Error())
   269  	}
   270  
   271  	bundleMap := bundle.ToMap()
   272  	if err = s.setBundleFromMap(ctx, bundleMap); err != nil {
   273  		logCtx.Error().Err(err).Str("querypack", mrn).Msg("query.hub> failed to set query pack retrieved from upstream")
   274  		return nil, err
   275  	}
   276  
   277  	// we need to assign the bundles to the asset
   278  	querypackMrns := []string{}
   279  	for k := range bundleMap.Packs {
   280  		querypackMrns = append(querypackMrns, k)
   281  	}
   282  
   283  	// assign a query pack locally
   284  	deltas := map[string]*AssignmentDelta{}
   285  	for i := range querypackMrns {
   286  		packMrn := querypackMrns[i]
   287  		deltas[packMrn] = &AssignmentDelta{
   288  			Mrn:    packMrn,
   289  			Action: AssignmentDelta_ADD,
   290  		}
   291  	}
   292  
   293  	s.DataLake.EnsureAsset(ctx, mrn)
   294  	_, err = s.DataLake.MutateBundle(ctx, &BundleMutationDelta{
   295  		OwnerMrn: mrn,
   296  		Deltas:   deltas,
   297  	}, true)
   298  
   299  	logCtx.Debug().Str("querypack", mrn).Msg("query.hub> fetched bundle from upstream")
   300  	return bundle, nil
   301  }