Skip to content

Commit be9871f

Browse files
[Osquerybeat] Add filtering functionality for osquery extension (#47396)
* osquerybeat filters * notice update * changelog fragment * PR Feedback * remove newly added dependency * remove dependency * fix dependency * PR feedback, reduce complexity * fix tests * linting and pr feedback
1 parent 8b682bb commit be9871f

File tree

5 files changed

+1140
-0
lines changed

5 files changed

+1140
-0
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# REQUIRED
2+
# Kind can be one of:
3+
# - breaking-change: a change to previously-documented behavior
4+
# - deprecation: functionality that is being removed in a later release
5+
# - bug-fix: fixes a problem in a previous version
6+
# - enhancement: extends functionality but does not break or fix existing behavior
7+
# - feature: new functionality
8+
# - known-issue: problems that we are aware of in a given version
9+
# - security: impacts on the security of a product or a user’s deployment.
10+
# - upgrade: important information for someone upgrading from a prior version
11+
# - other: does not fit into any of the other categories
12+
kind: enhancement
13+
14+
# REQUIRED for all kinds
15+
# Change summary; a 80ish characters long description of the change.
16+
summary: Add record filtering/scoping support to the osquery extension
17+
18+
# REQUIRED for breaking-change, deprecation, known-issue
19+
# Long description; in case the summary is not enough to describe the change
20+
# this field accommodate a description without length limits.
21+
# description:
22+
23+
# REQUIRED for breaking-change, deprecation, known-issue
24+
# impact:
25+
26+
# REQUIRED for breaking-change, deprecation, known-issue
27+
# action:
28+
29+
# REQUIRED for all kinds
30+
# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
31+
component: osquerybeat
32+
33+
# AUTOMATED
34+
# OPTIONAL to manually add other PR URLs
35+
# PR URL: A link the PR that added the changeset.
36+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
37+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
38+
# Please provide it if you are adding a fragment for a different PR.
39+
# pr: https://github.com/owner/repo/1234
40+
41+
# AUTOMATED
42+
# OPTIONAL to manually add other issue URLs
43+
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
44+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
45+
# issue: https://github.com/owner/repo/1234
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package filters
6+
7+
import (
8+
"reflect"
9+
"strconv"
10+
)
11+
12+
// resolveValue resolves the value of an input interface{} to a reflect.Value.
13+
func resolveValue(input any) (value reflect.Value, ok bool) {
14+
v := reflect.ValueOf(input)
15+
for v.IsValid() && (v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface) {
16+
if v.IsNil() {
17+
return reflect.Value{}, false
18+
}
19+
v = v.Elem()
20+
}
21+
if !v.IsValid() {
22+
return reflect.Value{}, false
23+
}
24+
return v, true
25+
}
26+
27+
// ToBool converts an input value to a boolean.
28+
func ToBool(input any) (result bool, ok bool) {
29+
v, ok := resolveValue(input)
30+
if !ok {
31+
return false, false
32+
}
33+
34+
switch v.Kind() {
35+
case reflect.Bool:
36+
return v.Bool(), true
37+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
38+
return v.Int() != 0, true
39+
case reflect.Float32, reflect.Float64:
40+
return v.Float() != 0, true
41+
case reflect.String:
42+
s := v.String()
43+
if b, err := strconv.ParseBool(s); err == nil {
44+
return b, true
45+
}
46+
return false, false
47+
default:
48+
return false, false
49+
}
50+
}
51+
52+
// ToInt64 converts an input value to an int64.
53+
func ToInt64(input any) (result int64, ok bool) {
54+
v, ok := resolveValue(input)
55+
if !ok {
56+
return 0, false
57+
}
58+
59+
switch v.Kind() {
60+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
61+
return v.Int(), true
62+
case reflect.Float32, reflect.Float64:
63+
return int64(v.Float()), true
64+
case reflect.String:
65+
s := v.String()
66+
if i, err := strconv.ParseInt(s, 10, 64); err == nil {
67+
return i, true
68+
}
69+
return 0, false
70+
default:
71+
return 0, false
72+
}
73+
}
74+
75+
// ToFloat64 converts an input value to a float64.
76+
func ToFloat64(input any) (result float64, ok bool) {
77+
v, ok := resolveValue(input)
78+
if !ok {
79+
return 0, false
80+
}
81+
82+
switch v.Kind() {
83+
case reflect.Float32, reflect.Float64:
84+
return v.Float(), true
85+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
86+
return float64(v.Int()), true
87+
case reflect.String:
88+
s := v.String()
89+
if f, err := strconv.ParseFloat(s, 64); err == nil {
90+
return f, true
91+
}
92+
return 0, false
93+
default:
94+
return 0, false
95+
}
96+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package filters
6+
7+
import "testing"
8+
9+
func TestToBool(t *testing.T) {
10+
type args struct {
11+
input any
12+
}
13+
tests := []struct {
14+
name string
15+
args args
16+
want bool
17+
}{
18+
{name: "bool_true", args: args{input: true}, want: true},
19+
{name: "bool_false", args: args{input: false}, want: false},
20+
{name: "bool_string_true", args: args{input: "true"}, want: true},
21+
{name: "bool_string_false", args: args{input: "false"}, want: false},
22+
{name: "bool_string_true_uppercase", args: args{input: "TRUE"}, want: true},
23+
{name: "bool_string_false_uppercase", args: args{input: "FALSE"}, want: false},
24+
{name: "bool_string_non_boolean", args: args{input: "not a boolean"}, want: false},
25+
{name: "bool_int_1", args: args{input: 1}, want: true},
26+
{name: "bool_int_0", args: args{input: 0}, want: false},
27+
{name: "bool_int_non_boolean", args: args{input: 100}, want: true},
28+
{name: "bool_float_1.0", args: args{input: 1.0}, want: true},
29+
{name: "bool_float_0.0", args: args{input: 0.0}, want: false},
30+
{name: "bool_float_non_boolean", args: args{input: 100.0}, want: true},
31+
}
32+
for _, tt := range tests {
33+
t.Run(tt.name, func(t *testing.T) {
34+
got, ok := ToBool(tt.args.input)
35+
if !ok {
36+
if tt.want == false {
37+
return
38+
}
39+
t.Errorf("%s: ToBool() failed to convert input to bool", tt.name)
40+
} else if got != tt.want {
41+
t.Errorf("%s: ToBool() = %v, want %v", tt.name, got, tt.want)
42+
}
43+
})
44+
}
45+
}
46+
47+
func TestToInt64(t *testing.T) {
48+
type args struct {
49+
input any
50+
}
51+
tests := []struct {
52+
name string
53+
args args
54+
want int64
55+
}{
56+
{name: "int64_1", args: args{input: 1}, want: 1},
57+
{name: "int64_0", args: args{input: 0}, want: 0},
58+
{name: "int64_non_integer", args: args{input: 100.0}, want: 100},
59+
{name: "int64_string_1", args: args{input: "1"}, want: 1},
60+
{name: "int64_string_0", args: args{input: "0"}, want: 0},
61+
{name: "int64_string_non_integer", args: args{input: "not an integer"}, want: 0},
62+
{name: "int64_float_1.0", args: args{input: 1.0}, want: 1},
63+
{name: "int64_float_0.0", args: args{input: 0.0}, want: 0},
64+
{name: "int64_float_non_integer", args: args{input: 100.0}, want: 100},
65+
}
66+
for _, tt := range tests {
67+
t.Run(tt.name, func(t *testing.T) {
68+
got, ok := ToInt64(tt.args.input)
69+
if !ok {
70+
if tt.want == 0 {
71+
return
72+
}
73+
t.Errorf("%s: ToInt64() failed to convert input to int64", tt.name)
74+
} else if got != tt.want {
75+
t.Errorf("%s: ToInt64() = %v, want %v", tt.name, got, tt.want)
76+
}
77+
})
78+
}
79+
}
80+
81+
func TestToFloat64(t *testing.T) {
82+
type args struct {
83+
input any
84+
}
85+
tests := []struct {
86+
name string
87+
args args
88+
want float64
89+
}{
90+
{name: "float64_1.0", args: args{input: 1.0}, want: 1.0},
91+
{name: "float64_0.0", args: args{input: 0.0}, want: 0.0},
92+
{name: "float64_non_float", args: args{input: 100}, want: 100.0},
93+
{name: "float64_string_1.0", args: args{input: "1.0"}, want: 1.0},
94+
{name: "float64_string_0.0", args: args{input: "0.0"}, want: 0.0},
95+
{name: "float64_string_non_float", args: args{input: "not a float"}, want: 0.0},
96+
{name: "float64_int_1", args: args{input: 1}, want: 1.0},
97+
{name: "float64_int_0", args: args{input: 0}, want: 0.0},
98+
{name: "float64_int_non_float", args: args{input: 100}, want: 100.0},
99+
}
100+
for _, tt := range tests {
101+
t.Run(tt.name, func(t *testing.T) {
102+
got, ok := ToFloat64(tt.args.input)
103+
if !ok {
104+
if tt.want == 0.0 {
105+
return
106+
}
107+
t.Errorf("%s: ToFloat64() failed to convert input to float64", tt.name)
108+
} else if got != tt.want {
109+
t.Errorf("%s: ToFloat64() = %v, want %v", tt.name, got, tt.want)
110+
}
111+
})
112+
}
113+
}

0 commit comments

Comments
 (0)