From fe8d0587bfa24644c4e60471a27ac93b8689e0ad Mon Sep 17 00:00:00 2001 From: Christian Zunker <827818+czunker@users.noreply.github.com> Date: Mon, 9 Sep 2024 13:53:37 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Find=20variant=20checks=20by=20p?= =?UTF-8?q?arent=20MRN=20(#1408)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🐛 Find variant checks by parent MRN Compliance only knows about parent MRNs and not variant MRNs. To allow a mapping from compliance control to executed check, we need to store the check with it's parent MRN. Signed-off-by: Christian Zunker --- policy/bundle.go | 5 +++++ policy/reportingjob.go | 1 + policy/resolver.go | 40 ++++++++++++++++++++++++++++++++++++---- policy/resolver_test.go | 30 +++++++++++++++++++----------- 4 files changed, 61 insertions(+), 15 deletions(-) diff --git a/policy/bundle.go b/policy/bundle.go index 3b741028..ae1db4cc 100644 --- a/policy/bundle.go +++ b/policy/bundle.go @@ -99,6 +99,11 @@ func BundleExecutionChecksum(policy *Policy, framework *Framework) string { if framework != nil { res = res.Add(framework.GraphExecutionChecksum) } + // 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 + res = res.Add(RESOLVER_VERSION) + return res.String() } diff --git a/policy/reportingjob.go b/policy/reportingjob.go index f7ebd675..b601fae6 100644 --- a/policy/reportingjob.go +++ b/policy/reportingjob.go @@ -37,5 +37,6 @@ func (r *ReportingJob) RefreshChecksum() { checksum = checksum.Add(notify[i]) } } + r.Checksum = checksum.String() } diff --git a/policy/resolver.go b/policy/resolver.go index 47e96e3e..bc12c8a3 100644 --- a/policy/resolver.go +++ b/policy/resolver.go @@ -8,6 +8,7 @@ import ( "encoding/base64" "encoding/binary" "math/rand" + "slices" "sort" "time" @@ -28,6 +29,10 @@ import ( const ( POLICY_SERVICE_NAME = "policy.api.mondoo.com" + // 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" ) type AssetMutation struct { @@ -1160,10 +1165,21 @@ func (cache *policyResolverCache) addCheckJob(ctx context.Context, check *explor Type: ReportingJob_CHECK, Mrns: []string{}, } - // We don't track the MRNs for variant queries through the cachem, since variant queries + // 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) @@ -1183,12 +1199,14 @@ func (cache *policyResolverCache) addCheckJob(ctx context.Context, check *explor 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") } - // If this is avariant query, its MRN is only the MRN of the check. - rj.Mrns = []string{check.Mrn} } else { // we set a placeholder for the execution query, just to indicate it will be added cache.global.executionQueries[check.Checksum] = nil @@ -1255,6 +1273,17 @@ func (cache *policyResolverCache) addDataQueryJob(ctx context.Context, query *ex // 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) @@ -1272,11 +1301,14 @@ func (cache *policyResolverCache) addDataQueryJob(ctx context.Context, query *ex 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") } - rj.Mrns = []string{query.Mrn} } else { // we set a placeholder for the execution query, just to indicate it will be added cache.global.executionQueries[query.Checksum] = nil diff --git a/policy/resolver_test.go b/policy/resolver_test.go index 08ecf0e9..2b45811c 100644 --- a/policy/resolver_test.go +++ b/policy/resolver_test.go @@ -234,9 +234,9 @@ policies: require.NoError(t, err) require.NotNil(t, rp) require.Len(t, rp.CollectorJob.ReportingJobs, 5) - ignoreJob := rp.CollectorJob.ReportingJobs["8Sis0SvMbtI="] + ignoreJob := rp.CollectorJob.ReportingJobs["q7gxFtwx4zg="] require.NotNil(t, ignoreJob) - childJob := ignoreJob.ChildJobs["YCeU4NjbMe0="] + childJob := ignoreJob.ChildJobs["GhqR9OVIDVM="] require.NotNil(t, childJob) require.Equal(t, explorer.ScoringSystem_IGNORE_SCORE, childJob.Scoring) }) @@ -291,12 +291,12 @@ policies: require.NoError(t, err) require.NotNil(t, rp) require.Len(t, rp.CollectorJob.ReportingJobs, 5) - ignoreJob := rp.CollectorJob.ReportingJobs["WVDbd6CWW30="] + ignoreJob := rp.CollectorJob.ReportingJobs["gqNWe4GO+UA="] require.NotNil(t, ignoreJob) - childJob := ignoreJob.ChildJobs["7Q8ymKH8W5c="] + childJob := ignoreJob.ChildJobs["LrvWHNnWZNQ="] require.NotNil(t, childJob) require.Equal(t, explorer.ScoringSystem_IGNORE_SCORE, childJob.Scoring) - activeJob := rp.CollectorJob.ReportingJobs["/w4u/z6FEsI="] + activeJob := rp.CollectorJob.ReportingJobs["+KeXN9zwDzA="] require.NotNil(t, activeJob) require.Equal(t, explorer.ScoringSystem_BANDED, activeJob.ScoringSystem) }) @@ -1717,13 +1717,15 @@ queries: } // 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.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("variant2"), qrIdToRj[b.Queries[1].CodeId].Mrns[1]) + 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, 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("variant2"), qrIdToRj[b.Queries[2].CodeId].Mrns[1]) + 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]) }) } @@ -1889,7 +1891,10 @@ queries: rj = qrIdToRj["//test.sth/queries/windows-uname"] require.NotNil(t, rj) - assert.ElementsMatch(t, []string{"//test.sth/queries/windows-uname"}, rj.Mrns) + assert.ElementsMatch(t, []string{ + "//test.sth/queries/windows-uname", + "//test.sth/queries/uname", + }, rj.Mrns) }) t.Run("resolve variant checks", func(t *testing.T) { @@ -1899,6 +1904,9 @@ queries: rj = qrIdToRj["eUdVwVDNIGA="] require.NotNil(t, rj) - assert.ElementsMatch(t, []string{"//test.sth/queries/check-os-windows"}, rj.Mrns) + assert.ElementsMatch(t, []string{ + "//test.sth/queries/check-os-windows", + "//test.sth/queries/check-os", + }, rj.Mrns) }) }