Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let network processor handle multiple IPs #41918

Merged
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ https://github.com/elastic/beats/compare/v8.8.1\...main[Check the HEAD diff]
- Remove unnecessary debug logs during idle connection teardown {issue}40824[40824]
- Fix incorrect cloud provider identification in add_cloud_metadata processor using provider priority mechanism {pull}41636[41636]
- Prevent panic if libbeat processors are loaded more than once. {issue}41475[41475] {pull}41857[51857]
- Allow network condition to handle multiple IPs {pull}41918[41918]
fearful-symmetry marked this conversation as resolved.
Show resolved Hide resolved

*Auditbeat*

Expand Down
23 changes: 23 additions & 0 deletions libbeat/conditions/conditions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,29 @@ var httpResponseTestEvent = &beat.Event{
},
}

var httpResponseEventIPList = &beat.Event{
Timestamp: time.Now(),
Fields: mapstr.M{
"@timestamp": "2024-12-05T09:51:23.642Z",
"ecs": mapstr.M{
"version": "8.11.0",
},
"host": mapstr.M{
"hostname": "testhost",
"os": mapstr.M{
"type": "linux",
"family": "debian",
"version": "11 (bullseye)",
"platform": "debian",
},
"ip": []string{
"10.1.0.55",
"fe80::4001:aff:fe9a:55",
},
},
},
}

func testConfig(t *testing.T, expected bool, event *beat.Event, config *Config) {
t.Helper()
logp.TestingSetup()
Expand Down
64 changes: 39 additions & 25 deletions libbeat/conditions/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,31 +94,31 @@ func (m multiNetworkMatcher) String() string {
return strings.Join(names, " OR ")
}

func makeMatcher(network string) (networkMatcher, error) {
m := singleNetworkMatcher{name: network, netContainsFunc: namedNetworks[network]}
if m.netContainsFunc == nil {
subnet, err := parseCIDR(network)
if err != nil {
return nil, err
}
m.netContainsFunc = subnet.Contains
}
return m, nil
}

func invalidTypeError(field string, value interface{}) error {
return fmt.Errorf("network condition attempted to set "+
"'%v' -> '%v' and encountered unexpected type '%T', only "+
"strings or []strings are allowed", field, value, value)
}

// NewNetworkCondition builds a new Network using the given configuration.
func NewNetworkCondition(fields map[string]interface{}) (*Network, error) {
cond := &Network{
fields: map[string]networkMatcher{},
log: logp.NewLogger(logName),
}

makeMatcher := func(network string) (networkMatcher, error) {
m := singleNetworkMatcher{name: network, netContainsFunc: namedNetworks[network]}
if m.netContainsFunc == nil {
subnet, err := parseCIDR(network)
if err != nil {
return nil, err
}
m.netContainsFunc = subnet.Contains
}
return m, nil
}

invalidTypeError := func(field string, value interface{}) error {
return fmt.Errorf("network condition attempted to set "+
"'%v' -> '%v' and encountered unexpected type '%T', only "+
"strings or []strings are allowed", field, value, value)
}

for field, value := range mapstr.M(fields).Flatten() {
switch v := value.(type) {
case string:
Expand Down Expand Up @@ -157,15 +157,23 @@ func (c *Network) Check(event ValuesMap) bool {
return false
}

ip := extractIP(value)
if ip == nil {
ipList := extractIP(value)
if len(ipList) == 0 {
c.log.Debugf("Invalid IP address in field=%v for network condition", field)
return false
}

if !network.Contains(ip) {
// match on an "any" basis when we find multiple IPs in the event;
// if the network matcher returns true for any seen IP, consider it a match
matches := 0
for _, ip := range ipList {
if network.Contains(ip) {
matches += 1
fearful-symmetry marked this conversation as resolved.
Show resolved Hide resolved
}
}
if matches == 0 {
return false
}

}

return true
Expand Down Expand Up @@ -202,12 +210,18 @@ func parseCIDR(value string) (*net.IPNet, error) {

// extractIP return an IP address if unk is an IP address string or a net.IP.
// Otherwise it returns nil.
func extractIP(unk interface{}) net.IP {
func extractIP(unk interface{}) []net.IP {
switch v := unk.(type) {
case string:
return net.ParseIP(v)
return []net.IP{net.ParseIP(v)}
case net.IP:
fearful-symmetry marked this conversation as resolved.
Show resolved Hide resolved
return v
return []net.IP{v}
case []string:
parsed := make([]net.IP, len(v))
for i, rawIP := range v {
parsed[i] = net.ParseIP(rawIP)
}
return parsed
default:
return nil
}
Expand Down
52 changes: 52 additions & 0 deletions libbeat/conditions/network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,26 @@ network:

testYAMLConfig(t, true, evt, yaml)
})

t.Run("IP list", func(t *testing.T) {
const yaml = `
network:
ip:
client: [loopback]
server: [loopback]
host: 10.10.0.0/8
`

evt := &beat.Event{Fields: mapstr.M{
"ip": mapstr.M{
"client": "127.0.0.1",
"server": "127.0.0.1",
"host": []string{"10.10.0.83", "fe80::4001:aff:fe9a:53"},
},
}}

testYAMLConfig(t, true, evt, yaml)
})
}

func TestNetworkCreate(t *testing.T) {
Expand Down Expand Up @@ -166,6 +186,22 @@ func TestNetworkCheck(t *testing.T) {
})
})

t.Run("multiple IPs field single match", func(t *testing.T) {
testConfig(t, true, httpResponseEventIPList, &Config{
Network: map[string]interface{}{
"host.ip": "10.1.0.0/24",
},
})
})

t.Run("multiple IPs field negative match", func(t *testing.T) {
testConfig(t, false, httpResponseEventIPList, &Config{
Network: map[string]interface{}{
"host.ip": "127.0.0.0/24",
},
})
})

// Multiple conditions are treated as an implicit AND.
t.Run("multiple fields negative match", func(t *testing.T) {
testConfig(t, false, httpResponseTestEvent, &Config{
Expand All @@ -191,6 +227,22 @@ func TestNetworkCheck(t *testing.T) {
},
})
})

t.Run("multiple values multiple IPs match", func(t *testing.T) {
testConfig(t, true, httpResponseEventIPList, &Config{
Network: map[string]interface{}{
"host.ip": []interface{}{"10.1.0.0/24", "127.0.0.0/24"},
},
})
})

t.Run("multiple values multiple IPs no match", func(t *testing.T) {
testConfig(t, false, httpResponseEventIPList, &Config{
Network: map[string]interface{}{
"host.ip": []interface{}{"12.1.0.0/24", "127.0.0.0/24"},
},
})
})
}

func TestNetworkPrivate(t *testing.T) {
Expand Down
Loading