Skip to content

Commit

Permalink
Add addUsernameFromFilePathStep to support moving chrome_login_data_e…
Browse files Browse the repository at this point in the history
…mails to KATC
  • Loading branch information
RebeccaMahany committed Aug 13, 2024
1 parent 3404ab6 commit 2c4d18e
Show file tree
Hide file tree
Showing 13 changed files with 93 additions and 12 deletions.
2 changes: 1 addition & 1 deletion ee/indexeddb/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const (

// DeserializeChrome deserializes a JS object that has been stored by Chrome
// in IndexedDB LevelDB-backed databases.
func DeserializeChrome(ctx context.Context, slogger *slog.Logger, row map[string][]byte) (map[string][]byte, error) {
func DeserializeChrome(ctx context.Context, slogger *slog.Logger, _ string, 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
4 changes: 2 additions & 2 deletions ee/indexeddb/values_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,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 All @@ -60,6 +60,6 @@ func Test_deserializeIndexeddbValue_InvalidType(t *testing.T) {
0x00, // properties_written
}

_, err := DeserializeChrome(context.TODO(), multislogger.NewNopLogger(), map[string][]byte{"data": testBytes})
_, err := DeserializeChrome(context.TODO(), multislogger.NewNopLogger(), "", map[string][]byte{"data": testBytes})
require.Error(t, err, "should not have been able to deserialize malformed object")
}
2 changes: 1 addition & 1 deletion ee/katc/case.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/serenize/snaker"
)

func camelToSnake(_ context.Context, _ *slog.Logger, row map[string][]byte) (map[string][]byte, error) {
func camelToSnake(_ context.Context, _ *slog.Logger, _ string, row map[string][]byte) (map[string][]byte, error) {
snakeCaseRow := make(map[string][]byte)
for k, v := range row {
snakeCaseKey := snaker.CamelToSnake(k)
Expand Down
2 changes: 1 addition & 1 deletion ee/katc/case_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func Test_camelToSnake(t *testing.T) {
t.Run(tt.testCaseName, func(t *testing.T) {
t.Parallel()

outputRows, err := camelToSnake(context.TODO(), multislogger.NewNopLogger(), map[string][]byte{
outputRows, err := camelToSnake(context.TODO(), multislogger.NewNopLogger(), "", map[string][]byte{
tt.input: nil,
})
require.NoError(t, err)
Expand Down
7 changes: 6 additions & 1 deletion ee/katc/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,15 @@ func (kst *katcSourceType) String() string {
// JSON KATC config.
type rowTransformStep struct {
name string
transformFunc func(ctx context.Context, slogger *slog.Logger, row map[string][]byte) (map[string][]byte, error)
transformFunc func(ctx context.Context, slogger *slog.Logger, sourcePath string, row map[string][]byte) (map[string][]byte, error)
}

const (
snappyDecodeTransformStep = "snappy"
deserializeFirefoxTransformStep = "deserialize_firefox"
deserializeChromeTransformStep = "deserialize_chrome"
camelToSnakeTransformStep = "camel_to_snake"
addUsernameFromFilePathStep = "add_username_from_filepath"
)

func (r *rowTransformStep) UnmarshalJSON(data []byte) error {
Expand All @@ -100,6 +101,10 @@ func (r *rowTransformStep) UnmarshalJSON(data []byte) error {
r.name = camelToSnakeTransformStep
r.transformFunc = camelToSnake
return nil
case addUsernameFromFilePathStep:
r.name = addUsernameFromFilePathStep
r.transformFunc = addUsernameFromFilePath
return nil
default:
return fmt.Errorf("unknown data processing step %s", s)
}
Expand Down
2 changes: 1 addition & 1 deletion ee/katc/deserialize_firefox.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const (
// References:
// * https://stackoverflow.com/a/59923297
// * https://searchfox.org/mozilla-central/source/js/src/vm/StructuredClone.cpp (see especially JSStructuredCloneReader::read)
func deserializeFirefox(ctx context.Context, slogger *slog.Logger, row map[string][]byte) (map[string][]byte, error) {
func deserializeFirefox(ctx context.Context, slogger *slog.Logger, _ string, row map[string][]byte) (map[string][]byte, error) {
// IndexedDB data is stored by key "data" pointing to the serialized object. We want to
// extract that serialized object, and discard the top-level "data" key.
data, ok := row["data"]
Expand Down
4 changes: 2 additions & 2 deletions ee/katc/deserialize_firefox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
func Test_deserializeFirefox_missingTopLevelDataKey(t *testing.T) {
t.Parallel()

_, err := deserializeFirefox(context.TODO(), multislogger.NewNopLogger(), map[string][]byte{
_, err := deserializeFirefox(context.TODO(), multislogger.NewNopLogger(), "", map[string][]byte{
"not_a_data_key": nil,
})
require.Error(t, err, "expect deserializeFirefox requires top-level data key")
Expand Down Expand Up @@ -50,7 +50,7 @@ func Test_deserializeFirefox_malformedData(t *testing.T) {
t.Run(tt.testCaseName, func(t *testing.T) {
t.Parallel()

_, err := deserializeFirefox(context.TODO(), multislogger.NewNopLogger(), map[string][]byte{
_, err := deserializeFirefox(context.TODO(), multislogger.NewNopLogger(), "", map[string][]byte{
"data": tt.data,
})
require.Error(t, err, "expect deserializeFirefox rejects malformed data")
Expand Down
2 changes: 1 addition & 1 deletion ee/katc/snappy.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

// snappyDecode is a dataProcessingStep that decodes data compressed with snappy.
// We use this to decode data retrieved from Firefox IndexedDB sqlite-backed databases.
func snappyDecode(ctx context.Context, _ *slog.Logger, row map[string][]byte) (map[string][]byte, error) {
func snappyDecode(ctx context.Context, _ *slog.Logger, _ string, row map[string][]byte) (map[string][]byte, error) {
decodedRow := make(map[string][]byte)

for k, v := range row {
Expand Down
2 changes: 1 addition & 1 deletion ee/katc/snappy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func Test_snappyDecode(t *testing.T) {
"some_key_b": snappy.Encode(nil, expectedRow["some_key_b"]),
}

results, err := snappyDecode(context.TODO(), multislogger.NewNopLogger(), encodedRow)
results, err := snappyDecode(context.TODO(), multislogger.NewNopLogger(), "", encodedRow)
require.NoError(t, err)

// Validate that the keys are unchanged, and that the data was correctly decoded
Expand Down
2 changes: 1 addition & 1 deletion ee/katc/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func (k *katcTable) generate(ctx context.Context, queryContext table.QueryContex

// Run any needed transformations on the row data
for _, step := range k.rowTransformSteps {
dataRawRow, err = step.transformFunc(ctx, k.slogger, dataRawRow)
dataRawRow, err = step.transformFunc(ctx, k.slogger, s.path, dataRawRow)
if err != nil {
k.slogger.Log(ctx, slog.LevelWarn,
"running transform func",
Expand Down
43 changes: 43 additions & 0 deletions ee/katc/username.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package katc

import (
"context"
"errors"
"log/slog"
"os"
"runtime"
"strings"
)

var homeDirLocations = map[string][]string{
"windows": {"/Users"},
"darwin": {"/Users"},
"linux": {"/home"},
}

// addUsernameFromFilePath is a dataProcessingStep that adds a `username` field to the row
// by checking the `sourcePath` for a user home directory prefix.
func addUsernameFromFilePath(_ context.Context, _ *slog.Logger, sourcePath string, row map[string][]byte) (map[string][]byte, error) {
homeDirs, ok := homeDirLocations[runtime.GOOS]
if !ok {
return row, errors.New("cannot determine home directories")
}

for _, homeDir := range homeDirs {
if !strings.HasPrefix(sourcePath, homeDir) {
continue
}

// Trim the home directory and the leading path separator.
// The next component of the path will then be the username.
remainingPath := strings.TrimPrefix(strings.TrimPrefix(sourcePath, homeDir), string(os.PathSeparator))
remainingPathComponents := strings.Split(remainingPath, string(os.PathSeparator))
if len(remainingPathComponents) < 1 {
continue
}
row["username"] = []byte(remainingPathComponents[0])
return row, nil
}

return row, nil
}
32 changes: 32 additions & 0 deletions ee/katc/username_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package katc

import (
"context"
"path/filepath"
"runtime"
"testing"

"github.com/kolide/launcher/pkg/log/multislogger"
"github.com/stretchr/testify/require"
)

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

// Create a path with an expected home directory
homeDirs := homeDirLocations[runtime.GOOS]
expectedUsername := "test-user"
firstRow := make(map[string][]byte)
sourcePath := filepath.Join(homeDirs[0], expectedUsername, "some", "path", "to", "db.sqlite")
result, err := addUsernameFromFilePath(context.TODO(), multislogger.NewNopLogger(), sourcePath, firstRow)
require.NoError(t, err)
require.Contains(t, result, "username")
require.Equal(t, expectedUsername, string(result["username"]))

// Create a path without an expected home directory
otherSourcePath := filepath.Join("some", "other", "path", "to", "db.sqlite")
secondRow := make(map[string][]byte)
resultWithoutUsername, err := addUsernameFromFilePath(context.TODO(), multislogger.NewNopLogger(), otherSourcePath, secondRow)
require.NoError(t, err)
require.NotContains(t, resultWithoutUsername, "username")
}
1 change: 1 addition & 0 deletions pkg/osquery/table/chrome_login_data_emails.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var profileDirs = map[string][]string{
}
var profileDirsDefault = []string{".config/google-chrome", ".config/chromium", "snap/chromium/current/.config/chromium"}

// DEPRECATED use katc_chrome_login_data_emails
func ChromeLoginDataEmails(slogger *slog.Logger) *table.Plugin {
c := &ChromeLoginDataEmailsTable{
slogger: slogger.With("table", "kolide_chrome_login_data_emails"),
Expand Down

0 comments on commit 2c4d18e

Please sign in to comment.