From 1c0a4bd44f53d3f93da4ecf75ab883b055bbf359 Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Sat, 14 Dec 2024 05:31:56 -0600 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=A7=B9=20Remove=20old=20resolver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This removes the old resolver and uses the rewrite everywhere. Should fix #1515 --- policy/bundle.go | 6 +- policy/resolver.go | 1347 +------------------------- policy/resolver_test.go | 1833 ------------------------------------ policy/resolver_v2_test.go | 46 +- 4 files changed, 26 insertions(+), 3206 deletions(-) diff --git a/policy/bundle.go b/policy/bundle.go index 7cd9e697..5fcd6cf5 100644 --- a/policy/bundle.go +++ b/policy/bundle.go @@ -102,11 +102,7 @@ func BundleExecutionChecksum(ctx context.Context, policy *Policy, framework *Fra // So far the checksum only includes the policy and the framework // It does not change if any of the jobs changes, only if the policy or the framework changes // To update the resolved policy, when we change how it is generated, change the incoporated version of the resolver - if IsNextGenResolver(ctx) { - res = res.Add(RESOLVER_VERSION_NG) - } else { - res = res.Add(RESOLVER_VERSION) - } + res = res.Add(RESOLVER_VERSION) return res.String() } diff --git a/policy/resolver.go b/policy/resolver.go index 1bcf5bb8..b31bf384 100644 --- a/policy/resolver.go +++ b/policy/resolver.go @@ -8,7 +8,6 @@ import ( "encoding/base64" "encoding/binary" "math/rand" - "slices" "sort" "time" @@ -32,8 +31,7 @@ const ( // This is used to change the checksum of the resolved policy when we want it to be recalculated // This can be updated, e.g., when we change how the report jobs are generated // A change of this string will force an update of all the stored resolved policies - RESOLVER_VERSION = "v2024-08-29" - RESOLVER_VERSION_NG = "v2024-12-02" + RESOLVER_VERSION = "v2024-12-02" ) type AssetMutation struct { @@ -452,18 +450,7 @@ func (s *LocalServices) resolve(ctx context.Context, policyMrn string, assetFilt return nil, errors.New("concurrent policy resolve") } -type nextGenResolverFeature struct{} - -func WithNextGenResolver(context.Context) context.Context { - return context.WithValue(context.Background(), nextGenResolverFeature{}, true) -} - -func IsNextGenResolver(ctx context.Context) bool { - return ctx.Value(nextGenResolverFeature{}) != nil -} - func (s *LocalServices) tryResolve(ctx context.Context, bundleMrn string, assetFilters []*explorer.Mquery) (*ResolvedPolicy, error) { - logCtx := logger.FromContext(ctx) now := time.Now() conf := s.NewCompilerConfig() @@ -491,9 +478,7 @@ func (s *LocalServices) tryResolve(ctx context.Context, bundleMrn string, assetF bundleMap := bundle.ToMap() - frameworkObj := bundleMap.Frameworks[bundleMrn] policyObj := bundleMap.Policies[bundleMrn] - resolvedPolicyExecutionChecksum := BundleExecutionChecksum(ctx, policyObj, frameworkObj) matchingFilters, err := MatchingAssetFilters(bundleMrn, assetFilters, policyObj) if err != nil { @@ -503,206 +488,18 @@ func (s *LocalServices) tryResolve(ctx context.Context, bundleMrn string, assetF return nil, explorer.NewAssetMatchError(bundleMrn, "policies", "no-matching-policy", assetFilters, policyObj.ComputedFilters) } - if IsNextGenResolver(ctx) { - resolvedPolicy, err := buildResolvedPolicy(ctx, bundleMrn, bundle, matchingFilters, time.Now(), conf) - if err != nil { - return nil, err - } - - err = s.DataLake.SetResolvedPolicy(ctx, bundleMrn, resolvedPolicy, V2Code, false) - if err != nil { - return nil, err - } - - return resolvedPolicy, nil - } - - assetFiltersMap := make(map[string]struct{}, len(matchingFilters)) - for i := range matchingFilters { - assetFiltersMap[matchingFilters[i].CodeId] = struct{}{} - } - - assetFiltersChecksum, err := ChecksumAssetFilters(matchingFilters, conf) - if err != nil { - return nil, err - } - - // ... and if the filters changed, try to look up the resolved policy again - if assetFiltersChecksum != allFiltersChecksum { - rp, err = s.DataLake.CachedResolvedPolicy(ctx, bundleMrn, assetFiltersChecksum, V2Code) - if err != nil { - return nil, err - } - if rp != nil { - return rp, nil - } - } - - // intermission: prep for the other phases - logCtx.Debug(). - Str("bundle mrn", bundleMrn). - Interface("asset filters", matchingFilters). - Msg("resolver> phase 1: no cached result, resolve the bundle now") - - cache := &resolverCache{ - baseChecksum: BundleExecutionChecksum(ctx, policyObj, frameworkObj), - assetFiltersChecksum: assetFiltersChecksum, - assetFilters: assetFiltersMap, - executionQueries: map[string]*ExecutionQuery{}, - codeIdToMrn: map[string][]string{}, - dataQueries: map[string]struct{}{}, - propsCache: explorer.NewPropsCache(), - queriesByMsum: map[string]*explorer.Mquery{}, - riskMrns: map[string]*explorer.Mquery{}, - riskInfos: map[string]*RiskFactor{}, - reportingJobsByUUID: map[string]*ReportingJob{}, - reportingJobsByMsum: map[string][]*ReportingJob{}, - reportingJobsByCodeId: map[string][]*ReportingJob{}, - reportingJobsActive: map[string]bool{}, - riskFactors: map[string]*RiskFactor{}, - bundleMap: bundleMap, - compilerConfig: conf, - } - - rjUUID := cache.relativeChecksum(policyObj.GraphExecutionChecksum) - - reportingJob := &ReportingJob{ - Uuid: rjUUID, - QrId: "root", - ChildJobs: map[string]*explorer.Impact{}, - Datapoints: map[string]bool{}, - Type: ReportingJob_POLICY, - } - - cache.reportingJobsByUUID[reportingJob.Uuid] = reportingJob - - if err := s.collectRisks(ctx, cache, bundleMrn); err != nil { - logCtx.Error(). - Err(err). - Str("bundle", bundleMrn). - Msg("resolver> internal error, trying to collect risk modifications") - return nil, err - } - - // phase 2: optimizations for assets - // assets are always connected to a space, so figure out if a space policy exists - // everything else in an asset can be aggregated into a shared policy - - // TODO: IMPLEMENT - - // phase 3: build the policy and scoring tree - policyToJobsCache := &policyResolverCache{ - removedPolicies: map[string]struct{}{}, - removedQueries: map[string]struct{}{}, - parentPolicies: map[string]struct{}{}, - childJobsByMrn: map[string][]*ReportingJob{}, - global: cache, - } - err = s.policyToJobs(ctx, bundleMrn, reportingJob, policyToJobsCache, now) + resolvedPolicy, err := buildResolvedPolicy(ctx, bundleMrn, bundle, matchingFilters, now, conf) if err != nil { - logCtx.Error(). - Err(err). - Str("policy", bundleMrn). - Msg("resolver> phase 3: internal error, trying to turn policy mrn into jobs") return nil, err } - logCtx.Debug(). - Str("policy", bundleMrn). - Msg("resolver> phase 3: turn policy into jobs [ok]") - // phase 4: get all queries + assign them reporting jobs + update scoring jobs - executionJob, collectorJob, err := s.jobsToQueries(ctx, bundleMrn, cache) + err = s.DataLake.SetResolvedPolicy(ctx, bundleMrn, resolvedPolicy, V2Code, false) if err != nil { - logCtx.Error(). - Err(err). - Str("policy", bundleMrn). - Msg("resolver> phase 4: internal error, trying to turn policy jobs into queries") - return nil, err - } - - // prune policies without children - // Its possible to get here and have a policy reporting job with no queries attached. - // This happens because a reporting job is created for a policy if its ComputedFilters - // field has a match with the asset filters. This field seems to include all filters - // for any checks/queries that are attached to the policy. If all the policies groups - // could say this group does not match, a reporting job for the policy is still created - // in this case, so we prune them here - reportingJobUUIDs := topologicalSortReportingJobs(collectorJob.ReportingJobs) - for _, rjUUID := range reportingJobUUIDs { - rj := collectorJob.ReportingJobs[rjUUID] - if rj.QrId == "root" { - // If we prune the root, we're going to get a broken resolved policy. - // For example, the framework code that follows wants to report to it. - continue - } - if rj.Type == ReportingJob_POLICY && (len(rj.ChildJobs)+len(rj.Datapoints) == 0) { - logCtx.Debug(). - Str("policy", bundleMrn). - Str("uuid", rjUUID). - Str("qrId", rj.QrId). - Msg("resolver> phase 4: pruning empty policy reporting job") - delete(collectorJob.ReportingJobs, rj.Uuid) - for _, parentUuid := range rj.Notify { - parentJob, ok := collectorJob.ReportingJobs[parentUuid] - if !ok { - continue - } - delete(parentJob.ChildJobs, rj.Uuid) - } - } - } - - logCtx.Debug(). - Str("policy", bundleMrn). - Msg("resolver> phase 4: aggregate queries and jobs [ok]") - - // phase 5: add frameworks and controls - resolvedFramework := ResolveFramework(bundleMrn, bundleMap.Frameworks) - cacheFrameworkJobs := &frameworkResolverCache{ - resolverCache: cache, - frameworkJobsByMrn: make(map[string]*ReportingJob), - } - if err := s.jobsToFrameworks(cacheFrameworkJobs, resolvedFramework, collectorJob, bundleMrn, reportingJob); err != nil { - logCtx.Error().Err(err). - Str("bundle", bundleMrn). - Msg("resolver> phase 5: internal error, trying to attach framework to resolved policy") return nil, err } - if err := s.jobsToControls(cacheFrameworkJobs, resolvedFramework, collectorJob); err != nil { - logCtx.Error(). - Err(err). - Str("bundle", bundleMrn). - Msg("resolver> phase 5: internal error, trying to attach controls to resolved policy [ok]") - } + return resolvedPolicy, nil - logCtx.Debug(). - Str("bundle", bundleMrn). - Msg("resolver> phase 5: resolve controls [ok]") - - // phase 6: refresh all checksums - refreshChecksums(executionJob, collectorJob) - - // the final phases are done in the DataLake - for _, rj := range collectorJob.ReportingJobs { - rj.RefreshChecksum() - } - - resolvedPolicy := ResolvedPolicy{ - GraphExecutionChecksum: resolvedPolicyExecutionChecksum, - Filters: matchingFilters, - FiltersChecksum: assetFiltersChecksum, - ExecutionJob: executionJob, - CollectorJob: collectorJob, - ReportingJobUuid: reportingJob.Uuid, - } - - err = s.DataLake.SetResolvedPolicy(ctx, bundleMrn, &resolvedPolicy, V2Code, false) - if err != nil { - return nil, err - } - - return &resolvedPolicy, nil } func refreshChecksums(executionJob *ExecutionJob, collectorJob *CollectorJob) { @@ -750,829 +547,6 @@ func refreshChecksums(executionJob *ExecutionJob, collectorJob *CollectorJob) { } } -func (s *LocalServices) collectRisks(ctx context.Context, cache *resolverCache, policyMrn string) error { - policyObj, ok := cache.bundleMap.Policies[policyMrn] - if !ok || policyObj == nil { - return errors.New("cannot find policy '" + policyMrn + "' while resolving") - } - - for _, g := range policyObj.Groups { - for _, p := range g.Policies { - if err := s.collectRisks(ctx, cache, p.Mrn); err != nil { - return err - } - } - } - - for _, rf := range policyObj.RiskFactors { - s.mergeRisk(cache, rf) - } - - return nil -} - -func (s *LocalServices) mergeRisk(cache *resolverCache, riskFactor *RiskFactor) { - if existing, ok := cache.riskFactors[riskFactor.Mrn]; ok { - if riskFactor.Magnitude != nil { - existing.Magnitude = riskFactor.Magnitude - } - - if riskFactor.Action != explorer.Action_UNSPECIFIED { - existing.Action = riskFactor.Action - } - } else { - cache.riskFactors[riskFactor.Mrn] = riskFactor - } -} - -func (s *LocalServices) policyToJobs(ctx context.Context, policyMrn string, ownerJob *ReportingJob, - parentCache *policyResolverCache, now time.Time, -) error { - ctx, span := tracer.Start(ctx, "resolver/policyToJobs") - defer span.End() - - policyObj, ok := parentCache.global.bundleMap.Policies[policyMrn] - if !ok || policyObj == nil { - return errors.New("cannot find policy '" + policyMrn + "' while resolving") - } - - if len(policyObj.Groups) == 0 && len(policyObj.RiskFactors) == 0 { - return nil - } - - cache := parentCache.clone() - cache.parentPolicies[policyMrn] = struct{}{} - - // properties to execution queries cache - parentCache.global.propsCache.Add(policyObj.Props...) - - // get a list of matching specs - matchingGroups := []*PolicyGroup{} - for i := range policyObj.Groups { - group := policyObj.Groups[i] - - // Filter out groups that are not active - if group.EndDate != 0 { - endDate := time.Unix(group.EndDate, 0) - if endDate.Before(now) { - continue - } - } - - if group.Filters == nil || len(group.Filters.Items) == 0 { - matchingGroups = append(matchingGroups, group) - continue - } - - for j := range group.Filters.Items { - filter := group.Filters.Items[j] - if _, ok := cache.global.assetFilters[filter.CodeId]; ok { - matchingGroups = append(matchingGroups, group) - break - } - } - } - - // aggregate all removed policies and queries - for i := range matchingGroups { - group := matchingGroups[i] - for i := range group.Policies { - policy := group.Policies[i] - if policy.Action == explorer.Action_DEACTIVATE { - cache.removedPolicies[policy.Mrn] = struct{}{} - } - } - for i := range group.Checks { - check := group.Checks[i] - if check.Action == explorer.Action_DEACTIVATE || (group.Type == GroupType_DISABLE && group.ReviewStatus != ReviewStatus_REJECTED) { - cache.removedQueries[check.Mrn] = struct{}{} - } - } - for i := range group.Queries { - query := group.Queries[i] - if query.Action == explorer.Action_DEACTIVATE || (group.Type == GroupType_DISABLE && group.ReviewStatus != ReviewStatus_REJECTED) { - cache.removedQueries[query.Mrn] = struct{}{} - } - } - } - - // resolve the rest - var err error - for i := range matchingGroups { - group := matchingGroups[i] - if err = s.policyGroupToJobs(ctx, group, ownerJob, cache, now); err != nil { - log.Error().Err(err).Msg("resolver> policyToJobs error") - return err - } - } - - if err = s.risksToJobs(ctx, policyObj, ownerJob, cache); err != nil { - return err - } - - // finalize - parentCache.addChildren(cache) - - return nil -} - -func (s *LocalServices) risksToJobs(ctx context.Context, policy *Policy, ownerJob *ReportingJob, cache *policyResolverCache) error { - matchingRisks := map[string]*RiskFactor{} - for _, policyRf := range policy.RiskFactors { - rf := cache.global.riskFactors[policyRf.Mrn] - if rf == nil { - return errors.New("cannot find risk factor '" + policyRf.Mrn + "' while resolving") - } - - switch rf.Action { - case explorer.Action_DEACTIVATE, explorer.Action_OUT_OF_SCOPE: - continue - } - if rf.Filters != nil { - for j := range rf.Filters.Items { - filter := rf.Filters.Items[j] - if _, ok := cache.global.assetFilters[filter.CodeId]; ok { - matchingRisks[rf.Mrn] = rf - break - } - } - } - } - - if len(matchingRisks) == 0 { - return nil - } - - for _, risk := range matchingRisks { - magnitude := risk.Magnitude - - cache.global.riskInfos[risk.Mrn] = &RiskFactor{ - Scope: risk.Scope, - Magnitude: magnitude, - Resources: risk.Resources, - DeprecatedV11Magnitude: magnitude.GetValue(), - DeprecatedV11IsAbsolute: magnitude.GetIsToxic(), - } - - rjUuid := cache.global.relativeChecksum(risk.Mrn) - - if riskJob := cache.global.reportingJobsByUUID[rjUuid]; riskJob != nil { - ownerJob.ChildJobs[riskJob.Uuid] = &explorer.Impact{ - Scoring: explorer.ScoringSystem_IGNORE_SCORE, - } - - riskJob.Notify = append(riskJob.Notify, ownerJob.Uuid) - continue - } - - riskJob := &ReportingJob{ - QrId: risk.Mrn, - Uuid: rjUuid, - ChildJobs: map[string]*explorer.Impact{}, - Type: ReportingJob_RISK_FACTOR, - Notify: []string{ownerJob.Uuid}, - } - - cache.global.reportingJobsByUUID[riskJob.Uuid] = riskJob - ownerJob.ChildJobs[riskJob.Uuid] = &explorer.Impact{ - Scoring: explorer.ScoringSystem_IGNORE_SCORE, - } - - for j := range risk.Checks { - check := risk.Checks[j] - if check.Checksum == "" { - return errors.New("invalid check encountered, missing checksum for: " + check.Mrn) - } - - if !check.Filters.Supports(cache.global.assetFilters) { - continue - } - - cache.addCheckJob(ctx, check, &explorer.Impact{ - Scoring: explorer.ScoringSystem_IGNORE_SCORE, - }, riskJob) - cache.global.riskMrns[risk.Mrn] = check - } - - for uuid := range riskJob.ChildJobs { - job := cache.global.reportingJobsByUUID[uuid] - job.Type = ReportingJob_RISK_FACTOR - } - } - - return nil -} - -func (s *LocalServices) policyGroupToJobs(ctx context.Context, group *PolicyGroup, ownerJob *ReportingJob, cache *policyResolverCache, now time.Time) error { - ctx, span := tracer.Start(ctx, "resolver/policyGroupToJobs") - defer span.End() - - // include referenced policies - for i := range group.Policies { - policy := group.Policies[i] - - impact := policy.Impact - if policy.Action == explorer.Action_IGNORE { - impact = &explorer.Impact{ - Scoring: explorer.ScoringSystem_IGNORE_SCORE, - } - } - - // ADD - if policy.Action == explorer.Action_UNSPECIFIED || policy.Action == explorer.Action_ACTIVATE || policy.Action == explorer.Action_IGNORE { - if _, ok := cache.parentPolicies[policy.Mrn]; ok { - return errors.New("trying to resolve policy spec twice, it is cyclical for MRN: " + policy.Mrn) - } - - if _, ok := cache.removedPolicies[policy.Mrn]; ok { - continue - } - - // before adding any reporting job, make sure this policy actually works for - // this set of asset filters - policyObj, ok := cache.global.bundleMap.Policies[policy.Mrn] - if !ok || policyObj == nil { - return errors.New("cannot find policy '" + policy.Mrn + "' while resolving") - } - - scoringSystem := policyObj.ScoringSystem - if ss := policy.ScoringSystem; ss != explorer.ScoringSystem_SCORING_UNSPECIFIED { - scoringSystem = ss - } - - // make sure this policy supports the selected filters, otherwise we - // don't need to include it - var found bool - for checksum := range policyObj.ComputedFilters.Items { - if _, ok := cache.global.assetFilters[checksum]; ok { - found = true - break - } - } - if !found { - continue - } - - // TODO: We currently enforce the policy object to only be created - // once per resolution. It can be attached to multiple other jobs, - // i.e. it can be called by multiple different owners. But it only - // translates into one reportingjob. This will need to be expanded - // in case we allow for modifications on the policy object. - // vv-------------- singular policyref ---------------- - var policyJob *ReportingJob - policyJobs := cache.childJobsByMrn[policy.Mrn] - if len(policyJobs) == 0 { - // FIXME: we are receiving policies here that may not have their checksum calculated - // This should not be the case, policy bundles that are downloaded - // should have their checksums updated. - policy.RefreshChecksum() - - policyJob = &ReportingJob{ - QrId: policy.Mrn, - Uuid: cache.global.relativeChecksum(policy.Checksum), - ChildJobs: map[string]*explorer.Impact{}, - Datapoints: map[string]bool{}, - ScoringSystem: scoringSystem, - Type: ReportingJob_POLICY, - } - cache.global.reportingJobsByUUID[policyJob.Uuid] = policyJob - cache.childJobsByMrn[policy.Mrn] = []*ReportingJob{policyJob} - } else { - if len(policyJobs) != 1 { - log.Warn().Msg("found more than one policy job for " + policy.Mrn) - } - policyJob = policyJobs[0] - } - // ^^-------------- singular policyref ---------------- - - // local aspects for the resolved policy - policyJob.Notify = append(policyJob.Notify, ownerJob.Uuid) - ownerJob.ChildJobs[policyJob.Uuid] = impact - - if err := s.policyToJobs(ctx, policy.Mrn, policyJob, cache, now); err != nil { - return err - } - - continue - } - - // MODIFY - if policy.Action == explorer.Action_MODIFY { - policyJobs, ok := cache.childJobsByMrn[policy.Mrn] - if !ok { - cache.global.errors = append(cache.global.errors, &policyResolutionError{ - ID: policy.Mrn, - IsPolicy: true, - Error: "cannot modify policy, it doesn't exist", - }) - continue - } - - for j := range policyJobs { - policyJob := policyJobs[j] - for _, id := range policyJob.Notify { - parentJob := cache.global.reportingJobsByUUID[id] - if parentJob != nil { - parentJob.ChildJobs[policyJob.Uuid] = impact - } - } - } - } - } - - // handle scoring queries - for i := range group.Checks { - check := group.Checks[i] - - if _, ok := cache.removedQueries[check.Mrn]; ok { - continue - } - - if base, ok := cache.global.bundleMap.Queries[check.Mrn]; ok { - check = check.Merge(base) - err := check.RefreshChecksum(ctx, - cache.global.compilerConfig, - explorer.QueryMap(cache.global.bundleMap.Queries).GetQuery, - ) - if err != nil { - return err - } - } - if check.Checksum == "" { - return errors.New("invalid check encountered, missing checksum for: " + check.Mrn) - } - - if !check.Filters.Supports(cache.global.assetFilters) { - continue - } - - // The type is IGNORED and the review status is not REJECTED - ignoreGroup := group.Type == GroupType_IGNORED && group.ReviewStatus != ReviewStatus_REJECTED - if check.Action == explorer.Action_IGNORE || ignoreGroup { - stillValid := CheckValidUntil(group.EndDate, check.Mrn) - if !stillValid { - // the exception is no longer valid => score the check - check.Action = explorer.Action_ACTIVATE - } - } - // If we ignore this check, we have to transfer this info to the impact var, - // which is used to inform how to aggregate the scores of all child jobs. - impact := check.Impact - if impact == nil { - impact = &explorer.Impact{} - } - if check.Action == explorer.Action_IGNORE || ignoreGroup { - impact.Scoring = explorer.ScoringSystem_IGNORE_SCORE - impact.Action = explorer.Action_IGNORE - } - - cache.global.propsCache.Add(check.Props...) - - if (check.Action == explorer.Action_UNSPECIFIED || check.Action == explorer.Action_ACTIVATE) && group.Type != GroupType_IGNORED { - cache.addCheckJob(ctx, check, impact, ownerJob) - continue - } - - // TODO: can we simplify this to simply IGNORE? - if check.Action == explorer.Action_MODIFY || check.Action == explorer.Action_IGNORE || ignoreGroup { - cache.modifyCheckJob(check, impact) - } - } - - // handle data queries - for i := range group.Queries { - query := group.Queries[i] - - if _, ok := cache.removedQueries[query.Mrn]; ok { - continue - } - - if base, ok := cache.global.bundleMap.Queries[query.Mrn]; ok { - query = query.Merge(base) - err := query.RefreshChecksum(ctx, - cache.global.compilerConfig, - explorer.QueryMap(cache.global.bundleMap.Queries).GetQuery, - ) - if err != nil { - return err - } - } - if query.Checksum == "" { - return errors.New("invalid query encountered, missing checksum for: " + query.Mrn) - } - - if !query.Filters.Supports(cache.global.assetFilters) { - continue - } - - // Dom: Note: we do not carry over the impact from data queries yet - - cache.global.propsCache.Add(query.Props...) - - // ADD - if query.Action == explorer.Action_UNSPECIFIED || query.Action == explorer.Action_ACTIVATE { - cache.addDataQueryJob(ctx, query, ownerJob) - } - } - - return nil -} - -func (cache *policyResolverCache) addCheckJob(ctx context.Context, check *explorer.Mquery, impact *explorer.Impact, ownerJob *ReportingJob) { - uuid := cache.global.relativeChecksum(check.Checksum) - rj := cache.global.reportingJobsByUUID[uuid] - - if rj == nil { - rj = &ReportingJob{ - Uuid: uuid, - QrId: check.Mrn, - ChildJobs: map[string]*explorer.Impact{}, - Datapoints: map[string]bool{}, - Type: ReportingJob_CHECK, - Mrns: []string{}, - } - // We don't track the MRNs for variant queries through the cache, since variant queries - // have no MQL, meaning they get the same code ID - if len(check.Variants) == 0 { - cache.global.codeIdToMrn[check.CodeId] = append(cache.global.codeIdToMrn[check.CodeId], check.Mrn) - // Because we have no variants, we might have to add the MRN of the parent job - if ownerJob != nil && ownerJob.Type == ReportingJob_CHECK && len(ownerJob.Mrns) > 0 { - // We might have variants, where multiple variant queries apply - // Don't save the same MRNs multiple times - for _, mrn := range ownerJob.Mrns { - if slices.Contains(cache.global.codeIdToMrn[check.CodeId], mrn) { - continue - } - cache.global.codeIdToMrn[check.CodeId] = append(cache.global.codeIdToMrn[check.CodeId], mrn) - } - } - } - cache.global.reportingJobsByUUID[uuid] = rj - cache.global.reportingJobsByMsum[check.Checksum] = append(cache.global.reportingJobsByMsum[check.Checksum], rj) - cache.childJobsByMrn[check.Mrn] = append(cache.childJobsByMrn[check.Mrn], rj) - } - - if _, ok := cache.global.reportingJobsByCodeId[check.CodeId]; !ok { - cache.global.reportingJobsByCodeId[check.CodeId] = []*ReportingJob{} - } - cache.global.reportingJobsByCodeId[check.CodeId] = append(cache.global.reportingJobsByCodeId[check.CodeId], rj) - - if ownerJob.ChildJobs[rj.Uuid] == nil { - ownerJob.ChildJobs[rj.Uuid] = impact - } - - // local aspects for the resolved policy - rj.Notify = append(rj.Notify, ownerJob.Uuid) - - if len(check.Variants) != 0 { - // Add parent MRN, so we can later also query the result by the variants parent MRN - // //.../queries/parent-check - // //.../queries/variant-1 - rj.Mrns = []string{check.Mrn} - err := cache.addCheckJobVariants(ctx, check, rj) - if err != nil { - log.Error().Err(err).Str("checkMrn", check.Mrn).Msg("failed to add data query variants") - } - } else { - // we set a placeholder for the execution query, just to indicate it will be added - cache.global.executionQueries[check.Checksum] = nil - cache.global.queriesByMsum[check.Checksum] = check - - // Add the MRN to all the reporting jobs with the same codeID - // This is used to track all the MRNs - for _, job := range cache.global.reportingJobsByCodeId[check.CodeId] { - // Set the MRNs to all the MRNs we collected so far - job.Mrns = cache.global.codeIdToMrn[check.CodeId] - } - } -} - -func (cache *policyResolverCache) addCheckJobVariants(ctx context.Context, query *explorer.Mquery, ownerJob *ReportingJob) error { - for i := range query.Variants { - mrn := query.Variants[i].Mrn - - if _, ok := cache.removedQueries[mrn]; ok { - continue - } - - v, ok := cache.global.bundleMap.Queries[mrn] - if !ok { - return errors.New("cannot find variant " + mrn) - } - if v.Checksum == "" { - return errors.New("invalid check encountered, missing checksum for: " + mrn) - } - - if !v.Filters.Supports(cache.global.assetFilters) { - continue - } - - // Dom: Note: we do not carry over the impact from data queries yet - - cache.global.propsCache.Add(v.Props...) - - // ADD - if v.Action == explorer.Action_UNSPECIFIED || v.Action == explorer.Action_ACTIVATE { - cache.addCheckJob(ctx, v, v.Impact, ownerJob) - } - } - - return nil -} - -func (cache *policyResolverCache) addDataQueryJob(ctx context.Context, query *explorer.Mquery, ownerJob *ReportingJob) { - uuid := cache.global.relativeChecksum(query.Mrn) - rj := cache.global.reportingJobsByUUID[uuid] - - if rj == nil { - rj = &ReportingJob{ - Uuid: uuid, - QrId: query.Mrn, - ChildJobs: map[string]*explorer.Impact{}, - Datapoints: map[string]bool{}, - Type: ReportingJob_DATA_QUERY, - Mrns: []string{}, - // FIXME: DEPRECATED, remove in v10.0 vv - DeprecatedV8IsData: true, - } - // We don't track the MRNs for variant queries through the cachem, since variant queries - // have no MQL, meaning they get the same code ID - if len(query.Variants) == 0 { - cache.global.codeIdToMrn[query.CodeId] = append(cache.global.codeIdToMrn[query.CodeId], query.Mrn) - // Because we have no variants, we might have to add the MRN of the parent job - if ownerJob != nil && ownerJob.Type == ReportingJob_DATA_QUERY && len(ownerJob.Mrns) > 0 { - // We might have variants, where multiple variant queries apply - // Don't save the same MRNs multiple times - for _, mrn := range ownerJob.Mrns { - if slices.Contains(cache.global.codeIdToMrn[query.CodeId], mrn) { - continue - } - cache.global.codeIdToMrn[query.CodeId] = append(cache.global.codeIdToMrn[query.CodeId], mrn) - } - } - } - cache.global.reportingJobsByUUID[uuid] = rj - cache.global.reportingJobsByMsum[query.Checksum] = append(cache.global.reportingJobsByMsum[query.Checksum], rj) - cache.childJobsByMrn[query.Mrn] = append(cache.childJobsByMrn[query.Mrn], rj) - } - - if _, ok := cache.global.reportingJobsByCodeId[query.CodeId]; !ok { - cache.global.reportingJobsByCodeId[query.CodeId] = []*ReportingJob{} - } - cache.global.reportingJobsByCodeId[query.CodeId] = append(cache.global.reportingJobsByCodeId[query.CodeId], rj) - - // local aspects for the resolved policy - rj.Notify = append(rj.Notify, ownerJob.Uuid) - if ownerJob.ChildJobs[rj.Uuid] == nil { - ownerJob.ChildJobs[rj.Uuid] = query.Impact - } - if len(query.Variants) != 0 { - // Add parent MRN, so we can later also query the result by the variants parent MRN - // //.../queries/parent-check - // //.../queries/variant-1 - rj.Mrns = []string{query.Mrn} - err := cache.addDataQueryVariants(ctx, query, rj) - if err != nil { - log.Error().Err(err).Str("queryMrn", query.Mrn).Msg("failed to add data query variants") - } - } else { - // we set a placeholder for the execution query, just to indicate it will be added - cache.global.executionQueries[query.Checksum] = nil - cache.global.dataQueries[query.Checksum] = struct{}{} - cache.global.queriesByMsum[query.Checksum] = query - // Add the MRN to all the reporting jobs with the same codeID - // This is used to track all the MRNs - for _, job := range cache.global.reportingJobsByCodeId[query.CodeId] { - // Set the MRNs to all the MRNs we collected so far - job.Mrns = cache.global.codeIdToMrn[query.CodeId] - } - } -} - -func (cache *policyResolverCache) addDataQueryVariants(ctx context.Context, query *explorer.Mquery, ownerJob *ReportingJob) error { - for i := range query.Variants { - mrn := query.Variants[i].Mrn - - if _, ok := cache.removedQueries[mrn]; ok { - continue - } - - v, ok := cache.global.bundleMap.Queries[mrn] - if !ok { - return errors.New("cannot find variant " + mrn) - } - if v.Checksum == "" { - return errors.New("invalid query encountered, missing checksum for: " + mrn) - } - - if !v.Filters.Supports(cache.global.assetFilters) { - continue - } - - // Dom: Note: we do not carry over the impact from data queries yet - - cache.global.propsCache.Add(v.Props...) - - // ADD - if v.Action == explorer.Action_UNSPECIFIED || v.Action == explorer.Action_ACTIVATE { - cache.addDataQueryJob(ctx, v, ownerJob) - } - } - - return nil -} - -func (cache *policyResolverCache) modifyCheckJob(check *explorer.Mquery, impact *explorer.Impact) { - queryJobs, ok := cache.childJobsByMrn[check.Mrn] - if !ok { - cache.global.errors = append(cache.global.errors, &policyResolutionError{ - ID: check.Mrn, - IsPolicy: true, - Error: "cannot modify query, it doesn't exist", - }) - return - } - - for i := range queryJobs { - queryJob := queryJobs[i] - for _, id := range queryJob.Notify { - parentJob := cache.global.reportingJobsByUUID[id] - if parentJob != nil { - parentJob.ChildJobs[queryJob.Uuid] = impact - } - } - } -} - -func (s *LocalServices) jobsToQueries(ctx context.Context, policyMrn string, cache *resolverCache) (*ExecutionJob, *CollectorJob, error) { - ctx, span := tracer.Start(ctx, "resolver/jobsToQueries") - defer span.End() - - logCtx := logger.FromContext(ctx) - collectorJob := &CollectorJob{ - ReportingJobs: map[string]*ReportingJob{}, - ReportingQueries: map[string]*StringArray{}, - Datapoints: map[string]*DataQueryInfo{}, - RiskMrns: map[string]*StringArray{}, - } - executionJob := &ExecutionJob{ - Queries: map[string]*ExecutionQuery{}, - } - - for _, rj := range cache.reportingJobsByUUID { - collectorJob.ReportingJobs[rj.Uuid] = rj - } - - // FIXME: sort by internal dependencies of props as well - - // next we can continue with queries, after properties are all done - for checksum, query := range cache.queriesByMsum { - codeID := query.CodeId - - if existing, ok := executionJob.Queries[codeID]; ok { - logCtx.Debug(). - Str("codeID", codeID). - Str("existing", existing.Query). - Str("new", query.Mql). - Msg("resolver> found duplicate query") - } - - _, isDataQuery := cache.dataQueries[query.Checksum] - - var propTypes map[string]*llx.Primitive - var propToChecksums map[string]string - if len(query.Props) != 0 { - propTypes = make(map[string]*llx.Primitive, len(query.Props)) - propToChecksums = make(map[string]string, len(query.Props)) - for j := range query.Props { - prop := query.Props[j] - - // we only get this if there is an override higher up in the policy - override, name, _ := cache.propsCache.Get(prop.Mrn) - if override != nil { - prop = override - } - if name == "" { - var err error - name, err = mrn.GetResource(prop.Mrn, MRN_RESOURCE_QUERY) - if err != nil { - return nil, nil, errors.New("failed to get property name") - } - } - - executionQuery, dataChecksum, err := mquery2executionQuery(prop, nil, map[string]string{}, collectorJob, false, cache.compilerConfig) - if err != nil { - return nil, nil, errors.New("resolver> failed to compile query for MRN " + prop.Mrn + ": " + err.Error()) - } - if dataChecksum == "" { - return nil, nil, errors.New("property returns too many value, cannot determine entrypoint checksum: '" + prop.Mql + "'") - } - cache.executionQueries[checksum] = executionQuery - executionJob.Queries[prop.CodeId] = executionQuery - - propTypes[name] = &llx.Primitive{Type: prop.Type} - propToChecksums[name] = dataChecksum - } - } - - executionQuery, _, err := mquery2executionQuery(query, propTypes, propToChecksums, collectorJob, !isDataQuery, cache.compilerConfig) - if err != nil { - return nil, nil, errors.New("resolver> failed to compile query for MRN " + query.Mrn + ": " + err.Error()) - } - - if executionQuery == nil { - // This case happens when we were able to compile with the - // v2 compiler but not the v1 compiler. In such case, we - // will expunge the query and reporting chain from the - // resolved policy - if reportingjobs, ok := cache.reportingJobsByMsum[query.Checksum]; ok { - for i := range reportingjobs { - rj := reportingjobs[i] - delete(cache.reportingJobsByUUID, rj.Uuid) - delete(collectorJob.ReportingJobs, rj.Uuid) - for _, parentID := range rj.Notify { - if parentJob, ok := collectorJob.ReportingJobs[parentID]; ok { - delete(parentJob.ChildJobs, rj.Uuid) - } - } - } - delete(cache.reportingJobsByMsum, query.Checksum) - } - - continue - } - - cache.executionQueries[checksum] = executionQuery - executionJob.Queries[codeID] = executionQuery - - // Scoring+Data Queries handling - reportingjobs, ok := cache.reportingJobsByMsum[query.Checksum] - if !ok { - logCtx.Debug(). - Interface("reportingJobs", cache.reportingJobsByMsum). - Str("query", query.Mrn). - Str("checksum", query.Checksum). - Str("policy", policyMrn). - Msg("resolver> phase 4: cannot find reporting job") - return nil, nil, errors.New("cannot find reporting job for query " + query.Mrn + " in policy " + policyMrn) - } - - for i := range reportingjobs { - rj := reportingjobs[i] - // (2) Scoring Queries handling - if !isDataQuery { - rj.QrId = codeID - - if query.Impact != nil { - for _, parentID := range rj.Notify { - parentJob, ok := collectorJob.ReportingJobs[parentID] - if !ok { - return nil, nil, errors.New("failed to connect datapoint to reporting job") - } - base := parentJob.ChildJobs[rj.Uuid] - query.Impact.AddBase(base) - } - } - - arr, ok := collectorJob.ReportingQueries[codeID] - if !ok { - arr = &StringArray{} - collectorJob.ReportingQueries[codeID] = arr - } - arr.Items = append(arr.Items, rj.Uuid) - - if err = connectDatapointsToReportingJob(executionQuery, rj, collectorJob.Datapoints); err != nil { - return nil, nil, err - } - - continue - } - - // (3) Data Queries handling - // We connect the datapoints to the data query reporting job. Previously we had connected the datapoints - /// to the data query's parent and had removed the data query RJ. We now keep those around to indicate which - // data queries have been ran. - if err = connectDatapointsToReportingJob(executionQuery, rj, collectorJob.Datapoints); err != nil { - return nil, nil, err - } - } - } - - for k, check := range cache.riskMrns { - uuid := cache.relativeChecksum(check.Checksum) - existing := collectorJob.RiskMrns[uuid] - if existing == nil { - existing = &StringArray{} - collectorJob.RiskMrns[uuid] = existing - } - - existing.Items = append(existing.Items, k) - } - collectorJob.RiskFactors = cache.riskInfos - - return executionJob, collectorJob, nil -} - func connectDatapointsToReportingJob(query *ExecutionQuery, job *ReportingJob, datapoints map[string]*DataQueryInfo) error { for _, dpId := range query.Datapoints { datapointInfo, ok := datapoints[dpId] @@ -1655,289 +629,6 @@ func mquery2executionQuery(query queryLike, props map[string]*llx.Primitive, pro return &res, dataChecksum, nil } -func ensureControlJob(cache *frameworkResolverCache, jobs map[string]*ReportingJob, controlMrn string, framework *ResolvedFramework, frameworkGroupByControlMrn map[string]*FrameworkGroup) *ReportingJob { - uuid := cache.relativeChecksum(controlMrn) - - if found, ok := jobs[uuid]; ok { - return found - } - - // If we ignore this control, we have to transfer this info to the impact var, - // which is used to inform how to aggregate the results of all child jobs. - impact := &explorer.Impact{} - if frameworkGroup, ok := frameworkGroupByControlMrn[controlMrn]; ok { - if frameworkGroup.Type == GroupType_IGNORED { - stillIgnore := CheckValidUntil(frameworkGroup.EndDate, controlMrn) - if stillIgnore { - impact.Scoring = explorer.ScoringSystem_IGNORE_SCORE - impact.Action = explorer.Action_IGNORE - } - } - } - - controlJob := &ReportingJob{ - Uuid: uuid, - QrId: controlMrn, - ChildJobs: map[string]*explorer.Impact{}, - ScoringSystem: explorer.ScoringSystem_WORST, - Type: ReportingJob_CONTROL, - } - jobs[uuid] = controlJob - - parents := framework.ReportTargets[controlMrn] - for parentMrn := range parents { - parentUuid := cache.relativeChecksum(parentMrn) - - frameworkJob, ok := cache.frameworkJobsByMrn[parentMrn] - if !ok { - continue - } - - frameworkJob.ChildJobs[uuid] = impact - controlJob.Notify = append(controlJob.Notify, parentUuid) - } - - return controlJob -} - -type frameworkResolverCache struct { - *resolverCache - frameworkJobsByMrn map[string]*ReportingJob -} - -func (s *LocalServices) jobsToFrameworks(cache *frameworkResolverCache, resolvedFramework *ResolvedFramework, job *CollectorJob, frameworkMrn string, parent *ReportingJob) error { - for k, rj := range job.ReportingJobs { - if frameworkJob := cache.bundleMap.Frameworks[rj.QrId]; frameworkJob != nil { - cache.frameworkJobsByMrn[rj.QrId] = job.ReportingJobs[k] - } - } - return s.jobsToFrameworksInner(cache, resolvedFramework, job, frameworkMrn, parent) -} - -func (s *LocalServices) jobsToFrameworksInner(cache *frameworkResolverCache, resolvedFramework *ResolvedFramework, job *CollectorJob, frameworkMrn string, parent *ReportingJob) error { - for source := range resolvedFramework.ReportSources[frameworkMrn] { - if childFramework, ok := cache.bundleMap.Frameworks[source]; ok { - var reportingJob *ReportingJob - if found, ok := cache.frameworkJobsByMrn[childFramework.Mrn]; ok { - // Look for an existing job. This will happen for asset and space frameworks. - // Creating a new job for these would likely cause confusion, as then we'd - // end up with multiple jobs with the same QrId - reportingJob = found - } else { - uuid := cache.relativeChecksum(childFramework.Mrn) - reportingJob = &ReportingJob{ - Uuid: uuid, - QrId: childFramework.Mrn, - ChildJobs: map[string]*explorer.Impact{}, - ScoringSystem: explorer.ScoringSystem_AVERAGE, - Type: ReportingJob_FRAMEWORK, - } - } - - if _, exist := parent.ChildJobs[reportingJob.Uuid]; !exist { - // If we already have a child job, we don't need to do anything - // In the case that its a space or asset, we definitely don't want to - // overwrite it as that would change the scoring system - impact := &explorer.Impact{} - if parent.Type == ReportingJob_FRAMEWORK { - impact.Scoring = explorer.ScoringSystem_AVERAGE - } else { - impact.Scoring = explorer.ScoringSystem_IGNORE_SCORE - } - parent.ChildJobs[reportingJob.Uuid] = impact - } - reportingJob.Notify = append(reportingJob.Notify, parent.Uuid) - job.ReportingJobs[reportingJob.Uuid] = reportingJob - cache.frameworkJobsByMrn[childFramework.Mrn] = reportingJob - if err := s.jobsToFrameworksInner(cache, resolvedFramework, job, source, reportingJob); err != nil { - return err - } - } - } - - return nil -} - -func (s *LocalServices) jobsToControls(cache *frameworkResolverCache, framework *ResolvedFramework, job *CollectorJob) error { - nuJobs := map[string]*ReportingJob{} - - // try to find all framework groups of type IGNORE, DISABLE or OUT_OF_SCOPE for this and depending frameworks - // these groups are needed to determine if a control is ignored/snoozed or disabled - frameworkGroupByControlMrn := map[string]*FrameworkGroup{} - assetFramework := cache.bundleMap.Frameworks[framework.Mrn] - if assetFramework != nil { - for i := range assetFramework.Dependencies { - depFramework := cache.bundleMap.Frameworks[assetFramework.Dependencies[i].Mrn] - if depFramework != nil { - groups := exceptionGroupForControl(depFramework.Groups) - for k, v := range groups { - frameworkGroupByControlMrn[k] = v - } - } - } - - // After we have checked all the dependent frameworks, we can check the asset framework. It has precedence - // over the dependent frameworks because it is the lowest level of the hierarchy. It means, it can override - // groups specified at a different level - groups := exceptionGroupForControl(assetFramework.Groups) - for k, v := range groups { - frameworkGroupByControlMrn[k] = v - } - } - - rjByMrn := map[string]*ReportingJob{} - for _, rj := range job.ReportingJobs { - queryMrns := cache.codeIdToMrn[rj.QrId] - for _, queryMrn := range queryMrns { - rjByMrn[queryMrn] = rj - } - } - - mrns := framework.TopologicalSort() - for _, mrn := range mrns { - node, ok := framework.Nodes[mrn] - if !ok { - continue - } - targets := framework.ReportTargets[mrn] - var curJob *ReportingJob - switch node.Type { - case ResolvedFrameworkNodeTypeCheck: - if len(targets) == 0 { - continue - } - rj, ok := rjByMrn[mrn] - if !ok { - continue - } - queryMrns := cache.codeIdToMrn[rj.QrId] - - for _, queryMrn := range queryMrns { - if queryMrn != mrn { - continue - } - uuid := cache.relativeChecksum(queryMrn) - queryJob := &ReportingJob{ - Uuid: uuid, - QrId: queryMrn, - ChildJobs: map[string]*explorer.Impact{}, - Type: ReportingJob_CHECK, - } - nuJobs[uuid] = queryJob - - queryJob.ChildJobs[rj.Uuid] = nil - rj.Notify = append(rj.Notify, queryJob.Uuid) - continue - } - - case ResolvedFrameworkNodeTypeQuery: - if len(targets) == 0 { - continue - } - - // the query may not be active and therefore not part of the bundle - // there is a similar guard for checks where we verify if there's a rj with the check's mrn - mquery := cache.bundleMap.Queries[mrn] - if mquery == nil { - continue - } - execQuery := cache.executionQueries[mquery.Checksum] - // the data query may be part of the bundle, but it may not match the current asset, - // which means it will not be part of the execution - if execQuery == nil { - continue - } - uuid := cache.relativeChecksum(mquery.Mrn) - queryJob, ok := job.ReportingJobs[uuid] - if !ok { - queryJob = &ReportingJob{ - Uuid: uuid, - QrId: mquery.Mrn, - ChildJobs: map[string]*explorer.Impact{}, - Type: ReportingJob_DATA_QUERY, - } - err := connectDatapointsToReportingJob(execQuery, queryJob, job.Datapoints) - if err != nil { - return err - } - } - nuJobs[uuid] = queryJob - continue - - case ResolvedFrameworkNodeTypeControl: - // skip controls which are part of a FrameworkGroup with type DISABLE - if group, ok := frameworkGroupByControlMrn[mrn]; ok { - if group.Type == GroupType_DISABLE || group.Type == GroupType_OUT_OF_SCOPE { - continue - } - } - - // Avoid adding controls which don't have any active children - shouldAdd := false - for child := range framework.ReportSources[mrn] { - if _, ok := nuJobs[cache.relativeChecksum(child)]; ok { - shouldAdd = true - break - } - } - - if !shouldAdd { - continue - } - - controlJob := ensureControlJob(cache, nuJobs, mrn, framework, frameworkGroupByControlMrn) - curJob = controlJob - case ResolvedFrameworkNodeTypeFramework: - curJob = job.ReportingJobs[cache.relativeChecksum(mrn)] - } - - // Ensure that child jobs notify their parents - for child := range framework.ReportSources[mrn] { - childJob, ok := nuJobs[cache.relativeChecksum(child)] - if !ok { - continue - } - if _, ok := curJob.ChildJobs[childJob.Uuid]; !ok { - curJob.ChildJobs[childJob.Uuid] = nil - } - shouldAdd := true - for _, parent := range childJob.Notify { - if parent == curJob.Uuid { - shouldAdd = false - break - } - } - if shouldAdd { - childJob.Notify = append(childJob.Notify, curJob.Uuid) - } - } - } - - for k, v := range nuJobs { - job.ReportingJobs[k] = v - } - - return nil -} - -func exceptionGroupForControl(groups []*FrameworkGroup) map[string]*FrameworkGroup { - exceptionGroupForControl := map[string]*FrameworkGroup{} - for j := range groups { - group := groups[j] - if group.Type != GroupType_IGNORED && group.Type != GroupType_DISABLE && group.Type != GroupType_OUT_OF_SCOPE { - continue - } - if group.ReviewStatus == ReviewStatus_REJECTED { - // The exception was rejected, so we don't care about it - continue - } - for k := range group.Controls { - exceptionGroupForControl[group.Controls[k].Mrn] = group - } - } - return exceptionGroupForControl -} - func (s *LocalServices) cacheUpstreamJobs(ctx context.Context, assetMrn string, resolvedPolicy *ResolvedPolicy) error { var err error @@ -1984,33 +675,3 @@ func CheckValidUntil(validUntil int64, mrn string) bool { } return stillIgnore } - -func topologicalSortReportingJobs(jobs map[string]*ReportingJob) []string { - graph := map[string][]string{} - for _, job := range jobs { - graph[job.Uuid] = []string{} - for child := range job.ChildJobs { - graph[job.Uuid] = append(graph[job.Uuid], child) - } - } - - sortedJobs := []string{} - visited := map[string]bool{} - var visit func(string) - visit = func(uuid string) { - if visited[uuid] { - return - } - visited[uuid] = true - for _, child := range graph[uuid] { - visit(child) - } - sortedJobs = append(sortedJobs, uuid) - } - - for uuid := range graph { - visit(uuid) - } - - return sortedJobs -} diff --git a/policy/resolver_test.go b/policy/resolver_test.go index e441db81..1af5e070 100644 --- a/policy/resolver_test.go +++ b/policy/resolver_test.go @@ -7,13 +7,8 @@ import ( "context" "strings" "testing" - "time" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.mondoo.com/cnquery/v11/explorer" - "go.mondoo.com/cnquery/v11/mrn" - "go.mondoo.com/cnquery/v11/providers" "go.mondoo.com/cnquery/v11/providers-sdk/v1/testutils" "go.mondoo.com/cnspec/v11/internal/datalakes/inmemory" "go.mondoo.com/cnspec/v11/policy" @@ -75,904 +70,6 @@ func riskFactorMrn(uid string) string { return "//test.sth/risks/" + uid } -func TestResolve_EmptyPolicy(t *testing.T) { - b := parseBundle(t, ` -owner_mrn: //test.sth -policies: -- uid: policy1 -`) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy1")}}, - }, []*policy.Bundle{b}) - - t.Run("resolve w/o filters", func(t *testing.T) { - _, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: policyMrn("policy1"), - }) - assert.EqualError(t, err, "rpc error: code = InvalidArgument desc = asset doesn't support any policies") - }) - - t.Run("resolve with empty filters", func(t *testing.T) { - _, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: policyMrn("policy1"), - AssetFilters: []*explorer.Mquery{{}}, - }) - assert.EqualError(t, err, "failed to compile query: failed to compile query '': query is not implemented ''") - }) - - t.Run("resolve with random filters", func(t *testing.T) { - _, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: policyMrn("policy1"), - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - assert.EqualError(t, err, - "rpc error: code = InvalidArgument desc = asset isn't supported by any policies\n"+ - "policies didn't provide any filters\n"+ - "asset supports: true\n") - }) -} - -func TestResolve_SimplePolicy(t *testing.T) { - b := parseBundle(t, ` -owner_mrn: //test.sth -policies: -- uid: policy1 - groups: - - type: chapter - filters: "true" - checks: - - uid: check1 - mql: asset.name == props.name - props: - - uid: name - mql: return "definitely not the asset name" - queries: - - uid: query1 - mql: asset{*} -`) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy1")}}, - }, []*policy.Bundle{b}) - - t.Run("resolve with correct filters", func(t *testing.T) { - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: policyMrn("policy1"), - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.ExecutionJob.Queries, 3) - require.Len(t, rp.Filters, 1) - require.Len(t, rp.CollectorJob.ReportingJobs, 3) - - qrIdToRj := map[string]*policy.ReportingJob{} - for _, rj := range rp.CollectorJob.ReportingJobs { - qrIdToRj[rj.QrId] = rj - } - // scoring queries report by code id - require.NotNil(t, qrIdToRj[b.Queries[1].CodeId]) - require.Len(t, qrIdToRj[b.Queries[1].CodeId].Mrns, 1) - require.Equal(t, queryMrn("check1"), qrIdToRj[b.Queries[1].CodeId].Mrns[0]) - // data queries report by mrn - require.NotNil(t, qrIdToRj[queryMrn("query1")]) - - require.Len(t, qrIdToRj[b.Queries[1].CodeId].Datapoints, 3) - require.Len(t, qrIdToRj[queryMrn("query1")].Datapoints, 1) - }) - - t.Run("resolve with many filters (one is correct)", func(t *testing.T) { - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: policyMrn("policy1"), - AssetFilters: []*explorer.Mquery{ - {Mql: "asset.family.contains(\"linux\")"}, - {Mql: "true"}, - {Mql: "asset.family.contains(\"windows\")"}, - }, - }) - require.NoError(t, err) - require.NotNil(t, rp) - }) - - t.Run("resolve with incorrect filters", func(t *testing.T) { - _, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: policyMrn("policy1"), - AssetFilters: []*explorer.Mquery{ - {Mql: "asset.family.contains(\"linux\")"}, - {Mql: "false"}, - {Mql: "asset.family.contains(\"windows\")"}, - }, - }) - assert.EqualError(t, err, - "rpc error: code = InvalidArgument desc = asset isn't supported by any policies\n"+ - "policies support: true\n"+ - "asset supports: asset.family.contains(\"linux\"), asset.family.contains(\"windows\"), false\n") - }) -} - -func TestResolve_PolicyActionIgnore(t *testing.T) { - b := parseBundle(t, ` -owner_mrn: //test.sth -policies: -- owner_mrn: //test.sth - mrn: //test.sth - groups: - - policies: - - uid: policy-active - - uid: policy-ignored - action: 4 -- uid: policy-active - owner_mrn: //test.sth - groups: - - type: chapter - filters: "true" - checks: - - uid: check1 - mql: asset.name == "definitely not the asset name" - queries: - - uid: query1 - mql: asset.arch -- uid: policy-ignored - owner_mrn: //test.sth - groups: - - type: chapter - filters: "true" - checks: - - uid: check1 - mql: asset.name == "definitely not the asset name" - queries: - - uid: query1 - mql: asset.arch -`) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy-active"), policyMrn("policy-ignored")}}, - }, []*policy.Bundle{b}) - - t.Run("resolve with ignored policy", func(t *testing.T) { - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "//test.sth", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.CollectorJob.ReportingJobs, 5) - ignoreJob := rp.CollectorJob.ReportingJobs["q7gxFtwx4zg="] - require.NotNil(t, ignoreJob) - childJob := ignoreJob.ChildJobs["GhqR9OVIDVM="] - require.NotNil(t, childJob) - require.Equal(t, explorer.ScoringSystem_IGNORE_SCORE, childJob.Scoring) - }) -} - -func TestResolve_PolicyActionScoringSystem(t *testing.T) { - b := parseBundle(t, ` -owner_mrn: //test.sth -policies: -- owner_mrn: //test.sth - mrn: //test.sth - groups: - - policies: - - uid: policy-active - scoring_system: 6 - - uid: policy-ignored - action: 4 -- uid: policy-active - owner_mrn: //test.sth - scoring_system: 2 - groups: - - type: chapter - filters: "true" - checks: - - uid: check1 - mql: asset.name == "definitely not the asset name" - queries: - - uid: query1 - mql: asset.arch -- uid: policy-ignored - owner_mrn: //test.sth - groups: - - type: chapter - filters: "true" - checks: - - uid: check1 - mql: asset.name == "definitely not the asset name" - queries: - - uid: query1 - mql: asset.arch -`) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy-active"), policyMrn("policy-ignored")}}, - }, []*policy.Bundle{b}) - - t.Run("resolve with scoring system", func(t *testing.T) { - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "//test.sth", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.CollectorJob.ReportingJobs, 5) - ignoreJob := rp.CollectorJob.ReportingJobs["gqNWe4GO+UA="] - require.NotNil(t, ignoreJob) - childJob := ignoreJob.ChildJobs["LrvWHNnWZNQ="] - require.NotNil(t, childJob) - require.Equal(t, explorer.ScoringSystem_IGNORE_SCORE, childJob.Scoring) - activeJob := rp.CollectorJob.ReportingJobs["+KeXN9zwDzA="] - require.NotNil(t, activeJob) - require.Equal(t, explorer.ScoringSystem_BANDED, activeJob.ScoringSystem) - }) -} - -func TestResolve_DisabledQuery(t *testing.T) { - b := parseBundle(t, ` -owner_mrn: //test.sth -policies: -- uid: policy-1 - owner_mrn: //test.sth - groups: - - type: chapter - filters: "true" - checks: - - uid: check1 - mql: 1 == 1 - action: 2 -`) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy-1")}}, - }, []*policy.Bundle{b}) - - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.CollectorJob.ReportingJobs, 1) -} - -func TestResolve_IgnoredQuery(t *testing.T) { - b := parseBundle(t, ` -owner_mrn: //test.sth -policies: -- uid: policy-1 - owner_mrn: //test.sth - groups: - - type: chapter - filters: "true" - checks: - - uid: check1 - mql: 1 == 1 -- mrn: asset1 - owner_mrn: //test.sth - groups: - - policies: - - uid: policy-1 - - checks: - - uid: check1 - action: 4 -`) - - _, srv, err := inmemory.NewServices(providers.DefaultRuntime(), nil) - require.NoError(t, err) - - ctx := context.Background() - _, err = srv.SetBundle(ctx, b) - require.NoError(t, err) - - bundleMap, err := b.Compile(context.Background(), conf.Schema, nil) - require.NoError(t, err) - - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.CollectorJob.ReportingJobs, 3) - - mrnToQueryId := map[string]string{} - for _, q := range bundleMap.Queries { - mrnToQueryId[q.Mrn] = q.CodeId - } - - rjTester := frameworkReportingJobTester{ - t: t, - queryIdToReportingJob: map[string]*policy.ReportingJob{}, - rjIdToReportingJob: rp.CollectorJob.ReportingJobs, - rjIdToDatapointJob: rp.CollectorJob.Datapoints, - dataQueriesMrns: map[string]struct{}{}, - } - - for _, rj := range rjTester.rjIdToReportingJob { - _, ok := rjTester.queryIdToReportingJob[rj.QrId] - require.False(t, ok) - rjTester.queryIdToReportingJob[rj.QrId] = rj - } - - queryRj := rjTester.queryIdToReportingJob[mrnToQueryId[queryMrn("check1")]] - // we ensure that even though ignored, theres an RJ for the query - require.NotNil(t, queryRj) - parent := queryRj.Notify[0] - parentRj := rjTester.rjIdToReportingJob[parent] - require.NotNil(t, parentRj) - require.Equal(t, explorer.ScoringSystem_IGNORE_SCORE, parentRj.ChildJobs[queryRj.Uuid].Scoring) -} - -func TestResolve_ExpiredGroups(t *testing.T) { - b := parseBundle(t, ` -owner_mrn: //test.sth -policies: -- uid: policy1 - groups: - - type: chapter - filters: "true" - checks: - - uid: check1 - mql: "1 == 1" - - uid: check2 - mql: "1 == 2" -`) - - _, srv, err := inmemory.NewServices(providers.DefaultRuntime(), nil) - require.NoError(t, err) - - _, err = srv.SetBundle(context.Background(), b) - require.NoError(t, err) - - _, err = srv.Assign(context.Background(), &policy.PolicyAssignment{ - AssetMrn: "asset1", - PolicyMrns: []string{policyMrn("policy1")}, - }) - require.NoError(t, err) - - filters, err := srv.GetPolicyFilters(context.Background(), &policy.Mrn{Mrn: "asset1"}) - require.NoError(t, err) - assetPolicy, err := srv.GetPolicy(context.Background(), &policy.Mrn{Mrn: "asset1"}) - require.NoError(t, err) - - err = srv.DataLake.SetPolicy(context.Background(), assetPolicy, filters.Items) - require.NoError(t, err) - - t.Run("resolve with single group", func(t *testing.T) { - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.ExecutionJob.Queries, 2) - }) - - t.Run("resolve with end dates", func(t *testing.T) { - assetPolicy, err := srv.GetPolicy(context.Background(), &policy.Mrn{Mrn: "asset1"}) - require.NoError(t, err) - m, err := mrn.NewChildMRN(b.OwnerMrn, explorer.MRN_RESOURCE_QUERY, "check2") - require.NoError(t, err) - - // Add a group with an end date in the future. This group deactivates a check - assetPolicy.Groups = append(assetPolicy.Groups, &policy.PolicyGroup{ - Uid: "not-expired", - EndDate: time.Now().Add(time.Hour).Unix(), - Checks: []*explorer.Mquery{ - { - Mrn: m.String(), - Action: explorer.Action_DEACTIVATE, - Impact: &explorer.Impact{ - Action: explorer.Action_DEACTIVATE, - }, - }, - }, - }) - - // Recompute the checksums so that the resolved policy is invalidated - assetPolicy.InvalidateAllChecksums() - err = assetPolicy.UpdateChecksums(context.Background(), srv.DataLake.GetRawPolicy, srv.DataLake.GetQuery, nil, conf) - require.NoError(t, err) - - // Set the asset policy - err = srv.DataLake.SetPolicy(context.Background(), assetPolicy, filters.Items) - require.NoError(t, err) - - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.ExecutionJob.Queries, 1) - - // Set the end date of the group to the past. This group deactivates a check, - // but it should not be taken into account because it is expired - assetPolicy.Groups[1].EndDate = time.Now().Add(-time.Hour).Unix() - - // Recompute the checksums so that the resolved policy is invalidated - assetPolicy.InvalidateAllChecksums() - err = assetPolicy.UpdateChecksums(context.Background(), srv.DataLake.GetRawPolicy, srv.DataLake.GetQuery, nil, conf) - require.NoError(t, err) - - // Set the asset policy - err = srv.DataLake.SetPolicy(context.Background(), assetPolicy, filters.Items) - require.NoError(t, err) - - rp, err = srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.ExecutionJob.Queries, 2) - }) -} - -func TestResolve_Frameworks(t *testing.T) { - bundleStr := ` -owner_mrn: //test.sth -policies: -- uid: policy1 - groups: - - filters: "true" - checks: - - uid: check-fail - mql: 1 == 2 - - uid: check-pass-1 - mql: 1 == 1 - - uid: check-pass-2 - mql: 2 == 2 - queries: - - uid: active-query - title: users - mql: users - - uid: active-query-2 - title: users length - mql: users.length - - uid: check-overlap - title: overlaps with check - mql: 1 == 1 -- uid: policy-inactive - groups: - - filters: "false" - checks: - - uid: inactive-fail - mql: 1 == 2 - - uid: inactive-pass - mql: 1 == 1 - - uid: inactive-pass-2 - mql: 2 == 2 - queries: - - uid: inactive-query - title: users group - mql: users { group} -frameworks: -- uid: framework1 - name: framework1 - groups: - - title: group1 - controls: - - uid: control1 - title: control1 - - uid: control2 - title: control2 - - uid: control3 - title: control3 - - uid: control4 - title: control4 - - uid: control5 - title: control5 -- uid: framework2 - name: framework2 - groups: - - title: group1 - controls: - - uid: control1 - title: control1 - - uid: control2 - title: control2 -- uid: parent-framework - dependencies: - - mrn: ` + frameworkMrn("framework1") + ` - -framework_maps: -- uid: framework-map1 - framework_owner: - uid: framework1 - policy_dependencies: - - uid: policy1 - controls: - - uid: control1 - checks: - - uid: check-pass-1 - queries: - - uid: active-query - - uid: active-query-2 - - uid: control2 - checks: - - uid: check-pass-2 - - uid: check-fail - - uid: control4 - controls: - - uid: control1 -- uid: framework-map2 - framework_owner: - uid: framework1 - policy_dependencies: - - uid: policy1 - controls: - - uid: control4 - controls: - - uid: control1 - - uid: control5 - controls: - - uid: control1 -` - - t.Run("resolve with correct filters", func(t *testing.T) { - b := parseBundle(t, bundleStr) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy1"), policyMrn("policy-inactive")}, frameworks: []string{frameworkMrn("parent-framework")}}, - }, []*policy.Bundle{b}) - - bundle, err := srv.GetBundle(context.Background(), &policy.Mrn{Mrn: "asset1"}) - require.NoError(t, err) - - bundleMap, err := bundle.Compile(context.Background(), conf.Schema, nil) - require.NoError(t, err) - - mrnToQueryId := map[string]string{} - for _, q := range bundleMap.Queries { - mrnToQueryId[q.Mrn] = q.CodeId - } - - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - - // Check that there are no duplicates in the reporting job's notify list - for _, rj := range rp.CollectorJob.ReportingJobs { - requireUnique(t, rj.Notify) - for _, pRjUuid := range rj.Notify { - pRj := rp.CollectorJob.ReportingJobs[pRjUuid] - require.NotNil(t, pRj) - require.Contains(t, pRj.ChildJobs, rj.Uuid) - } - } - - require.Len(t, rp.ExecutionJob.Queries, 5) - - rjTester := frameworkReportingJobTester{ - t: t, - queryIdToReportingJob: map[string]*policy.ReportingJob{}, - rjIdToReportingJob: rp.CollectorJob.ReportingJobs, - rjIdToDatapointJob: rp.CollectorJob.Datapoints, - dataQueriesMrns: map[string]struct{}{}, - } - - for _, p := range bundleMap.Policies { - for _, g := range p.Groups { - for _, q := range g.Queries { - rjTester.dataQueriesMrns[q.Mrn] = struct{}{} - } - } - } - - for _, rj := range rjTester.rjIdToReportingJob { - _, ok := rjTester.queryIdToReportingJob[rj.QrId] - require.False(t, ok) - rjTester.queryIdToReportingJob[rj.QrId] = rj - } - - // control3 had no checks, so it should not have a reporting job. - // TODO: is that the desired behavior? - require.Nil(t, rjTester.queryIdToReportingJob[controlMrn("control3")]) - rjTester.requireReportsTo(mrnToQueryId[queryMrn("check-pass-1")], queryMrn("check-pass-1")) - rjTester.requireReportsTo(mrnToQueryId[queryMrn("check-pass-2")], queryMrn("check-pass-2")) - rjTester.requireReportsTo(mrnToQueryId[queryMrn("check-fail")], queryMrn("check-fail")) - - queryJob1 := rjTester.queryIdToReportingJob[queryMrn("active-query")] - require.Equal(t, 1, len(queryJob1.Datapoints)) - - queryJob2 := rjTester.queryIdToReportingJob[queryMrn("active-query-2")] - require.Equal(t, 1, len(queryJob2.Datapoints)) - - // scoring queries - rjTester.requireReportsTo(queryMrn("check-pass-1"), controlMrn("control1")) - rjTester.requireReportsTo(queryMrn("check-pass-2"), controlMrn("control2")) - rjTester.requireReportsTo(queryMrn("check-fail"), controlMrn("control2")) - // note: data queries RJs are reporting by MRN, not code id - rjTester.requireReportsTo(queryMrn("active-query"), controlMrn("control1")) - rjTester.requireReportsTo(queryMrn("active-query-2"), controlMrn("control1")) - - rjTester.requireReportsTo(controlMrn("control1"), frameworkMrn("framework1")) - rjTester.requireReportsTo(controlMrn("control1"), controlMrn("control4")) - rjTester.requireReportsTo(controlMrn("control2"), frameworkMrn("framework1")) - rjTester.requireReportsTo(controlMrn("control4"), frameworkMrn("framework1")) - rjTester.requireReportsTo(controlMrn("control5"), frameworkMrn("framework1")) - rjTester.requireReportsTo(frameworkMrn("framework1"), frameworkMrn("parent-framework")) - rjTester.requireReportsTo(frameworkMrn("parent-framework"), "root") - - require.Nil(t, rjTester.queryIdToReportingJob[queryMrn("inactive-fail")]) - require.Nil(t, rjTester.queryIdToReportingJob[queryMrn("inactive-pass")]) - require.Nil(t, rjTester.queryIdToReportingJob[queryMrn("inactive-pass-2")]) - - require.Nil(t, rjTester.queryIdToReportingJob[queryMrn("inactive-query")]) - }) - - t.Run("test resolving with inactive data queries", func(t *testing.T) { - // test that creating a bundle with inactive data queries (where the packs/policies are inactive) - // will still end up in a successfully resolved policy for the asset - bundleStr := ` -owner_mrn: //test.sth -policies: -- uid: policy1 - groups: - - filters: "true" - queries: - - uid: active-query - title: users - mql: users -- uid: policy-inactive - groups: - - filters: "false" - queries: - - uid: inactive-query - title: users group - mql: users { group} -frameworks: -- uid: framework1 - name: framework1 - groups: - - title: group1 - controls: - - uid: control1 - title: control1 - - uid: control2 - title: control2 -- uid: parent-framework - dependencies: - - mrn: ` + frameworkMrn("framework1") + ` - -framework_maps: -- uid: framework-map1 - framework_owner: - uid: framework1 - policy_dependencies: - - uid: policy1 - - uid: policy-inactive - controls: - - uid: control1 - queries: - - uid: active-query - - uid: control2 - queries: - - uid: inactive-query -` - b := parseBundle(t, bundleStr) - - // we do not activate policy-inactive, which means that its query should not get executed - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy1")}, frameworks: []string{frameworkMrn("parent-framework")}}, - }, []*policy.Bundle{b}) - - bundle, err := srv.GetBundle(context.Background(), &policy.Mrn{Mrn: "asset1"}) - require.NoError(t, err) - - bundleMap, err := bundle.Compile(context.Background(), conf.Schema, nil) - require.NoError(t, err) - - mrnToQueryId := map[string]string{} - for _, q := range bundleMap.Queries { - mrnToQueryId[q.Mrn] = q.CodeId - } - - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - - // Check that there are no duplicates in the reporting job's notify list - for _, rj := range rp.CollectorJob.ReportingJobs { - requireUnique(t, rj.Notify) - } - - require.Len(t, rp.ExecutionJob.Queries, 1) - - rjTester := frameworkReportingJobTester{ - t: t, - queryIdToReportingJob: map[string]*policy.ReportingJob{}, - rjIdToReportingJob: rp.CollectorJob.ReportingJobs, - rjIdToDatapointJob: rp.CollectorJob.Datapoints, - dataQueriesMrns: map[string]struct{}{}, - } - - for _, p := range bundleMap.Policies { - for _, g := range p.Groups { - for _, q := range g.Queries { - rjTester.dataQueriesMrns[q.Mrn] = struct{}{} - } - } - } - - for _, rj := range rjTester.rjIdToReportingJob { - _, ok := rjTester.queryIdToReportingJob[rj.QrId] - require.False(t, ok) - rjTester.queryIdToReportingJob[rj.QrId] = rj - } - - queryJob1 := rjTester.queryIdToReportingJob[queryMrn("active-query")] - require.Equal(t, 1, len(queryJob1.Datapoints)) - - // queries - rjTester.requireReportsTo(queryMrn("active-query"), controlMrn("control1")) - require.Nil(t, rjTester.queryIdToReportingJob[queryMrn("inactive-query")]) - - rjTester.requireReportsTo(controlMrn("control1"), frameworkMrn("framework1")) - // the data query here is disabled, control2 has no rj - require.Nil(t, rjTester.queryIdToReportingJob[controlMrn("control2")]) - rjTester.requireReportsTo(frameworkMrn("framework1"), frameworkMrn("parent-framework")) - rjTester.requireReportsTo(frameworkMrn("parent-framework"), "root") - }) - - t.Run("test resolving with non-matching data queries", func(t *testing.T) { - // test that creating a bundle with active data queries that do not match the asset, based on the - // policy asset filters, will still create a resolved policy for the asset - bundleStr := ` -owner_mrn: //test.sth -policies: -- uid: policy1 - groups: - - filters: "false" - queries: - - uid: query-1 - title: users - mql: users -- uid: policy2 - groups: - - filters: "true" - queries: - - uid: query-2 - title: users length - mql: users.length - -frameworks: -- uid: framework1 - name: framework1 - groups: - - title: group1 - controls: - - uid: control1 - title: control1 -- uid: parent-framework - dependencies: - - mrn: ` + frameworkMrn("framework1") + ` - -framework_maps: -- uid: framework-map1 - framework_owner: - uid: framework1 - policy_dependencies: - - uid: policy1 - - uid: policy2 - controls: - - uid: control1 - queries: - - uid: query-1 - - uid: query-2 -` - b := parseBundle(t, bundleStr) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy1"), policyMrn("policy2")}, frameworks: []string{frameworkMrn("parent-framework")}}, - }, []*policy.Bundle{b}) - - bundle, err := srv.GetBundle(context.Background(), &policy.Mrn{Mrn: "asset1"}) - require.NoError(t, err) - - bundleMap, err := bundle.Compile(context.Background(), conf.Schema, nil) - require.NoError(t, err) - - mrnToQueryId := map[string]string{} - for _, q := range bundleMap.Queries { - mrnToQueryId[q.Mrn] = q.CodeId - } - - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - - // Check that there are no duplicates in the reporting job's notify list - for _, rj := range rp.CollectorJob.ReportingJobs { - requireUnique(t, rj.Notify) - } - - require.Len(t, rp.ExecutionJob.Queries, 1) - - rjTester := frameworkReportingJobTester{ - t: t, - queryIdToReportingJob: map[string]*policy.ReportingJob{}, - rjIdToReportingJob: rp.CollectorJob.ReportingJobs, - rjIdToDatapointJob: rp.CollectorJob.Datapoints, - dataQueriesMrns: map[string]struct{}{}, - } - - for _, p := range bundleMap.Policies { - for _, g := range p.Groups { - for _, q := range g.Queries { - rjTester.dataQueriesMrns[q.Mrn] = struct{}{} - } - } - } - - for _, rj := range rjTester.rjIdToReportingJob { - _, ok := rjTester.queryIdToReportingJob[rj.QrId] - require.False(t, ok) - rjTester.queryIdToReportingJob[rj.QrId] = rj - } - - queryJob1 := rjTester.queryIdToReportingJob[queryMrn("query-2")] - require.Equal(t, 1, len(queryJob1.Datapoints)) - - rjTester.requireReportsTo(queryMrn("query-2"), controlMrn("control1")) - // query-1 is part of the policy that does not match the asset (even though it's active) - // there should be no rjs for it - require.Nil(t, rjTester.queryIdToReportingJob[queryMrn("query-1")]) - rjTester.requireReportsTo(controlMrn("control1"), frameworkMrn("framework1")) - rjTester.requireReportsTo(frameworkMrn("framework1"), frameworkMrn("parent-framework")) - rjTester.requireReportsTo(frameworkMrn("parent-framework"), "root") - }) - - t.Run("test checksumming", func(t *testing.T) { - bInitial := parseBundle(t, bundleStr) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy1")}, frameworks: []string{frameworkMrn("parent-framework")}}, - }, []*policy.Bundle{bInitial}) - - rpInitial, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rpInitial) - - bFrameworkUpdate := parseBundle(t, bundleStr) - bFrameworkUpdate.Frameworks[0].Groups[0].Controls = bFrameworkUpdate.Frameworks[0].Groups[0].Controls[:2] - - srv = initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy1")}, frameworks: []string{frameworkMrn("parent-framework")}}, - }, []*policy.Bundle{bFrameworkUpdate}) - - rpFrameworkUpdate, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rpFrameworkUpdate) - - require.NotEqual(t, rpInitial.GraphExecutionChecksum, rpFrameworkUpdate.GraphExecutionChecksum) - }) -} - -type frameworkReportingJobTester struct { - t *testing.T - queryIdToReportingJob map[string]*policy.ReportingJob - rjIdToDatapointJob map[string]*policy.DataQueryInfo - rjIdToReportingJob map[string]*policy.ReportingJob - dataQueriesMrns map[string]struct{} -} - func isFramework(queryId string) bool { return strings.Contains(queryId, "/frameworks/") } @@ -984,933 +81,3 @@ func isControl(queryId string) bool { func isPolicy(queryId string) bool { return strings.Contains(queryId, "/policies/") } - -func (tester *frameworkReportingJobTester) requireReportsTo(childQueryId string, parentQueryId string) { - tester.t.Helper() - - childRj, ok := tester.queryIdToReportingJob[childQueryId] - require.True(tester.t, ok) - - parentRj, ok := tester.queryIdToReportingJob[parentQueryId] - require.True(tester.t, ok) - - require.Contains(tester.t, parentRj.ChildJobs, childRj.Uuid) - require.Contains(tester.t, childRj.Notify, parentRj.Uuid) - - if isFramework(parentQueryId) { - require.Equal(tester.t, policy.ReportingJob_FRAMEWORK, parentRj.Type) - require.Equal(tester.t, explorer.ScoringSystem_AVERAGE, parentRj.ScoringSystem) - } else if isControl(parentQueryId) { - require.Equal(tester.t, policy.ReportingJob_CONTROL, parentRj.Type) - } else if isPolicy(parentQueryId) || parentQueryId == "root" { - require.Equal(tester.t, policy.ReportingJob_POLICY, parentRj.Type) - // The root/asset reporting job is not a framework, but a policy - childImpact := parentRj.ChildJobs[childRj.Uuid] - require.Equal(tester.t, explorer.ScoringSystem_IGNORE_SCORE, childImpact.Scoring) - } else { - require.Equal(tester.t, policy.ReportingJob_CHECK, parentRj.Type) - } - - if isControl(childQueryId) { - require.Equal(tester.t, policy.ReportingJob_CONTROL, childRj.Type) - } else if isFramework(childQueryId) { - require.Equal(tester.t, policy.ReportingJob_FRAMEWORK, childRj.Type) - require.Equal(tester.t, explorer.ScoringSystem_AVERAGE, childRj.ScoringSystem) - } else if isPolicy(childQueryId) { - require.Equal(tester.t, policy.ReportingJob_POLICY, childRj.Type) - } else { - _, isData := tester.dataQueriesMrns[childQueryId] - if isData { - require.Equal(tester.t, policy.ReportingJob_DATA_QUERY, childRj.Type) - } else { - require.Equal(tester.t, policy.ReportingJob_CHECK, childRj.Type) - } - } -} - -func TestResolve_CheckValidUntil(t *testing.T) { - stillValid := policy.CheckValidUntil(time.Now().Unix(), "test123") - require.False(t, stillValid) - stillValid = policy.CheckValidUntil(time.Now().Add(time.Hour*1).Unix(), "test123") - require.True(t, stillValid) - // forever - stillValid = policy.CheckValidUntil(0, "test123") - require.True(t, stillValid) - // expired - stillValid = policy.CheckValidUntil(time.Now().Add(-time.Hour*1).Unix(), "test123") - require.False(t, stillValid) -} - -func TestResolve_Exceptions(t *testing.T) { - bundleString := ` -owner_mrn: //test.sth -policies: -- uid: ssh-policy - name: SSH Policy - groups: - - filters: "true" - checks: - - uid: sshd-ciphers-01 - title: Prevent weaker CBC ciphers from being used - mql: sshd.config.ciphers.none( /cbc/ ) - impact: 60 - - uid: sshd-ciphers-02 - title: Do not allow ciphers with few bits - mql: sshd.config.ciphers.none( /128/ ) - impact: 60 - - uid: sshd-config-permissions - title: SSH config editing should be limited to admins - mql: sshd.config.file.permissions.mode == 0644 - impact: 100 - -frameworks: -- uid: mondoo-ucf - mrn: //test.sth/framework/mondoo-ucf - name: Unified Compliance Framework - groups: - - title: System hardening - controls: - - uid: mondoo-ucf-01 - title: Only use strong ciphers - - uid: mondoo-ucf-02 - title: Limit access to system configuration - - uid: mondoo-ucf-03 - title: Only use ciphers with sufficient bits - - title: exception-1 - type: 4 - controls: - - uid: mondoo-ucf-02 - -framework_maps: - - uid: compliance-to-ssh-policy - mrn: //test.sth/framework/compliance-to-ssh-policy - framework_owner: - uid: mondoo-ucf - policy_dependencies: - - uid: ssh-policy - controls: - - uid: mondoo-ucf-01 - checks: - - uid: sshd-ciphers-01 - - uid: sshd-ciphers-02 - - uid: mondoo-ucf-02 - checks: - - uid: sshd-config-permissions - - uid: mondoo-ucf-03 - checks: - - uid: sshd-ciphers-02 -` - - _, srv, err := inmemory.NewServices(providers.DefaultRuntime(), nil) - require.NoError(t, err) - - t.Run("resolve with ignored control", func(t *testing.T) { - b := parseBundle(t, bundleString) - - srv = initResolver(t, []*testAsset{ - { - asset: "asset1", - policies: []string{policyMrn("ssh-policy")}, - frameworks: []string{"//test.sth/framework/mondoo-ucf"}, - }, - }, []*policy.Bundle{b}) - - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.CollectorJob.ReportingJobs, 12) - var frameworkJob *policy.ReportingJob - for _, rj := range rp.CollectorJob.ReportingJobs { - if rj.QrId == "//test.sth/framework/mondoo-ucf" { - frameworkJob = rj - break - } - } - require.NotNil(t, frameworkJob) - require.Equal(t, frameworkJob.Type, policy.ReportingJob_FRAMEWORK) - var childJob *explorer.Impact - for uuid, j := range frameworkJob.ChildJobs { - if rp.CollectorJob.ReportingJobs[uuid].QrId == "//test.sth/controls/mondoo-ucf-02" { - childJob = j - break - } - } - require.NotNil(t, childJob) - require.Equal(t, explorer.ScoringSystem_IGNORE_SCORE, childJob.Scoring) - require.Len(t, frameworkJob.ChildJobs, 3) - }) - - t.Run("resolve with ignored control and validUntil", func(t *testing.T) { - b := parseBundle(t, bundleString) - b.Frameworks[0].Groups[1].EndDate = time.Now().Add(time.Hour).Unix() - - srv = initResolver(t, []*testAsset{ - { - asset: "asset1", - policies: []string{policyMrn("ssh-policy")}, - frameworks: []string{"//test.sth/framework/mondoo-ucf"}, - }, - }, []*policy.Bundle{b}) - - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.CollectorJob.ReportingJobs, 12) - var frameworkJob *policy.ReportingJob - for _, rj := range rp.CollectorJob.ReportingJobs { - if rj.QrId == "//test.sth/framework/mondoo-ucf" { - frameworkJob = rj - break - } - } - require.Equal(t, frameworkJob.Type, policy.ReportingJob_FRAMEWORK) - var childJob *explorer.Impact - for uuid, j := range frameworkJob.ChildJobs { - if rp.CollectorJob.ReportingJobs[uuid].QrId == "//test.sth/controls/mondoo-ucf-02" { - childJob = j - break - } - } - require.Equal(t, explorer.ScoringSystem_IGNORE_SCORE, childJob.Scoring) - require.Len(t, frameworkJob.ChildJobs, 3) - }) - - t.Run("resolve with expired validUntil", func(t *testing.T) { - b := parseBundle(t, bundleString) - b.Frameworks[0].Groups[1].EndDate = time.Now().Add(-time.Hour).Unix() - - srv = initResolver(t, []*testAsset{ - { - asset: "asset1", - policies: []string{policyMrn("ssh-policy")}, - frameworks: []string{"//test.sth/framework/mondoo-ucf"}, - }, - }, []*policy.Bundle{b}) - - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.CollectorJob.ReportingJobs, 12) - var frameworkJob *policy.ReportingJob - for _, rj := range rp.CollectorJob.ReportingJobs { - if rj.QrId == "//test.sth/framework/mondoo-ucf" { - frameworkJob = rj - break - } - } - require.Equal(t, frameworkJob.Type, policy.ReportingJob_FRAMEWORK) - var childJob *explorer.Impact - for uuid, j := range frameworkJob.ChildJobs { - if rp.CollectorJob.ReportingJobs[uuid].QrId == "//test.sth/controls/mondoo-ucf-02" { - childJob = j - break - } - } - require.Equal(t, explorer.ScoringSystem_SCORING_UNSPECIFIED, childJob.Scoring) - require.Len(t, frameworkJob.ChildJobs, 3) - }) - - t.Run("resolve with disabled control", func(t *testing.T) { - b := parseBundle(t, bundleString) - b.Frameworks = append(b.Frameworks, &policy.Framework{ - Mrn: "//test.sth/framework/test", - Dependencies: []*policy.FrameworkRef{ - { - Mrn: b.Frameworks[0].Mrn, - Action: explorer.Action_ACTIVATE, - }, - }, - Groups: []*policy.FrameworkGroup{ - { - Uid: "test", - Type: policy.GroupType_DISABLE, - Controls: []*policy.Control{ - {Uid: b.Frameworks[0].Groups[0].Controls[0].Uid}, - }, - }, - }, - }) - - srv = initResolver(t, []*testAsset{ - { - asset: "asset1", - policies: []string{policyMrn("ssh-policy")}, - frameworks: []string{"//test.sth/framework/mondoo-ucf", "//test.sth/framework/test"}, - }, - }, []*policy.Bundle{b}) - - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.CollectorJob.ReportingJobs, 12) - var frameworkJob *policy.ReportingJob - for _, rj := range rp.CollectorJob.ReportingJobs { - if rj.QrId == "//test.sth/framework/mondoo-ucf" { - frameworkJob = rj - break - } - } - require.NotNil(t, frameworkJob) - require.Equal(t, frameworkJob.Type, policy.ReportingJob_FRAMEWORK) - require.Len(t, frameworkJob.ChildJobs, 2) - }) - - t.Run("resolve with out of scope control", func(t *testing.T) { - b := parseBundle(t, bundleString) - b.Frameworks = append(b.Frameworks, &policy.Framework{ - Mrn: "//test.sth/framework/test", - Dependencies: []*policy.FrameworkRef{ - { - Mrn: b.Frameworks[0].Mrn, - Action: explorer.Action_ACTIVATE, - }, - }, - Groups: []*policy.FrameworkGroup{ - { - Uid: "test", - Type: policy.GroupType_OUT_OF_SCOPE, - Controls: []*policy.Control{ - {Uid: b.Frameworks[0].Groups[0].Controls[0].Uid}, - }, - }, - }, - }) - - srv = initResolver(t, []*testAsset{ - { - asset: "asset1", - policies: []string{policyMrn("ssh-policy")}, - frameworks: []string{"//test.sth/framework/mondoo-ucf", "//test.sth/framework/test"}, - }, - }, []*policy.Bundle{b}) - - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.CollectorJob.ReportingJobs, 12) - var frameworkJob *policy.ReportingJob - for _, rj := range rp.CollectorJob.ReportingJobs { - if rj.QrId == "//test.sth/framework/mondoo-ucf" { - frameworkJob = rj - break - } - } - require.NotNil(t, frameworkJob) - require.Equal(t, frameworkJob.Type, policy.ReportingJob_FRAMEWORK) - require.Len(t, frameworkJob.ChildJobs, 2) - }) - - t.Run("resolve with rejected disable exception", func(t *testing.T) { - b := parseBundle(t, bundleString) - b.Frameworks[0].Groups[1].Type = policy.GroupType_DISABLE - b.Frameworks[0].Groups[1].ReviewStatus = policy.ReviewStatus_REJECTED - - srv = initResolver(t, []*testAsset{ - { - asset: "asset1", - policies: []string{policyMrn("ssh-policy")}, - frameworks: []string{"//test.sth/framework/mondoo-ucf"}, - }, - }, []*policy.Bundle{b}) - - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.CollectorJob.ReportingJobs, 12) - var frameworkJob *policy.ReportingJob - for _, rj := range rp.CollectorJob.ReportingJobs { - if rj.QrId == "//test.sth/framework/mondoo-ucf" { - frameworkJob = rj - break - } - } - require.Equal(t, frameworkJob.Type, policy.ReportingJob_FRAMEWORK) - require.Len(t, frameworkJob.ChildJobs, 3) - }) -} - -func requireUnique(t *testing.T, items []string) { - seen := make(map[string]bool) - for _, item := range items { - if seen[item] { - t.Errorf("duplicate item found: %s", item) - } - seen[item] = true - } -} - -// TestResolve_PoliciesMatchingAgainstIncorrectPlatform tests that policies are not matched against -// assets that do not match the asset filter. It was possible that the reporting structure had -// a node for the policy, but no actual reporting job for it. To the user, this could look -// like the policy was executed. The issue was that a policy was considered matching if either -// the groups or any of its queries filters matched. This tests to ensure that if the policies -// group filtered it out, it doesn't show up in the reporting structure -func TestResolve_PoliciesMatchingAgainstIncorrectPlatform(t *testing.T) { - b := parseBundle(t, ` -owner_mrn: //test.sth -policies: -- uid: policy1 - groups: - - type: chapter - filters: "true" - checks: - - uid: check1 -- uid: policy2 - groups: - - type: chapter - filters: "false" - checks: - - uid: check2 -- uid: pack1 - groups: - - type: chapter - filters: "true" - queries: - - uid: dataquery1 -- uid: pack2 - groups: - - type: chapter - filters: "false" - queries: - - uid: dataquery2 - -queries: -- uid: check1 - title: check1 - mql: true -- uid: check2 - title: check2 - filters: | - true - mql: | - 1 == 1 -- uid: dataquery1 - title: dataquery1 - mql: | - asset.name -- uid: dataquery2 - title: dataquery2 - filters: | - true - mql: | - asset.version -`) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy1"), policyMrn("policy2"), policyMrn("pack1"), policyMrn("pack2")}}, - }, []*policy.Bundle{b}) - - t.Run("resolve with correct filters", func(t *testing.T) { - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - - require.Len(t, rp.CollectorJob.ReportingJobs, 5) - - qrIdToRj := map[string]*policy.ReportingJob{} - for _, rj := range rp.CollectorJob.ReportingJobs { - qrIdToRj[rj.QrId] = rj - } - require.NotNil(t, qrIdToRj[policyMrn("policy1")]) - require.NotNil(t, qrIdToRj[policyMrn("pack1")]) - require.Nil(t, qrIdToRj[policyMrn("policy2")]) - require.Nil(t, qrIdToRj[policyMrn("pack2")]) - }) -} - -func TestResolve_NeverPruneRoot(t *testing.T) { - b := parseBundle(t, ` -owner_mrn: //test.sth -policies: -- uid: policy1 - groups: - - type: chapter - filters: "false" - checks: - - uid: check1 - -queries: -- uid: check1 - title: check1 - filters: | - true - mql: | - 1 == 1 -`) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy1")}}, - }, []*policy.Bundle{b}) - - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "true"}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - - require.Len(t, rp.CollectorJob.ReportingJobs, 1) - - qrIdToRj := map[string]*policy.ReportingJob{} - for _, rj := range rp.CollectorJob.ReportingJobs { - qrIdToRj[rj.QrId] = rj - } - require.NotNil(t, qrIdToRj["root"]) -} - -func TestResolve_PoliciesMatchingFilters(t *testing.T) { - b := parseBundle(t, ` -owner_mrn: //test.sth -policies: -- uid: policy1 - groups: - - type: chapter - checks: - - uid: check1 - - uid: check2 -queries: -- uid: check1 - title: check1 - filters: - - mql: asset.name == "asset1" - - mql: asset.name == "asset2" - mql: | - asset.version -- uid: check2 - title: check2 - filters: - - mql: | - asset.name == "asset1" - asset.name == "asset2" - mql: | - asset.platform -`) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy1")}}, - }, []*policy.Bundle{b}) - - t.Run("resolve with correct filters", func(t *testing.T) { - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "asset.name == \"asset1\""}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - - require.Len(t, rp.ExecutionJob.Queries, 1) - for _, v := range rp.ExecutionJob.Queries { - require.Equal(t, "asset.version\n", v.Query) - } - }) -} - -func TestResolve_TwoMrns(t *testing.T) { - b := parseBundle(t, ` -owner_mrn: //test.sth -policies: -- uid: policy1 - groups: - - filters: - - mql: asset.name == "asset1" - checks: - - uid: check1 - mql: asset.name == props.name - props: - - uid: name - mql: return "definitely not the asset name" - - uid: check2 - mql: asset.name == props.name - props: - - uid: name - mql: return "definitely not the asset name" -`) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy1")}}, - }, []*policy.Bundle{b}) - - t.Run("resolve two MRNs to one codeID matching filter", func(t *testing.T) { - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: policyMrn("policy1"), - AssetFilters: []*explorer.Mquery{{Mql: "asset.name == \"asset1\""}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.ExecutionJob.Queries, 2) - require.Len(t, rp.CollectorJob.ReportingJobs, 3) - - qrIdToRj := map[string]*policy.ReportingJob{} - for _, rj := range rp.CollectorJob.ReportingJobs { - qrIdToRj[rj.QrId] = rj - } - // scoring queries report by code id - require.NotNil(t, qrIdToRj[b.Queries[1].CodeId]) - require.Len(t, qrIdToRj[b.Queries[1].CodeId].Mrns, 2) - require.Equal(t, queryMrn("check1"), qrIdToRj[b.Queries[1].CodeId].Mrns[0]) - require.Equal(t, queryMrn("check2"), qrIdToRj[b.Queries[1].CodeId].Mrns[1]) - - require.Len(t, qrIdToRj[b.Queries[0].CodeId].Mrns, 2) - require.Equal(t, queryMrn("check1"), qrIdToRj[b.Queries[0].CodeId].Mrns[0]) - require.Equal(t, queryMrn("check2"), qrIdToRj[b.Queries[0].CodeId].Mrns[1]) - }) -} - -func TestResolve_TwoMrns_FilterMismatch(t *testing.T) { - b := parseBundle(t, ` -owner_mrn: //test.sth -policies: -- uid: policy1 - groups: - - checks: - - uid: check1 - mql: asset.name == props.name - props: - - uid: name - mql: return "definitely not the asset name" - filters: - - mql: asset.name == "asset1" - - uid: check2 - mql: asset.name == props.name - props: - - uid: name - mql: return "definitely not the asset name" - filters: - - mql: asset.name == "asset2" -`) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy1")}}, - }, []*policy.Bundle{b}) - - t.Run("resolve two MRNs to one codeID matching filter", func(t *testing.T) { - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: policyMrn("policy1"), - AssetFilters: []*explorer.Mquery{{Mql: "asset.name == \"asset1\""}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.ExecutionJob.Queries, 2) - require.Len(t, rp.CollectorJob.ReportingJobs, 2) - - qrIdToRj := map[string]*policy.ReportingJob{} - for _, rj := range rp.CollectorJob.ReportingJobs { - qrIdToRj[rj.QrId] = rj - } - - require.Len(t, qrIdToRj[b.Queries[0].CodeId].Mrns, 1) - require.Equal(t, queryMrn("check1"), qrIdToRj[b.Queries[0].CodeId].Mrns[0]) - }) -} - -func TestResolve_TwoMrns_DataQueries(t *testing.T) { - b := parseBundle(t, ` -owner_mrn: //test.sth -policies: -- uid: policy1 - groups: - - filters: - - mql: asset.name == "asset1" - checks: - - uid: check1 - mql: asset.name == props.name - props: - - uid: name - mql: return "definitely not the asset name" - - queries: - - uid: active-query - title: users - mql: users - - uid: active-query-2 - title: users length - mql: users -`) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy1")}}, - }, []*policy.Bundle{b}) - - t.Run("resolve two MRNs to one codeID matching filter", func(t *testing.T) { - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: policyMrn("policy1"), - AssetFilters: []*explorer.Mquery{{Mql: "asset.name == \"asset1\""}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.ExecutionJob.Queries, 3) - require.Len(t, rp.CollectorJob.ReportingJobs, 4) - - qrIdToRj := map[string]*policy.ReportingJob{} - for _, rj := range rp.CollectorJob.ReportingJobs { - qrIdToRj[rj.QrId] = rj - } - // data queries or not added by their code ID but by their MRN - require.NotNil(t, qrIdToRj[b.Queries[1].Mrn]) - require.Len(t, qrIdToRj[b.Queries[1].Mrn].Mrns, 2) - require.Equal(t, queryMrn("active-query"), qrIdToRj[b.Queries[1].Mrn].Mrns[0]) - require.Equal(t, queryMrn("active-query-2"), qrIdToRj[b.Queries[1].Mrn].Mrns[1]) - - require.Len(t, qrIdToRj[b.Queries[2].Mrn].Mrns, 2) - require.Equal(t, queryMrn("active-query"), qrIdToRj[b.Queries[2].Mrn].Mrns[0]) - require.Equal(t, queryMrn("active-query-2"), qrIdToRj[b.Queries[2].Mrn].Mrns[1]) - }) -} - -func TestResolve_TwoMrns_Variants(t *testing.T) { - b := parseBundle(t, ` -owner_mrn: //test.sth -policies: -- uid: policy1 - groups: - - checks: - - uid: check-variants -queries: - - uid: check-variants - variants: - - uid: variant1 - - uid: variant2 - - uid: variant1 - mql: asset.name == "test1" - filters: asset.family.contains("unix") - - uid: variant2 - mql: asset.name == "test1" - filters: asset.name == "asset1" -`) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("policy1")}}, - }, []*policy.Bundle{b}) - - t.Run("resolve two variants to different codeIDs matching filter", func(t *testing.T) { - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: policyMrn("policy1"), - AssetFilters: []*explorer.Mquery{ - {Mql: "asset.name == \"asset1\""}, - {Mql: "asset.family.contains(\"unix\")"}, - }, - }) - require.NoError(t, err) - require.NotNil(t, rp) - require.Len(t, rp.ExecutionJob.Queries, 1) - require.Len(t, rp.CollectorJob.ReportingJobs, 4) - - qrIdToRj := map[string]*policy.ReportingJob{} - for _, rj := range rp.CollectorJob.ReportingJobs { - qrIdToRj[rj.QrId] = rj - } - // scoring queries report by code id - require.NotNil(t, qrIdToRj[b.Queries[1].CodeId]) - require.Len(t, qrIdToRj[b.Queries[1].CodeId].Mrns, 3) - require.Equal(t, queryMrn("variant1"), qrIdToRj[b.Queries[1].CodeId].Mrns[0]) - require.Equal(t, queryMrn("check-variants"), qrIdToRj[b.Queries[1].CodeId].Mrns[1]) - require.Equal(t, queryMrn("variant2"), qrIdToRj[b.Queries[1].CodeId].Mrns[2]) - - require.Len(t, qrIdToRj[b.Queries[2].CodeId].Mrns, 3) - require.Equal(t, queryMrn("variant1"), qrIdToRj[b.Queries[2].CodeId].Mrns[0]) - require.Equal(t, queryMrn("check-variants"), qrIdToRj[b.Queries[2].CodeId].Mrns[1]) - require.Equal(t, queryMrn("variant2"), qrIdToRj[b.Queries[2].CodeId].Mrns[2]) - }) -} - -func TestResolve_RiskFactors(t *testing.T) { - b := parseBundle(t, ` -owner_mrn: //test.sth -queries: -- uid: query-1 - title: query-1 - mql: 1 == 1 -- uid: query-2 - title: query-2 - mql: 1 == 2 -policies: - - name: testpolicy1 - uid: testpolicy1 - risk_factors: - - uid: sshd-service - magnitude: 0.9 - - uid: sshd-service-na - action: 2 - groups: - - filters: asset.name == "asset1" - checks: - - uid: query-1 - - uid: query-2 - policies: - - uid: risk-factors-security - - uid: risk-factors-security - name: Mondoo Risk Factors analysis - version: "1.0.0" - risk_factors: - - uid: sshd-service - title: SSHd Service running - indicator: asset-in-use - magnitude: 0.6 - filters: - - mql: | - asset.name == "asset1" - checks: - - uid: sshd-service-running - mql: 1 == 1 - - uid: sshd-service-na - title: SSHd Service running - indicator: asset-in-use - magnitude: 0.5 - filters: - - mql: | - asset.name == "asset1" - checks: - - uid: sshd-service-running-na - mql: 1 == 2 -`) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("testpolicy1")}}, - }, []*policy.Bundle{b}) - - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: "asset1", - AssetFilters: []*explorer.Mquery{{Mql: "asset.name == \"asset1\""}}, - }) - require.NoError(t, err) - require.NotNil(t, rp) - - qrIdToRj := map[string]*policy.ReportingJob{} - for _, rj := range rp.CollectorJob.ReportingJobs { - qrIdToRj[rj.QrId] = rj - } - - require.Len(t, rp.CollectorJob.ReportingJobs, 7) - require.NotNil(t, qrIdToRj[policyMrn("testpolicy1")]) - require.NotNil(t, qrIdToRj[policyMrn("risk-factors-security")]) - rfRj := qrIdToRj["//test.sth/risks/sshd-service"] - require.NotNil(t, rfRj) - require.Nil(t, qrIdToRj["//test.sth/risks/sshd-service-na"]) - - var queryRjUuid string - for uuid := range rfRj.ChildJobs { - queryRjUuid = uuid - } - require.NotEmpty(t, queryRjUuid) - queryRj := rp.CollectorJob.ReportingJobs[queryRjUuid] - require.NotNil(t, queryRj) - - require.Contains(t, rfRj.ChildJobs, queryRj.Uuid) - - require.Equal(t, float32(0.9), rp.CollectorJob.RiskFactors["//test.sth/risks/sshd-service"].Magnitude.GetValue()) -} - -func TestResolve_Variants(t *testing.T) { - b := parseBundle(t, ` -owner_mrn: //test.sth -policies: - - uid: example2 - name: Another policy - version: "1.0.0" - groups: - # Additionally it defines some queries of its own - - type: chapter - title: Some uname infos - queries: - # In this case, we are using a shared query that is defined below - - uid: uname - checks: - - uid: check-os - variants: - - uid: check-os-unix - - uid: check-os-windows - -queries: - # This is a composed query which has two variants: one for unix type systems - # and one for windows, where we don't run the additional argument. - # If you run the "uname" query, it will pick matching sub-queries for you. - - uid: uname - title: Collect uname info - variants: - - uid: unix-uname - - uid: windows-uname - - uid: unix-uname - mql: command("uname -a").stdout - filters: asset.family.contains("unix") - - uid: windows-uname - mql: command("uname").stdout - filters: asset.family.contains("windows") - - - uid: check-os-unix - filters: asset.family.contains("unix") - title: A check only run on Linux/macOS - mql: users.contains(name == "root") - - uid: check-os-windows - filters: asset.family.contains("windows") - title: A check only run on Windows - mql: users.contains(name == "Administrator")`) - - srv := initResolver(t, []*testAsset{ - {asset: "asset1", policies: []string{policyMrn("example2")}}, - }, []*policy.Bundle{b}) - - ctx := context.Background() - _, err := srv.SetBundle(ctx, b) - require.NoError(t, err) - - _, err = b.Compile(context.Background(), conf.Schema, nil) - require.NoError(t, err) - - rp, err := srv.Resolve(context.Background(), &policy.ResolveReq{ - PolicyMrn: policyMrn("example2"), - AssetFilters: []*explorer.Mquery{{Mql: "asset.family.contains(\"windows\")"}}, - }) - - require.NoError(t, err) - require.NotNil(t, rp) - qrIdToRj := map[string]*policy.ReportingJob{} - for _, rj := range rp.CollectorJob.ReportingJobs { - qrIdToRj[rj.QrId] = rj - } - - t.Run("resolve variant data queries", func(t *testing.T) { - rj := qrIdToRj["//test.sth/queries/uname"] - require.NotNil(t, rj) - assert.ElementsMatch(t, []string{"//test.sth/queries/uname"}, rj.Mrns) - - rj = qrIdToRj["//test.sth/queries/windows-uname"] - require.NotNil(t, rj) - assert.ElementsMatch(t, []string{ - "//test.sth/queries/windows-uname", - "//test.sth/queries/uname", - }, rj.Mrns) - }) - - t.Run("resolve variant checks", func(t *testing.T) { - rj := qrIdToRj["//test.sth/queries/check-os"] - require.NotNil(t, rj) - assert.ElementsMatch(t, []string{"//test.sth/queries/check-os"}, rj.Mrns) - - rj = qrIdToRj["eUdVwVDNIGA="] - require.NotNil(t, rj) - assert.ElementsMatch(t, []string{ - "//test.sth/queries/check-os-windows", - "//test.sth/queries/check-os", - }, rj.Mrns) - }) -} diff --git a/policy/resolver_v2_test.go b/policy/resolver_v2_test.go index dadaed59..e7514671 100644 --- a/policy/resolver_v2_test.go +++ b/policy/resolver_v2_test.go @@ -267,12 +267,8 @@ func (r *resolvedPolicyTesterReportingJobBuilder) testIt(t *testing.T, rp *polic } } -func contextResolverV2() context.Context { - return policy.WithNextGenResolver(context.Background()) -} - func TestResolveV2_EmptyPolicy(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -311,7 +307,7 @@ policies: } func TestResolveV2_SimplePolicy(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -390,7 +386,7 @@ policies: func TestResolveV2_PolicyWithImpacts(t *testing.T) { // For impacts, we always find the worst impact specified for a query in a policy bundle. // All instances of the query use that impact - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -472,7 +468,7 @@ queries: } func TestResolveV2_PolicyWithScoringSystem(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -525,7 +521,7 @@ policies: } func TestResolveV2_PolicyWithScoringSystemOverride(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -579,7 +575,7 @@ policies: } func TestResolveV2_PolicyActionIgnore(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -643,7 +639,7 @@ policies: } func TestResolveV2_PolicyActionScoringSystem(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -709,7 +705,7 @@ policies: } func TestResolveV2_IgnoredQuery(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -753,7 +749,7 @@ policies: } func TestResolveV2_Frameworks(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() bundleStr := ` owner_mrn: //test.sth policies: @@ -1142,7 +1138,7 @@ framework_maps: // the groups or any of its queries filters matched. This tests to ensure that if the policies // group filtered it out, it doesn't show up in the reporting structure func TestResolveV2_PoliciesMatchingAgainstIncorrectPlatform(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -1215,7 +1211,7 @@ queries: } func TestResolveV2_NeverPruneRoot(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -1249,7 +1245,7 @@ queries: } func TestResolveV2_PoliciesMatchingFilters(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -1299,7 +1295,7 @@ queries: } func TestResolveV2_TwoMrns(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -1349,7 +1345,7 @@ policies: } func TestResolveV2_TwoMrns_FilterMismatch(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -1396,7 +1392,7 @@ policies: } func TestResolveV2_TwoMrns_DataQueries(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -1451,7 +1447,7 @@ policies: } func TestResolveV2_TwoMrns_Variants(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -1499,7 +1495,7 @@ queries: } func TestResolveV2_Variants(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -1581,7 +1577,7 @@ queries: } func TestResolveV2_RiskFactors(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth queries: @@ -1682,7 +1678,7 @@ policies: } func TestResolveV2_FrameworkExceptions(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() bundleString := ` owner_mrn: //test.sth policies: @@ -1900,7 +1896,7 @@ framework_maps: } func TestResolveV2_PolicyExceptionIgnored(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: @@ -1956,7 +1952,7 @@ policies: } func TestResolveV2_PolicyExceptionDisabled(t *testing.T) { - ctx := contextResolverV2() + ctx := context.Background() b := parseBundle(t, ` owner_mrn: //test.sth policies: From 5a36396e4a760e0c4e39b8203d19ed5efa2f45f3 Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Sat, 14 Dec 2024 10:53:51 -0600 Subject: [PATCH 2/2] fix tests --- policy/bundle.go | 2 +- policy/scan/local_scanner_test.go | 103 ++++++++---------- .../scan/testdata/exception-groups.mql.yaml | 9 +- 3 files changed, 51 insertions(+), 63 deletions(-) diff --git a/policy/bundle.go b/policy/bundle.go index 5fcd6cf5..7b8cb559 100644 --- a/policy/bundle.go +++ b/policy/bundle.go @@ -620,7 +620,7 @@ func (c *bundleCache) removeFailing(res *Bundle) { group := policy.Groups[j] group.Queries = explorer.FilterQueryMRNs(c.removeQueries, group.Queries) group.Checks = explorer.FilterQueryMRNs(c.removeQueries, group.Checks) - if len(group.Queries)+len(group.Checks) > 0 { + if len(group.Policies)+len(group.Queries)+len(group.Checks) > 0 { groups = append(groups, group) } } diff --git a/policy/scan/local_scanner_test.go b/policy/scan/local_scanner_test.go index f8d03447..363e939f 100644 --- a/policy/scan/local_scanner_test.go +++ b/policy/scan/local_scanner_test.go @@ -176,7 +176,6 @@ func (s *LocalScannerSuite) TestRunIncognito_ExceptionGroups() { s.job.Bundle = bundle s.job.PolicyFilters = []string{"asset-policy"} - bundleMap := bundle.ToMap() ctx := context.Background() scanner := NewLocalScanner(DisableProgressBar()) @@ -195,18 +194,6 @@ func (s *LocalScannerSuite) TestRunIncognito_ExceptionGroups() { p := full.ResolvedPolicies[k] - // Get the code id for all the executed queries - executedQueries := []string{} - for qCodeId := range p.ExecutionJob.Queries { - executedQueries = append(executedQueries, qCodeId) - } - - expectedQueries := []string{ - bundleMap.Queries["//local.cnspec.io/run/local-execution/queries/ignored-query"].CodeId, - bundleMap.Queries["//local.cnspec.io/run/local-execution/queries/sshd-score-01"].CodeId, - } - s.ElementsMatch(expectedQueries, executedQueries) - queryIdToReportingJob := map[string]*policy.ReportingJob{} for _, rj := range p.CollectorJob.ReportingJobs { _, ok := queryIdToReportingJob[rj.QrId] @@ -215,13 +202,26 @@ func (s *LocalScannerSuite) TestRunIncognito_ExceptionGroups() { } // Make sure the ignored query is ignored - queryRj := queryIdToReportingJob[bundleMap.Queries["//local.cnspec.io/run/local-execution/queries/ignored-query"].CodeId] - s.Require().NotNil(queryRj) - - parent := queryRj.Notify[0] - parentJob := p.CollectorJob.ReportingJobs[parent] - s.Require().NotNil(parentJob) - s.Equal(explorer.ScoringSystem_IGNORE_SCORE, parentJob.ChildJobs[queryRj.Uuid].Scoring) + { + queryRj := queryIdToReportingJob["//local.cnspec.io/run/local-execution/queries/ignored-query"] + s.Require().NotNil(queryRj) + + parent := queryRj.Notify[0] + parentJob := p.CollectorJob.ReportingJobs[parent] + s.Require().NotNil(parentJob) + s.Equal(explorer.ScoringSystem_IGNORE_SCORE, parentJob.ChildJobs[queryRj.Uuid].Scoring) + } + // Make sure the ignored query is reported as disabled + { + queryRj := queryIdToReportingJob["//local.cnspec.io/run/local-execution/queries/deactivate-query"] + s.Require().NotNil(queryRj) + var child string + for c := range queryRj.ChildJobs { + child = c + break + } + s.Equal(explorer.ScoringSystem_DISABLED, queryRj.ChildJobs[child].Scoring) + } } } @@ -230,8 +230,8 @@ func (s *LocalScannerSuite) TestRunIncognito_ExceptionGroups_RejectedReview() { bundle, err := loader.BundleFromPaths("./testdata/exception-groups.mql.yaml") s.Require().NoError(err) - bundle.Policies[1].Groups[0].ReviewStatus = policy.ReviewStatus_REJECTED bundle.Policies[1].Groups[1].ReviewStatus = policy.ReviewStatus_REJECTED + bundle.Policies[1].Groups[2].ReviewStatus = policy.ReviewStatus_REJECTED _, err = bundle.CompileExt(context.Background(), policy.BundleCompileConf{ CompilerConfig: s.conf, @@ -255,8 +255,8 @@ func (s *LocalScannerSuite) TestRunIncognito_ExceptionGroups_RejectedReview() { s.Equal(1, len(full.Reports)) for k, r := range full.Reports { - // Verify the score is 16 - s.Equal(uint32(16), r.GetScore().Value) + // Verify the score is 33 + s.Equal(uint32(33), r.GetScore().Value) p := full.ResolvedPolicies[k] @@ -281,13 +281,15 @@ func (s *LocalScannerSuite) TestRunIncognito_ExceptionGroups_RejectedReview() { } // Make sure the ignored query is ignored - queryRj := queryIdToReportingJob[bundleMap.Queries["//local.cnspec.io/run/local-execution/queries/ignored-query"].CodeId] + queryRj := queryIdToReportingJob["//local.cnspec.io/run/local-execution/queries/ignored-query"] s.Require().NotNil(queryRj) parent := queryRj.Notify[0] parentJob := p.CollectorJob.ReportingJobs[parent] s.Require().NotNil(parentJob) - s.Equal(explorer.ScoringSystem_SCORING_UNSPECIFIED, parentJob.ChildJobs[queryRj.Uuid].Scoring) + impact, ok := parentJob.ChildJobs[queryRj.Uuid] + s.Require().True(ok) + s.Require().Nil(impact) } } @@ -304,7 +306,6 @@ func (s *LocalScannerSuite) TestRunIncognito_QueryExceptions() { s.job.Bundle = bundle s.job.PolicyFilters = []string{"asset-policy"} - bundleMap := bundle.ToMap() ctx := context.Background() scanner := NewLocalScanner(DisableProgressBar()) @@ -323,18 +324,6 @@ func (s *LocalScannerSuite) TestRunIncognito_QueryExceptions() { p := full.ResolvedPolicies[k] - // Get the code id for all the executed queries - executedQueries := []string{} - for qCodeId := range p.ExecutionJob.Queries { - executedQueries = append(executedQueries, qCodeId) - } - - expectedQueries := []string{ - bundleMap.Queries["//local.cnspec.io/run/local-execution/queries/ignored-query"].CodeId, - bundleMap.Queries["//local.cnspec.io/run/local-execution/queries/sshd-score-01"].CodeId, - } - s.ElementsMatch(expectedQueries, executedQueries) - queryIdToReportingJob := map[string]*policy.ReportingJob{} for _, rj := range p.CollectorJob.ReportingJobs { _, ok := queryIdToReportingJob[rj.QrId] @@ -343,7 +332,7 @@ func (s *LocalScannerSuite) TestRunIncognito_QueryExceptions() { } // Make sure the ignored query is ignored - queryRj := queryIdToReportingJob[bundleMap.Queries["//local.cnspec.io/run/local-execution/queries/ignored-query"].CodeId] + queryRj := queryIdToReportingJob["//local.cnspec.io/run/local-execution/queries/ignored-query"] s.Require().NotNil(queryRj) parent := queryRj.Notify[0] @@ -366,7 +355,6 @@ func (s *LocalScannerSuite) TestRunIncognito_QueryExceptions_MultipleGroups() { s.job.Bundle = bundle s.job.PolicyFilters = []string{"asset-policy"} - bundleMap := bundle.ToMap() ctx := context.Background() scanner := NewLocalScanner(DisableProgressBar()) @@ -385,18 +373,6 @@ func (s *LocalScannerSuite) TestRunIncognito_QueryExceptions_MultipleGroups() { p := full.ResolvedPolicies[k] - // Get the code id for all the executed queries - executedQueries := []string{} - for qCodeId := range p.ExecutionJob.Queries { - executedQueries = append(executedQueries, qCodeId) - } - - expectedQueries := []string{ - bundleMap.Queries["//local.cnspec.io/run/local-execution/queries/ignored-query"].CodeId, - bundleMap.Queries["//local.cnspec.io/run/local-execution/queries/sshd-score-01"].CodeId, - } - s.ElementsMatch(expectedQueries, executedQueries) - queryIdToReportingJob := map[string]*policy.ReportingJob{} for _, rj := range p.CollectorJob.ReportingJobs { _, ok := queryIdToReportingJob[rj.QrId] @@ -405,13 +381,26 @@ func (s *LocalScannerSuite) TestRunIncognito_QueryExceptions_MultipleGroups() { } // Make sure the ignored query is ignored - queryRj := queryIdToReportingJob[bundleMap.Queries["//local.cnspec.io/run/local-execution/queries/ignored-query"].CodeId] + queryRj := queryIdToReportingJob["//local.cnspec.io/run/local-execution/queries/ignored-query"] s.Require().NotNil(queryRj) - parent := queryRj.Notify[0] - parentJob := p.CollectorJob.ReportingJobs[parent] - s.Require().NotNil(parentJob) - s.Equal(explorer.ScoringSystem_IGNORE_SCORE, parentJob.ChildJobs[queryRj.Uuid].Scoring) + { + parent := queryRj.Notify[0] + parentJob := p.CollectorJob.ReportingJobs[parent] + s.Require().NotNil(parentJob) + s.Equal(explorer.ScoringSystem_IGNORE_SCORE, parentJob.ChildJobs[queryRj.Uuid].Scoring) + } + // Make sure the ignored query is reported as disabled + { + queryRj := queryIdToReportingJob["//local.cnspec.io/run/local-execution/queries/deactivate-query"] + s.Require().NotNil(queryRj) + var child string + for c := range queryRj.ChildJobs { + child = c + break + } + s.Equal(explorer.ScoringSystem_DISABLED, queryRj.ChildJobs[child].Scoring) + } } } diff --git a/policy/scan/testdata/exception-groups.mql.yaml b/policy/scan/testdata/exception-groups.mql.yaml index a4acd98a..42de561a 100644 --- a/policy/scan/testdata/exception-groups.mql.yaml +++ b/policy/scan/testdata/exception-groups.mql.yaml @@ -13,17 +13,16 @@ policies: - uid: ignored-query - uid: deactivate-query - uid : asset-policy - groups: + groups: + - policies: + - uid: sshd-server-policy - checks: - uid: ignored-query type: 4 - policies: - - uid: sshd-server-policy - checks: - uid: deactivate-query type: 5 - policies: - - uid: sshd-server-policy + queries: - uid: sshd-score-01 title: Ensure SSH MaxAuthTries is set to 4 or less