Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into becca/katc-cfg-update
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaMahany committed Jul 3, 2024
2 parents 03777f0 + 6208861 commit 4d68485
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 19 deletions.
4 changes: 2 additions & 2 deletions ee/indexeddb/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ const (
tokenNull byte = 0x30
)

// deserializeChrome deserializes a JS object that has been stored by Chrome
// DeserializeChrome deserializes a JS object that has been stored by Chrome
// in IndexedDB LevelDB-backed databases.
func deserializeChrome(_ context.Context, _ *slog.Logger, row map[string][]byte) (map[string][]byte, error) {
func DeserializeChrome(_ context.Context, _ *slog.Logger, row map[string][]byte) (map[string][]byte, error) {
data, ok := row["data"]
if !ok {
return nil, errors.New("row missing top-level data key")
Expand Down
2 changes: 1 addition & 1 deletion ee/indexeddb/values_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func Test_deserializeIndexeddbValue(t *testing.T) {
0x01, // properties_written
}

obj, err := deserializeChrome(context.TODO(), multislogger.NewNopLogger(), map[string][]byte{"data": testBytes})
obj, err := DeserializeChrome(context.TODO(), multislogger.NewNopLogger(), map[string][]byte{"data": testBytes})
require.NoError(t, err, "deserializing object")

// Confirm we got an id property for the object
Expand Down
15 changes: 13 additions & 2 deletions ee/katc/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"log/slog"
"runtime"

"github.com/kolide/launcher/ee/indexeddb"
"github.com/osquery/osquery-go"
"github.com/osquery/osquery-go/plugin/table"
)
Expand All @@ -28,7 +29,8 @@ type sourceData struct {
}

const (
sqliteSourceType = "sqlite"
sqliteSourceType = "sqlite"
indexeddbLeveldbSourceType = "indexeddb_leveldb"
)

func (kst *katcSourceType) UnmarshalJSON(data []byte) error {
Expand All @@ -43,6 +45,10 @@ func (kst *katcSourceType) UnmarshalJSON(data []byte) error {
kst.name = sqliteSourceType
kst.dataFunc = sqliteData
return nil
case indexeddbLeveldbSourceType:
kst.name = indexeddbLeveldbSourceType
kst.dataFunc = indexeddbLeveldbData
return nil
default:
return fmt.Errorf("unknown table type %s", s)
}
Expand All @@ -59,6 +65,7 @@ type rowTransformStep struct {
const (
snappyDecodeTransformStep = "snappy"
deserializeFirefoxTransformStep = "deserialize_firefox"
deserializeChromeTransformStep = "deserialize_chrome"
camelToSnakeTransformStep = "camel_to_snake"
)

Expand All @@ -78,6 +85,10 @@ func (r *rowTransformStep) UnmarshalJSON(data []byte) error {
r.name = deserializeFirefoxTransformStep
r.transformFunc = deserializeFirefox
return nil
case deserializeChromeTransformStep:
r.name = deserializeChromeTransformStep
r.transformFunc = indexeddb.DeserializeChrome
return nil
case camelToSnakeTransformStep:
r.name = camelToSnakeTransformStep
r.transformFunc = camelToSnake
Expand Down Expand Up @@ -128,7 +139,7 @@ func ConstructKATCTables(config map[string]string, slogger *slog.Logger) []osque
var cfg katcTableConfig
if err := json.Unmarshal(rawTableConfig, &cfg); err != nil {
slogger.Log(context.TODO(), slog.LevelWarn,
"unable to unmarshal config for Kolide ATC table, skipping",
"unable to unmarshal config for KATC table, skipping",
"err", err,
)
continue
Expand Down
17 changes: 17 additions & 0 deletions ee/katc/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,23 @@ func TestConstructKATCTables(t *testing.T) {
},
expectedPluginCount: 1,
},
{
testCaseName: "indexeddb_leveldb",
katcConfig: map[string]string{
"tables": fmt.Sprintf(`[
{
"name": "kolide_indexeddb_leveldb_test",
"source_type": "indexeddb_leveldb",
"filter": "%s",
"columns": ["data"],
"source_paths": ["/some/path/to/db.indexeddb.leveldb"],
"source_query": "db.store",
"row_transform_steps": ["deserialize_chrome"]
}
]`, runtime.GOOS),
},
expectedPluginCount: 1,
},
{
testCaseName: "multiple plugins",
katcConfig: map[string]string{
Expand Down
73 changes: 73 additions & 0 deletions ee/katc/indexeddb_leveldb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package katc

import (
"context"
"fmt"
"log/slog"
"path/filepath"
"strings"

"github.com/kolide/launcher/ee/indexeddb"
"github.com/osquery/osquery-go/plugin/table"
)

// indexeddbLeveldbData retrieves data from the LevelDB-backed IndexedDB instances
// found at the filepath in `sourcePattern`. It retrieves all rows from the database
// and object store specified in `query`, which it expects to be in the format
// `<db name>.<object store name>`.
func indexeddbLeveldbData(ctx context.Context, slogger *slog.Logger, sourcePaths []string, query string, sourceConstraints *table.ConstraintList) ([]sourceData, error) {
results := make([]sourceData, 0)
for _, sourcePath := range sourcePaths {
pathPattern := sourcePatternToGlobbablePattern(sourcePath)
leveldbs, err := filepath.Glob(pathPattern)
if err != nil {
return nil, fmt.Errorf("globbing for leveldb files: %w", err)
}

// Extract database and table from query
dbName, objectStoreName, err := extractQueryTargets(query)
if err != nil {
return nil, fmt.Errorf("getting db and object store names: %w", err)
}

// Query databases
for _, db := range leveldbs {
// Check to make sure `db` adheres to sourceConstraints
valid, err := checkPathConstraints(db, sourceConstraints)
if err != nil {
return nil, fmt.Errorf("checking source path constraints: %w", err)
}
if !valid {
continue
}

rowsFromDb, err := indexeddb.QueryIndexeddbObjectStore(db, dbName, objectStoreName)
if err != nil {
return nil, fmt.Errorf("querying %s: %w", db, err)
}
results = append(results, sourceData{
path: db,
rows: rowsFromDb,
})
}
}

return results, nil
}

// extractQueryTargets retrieves the targets of the query (the database name and the object store name)
// from the query. IndexedDB is a NoSQL database, so we expect to retrieve all rows from the given
// object store within the given database name.
func extractQueryTargets(query string) (string, string, error) {
parts := strings.Split(query, ".")
if len(parts) != 2 {
return "", "", fmt.Errorf("unable to extract query targets from query: expected `<db name>.<obj store name>`, got `%s`", query)
}
if len(parts[0]) == 0 {
return "", "", fmt.Errorf("missing db name in query `%s`", query)
}
if len(parts[1]) == 0 {
return "", "", fmt.Errorf("missing object store name in query `%s`", query)
}
return parts[0], parts[1], nil
}
62 changes: 62 additions & 0 deletions ee/katc/indexeddb_leveldb_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package katc

import (
"testing"

"github.com/stretchr/testify/require"
)

func Test_extractQueryTargets(t *testing.T) {
t.Parallel()

for _, tt := range []struct {
testCaseName string
query string
expectedDbName string
expectedObjectStoreName string
expectErr bool
}{
{
testCaseName: "correctly formed query",
query: "some_db.some_obj_store",
expectedDbName: "some_db",
expectedObjectStoreName: "some_obj_store",
expectErr: false,
},
{
testCaseName: "missing db name",
query: ".some_obj_store",
expectErr: true,
},
{
testCaseName: "missing object store name",
query: "some_db.",
expectErr: true,
},
{
testCaseName: "query missing separator",
query: "some_db some_obj_store",
expectErr: true,
},
{
testCaseName: "query has too many components",
query: "some_db.some_obj_store.some_other_component",
expectErr: true,
},
} {
tt := tt
t.Run(tt.testCaseName, func(t *testing.T) {
t.Parallel()

dbName, objStoreName, err := extractQueryTargets(tt.query)

if tt.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, tt.expectedDbName, dbName)
require.Equal(t, tt.expectedObjectStoreName, objStoreName)
}
})
}
}
11 changes: 4 additions & 7 deletions ee/katc/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,12 @@ func sqliteData(ctx context.Context, slogger *slog.Logger, sourcePaths []string,
return results, nil
}

// sourcePatternToGlobbablePattern translates the source pattern, which adheres to LIKE
// sqlite syntax for consistency with other osquery tables, into a pattern that can be
// sourcePatternToGlobbablePattern translates the source pattern, which allows for
// using % wildcards for consistency with other osquery tables, into a pattern that can be
// accepted by filepath.Glob.
func sourcePatternToGlobbablePattern(sourcePattern string) string {
// % matches zero or more characters in LIKE, corresponds to * in glob syntax
globbablePattern := strings.Replace(sourcePattern, "%", `*`, -1)
// _ matches a single character in LIKE, corresponds to ? in glob syntax
globbablePattern = strings.Replace(globbablePattern, "_", `?`, -1)
return globbablePattern
// % matches zero or more characters, corresponds to * in glob syntax
return strings.Replace(sourcePattern, "%", `*`, -1)
}

// querySqliteDb queries the database at the given path, returning rows of results
Expand Down
9 changes: 2 additions & 7 deletions ee/katc/sqlite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,10 @@ func TestSourcePatternToGlobbablePattern(t *testing.T) {
sourcePattern: filepath.Join(rootDir, "path", "to", "%", "directory", "db.sqlite"),
expectedPattern: filepath.Join(rootDir, "path", "to", "*", "directory", "db.sqlite"),
},
{
testCaseName: "underscore wildcard",
sourcePattern: filepath.Join(rootDir, "path", "to", "_", "directory", "db.sqlite"),
expectedPattern: filepath.Join(rootDir, "path", "to", "?", "directory", "db.sqlite"),
},
{
testCaseName: "multiple wildcards",
sourcePattern: filepath.Join(rootDir, "path", "to", "_", "directory", "%.sqlite"),
expectedPattern: filepath.Join(rootDir, "path", "to", "?", "directory", "*.sqlite"),
sourcePattern: filepath.Join(rootDir, "path", "to", "*", "directory", "%.sqlite"),
expectedPattern: filepath.Join(rootDir, "path", "to", "*", "directory", "*.sqlite"),
},
} {
tt := tt
Expand Down

0 comments on commit 4d68485

Please sign in to comment.