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 }