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
31 changes: 20 additions & 11 deletions central/sensor/service/pipeline/nodeindex/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,23 +130,32 @@ func sendComplianceAck(ctx context.Context, node *storage.Node, injector common.
if injector == nil {
return
}
reply := replyCompliance(node.GetClusterId(), node.GetName(), central.NodeInventoryACK_ACK)
if err := injector.InjectMessage(ctx, reply); err != nil {
log.Warnf("Failed sending node-indexing-ACK to Sensor for %s: %v", nodeDatastore.NodeString(node), err)
} else {
log.Debugf("Sent node-indexing-ACK for %s", nodeDatastore.NodeString(node))
// Always send SensorACK (new path).
if err := injector.InjectMessage(ctx, &central.MsgToSensor{
Msg: &central.MsgToSensor_SensorAck{
SensorAck: &central.SensorACK{
Action: central.SensorACK_ACK,
MessageType: central.SensorACK_NODE_INDEX_REPORT,
ResourceId: node.GetName(),
},
},
}); err != nil {
log.Warnf("Failed injecting SensorACK for node index report (node=%s): %v", node.GetName(), err)
}
}

func replyCompliance(clusterID, nodeName string, t central.NodeInventoryACK_Action) *central.MsgToSensor {
return &central.MsgToSensor{
// Always send legacy NodeInventoryACK for backward compatibility.
if err := injector.InjectMessage(ctx, &central.MsgToSensor{
Msg: &central.MsgToSensor_NodeInventoryAck{
NodeInventoryAck: &central.NodeInventoryACK{
ClusterId: clusterID,
NodeName: nodeName,
Action: t,
ClusterId: node.GetClusterId(),
NodeName: node.GetName(),
Action: central.NodeInventoryACK_ACK,
MessageType: central.NodeInventoryACK_NodeIndexer,
},
},
}); err != nil {
log.Warnf("Failed injecting legacy NodeInventoryACK for node index report (node=%s): %v", node.GetName(), err)
}

log.Debugf("Sent node-indexing ACKs for node %s in cluster %s", node.GetName(), node.GetClusterId())
}
99 changes: 99 additions & 0 deletions central/sensor/service/pipeline/nodeindex/pipeline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import (
"github.com/stackrox/rox/generated/internalapi/central"
v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4"
"github.com/stackrox/rox/generated/storage"
"github.com/stackrox/rox/pkg/concurrency"
"github.com/stackrox/rox/pkg/features"
nodesEnricherMocks "github.com/stackrox/rox/pkg/nodes/enricher/mocks"
"github.com/stackrox/rox/pkg/protoassert"
"github.com/stackrox/rox/pkg/sync"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
)
Expand Down Expand Up @@ -77,6 +80,58 @@ func TestPipelineEnrichesAndUpserts(t *testing.T) {
}
}

func TestPipelineSendsSensorAndLegacyACKs(t *testing.T) {
t.Setenv(features.NodeIndexEnabled.EnvVar(), "true")
t.Setenv(features.ScannerV4.EnvVar(), "true")

ctrl := gomock.NewController(t)
clusterStore := clusterDatastoreMocks.NewMockDataStore(ctrl)
nodeDatastore := nodeDatastoreMocks.NewMockDataStore(ctrl)
riskManager := riskManagerMocks.NewMockManager(ctrl)
enricher := nodesEnricherMocks.NewMockNodeEnricher(ctrl)

node := storage.Node{
Id: "1",
Name: "node-name",
ClusterId: "cluster-id",
}
msg := createMsg(mockIndexReport)

gomock.InOrder(
nodeDatastore.EXPECT().GetNode(gomock.Any(), gomock.Eq(node.GetId())).Times(1).Return(&node, true, nil),
enricher.EXPECT().EnrichNodeWithVulnerabilities(gomock.Any(), nil, gomock.Any()).Times(1).Return(nil),
riskManager.EXPECT().CalculateRiskAndUpsertNode(gomock.Any()).Times(1).Return(nil),
)

injector := &recordingInjector{}
p := &pipelineImpl{
clusterStore: clusterStore,
nodeDatastore: nodeDatastore,
riskManager: riskManager,
enricher: enricher,
}

err := p.Run(t.Context(), node.GetClusterId(), msg, injector)
assert.NoError(t, err)

protoassert.SlicesEqual(t, []*central.SensorACK{
{
Action: central.SensorACK_ACK,
MessageType: central.SensorACK_NODE_INDEX_REPORT,
ResourceId: node.GetName(),
},
}, injector.getSentSensorACKs())

protoassert.SlicesEqual(t, []*central.NodeInventoryACK{
{
ClusterId: node.GetClusterId(),
NodeName: node.GetName(),
Action: central.NodeInventoryACK_ACK,
MessageType: central.NodeInventoryACK_NodeIndexer,
},
}, injector.getSentACKs())
}

func createMsg(ir *v4.IndexReport) *central.MsgFromSensor {
return &central.MsgFromSensor{
Msg: &central.MsgFromSensor_Event{
Expand All @@ -90,6 +145,50 @@ func createMsg(ir *v4.IndexReport) *central.MsgFromSensor {
}
}

type recordingInjector struct {
lock sync.Mutex
legacy []*central.NodeInventoryACK
sensor []*central.SensorACK
}

func (r *recordingInjector) InjectMessage(_ concurrency.Waitable, msg *central.MsgToSensor) error {
r.lock.Lock()
defer r.lock.Unlock()
if ack := msg.GetNodeInventoryAck(); ack != nil {
r.legacy = append(r.legacy, ack.CloneVT())
}
if ack := msg.GetSensorAck(); ack != nil {
r.sensor = append(r.sensor, ack.CloneVT())
}
return nil
}

func (r *recordingInjector) InjectMessageIntoQueue(_ *central.MsgFromSensor) {}

func (r *recordingInjector) getSentACKs() []*central.NodeInventoryACK {
r.lock.Lock()
defer r.lock.Unlock()
out := make([]*central.NodeInventoryACK, 0, len(r.legacy))
for _, ack := range r.legacy {
if ack != nil {
out = append(out, ack)
}
}
return out
}

func (r *recordingInjector) getSentSensorACKs() []*central.SensorACK {
r.lock.Lock()
defer r.lock.Unlock()
out := make([]*central.SensorACK, 0, len(r.sensor))
for _, ack := range r.sensor {
if ack != nil {
out = append(out, ack)
}
}
return out
}

var (
mockIndexReport = &v4.IndexReport{
HashId: "",
Expand Down
40 changes: 29 additions & 11 deletions central/sensor/service/pipeline/nodeinventory/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (p *pipelineImpl) Run(ctx context.Context, _ string, msg *central.MsgFromSe

if shouldDiscardMsg(node) {
// To prevent resending the inventory, still acknowledge receipt of it
sendComplianceAck(ctx, node, ninv, injector)
replyCompliance(ctx, node.GetClusterId(), ninv.GetNodeName(), central.NodeInventoryACK_ACK, injector)
log.Debug("Discarding v2 NodeScan in favor of v4 NodeScan")
return nil
}
Expand All @@ -115,7 +115,7 @@ func (p *pipelineImpl) Run(ctx context.Context, _ string, msg *central.MsgFromSe
return err
}

sendComplianceAck(ctx, node, ninv, injector)
replyCompliance(ctx, node.GetClusterId(), ninv.GetNodeName(), central.NodeInventoryACK_ACK, injector)
return nil
}

Expand All @@ -138,20 +138,27 @@ func shouldDiscardMsg(node *storage.Node) bool {
return false
}

func sendComplianceAck(ctx context.Context, node *storage.Node, ninv *storage.NodeInventory, injector common.MessageInjector) {
// replyCompliance uses injector to send a SensorACK and NodeInventoryACK to Compliance.
func replyCompliance(ctx context.Context, clusterID, nodeName string, t central.NodeInventoryACK_Action, injector common.MessageInjector) {
if injector == nil {
return
}
reply := replyCompliance(node.GetClusterId(), ninv.GetNodeName(), central.NodeInventoryACK_ACK)
if err := injector.InjectMessage(ctx, reply); err != nil {
log.Warnf("Failed sending node-inventory-ACK to Sensor for %s: %v", nodeDatastore.NodeString(node), err)
} else {
log.Debugf("Sent node-inventory-ACK for %s", nodeDatastore.NodeString(node))

// Always send SensorACK (new path).
if err := injector.InjectMessage(ctx, &central.MsgToSensor{
Msg: &central.MsgToSensor_SensorAck{
SensorAck: &central.SensorACK{
Action: convertLegacyActionToSensor(t),
MessageType: central.SensorACK_NODE_INVENTORY,
ResourceId: nodeName,
},
},
}); err != nil {
log.Warnf("Failed injecting SensorACK for node inventory (clusterID=%s, nodeName=%s): %v", clusterID, nodeName, err)
}
}

func replyCompliance(clusterID, nodeName string, t central.NodeInventoryACK_Action) *central.MsgToSensor {
return &central.MsgToSensor{
// Always send legacy NodeInventoryACK for backward compatibility.
if err := injector.InjectMessage(ctx, &central.MsgToSensor{
Msg: &central.MsgToSensor_NodeInventoryAck{
NodeInventoryAck: &central.NodeInventoryACK{
ClusterId: clusterID,
Expand All @@ -160,7 +167,18 @@ func replyCompliance(clusterID, nodeName string, t central.NodeInventoryACK_Acti
MessageType: central.NodeInventoryACK_NodeInventory,
},
},
}); err != nil {
log.Warnf("Failed injecting legacy NodeInventoryACK for node inventory (clusterID=%s, nodeName=%s): %v", clusterID, nodeName, err)
}

log.Debugf("Sent node-inventory ACKs for node %s in cluster %s", nodeName, clusterID)
}

func (p *pipelineImpl) OnFinish(_ string) {}

func convertLegacyActionToSensor(action central.NodeInventoryACK_Action) central.SensorACK_Action {
if action == central.NodeInventoryACK_ACK {
return central.SensorACK_ACK
}
return central.SensorACK_NACK
}
90 changes: 87 additions & 3 deletions central/sensor/service/pipeline/nodeinventory/pipeline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,24 +167,92 @@ func Test_pipelineImpl_Run(t *testing.T) {
if len(tt.wantInjectorContain) == 0 {
assert.Len(t, inj.getSentACKs(), 0)
} else {
protoassert.SlicesEqual(t, tt.wantInjectorContain, inj.getSentACKs())
protoassert.SlicesEqual(t, tt.wantInjectorContain, inj.getSentACKs(), "sent ACKs: %v", inj.getSentACKs())
}
}
})
}
}

func Test_pipelineImpl_Run_SendsSensorAndLegacyACKs(t *testing.T) {
ctrl := gomock.NewController(t)
clusterStore := clusterDatastoreMocks.NewMockDataStore(ctrl)
nodeDatastore := nodeDatastoreMocks.NewMockDataStore(ctrl)
riskManager := riskManagerMocks.NewMockManager(ctrl)
enricher := nodesEnricherMocks.NewMockNodeEnricher(ctrl)

clusterID := "cluster-1"
nodeName := "node-name"
node := storage.Node{
Id: "node-id",
ClusterId: clusterID,
}
msg := &central.MsgFromSensor{
Msg: &central.MsgFromSensor_Event{
Event: &central.SensorEvent{
Action: central.ResourceAction_CREATE_RESOURCE,
Resource: &central.SensorEvent_NodeInventory{
NodeInventory: &storage.NodeInventory{
NodeId: node.GetId(),
NodeName: nodeName,
},
},
},
},
}
injector := &recordingInjector{}

gomock.InOrder(
nodeDatastore.EXPECT().GetNode(gomock.Any(), gomock.Eq(node.GetId())).Times(1).Return(&node, true, nil),
enricher.EXPECT().EnrichNodeWithVulnerabilities(gomock.Any(), gomock.Any(), nil).Times(1).Return(nil),
riskManager.EXPECT().CalculateRiskAndUpsertNode(gomock.Any()).Times(1).Return(nil),
)

p := &pipelineImpl{
clusterStore: clusterStore,
nodeDatastore: nodeDatastore,
enricher: enricher,
riskManager: riskManager,
}

err := p.Run(context.Background(), clusterID, msg, injector)
assert.NoError(t, err)

protoassert.SlicesEqual(t, []*central.NodeInventoryACK{
{
ClusterId: clusterID,
NodeName: nodeName,
Action: central.NodeInventoryACK_ACK,
MessageType: central.NodeInventoryACK_NodeInventory,
},
}, injector.getSentACKs(), "legacy ACKs")

protoassert.SlicesEqual(t, []*central.SensorACK{
{
Action: central.SensorACK_ACK,
MessageType: central.SensorACK_NODE_INVENTORY,
ResourceId: nodeName,
},
}, injector.getSentSensorACKs(), "sensor ACKs")
}

var _ common.MessageInjector = (*recordingInjector)(nil)

type recordingInjector struct {
lock sync.Mutex
messages []*central.NodeInventoryACK
sensor []*central.SensorACK
}

func (r *recordingInjector) InjectMessage(_ concurrency.Waitable, msg *central.MsgToSensor) error {
r.lock.Lock()
defer r.lock.Unlock()
r.messages = append(r.messages, msg.GetNodeInventoryAck().CloneVT())
if ack := msg.GetNodeInventoryAck(); ack != nil {
r.messages = append(r.messages, ack.CloneVT())
}
if ack := msg.GetSensorAck(); ack != nil {
r.sensor = append(r.sensor, ack.CloneVT())
}
return nil
}

Expand All @@ -194,6 +262,22 @@ func (r *recordingInjector) getSentACKs() []*central.NodeInventoryACK {
r.lock.Lock()
defer r.lock.Unlock()
copied := make([]*central.NodeInventoryACK, 0, len(r.messages))
copied = append(copied, r.messages...)
for _, m := range r.messages {
if m != nil {
copied = append(copied, m)
}
}
return copied
}

func (r *recordingInjector) getSentSensorACKs() []*central.SensorACK {
r.lock.Lock()
defer r.lock.Unlock()
copied := make([]*central.SensorACK, 0, len(r.sensor))
for _, m := range r.sensor {
if m != nil {
copied = append(copied, m)
}
}
return copied
}
Loading