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

update dataflatten tables to parse raw_data in query #1732

Merged
merged 5 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
57 changes: 46 additions & 11 deletions ee/tables/dataflattentable/tables.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,12 @@ func AllTablePlugins(slogger *slog.Logger) []osquery.OsqueryPlugin {
}

func TablePlugin(slogger *slog.Logger, dataSourceType DataSourceType) osquery.OsqueryPlugin {
columns := Columns(table.TextColumn("path"))
columns := Columns(table.TextColumn("path"), table.TextColumn("raw_data"))

t := &Table{
tableName: dataSourceType.TableName(),
flattenFileFunc: dataSourceType.FlattenFileFunc(""),
tableName: dataSourceType.TableName(),
flattenFileFunc: dataSourceType.FlattenFileFunc(""),
flattenBytesFunc: dataSourceType.FlattenBytesFunc(""),
}

t.slogger = slogger.With("table", t.tableName)
Expand All @@ -137,8 +138,15 @@ func (t *Table) generate(ctx context.Context, queryContext table.QueryContext) (
var results []map[string]string

requestedPaths := tablehelpers.GetConstraints(queryContext, "path")
if len(requestedPaths) == 0 {
return results, fmt.Errorf("The %s table requires that you specify a single constraint for path", t.tableName)
requestedRawDatas := tablehelpers.GetConstraints(queryContext, "raw_data")

if len(requestedPaths)+len(requestedRawDatas) == 0 {
James-Pickett marked this conversation as resolved.
Show resolved Hide resolved
return results, fmt.Errorf("The %s table requires that you specify a single constraint for path or raw_data", t.tableName)
James-Pickett marked this conversation as resolved.
Show resolved Hide resolved
}

flattenOpts := []dataflatten.FlattenOpts{
dataflatten.WithSlogger(t.slogger),
dataflatten.WithNestedPlist(),
RebeccaMahany marked this conversation as resolved.
Show resolved Hide resolved
}

for _, requestedPath := range requestedPaths {
Expand All @@ -151,7 +159,7 @@ func (t *Table) generate(ctx context.Context, queryContext table.QueryContext) (

for _, filePath := range filePaths {
for _, dataQuery := range tablehelpers.GetConstraints(queryContext, "query", tablehelpers.WithDefaults("*")) {
subresults, err := t.generatePath(ctx, filePath, dataQuery)
subresults, err := t.generatePath(ctx, filePath, dataQuery, append(flattenOpts, dataflatten.WithQuery(strings.Split(dataQuery, "/")))...)
RebeccaMahany marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
t.slogger.Log(ctx, slog.LevelInfo,
"failed to get data for path",
Expand All @@ -165,16 +173,43 @@ func (t *Table) generate(ctx context.Context, queryContext table.QueryContext) (
}
}
}

for _, rawdata := range requestedRawDatas {
for _, dataQuery := range tablehelpers.GetConstraints(queryContext, "query", tablehelpers.WithDefaults("*")) {
subresults, err := t.generateRawData(ctx, rawdata, dataQuery, append(flattenOpts, dataflatten.WithQuery(strings.Split(dataQuery, "/")))...)
if err != nil {
t.slogger.Log(ctx, slog.LevelInfo,
"failed to get data for raw_data",
James-Pickett marked this conversation as resolved.
Show resolved Hide resolved
"err", err,
)
continue
}

results = append(results, subresults...)
}
}

return results, nil
}

func (t *Table) generatePath(ctx context.Context, filePath string, dataQuery string) ([]map[string]string, error) {
flattenOpts := []dataflatten.FlattenOpts{
dataflatten.WithSlogger(t.slogger),
dataflatten.WithNestedPlist(),
dataflatten.WithQuery(strings.Split(dataQuery, "/")),
func (t *Table) generateRawData(ctx context.Context, rawdata string, dataQuery string, flattenOpts ...dataflatten.FlattenOpts) ([]map[string]string, error) {
data, err := t.flattenBytesFunc([]byte(rawdata), flattenOpts...)
if err != nil {
t.slogger.Log(ctx, slog.LevelInfo,
"failure parsing raw data",
"err", err,
)
return nil, fmt.Errorf("parsing data: %w", err)
}

rowData := map[string]string{
"raw_data": rawdata,
James-Pickett marked this conversation as resolved.
Show resolved Hide resolved
}

return ToMap(data, dataQuery, rowData), nil
}

func (t *Table) generatePath(ctx context.Context, filePath string, dataQuery string, flattenOpts ...dataflatten.FlattenOpts) ([]map[string]string, error) {
data, err := t.flattenFileFunc(filePath, flattenOpts...)
if err != nil {
t.slogger.Log(ctx, slog.LevelInfo,
Expand Down
38 changes: 32 additions & 6 deletions ee/tables/dataflattentable/tables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dataflattentable
import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"sort"
Expand All @@ -24,9 +25,9 @@ func TestDataFlattenTablePlist_Animals(t *testing.T) {

// Test plist parsing both the json and xml forms
testTables := map[string]Table{
"plist": {slogger: slogger, flattenFileFunc: dataflatten.PlistFile},
"xml": {slogger: slogger, flattenFileFunc: dataflatten.PlistFile},
"json": {slogger: slogger, flattenFileFunc: dataflatten.JsonFile},
"plist": {slogger: slogger, flattenFileFunc: dataflatten.PlistFile, flattenBytesFunc: dataflatten.Plist},
"xml": {slogger: slogger, flattenFileFunc: dataflatten.PlistFile, flattenBytesFunc: dataflatten.Plist},
"json": {slogger: slogger, flattenFileFunc: dataflatten.JsonFile, flattenBytesFunc: dataflatten.Json},
}

var tests = []struct {
Expand Down Expand Up @@ -57,13 +58,14 @@ func TestDataFlattenTablePlist_Animals(t *testing.T) {
for _, tt := range tests {
for dataType, tableFunc := range testTables {
testFile := filepath.Join("testdata", "animals."+dataType)
mockQC := tablehelpers.MockQueryContext(map[string][]string{

// test file path
mockPathQC := tablehelpers.MockQueryContext(map[string][]string{
"path": {testFile},
"query": tt.queries,
})

rows, err := tableFunc.generate(context.TODO(), mockQC)

rows, err := tableFunc.generate(context.TODO(), mockPathQC)
require.NoError(t, err)

// delete the path and query keys, so we don't need to enumerate them in the test case
Expand All @@ -77,6 +79,30 @@ func TestDataFlattenTablePlist_Animals(t *testing.T) {
sort.SliceStable(rows, func(i, j int) bool { return rows[i]["fullkey"] < rows[j]["fullkey"] })

require.EqualValues(t, tt.expected, rows, "table type %s test", dataType)

// test bytes path
raw_data, err := os.ReadFile(testFile)
require.NoError(t, err)

mockBytesQC := tablehelpers.MockQueryContext(map[string][]string{
"raw_data": {string(raw_data)},
"query": tt.queries,
})

rows, err = tableFunc.generate(context.TODO(), mockBytesQC)
require.NoError(t, err)

// delete the query keys, so we don't need to enumerate them in the test case
for _, row := range rows {
delete(row, "query")
delete(row, "raw_data")
}

// Despite being an array. data is returned unordered. Sort it.
sort.SliceStable(tt.expected, func(i, j int) bool { return tt.expected[i]["fullkey"] < tt.expected[j]["fullkey"] })
sort.SliceStable(rows, func(i, j int) bool { return rows[i]["fullkey"] < rows[j]["fullkey"] })

require.EqualValues(t, tt.expected, rows, "table type %s test", dataType)
}
}

Expand Down
8 changes: 4 additions & 4 deletions ee/tables/dataflattentable/testdata/animals.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@
"users": [
{
"favorites": [
"ants"
"ants"
],
"uuid": "abc123",
"name": "Alex Aardvark",
"id": 1
},
{
"favorites": [
"mice",
"birds"
"mice",
"birds"
],
"uuid": "def456",
"name": "Bailey Bobcat",
"id": 2
},
{
"favorites": [
"seeds"
"seeds"
],
"uuid": "ghi789",
"name": "Cam Chipmunk",
Expand Down
Loading