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
20 changes: 9 additions & 11 deletions sensor/common/networkflow/manager/enrichment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,7 @@ func TestEnrichContainerEndpoint_EdgeCases(t *testing.T) {
expectedResultNG EnrichmentResult
expectedResultPLOP EnrichmentResult
expectedReasonNG EnrichmentReasonEp
prePopulateData func(*testing.T,
map[indicator.ContainerEndpoint]timestamp.MicroTS,
map[indicator.ProcessListening]timestamp.MicroTS)
prePopulateData func(*testing.T, map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp)
}{
"Fresh endpoint with no process info should yield result EnrichmentResultSuccess for Network Graph and EnrichmentResultInvalidInput for PLOP": {
setupEndpoint: func() (*containerEndpoint, *connStatus) {
Expand Down Expand Up @@ -326,15 +324,13 @@ func TestEnrichContainerEndpoint_EdgeCases(t *testing.T) {
expectedResultNG: EnrichmentResultSuccess,
expectedResultPLOP: EnrichmentResultSuccess,
expectedReasonNG: EnrichmentReasonEpDuplicate,
prePopulateData: func(t *testing.T, enrichedEndpoints map[indicator.ContainerEndpoint]timestamp.MicroTS,
processesListening map[indicator.ProcessListening]timestamp.MicroTS) {
prePopulateData: func(t *testing.T, data map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp) {
// Pre-populate with newer timestamp to trigger duplicate detection
endpointIndicator := indicator.ContainerEndpoint{
Entity: networkgraph.EntityForDeployment("test-deployment"),
Port: 80,
Protocol: net.TCP.ToProtobuf(),
}
enrichedEndpoints[endpointIndicator] = timestamp.Now() // newer timestamp
processIndicator := indicator.ProcessListening{
ContainerName: "test-container",
DeploymentID: "test-deployment",
Expand All @@ -349,7 +345,10 @@ func TestEnrichContainerEndpoint_EdgeCases(t *testing.T) {
PodUID: "test-pod-uid",
Namespace: "test-namespace",
}
processesListening[processIndicator] = timestamp.Now() // newer timestamp
data[endpointIndicator] = &indicator.ProcessListeningWithTimestamp{
ProcessListening: &processIndicator,
LastSeen: timestamp.Now(), // a newer timestamp
}
},
},
}
Expand All @@ -371,18 +370,17 @@ func TestEnrichContainerEndpoint_EdgeCases(t *testing.T) {

// Setup test data
ep, status := tt.setupEndpoint()
enrichedEndpoints := make(map[indicator.ContainerEndpoint]timestamp.MicroTS)
processesListening := make(map[indicator.ProcessListening]timestamp.MicroTS)
enrichedEndpointsProcesses := make(map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp)

// Pre-populate data if validation function needs it
if tt.prePopulateData != nil {
tt.prePopulateData(t, enrichedEndpoints, processesListening)
tt.prePopulateData(t, enrichedEndpointsProcesses)
}

// Execute the enrichment
now := timestamp.Now()
resultNG, resultPLOP, reasonNG, _ := m.enrichContainerEndpoint(
now, ep, status, enrichedEndpoints, processesListening, now)
now, ep, status, enrichedEndpointsProcesses, now)

// Assert results
assert.Equal(t, tt.expectedResultNG, resultNG, "Network graph enrichment result mismatch")
Expand Down
5 changes: 5 additions & 0 deletions sensor/common/networkflow/manager/indicator/indicator.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ type ProcessListening struct {
Port uint16 // 2 bytes
}

type ProcessListeningWithTimestamp struct {
ProcessListening *ProcessListening
LastSeen timestamp.MicroTS
}

func (i *ProcessListening) ToProto(ts timestamp.MicroTS) *storage.ProcessListeningOnPortFromSensor {
proto := &storage.ProcessListeningOnPortFromSensor{
Port: uint32(i.Port),
Expand Down
60 changes: 39 additions & 21 deletions sensor/common/networkflow/manager/manager_enrich_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func (m *networkFlowManager) executeEndpointAction(
ep *containerEndpoint,
status *connStatus,
hostConns *hostConnections,
enrichedEndpoints map[indicator.ContainerEndpoint]timestamp.MicroTS,
enrichedEndpointsProcesses map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp,
now timestamp.MicroTS,
) {
switch action {
Expand All @@ -30,7 +30,7 @@ func (m *networkFlowManager) executeEndpointAction(
flowMetrics.HostConnectionsOperations.WithLabelValues("remove", "endpoints").Inc()
case PostEnrichmentActionMarkInactive:
concurrency.WithLock(&m.activeEndpointsMutex, func() {
if ok := deactivateEndpointNoLock(ep, m.activeEndpoints, enrichedEndpoints, now); !ok {
if ok := deactivateEndpointNoLock(ep, m.activeEndpoints, enrichedEndpointsProcesses, now); !ok {
log.Debugf("Cannot mark endpoint as inactive: endpoint is rotten")
}
})
Expand All @@ -48,15 +48,14 @@ func (m *networkFlowManager) executeEndpointAction(
}

func (m *networkFlowManager) enrichHostContainerEndpoints(now timestamp.MicroTS, hostConns *hostConnections,
enrichedEndpoints map[indicator.ContainerEndpoint]timestamp.MicroTS,
processesListening map[indicator.ProcessListening]timestamp.MicroTS) {
enrichedEndpointsProcesses map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp) {
concurrency.WithLock(&hostConns.mutex, func() {
flowMetrics.HostProcessesEvents.WithLabelValues("add").Add(float64(len(hostConns.endpoints)))
flowMetrics.HostConnectionsOperations.WithLabelValues("enrich", "endpoints").Add(float64(len(hostConns.endpoints)))
for ep, status := range hostConns.endpoints {
resultNG, resultPLOP, reasonNG, reasonPLOP := m.enrichContainerEndpoint(now, &ep, status, enrichedEndpoints, processesListening, now)
resultNG, resultPLOP, reasonNG, reasonPLOP := m.enrichContainerEndpoint(now, &ep, status, enrichedEndpointsProcesses, now)
action := m.handleEndpointEnrichmentResult(resultNG, resultPLOP, reasonNG, reasonPLOP, &ep)
m.executeEndpointAction(action, &ep, status, hostConns, enrichedEndpoints, now)
m.executeEndpointAction(action, &ep, status, hostConns, enrichedEndpointsProcesses, now)
updateEndpointMetric(now, action, resultNG, resultPLOP, reasonNG, reasonPLOP, status)
}
})
Expand All @@ -73,8 +72,7 @@ func (m *networkFlowManager) enrichContainerEndpoint(
now timestamp.MicroTS,
ep *containerEndpoint,
status *connStatus,
enrichedEndpoints map[indicator.ContainerEndpoint]timestamp.MicroTS,
processesListening map[indicator.ProcessListening]timestamp.MicroTS,
enrichedEndpointsProcesses map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp,
lastUpdate timestamp.MicroTS,
) (resultNG, resultPLOP EnrichmentResult, reasonNG, reasonPLOP EnrichmentReasonEp) {
isFresh := status.isFresh(now)
Expand Down Expand Up @@ -102,11 +100,18 @@ func (m *networkFlowManager) enrichContainerEndpoint(
}

container := containerResult.Container
processIndicator := &indicator.ProcessListeningWithTimestamp{
ProcessListening: nil,
LastSeen: status.lastSeen,
}

// SECTION: ENRICHMENT OF PROCESSES LISTENING ON PORTS
if env.ProcessesListeningOnPort.BooleanSetting() {
status.enrichmentConsumption.consumedPLOP = true
resultPLOP, reasonPLOP = m.enrichPLOP(ep, container, processesListening, status.lastSeen)
var pi *indicator.ProcessListening
pi, resultPLOP, reasonPLOP = m.enrichPLOP(ep, container)
// Always store processIndicator, even if nil.
processIndicator.ProcessListening = pi
} else {
resultPLOP = EnrichmentResultSkipped
reasonPLOP = EnrichmentReasonEpFeaturePlopDisabled
Expand All @@ -122,11 +127,11 @@ func (m *networkFlowManager) enrichContainerEndpoint(

// Multiple endpoints from a collector can result in a single enriched endpoint,
// hence update the timestamp only if we have a more recent endpoint than the one we have already enriched.
if oldTS, found := enrichedEndpoints[ind]; found && oldTS >= status.lastSeen {
if oldValue, found := enrichedEndpointsProcesses[ind]; found && oldValue.LastSeen >= status.lastSeen {
return EnrichmentResultSuccess, resultPLOP, EnrichmentReasonEpDuplicate, reasonPLOP
}

enrichedEndpoints[ind] = status.lastSeen
enrichedEndpointsProcesses[ind] = processIndicator
if !features.SensorCapturesIntermediateEvents.Enabled() {
return EnrichmentResultSuccess, resultPLOP, EnrichmentReasonEpFeatureDisabled, reasonPLOP
}
Expand All @@ -146,12 +151,11 @@ func (m *networkFlowManager) enrichContainerEndpoint(
func (m *networkFlowManager) enrichPLOP(
ep *containerEndpoint,
container clusterentities.ContainerMetadata,
processesListening map[indicator.ProcessListening]timestamp.MicroTS,
lastSeen timestamp.MicroTS) (resultPLOP EnrichmentResult, reasonPLOP EnrichmentReasonEp) {
) (ind *indicator.ProcessListening, resultPLOP EnrichmentResult, reasonPLOP EnrichmentReasonEp) {
if ep.processKey == emptyProcessInfo {
return EnrichmentResultInvalidInput, EnrichmentReasonEpEmptyProcessInfo
return nil, EnrichmentResultInvalidInput, EnrichmentReasonEpEmptyProcessInfo
}
indicatorPLOP := indicator.ProcessListening{
return &indicator.ProcessListening{
PodID: container.PodID,
ContainerName: container.ContainerName,
DeploymentID: container.DeploymentID,
Expand All @@ -160,28 +164,42 @@ func (m *networkFlowManager) enrichPLOP(
Protocol: ep.endpoint.L4Proto.ToProtobuf(),
PodUID: container.PodUID,
Namespace: container.Namespace,
}
processesListening[indicatorPLOP] = lastSeen
return EnrichmentResultSuccess, EnrichmentReasonEp("")
}, EnrichmentResultSuccess, EnrichmentReasonEp("")
}

// deactivateEndpointNoLock removes endpoint from active endpoints and sets the timestamp in enrichedEndpoints.
// It returns error when endpoint is not found in active endpoints.
func deactivateEndpointNoLock(ep *containerEndpoint,
activeEndpoints map[containerEndpoint]*containerEndpointIndicatorWithAge,
enrichedEndpoints map[indicator.ContainerEndpoint]timestamp.MicroTS,
enrichedEndpointsProcesses map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp,
now timestamp.MicroTS) bool {
activeEp, found := activeEndpoints[*ep]
if !found {
return false // endpoint rotten
}
// Active endpoint found for historical container => removing from active endpoints and setting last-seen.
enrichedEndpoints[activeEp.ContainerEndpoint] = now
// Active endpoint found for historical container =>
// (1) setting last-seen - even if not present in enrichedEndpointsProcesses and
// (2) removing from active endpoints.
setLastSeenOrAdd(enrichedEndpointsProcesses, activeEp.ContainerEndpoint, now)
delete(activeEndpoints, *ep)
flowMetrics.SetActiveEndpointsTotalGauge(len(activeEndpoints))
return true
}

// setLastSeenOrAdd checks the map `m` for presence of `key`.
// If `key` is found, it changes the `LastSeen` to `ts` (close active endpoint, keep process as it was).
// If `key` is not found, it adds it with empty `ProcessListening` and `LastSeen` set to `ts` (artificially close active endpoint).
func setLastSeenOrAdd(m map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp, key indicator.ContainerEndpoint, ts timestamp.MicroTS) {
if value := m[key]; value == nil {
m[key] = &indicator.ProcessListeningWithTimestamp{
ProcessListening: nil,
LastSeen: ts,
}
} else {
m[key].LastSeen = ts
}
}

// handleConnectionEnrichmentResult prints user-readable logs explaining the result of the enrichments and returns an action
// to execute after the enrichment.
func (m *networkFlowManager) handleEndpointEnrichmentResult(
Expand Down
30 changes: 13 additions & 17 deletions sensor/common/networkflow/manager/manager_enrich_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,7 @@ func (s *TestNetworkFlowManagerEnrichmentTestSuite) TestEnrichContainerEndpoint(
lastSeen timestamp.MicroTS
plopFeatEnabled bool
offlineEnrichmentFeatEnabled bool
enrichedEndpoints map[indicator.ContainerEndpoint]timestamp.MicroTS
enrichedProcesses map[indicator.ProcessListening]timestamp.MicroTS
enrichedEndpointsProcesses map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp
expected struct {
resultNG EnrichmentResult
resultPLOP EnrichmentResult
Expand Down Expand Up @@ -313,8 +312,7 @@ func (s *TestNetworkFlowManagerEnrichmentTestSuite) TestEnrichContainerEndpoint(
epInActiveEndpoints: nil,
plopFeatEnabled: true,
offlineEnrichmentFeatEnabled: true,
enrichedEndpoints: make(map[indicator.ContainerEndpoint]timestamp.MicroTS),
enrichedProcesses: make(map[indicator.ProcessListening]timestamp.MicroTS),
enrichedEndpointsProcesses: make(map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp),
expected: struct {
resultNG EnrichmentResult
resultPLOP EnrichmentResult
Expand All @@ -338,8 +336,7 @@ func (s *TestNetworkFlowManagerEnrichmentTestSuite) TestEnrichContainerEndpoint(
epInActiveEndpoints: nil,
plopFeatEnabled: false,
offlineEnrichmentFeatEnabled: true,
enrichedEndpoints: make(map[indicator.ContainerEndpoint]timestamp.MicroTS),
enrichedProcesses: make(map[indicator.ProcessListening]timestamp.MicroTS),
enrichedEndpointsProcesses: make(map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp),
expected: struct {
resultNG EnrichmentResult
resultPLOP EnrichmentResult
Expand All @@ -363,8 +360,7 @@ func (s *TestNetworkFlowManagerEnrichmentTestSuite) TestEnrichContainerEndpoint(
epInActiveEndpoints: nil,
plopFeatEnabled: true,
offlineEnrichmentFeatEnabled: true,
enrichedEndpoints: make(map[indicator.ContainerEndpoint]timestamp.MicroTS),
enrichedProcesses: make(map[indicator.ProcessListening]timestamp.MicroTS),
enrichedEndpointsProcesses: make(map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp),
expected: struct {
resultNG EnrichmentResult
resultPLOP EnrichmentResult
Expand Down Expand Up @@ -397,8 +393,7 @@ func (s *TestNetworkFlowManagerEnrichmentTestSuite) TestEnrichContainerEndpoint(
plopFeatEnabled: true,
offlineEnrichmentFeatEnabled: true,
lastSeen: timestamp.InfiniteFuture, // required for SuccessActive result
enrichedEndpoints: make(map[indicator.ContainerEndpoint]timestamp.MicroTS),
enrichedProcesses: make(map[indicator.ProcessListening]timestamp.MicroTS),
enrichedEndpointsProcesses: make(map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp),
expected: struct {
resultNG EnrichmentResult
resultPLOP EnrichmentResult
Expand Down Expand Up @@ -431,10 +426,12 @@ func (s *TestNetworkFlowManagerEnrichmentTestSuite) TestEnrichContainerEndpoint(
plopFeatEnabled: true,
offlineEnrichmentFeatEnabled: true,
lastSeen: now - 10, // message being 10units old should trigger `EnrichmentReasonEpDuplicate`
enrichedEndpoints: map[indicator.ContainerEndpoint]timestamp.MicroTS{
containerEndpointIndicator1: now - 1, // existing state in memory must "be younger" than lastSeen
enrichedEndpointsProcesses: map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp{
containerEndpointIndicator1: {
ProcessListening: nil,
LastSeen: now - 1, // existing state in memory must "be younger" than lastSeen
},
},
enrichedProcesses: make(map[indicator.ProcessListening]timestamp.MicroTS),
expected: struct {
resultNG EnrichmentResult
resultPLOP EnrichmentResult
Expand Down Expand Up @@ -467,8 +464,7 @@ func (s *TestNetworkFlowManagerEnrichmentTestSuite) TestEnrichContainerEndpoint(
plopFeatEnabled: true,
offlineEnrichmentFeatEnabled: false,
lastSeen: timestamp.InfiniteFuture,
enrichedEndpoints: make(map[indicator.ContainerEndpoint]timestamp.MicroTS),
enrichedProcesses: make(map[indicator.ProcessListening]timestamp.MicroTS),
enrichedEndpointsProcesses: make(map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp),
expected: struct {
resultNG EnrichmentResult
resultPLOP EnrichmentResult
Expand Down Expand Up @@ -524,12 +520,12 @@ func (s *TestNetworkFlowManagerEnrichmentTestSuite) TestEnrichContainerEndpoint(
}

// Execute test
resultNG, resultPLOP, reasonNG, reasonPLOP := m.enrichContainerEndpoint(now, ep.endpoint, ep.status, tc.enrichedEndpoints, tc.enrichedProcesses, now)
resultNG, resultPLOP, reasonNG, reasonPLOP := m.enrichContainerEndpoint(now, ep.endpoint, ep.status, tc.enrichedEndpointsProcesses, now)
action := m.handleEndpointEnrichmentResult(resultNG, resultPLOP, reasonNG, reasonPLOP, ep.endpoint)

// Assert using helper
assertions := newEnrichmentAssertion(s.T())
assertions.assertEndpointEnrichment(resultNG, resultPLOP, reasonNG, reasonPLOP, action, tc.enrichedEndpoints, tc.expected)
assertions.assertEndpointEnrichment(resultNG, resultPLOP, reasonNG, reasonPLOP, action, tc.enrichedEndpointsProcesses, tc.expected)
})
}
}
20 changes: 9 additions & 11 deletions sensor/common/networkflow/manager/manager_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,12 +405,11 @@ func (m *networkFlowManager) enrichAndSend() {
// and updates them by adding data from different sources (enriching).
// It updates m.activeEndpoints and m.activeConnections if EE is open (i.e., lastSeen is set to null by Collector).
// Enriched-entities for which the enrichment should be retried are not returned from currentEnrichedConnsAndEndpoints!
currentConns, currentEndpoints, currentProcesses := m.currentEnrichedConnsAndEndpoints()
currentConns, currentEndpointsProcesses := m.currentEnrichedConnsAndEndpoints()

// The new changes are sent to Central using the update computer implementation.
updatedConns := m.updateComputer.ComputeUpdatedConns(currentConns)
updatedEndpoints := m.updateComputer.ComputeUpdatedEndpoints(currentEndpoints)
updatedProcesses := m.updateComputer.ComputeUpdatedProcesses(currentProcesses)
updatedEndpoints, updatedProcesses := m.updateComputer.ComputeUpdatedEndpointsAndProcesses(currentEndpointsProcesses)

flowMetrics.NumUpdatesSentToCentralCounter.WithLabelValues("connections").Add(float64(len(updatedConns)))
flowMetrics.NumUpdatesSentToCentralCounter.WithLabelValues("endpoints").Add(float64(len(updatedEndpoints)))
Expand All @@ -426,14 +425,15 @@ func (m *networkFlowManager) enrichAndSend() {
if len(updatedConns)+len(updatedEndpoints) > 0 {
if sent := m.sendConnsEps(updatedConns, updatedEndpoints); sent {
// Inform the updateComputer that sending has succeeded
m.updateComputer.OnSuccessfulSend(currentConns, currentEndpoints, nil)
m.updateComputer.OnSuccessfulSendConnections(currentConns)
m.updateComputer.OnSuccessfulSendEndpoints(currentEndpointsProcesses)
}
}

if env.ProcessesListeningOnPort.BooleanSetting() && len(updatedProcesses) > 0 {
if sent := m.sendProcesses(updatedProcesses); sent {
// Inform the updateComputer that sending has succeeded
m.updateComputer.OnSuccessfulSend(nil, nil, currentProcesses)
m.updateComputer.OnSuccessfulSendProcesses(currentEndpointsProcesses)
}
}
metrics.SetNetworkFlowBufferSizeGauge(len(m.sensorUpdates))
Expand Down Expand Up @@ -479,20 +479,18 @@ func (m *networkFlowManager) sendProcesses(processes []*storage.ProcessListening

func (m *networkFlowManager) currentEnrichedConnsAndEndpoints() (
enrichedConnections map[indicator.NetworkConn]timestamp.MicroTS,
enrichedEndpoints map[indicator.ContainerEndpoint]timestamp.MicroTS,
enrichedProcesses map[indicator.ProcessListening]timestamp.MicroTS,
enrichedEndpointsProcesses map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp,
) {
now := timestamp.Now()
allHostConns := m.getAllHostConnections()

enrichedConnections = make(map[indicator.NetworkConn]timestamp.MicroTS)
enrichedEndpoints = make(map[indicator.ContainerEndpoint]timestamp.MicroTS)
enrichedProcesses = make(map[indicator.ProcessListening]timestamp.MicroTS)
enrichedEndpointsProcesses = make(map[indicator.ContainerEndpoint]*indicator.ProcessListeningWithTimestamp)
for _, hostConns := range allHostConns {
m.enrichHostConnections(now, hostConns, enrichedConnections)
m.enrichHostContainerEndpoints(now, hostConns, enrichedEndpoints, enrichedProcesses)
m.enrichHostContainerEndpoints(now, hostConns, enrichedEndpointsProcesses)
}
return enrichedConnections, enrichedEndpoints, enrichedProcesses
return enrichedConnections, enrichedEndpointsProcesses
}

func (m *networkFlowManager) getAllHostConnections() []*hostConnections {
Expand Down
Loading
Loading