fleet/orbit/pkg/table/dataflattentable/tables.go
2023-11-01 20:11:35 -06:00

137 lines
3.5 KiB
Go

// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflattentable
import (
"context"
"fmt"
"path/filepath"
"strings"
"github.com/fleetdm/fleet/v4/orbit/pkg/dataflatten"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/tablehelpers"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/osquery/osquery-go"
"github.com/osquery/osquery-go/plugin/table"
)
type DataSourceType int
const (
PlistType DataSourceType = iota + 1
JsonType
JsonlType
ExecType
XmlType
IniType
KeyValueType
)
type Table struct {
logger log.Logger
tableName string
flattenFileFunc func(string, ...dataflatten.FlattenOpts) ([]dataflatten.Row, error)
flattenBytesFunc func([]byte, ...dataflatten.FlattenOpts) ([]dataflatten.Row, error)
execArgs []string
binDirs []string
keyValueSeparator string
}
// AllTablePlugins is a helper to return all the expected flattening tables.
func AllTablePlugins(logger log.Logger) []osquery.OsqueryPlugin {
return []osquery.OsqueryPlugin{
TablePlugin(logger, JsonType),
TablePlugin(logger, XmlType),
TablePlugin(logger, IniType),
TablePlugin(logger, PlistType),
TablePlugin(logger, JsonlType),
}
}
func TablePlugin(logger log.Logger, dataSourceType DataSourceType) osquery.OsqueryPlugin {
columns := Columns(table.TextColumn("path"))
t := &Table{
logger: logger,
}
switch dataSourceType {
case PlistType:
t.flattenFileFunc = dataflatten.PlistFile
t.tableName = "parse_plist"
case JsonType:
t.flattenFileFunc = dataflatten.JsonFile
t.tableName = "parse_json"
case JsonlType:
t.flattenFileFunc = dataflatten.JsonlFile
t.tableName = "parse_jsonl"
case XmlType:
t.flattenFileFunc = dataflatten.XmlFile
t.tableName = "parse_xml"
case IniType:
t.flattenFileFunc = dataflatten.IniFile
t.tableName = "parse_ini"
default:
panic("Unknown data source type")
}
return table.NewPlugin(t.tableName, columns, t.generate)
}
func (t *Table) generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
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)
}
for _, requestedPath := range requestedPaths {
// We take globs in via the sql %, but glob needs *. So convert.
filePaths, err := filepath.Glob(strings.ReplaceAll(requestedPath, `%`, `*`))
if err != nil {
return results, fmt.Errorf("bad glob: %w", err)
}
for _, filePath := range filePaths {
for _, dataQuery := range tablehelpers.GetConstraints(queryContext, "query", tablehelpers.WithDefaults("*")) {
subresults, err := t.generatePath(filePath, dataQuery)
if err != nil {
level.Info(t.logger).Log(
"msg", "failed to get data for path",
"path", filePath,
"err", err,
)
continue
}
results = append(results, subresults...)
}
}
}
return results, nil
}
func (t *Table) generatePath(filePath string, dataQuery string) ([]map[string]string, error) {
flattenOpts := []dataflatten.FlattenOpts{
dataflatten.WithLogger(t.logger),
dataflatten.WithNestedPlist(),
dataflatten.WithQuery(strings.Split(dataQuery, "/")),
}
data, err := t.flattenFileFunc(filePath, flattenOpts...)
if err != nil {
level.Info(t.logger).Log("msg", "failure parsing file", "file", filePath)
return nil, fmt.Errorf("parsing data: %w", err)
}
rowData := map[string]string{
"path": filePath,
}
return ToMap(data, dataQuery, rowData), nil
}