Skip to content

Commit

Permalink
🐛 extend risk factor checksum calculation (#1214)
Browse files Browse the repository at this point in the history
This now includes all fields and separates the checksum between the execution and content part. The RiskFactor itself only tracks the content checksum, while we use the execution checksum when we calculate the policy checksum.

Particularly this last part was missing from risk calculations and cause the policy content/execution checksums to not get updated.

I also cleaned up a weird for-loop in the referenced policies checksum calculation.

Signed-off-by: Dominik Richter <[email protected]>
  • Loading branch information
arlimus authored Apr 1, 2024
1 parent c32aed1 commit b0e12dc
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 27 deletions.
23 changes: 19 additions & 4 deletions policy/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"go.mondoo.com/cnquery/v10/mqlc"
"go.mondoo.com/cnquery/v10/mrn"
"go.mondoo.com/cnquery/v10/types"
"go.mondoo.com/cnquery/v10/utils/sortx"
"google.golang.org/protobuf/proto"
)

Expand Down Expand Up @@ -300,11 +301,8 @@ func (p *Policy) UpdateChecksums(ctx context.Context,

// POLICIES (must be sorted)
policyMRNs := make([]string, len(group.Policies))
i = 0
for i := range group.Policies {
policy := group.Policies[i]
policyMRNs[i] = policy.Mrn
i++
policyMRNs[i] = group.Policies[i].Mrn
}
sort.Strings(policyMRNs)
for _, policyMRN := range policyMRNs {
Expand Down Expand Up @@ -570,6 +568,23 @@ func (p *Policy) updateAllChecksums(ctx context.Context,
}
}

// RISKS
riskIdx := make(map[string]*RiskFactor, len(p.RiskFactors))
for i := range p.RiskFactors {
cur := p.RiskFactors[i]
riskIdx[cur.Mrn] = cur
}

sortedRiskMRNs := sortx.Keys(riskIdx)
for _, riskMRN := range sortedRiskMRNs {
esum, csum, err := riskIdx[riskMRN].RefreshChecksum(ctx, conf)
if err != nil {
return err
}
executionChecksum = executionChecksum.AddUint(uint64(esum))
contentChecksum = contentChecksum.AddUint(uint64(csum))
}

p.LocalExecutionChecksum = executionChecksum.String()
p.LocalContentChecksum = executionChecksum.AddUint(uint64(contentChecksum)).String()

Expand Down
67 changes: 44 additions & 23 deletions policy/risk_factor.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package policy
import (
"context"

"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"go.mondoo.com/cnquery/v10/checksums"
"go.mondoo.com/cnquery/v10/mqlc"
Expand Down Expand Up @@ -53,49 +54,69 @@ func (r *RiskFactor) RefreshMRN(ownerMRN string) error {
return nil
}

func (r *RiskFactor) RefreshChecksum(conf mqlc.CompilerConfig) error {
func (r *RiskFactor) ExecutionChecksum(ctx context.Context, conf mqlc.CompilerConfig) (checksums.Fast, error) {
c := checksums.New.
Add(r.Mrn).
Add(r.Title).
Add(r.Docs.Active).
Add(r.Docs.Inactive).
AddUint(uint64(r.Scope)).
AddUint(uint64(r.Magnitude))

if r.IsAbsolute {
c = c.AddUint(1)
} else {
c = c.AddUint(0)
}

var err error
c, err = r.Filters.ComputeChecksum(c, r.Mrn, conf)
if err != nil {
return err
return c, err
}

for i := range r.Checks {
check := r.Checks[i]
if err := check.RefreshChecksum(context.Background(), conf, nil); err != nil {
return err
if err := check.RefreshChecksum(ctx, conf, nil); err != nil {
return c, err
}
}

if r.IsAbsolute {
c = c.Add("1")
} else {
c = c.Add("0")
if check.Checksum == "" {
return c, errors.New("failed to get checksum for risk query " + check.Mrn)
}

c = c.Add(check.Checksum)
}

for i := range r.Software {
cur := r.Software[i]
c = c.Add(cur.Type).
Add(cur.Name).
Add(cur.Namespace).
Add(cur.Version).
Add(cur.MqlMrn)
sw := r.Software[i]
c = c.Add(sw.MqlMrn).Add(sw.Name).Add(sw.Namespace).Add(sw.Type).Add(sw.Version)
}

for i := range r.Resources {
cur := r.Resources[i]
c = c.Add(cur.Selector)
rc := r.Resources[i]
c = c.Add(rc.Selector)
}

r.Checksum = c.String()
return nil
return c, nil
}

// RefreshChecksum updates the Checksum field of this RiskFactor and returns
// both the ExecutionChecksum and the ContentChecksum.
func (r *RiskFactor) RefreshChecksum(ctx context.Context, conf mqlc.CompilerConfig) (checksums.Fast, checksums.Fast, error) {
csum := checksums.New

esum, err := r.ExecutionChecksum(ctx, conf)
if err != nil {
return esum, csum, err
}

csum = csum.AddUint(uint64(esum)).
Add(r.Mrn).
Add(r.Title)

if r.Docs != nil {
csum = csum.Add(r.Docs.Active).Add(r.Docs.Inactive)
}

r.Checksum = csum.String()
return esum, csum, nil
}

func (r *RiskFactor) AdjustRiskScore(score *Score, isDetected bool) {
Expand Down
121 changes: 121 additions & 0 deletions policy/risk_factor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,137 @@
package policy

import (
"context"
"strconv"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.mondoo.com/cnquery/v10"
"go.mondoo.com/cnquery/v10/explorer"
"go.mondoo.com/cnquery/v10/mqlc"
"go.mondoo.com/cnquery/v10/providers-sdk/v1/testutils"
)

func risks(risks ...*ScoredRiskFactor) *ScoredRiskFactors {
return &ScoredRiskFactors{Items: risks}
}

func TestRiskFactor_Checksums(t *testing.T) {
base := RiskFactor{
Mrn: "//long/mrn",
Title: "Some title",
Docs: &RiskFactorDocs{
Active: "Does exist",
Inactive: "Does not exist",
},
Filters: &explorer.Filters{
Items: map[string]*explorer.Mquery{
"//filters/mrn": {
Mrn: "//filters/mrn",
Mql: "true",
},
},
},
Checks: []*explorer.Mquery{
{
Mrn: "//some/check/1",
Mql: "1 == 1",
},
{
Mrn: "//some/check/2",
Mql: "2 == 2",
},
},
Scope: ScopeType_SOFTWARE_AND_RESOURCE,
Magnitude: 0.5,
IsAbsolute: true,
Software: []*SoftwareSelector{{
Name: "mypackage",
Version: "1.2.3",
}},
Resources: []*ResourceSelector{{
Selector: "mondoo",
}},
}

coreSchema := testutils.MustLoadSchema(testutils.SchemaProvider{Provider: "core"})
conf := mqlc.NewConfig(coreSchema, cnquery.DefaultFeatures)

ctx := context.Background()
baseEsum, baseCsum, err := base.RefreshChecksum(ctx, conf)
require.NoError(t, err)
require.NotEqual(t, baseEsum, baseCsum)

contentChanges := []func(RiskFactor) RiskFactor{
// 0
func(rf RiskFactor) RiskFactor {
rf.Title = ""
return rf
},
// 1
func(rf RiskFactor) RiskFactor {
rf.Docs = nil
return rf
},
}

for i := range contentChanges {
test := contentChanges[i]
t.Run("contentChange/"+strconv.Itoa(i), func(t *testing.T) {
mod := test(base)
esum, csum, err := mod.RefreshChecksum(ctx, conf)
assert.NoError(t, err)
assert.Equal(t, baseEsum, esum)
assert.NotEqual(t, baseCsum, csum)
})
}

executionChanges := []func(RiskFactor) RiskFactor{
// 0
func(rf RiskFactor) RiskFactor {
rf.Checks = rf.Checks[0:1]
return rf
},
// 1
func(rf RiskFactor) RiskFactor {
rf.Checks[0].Mql = "0 != 1"
return rf
},
// 2
func(rf RiskFactor) RiskFactor {
rf.Resources[0].Selector = "asset"
return rf
},
// 3
func(rf RiskFactor) RiskFactor {
rf.Software[0].Name = "mondoo"
return rf
},
// 4
func(rf RiskFactor) RiskFactor {
rf.IsAbsolute = false
return rf
},
// 5
func(rf RiskFactor) RiskFactor {
rf.Magnitude = 0.79
return rf
},
}

for i := range executionChanges {
test := executionChanges[i]
t.Run("executionChanges/"+strconv.Itoa(i), func(t *testing.T) {
mod := test(base)
esum, csum, err := mod.RefreshChecksum(ctx, conf)
assert.NoError(t, err)
assert.NotEqual(t, baseEsum, esum)
assert.NotEqual(t, baseCsum, csum)
})
}
}

func TestRiskFactor_AdjustRiskScore(t *testing.T) {
tests := []struct {
risk RiskFactor
Expand Down

0 comments on commit b0e12dc

Please sign in to comment.