Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions central/sensor/service/connection/connection_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"math"
"slices"

"github.com/pkg/errors"
compScanSetting "github.com/stackrox/rox/central/complianceoperator/v2/scanconfigurations/datastore"
Expand Down Expand Up @@ -544,6 +545,13 @@ func (c *sensorConnection) getPolicySyncMsgFromPolicies(policies []*storage.Poli
// Otherwise, sensor supports the same version or newer as central
}

// Remove exclude_by_kind exclusions before syncing to a Sensor that
// lacks WorkloadKindExclusionCap. Sensor forwards this same list to
// the admission controller, so AC evaluates the same copy.
if !c.HasCapability(centralsensor.WorkloadKindExclusionCap) {
policies = stripExcludeByKindExclusions(policies)
}

return &central.MsgToSensor{
Msg: &central.MsgToSensor_PolicySync{
PolicySync: &central.PolicySync{
Expand All @@ -553,6 +561,32 @@ func (c *sensorConnection) getPolicySyncMsgFromPolicies(policies []*storage.Poli
}, nil
}

// stripExcludeByKindExclusions returns a copy of policies with every exclusion
// that sets exclude_by_kind removed. Stored policies are not mutated. An old
// Sensor compiles an exclude_by_kind item as an unconstrained exclusion (nil
// name and nil scope match every workload). Sensor does not evaluate image
// exclusions, so an image+kind item with only the kind matcher removed is that
// same unconstrained exclusion and is removed entirely. An exclusion with no
// kind matcher is kept, including an image combined with a deployment name or
// scope.
func stripExcludeByKindExclusions(policies []*storage.Policy) []*storage.Policy {
out := make([]*storage.Policy, 0, len(policies))
for _, p := range policies {
if !slices.ContainsFunc(p.GetExclusions(), exclusionSetsExcludeByKind) {
out = append(out, p)
continue
}
cloned := p.CloneVT()
cloned.Exclusions = slices.DeleteFunc(cloned.GetExclusions(), exclusionSetsExcludeByKind)
out = append(out, cloned)
}
return out
}

func exclusionSetsExcludeByKind(e *storage.Exclusion) bool {
return e.GetExcludeByKind() != nil
}

func (c *sensorConnection) getNetworkBaselineSyncMsg(ctx context.Context) (*central.MsgToSensor, error) {
var networkBaselines []*storage.NetworkBaseline
walkFn := func() error {
Expand Down
96 changes: 96 additions & 0 deletions central/sensor/service/connection/connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,102 @@ func (s *testSuite) TestGetPolicySyncMsgFromPoliciesDoesntDowngradeInvalidVersio
s.Equal(policyversion.CurrentVersion().String(), policySync.GetPolicies()[0].GetPolicyVersion())
}

func (s *testSuite) TestGetPolicySyncMsgFromPoliciesStripsExcludeByKind() {
kindExclusion := &storage.Exclusion{
Name: "skip-cronjobs",
Matcher: &storage.Exclusion_ExcludeByKind_{
ExcludeByKind: &storage.Exclusion_ExcludeByKind{
Kinds: []storage.Exclusion_WorkloadKind{storage.Exclusion_CRON_JOB},
},
},
}
nameExclusion := &storage.Exclusion{
Name: "skip-web",
Matcher: &storage.Exclusion_Deployment_{
Deployment: &storage.Exclusion_Deployment{Name: "web"},
},
}
imageExclusion := &storage.Exclusion{
Name: "skip-image",
Image: &storage.Exclusion_Image{Name: "stackrox.io"},
}
imageAndKindExclusion := &storage.Exclusion{
Name: "skip-image-and-jobs",
Image: &storage.Exclusion_Image{Name: "example.io"},
Matcher: &storage.Exclusion_ExcludeByKind_{
ExcludeByKind: &storage.Exclusion_ExcludeByKind{
Kinds: []storage.Exclusion_WorkloadKind{storage.Exclusion_JOB},
},
},
}
imageAndDeploymentExclusion := &storage.Exclusion{
Name: "skip-image-and-web",
Image: &storage.Exclusion_Image{Name: "nginx"},
Matcher: &storage.Exclusion_Deployment_{
Deployment: &storage.Exclusion_Deployment{Name: "web-image"},
},
}
policy := &storage.Policy{
Name: "test-policy",
Exclusions: []*storage.Exclusion{
kindExclusion,
nameExclusion,
imageExclusion,
imageAndKindExclusion,
imageAndDeploymentExclusion,
},
}

s.Run("without capability strips kind items and keeps others", func() {
original := policy.CloneVT()
msg, err := (&sensorConnection{}).getPolicySyncMsgFromPolicies([]*storage.Policy{policy})
s.Require().NoError(err)

synced := msg.GetPolicySync().GetPolicies()
s.Require().Len(synced, 1)
got := synced[0].GetExclusions()
s.Require().Len(got, 3)
s.Equal("skip-web", got[0].GetName())
s.Equal("web", got[0].GetDeployment().GetName())
s.Equal("skip-image", got[1].GetName())
s.Equal("stackrox.io", got[1].GetImage().GetName())
s.Equal("skip-image-and-web", got[2].GetName())
s.Equal("nginx", got[2].GetImage().GetName())
s.Equal("web-image", got[2].GetDeployment().GetName())
protoassert.Equal(s.T(), original, policy)
})

s.Run("with capability keeps kind items", func() {
conn := &sensorConnection{
capabilities: set.NewSet(centralsensor.WorkloadKindExclusionCap),
}
msg, err := conn.getPolicySyncMsgFromPolicies([]*storage.Policy{policy})
s.Require().NoError(err)

got := msg.GetPolicySync().GetPolicies()[0].GetExclusions()
s.Require().Len(got, 5)
s.NotNil(got[0].GetExcludeByKind())
s.NotNil(got[3].GetExcludeByKind())
s.Equal(storage.Exclusion_JOB, got[3].GetExcludeByKind().GetKinds()[0])
s.Equal("example.io", got[3].GetImage().GetName())
s.Equal("web-image", got[4].GetDeployment().GetName())
})

s.Run("later capable sensor still sees original kind items", func() {
// PreparePoliciesAndBroadcast reuses the same slice across connections.
input := []*storage.Policy{policy}
_, err := (&sensorConnection{}).getPolicySyncMsgFromPolicies(input)
s.Require().NoError(err)

capable := &sensorConnection{
capabilities: set.NewSet(centralsensor.WorkloadKindExclusionCap),
}
msg, err := capable.getPolicySyncMsgFromPolicies(input)
s.Require().NoError(err)
s.NotNil(msg.GetPolicySync().GetPolicies()[0].GetExclusions()[0].GetExcludeByKind())
})
}

func (s *testSuite) TestSendsAuditLogSyncMessageIfEnabledOnRun() {
ctx := context.Background()
clusterID := "this-cluster"
Expand Down
Loading