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 }