From 453b87f321d5f7aa62d8a7c89601eb374ef6c2b7 Mon Sep 17 00:00:00 2001 From: Micah-Kolide <109157253+Micah-Kolide@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:31:04 -0700 Subject: [PATCH 1/9] Add socketfilterfw exec parser (without tests for now) --- ee/allowedcmd/cmd_darwin.go | 4 + .../execparsers/socketfilterfw/parser.go | 73 +++++++++++++++++++ .../socketfilterfw/socketfilterfw.go | 17 +++++ pkg/osquery/table/platform_tables_darwin.go | 2 + 4 files changed, 96 insertions(+) create mode 100755 ee/tables/execparsers/socketfilterfw/parser.go create mode 100644 ee/tables/execparsers/socketfilterfw/socketfilterfw.go diff --git a/ee/allowedcmd/cmd_darwin.go b/ee/allowedcmd/cmd_darwin.go index fe97151e1..a4ec3538a 100644 --- a/ee/allowedcmd/cmd_darwin.go +++ b/ee/allowedcmd/cmd_darwin.go @@ -119,6 +119,10 @@ func Scutil(ctx context.Context, arg ...string) (*exec.Cmd, error) { return validatedCommand(ctx, "/usr/sbin/scutil", arg...) } +func Socketfilterfw(ctx context.Context, arg ...string) (*exec.Cmd, error) { + return validatedCommand(ctx, "/usr/libexec/ApplicationFirewall/socketfilterfw", arg...) +} + func Softwareupdate(ctx context.Context, arg ...string) (*exec.Cmd, error) { return validatedCommand(ctx, "/usr/sbin/softwareupdate", arg...) } diff --git a/ee/tables/execparsers/socketfilterfw/parser.go b/ee/tables/execparsers/socketfilterfw/parser.go new file mode 100755 index 000000000..8672408dd --- /dev/null +++ b/ee/tables/execparsers/socketfilterfw/parser.go @@ -0,0 +1,73 @@ +package socketfilterfw + +import ( + "bufio" + "io" + "regexp" + "strconv" + "strings" +) + +var lineRegex = regexp.MustCompile("(state|block|signed|stealth|log mode|log option)(?:.*\\s)([0-9a-z]+)") +var disabledStateRegex = regexp.MustCompile("(0|off|disabled)") + +// socketfilterfw returns lines for each `get` argument supplied. +// The output data is in the same order as the supplied arguments. +// +// Each line describes a part of the feature and what state it's in. +// These are not very well-formed, so I'm doing some regex magic to +// know which option the current line is, and then sanitize the state. +func socketfilterfwParse(reader io.Reader) (any, error) { + results := make([]map[string]string, 0) + row := make(map[string]string) + + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + line := strings.ToLower(scanner.Text()) + matches := lineRegex.FindStringSubmatch(line) + if len(matches) != 3 { + continue + } + + var key string + value := sanitizeState(matches[2]) + + switch matches[1] { + case "state": + key = "global_state_enabled" + case "block": + key = "block_all_enabled" + case "signed": + key = "allow_signed_enabled" + case "stealth": + key = "stealth_enabled" + case "log mode": + key = "logging_enabled" + case "log option": + key = "logging_option" + // The logging option value differs from the booleans. + // Can be one of `throttled`, `brief`, or `detail`. + value = matches[2] + default: + continue + } + + row[key] = value + } + + // There should only be one row of data for application firewall, + // so this append is slightly awkward but should be fine. + results = append(results, row) + + return results, nil +} + +// sanitizeState takes in a state like string and returns +// the correct boolean to create a consistent state value. +// +// When the "block all" firewall option is enabled, it doesn't +// include a state like string, which is why we search for +// a disabled state, and return the reversed value of that match. +func sanitizeState(state string) string { + return strconv.FormatBool(!disabledStateRegex.MatchString(state)) +} diff --git a/ee/tables/execparsers/socketfilterfw/socketfilterfw.go b/ee/tables/execparsers/socketfilterfw/socketfilterfw.go new file mode 100644 index 000000000..d07f68bfb --- /dev/null +++ b/ee/tables/execparsers/socketfilterfw/socketfilterfw.go @@ -0,0 +1,17 @@ +package socketfilterfw + +import ( + "io" +) + +type parser struct{} + +var Parser = New() + +func New() parser { + return parser{} +} + +func (p parser) Parse(reader io.Reader) (any, error) { + return socketfilterfwParse(reader) +} diff --git a/pkg/osquery/table/platform_tables_darwin.go b/pkg/osquery/table/platform_tables_darwin.go index 74eb84ba2..0a9010a40 100644 --- a/pkg/osquery/table/platform_tables_darwin.go +++ b/pkg/osquery/table/platform_tables_darwin.go @@ -15,6 +15,7 @@ import ( "github.com/kolide/launcher/ee/tables/dataflattentable" "github.com/kolide/launcher/ee/tables/execparsers/remotectl" "github.com/kolide/launcher/ee/tables/execparsers/repcli" + "github.com/kolide/launcher/ee/tables/execparsers/socketfilterfw" "github.com/kolide/launcher/ee/tables/execparsers/softwareupdate" "github.com/kolide/launcher/ee/tables/filevault" "github.com/kolide/launcher/ee/tables/firmwarepasswd" @@ -123,6 +124,7 @@ func platformSpecificTables(slogger *slog.Logger, currentOsquerydBinaryPath stri munki.MunkiReport(), dataflattentable.TablePluginExec(slogger, "kolide_nix_upgradeable", dataflattentable.XmlType, allowedcmd.NixEnv, []string{"--query", "--installed", "-c", "--xml"}), dataflattentable.NewExecAndParseTable(slogger, "kolide_remotectl", remotectl.Parser, allowedcmd.Remotectl, []string{`dumpstate`}), + dataflattentable.NewExecAndParseTable(slogger, "kolide_socketfilterfw", socketfilterfw.Parser, allowedcmd.Socketfilterfw, []string{"--getglobalstate", "--getblockall", "--getallowsigned", "--getstealthmode", "--getloggingmode", "--getloggingopt"}, dataflattentable.WithIncludeStderr()), dataflattentable.NewExecAndParseTable(slogger, "kolide_softwareupdate", softwareupdate.Parser, allowedcmd.Softwareupdate, []string{`--list`, `--no-scan`}, dataflattentable.WithIncludeStderr()), dataflattentable.NewExecAndParseTable(slogger, "kolide_softwareupdate_scan", softwareupdate.Parser, allowedcmd.Softwareupdate, []string{`--list`}, dataflattentable.WithIncludeStderr()), dataflattentable.NewExecAndParseTable(slogger, "kolide_carbonblack_repcli_status", repcli.Parser, allowedcmd.Repcli, []string{"status"}, dataflattentable.WithIncludeStderr()), From e99773f986575e36a87b412bdf08dab6fe4f06b4 Mon Sep 17 00:00:00 2001 From: Micah-Kolide <109157253+Micah-Kolide@users.noreply.github.com> Date: Tue, 30 Jul 2024 15:18:00 -0700 Subject: [PATCH 2/9] Fix signed keys since there are two not a single one, and only append if the row has data in it --- ee/tables/execparsers/socketfilterfw/parser.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ee/tables/execparsers/socketfilterfw/parser.go b/ee/tables/execparsers/socketfilterfw/parser.go index 8672408dd..f31aa6ee6 100755 --- a/ee/tables/execparsers/socketfilterfw/parser.go +++ b/ee/tables/execparsers/socketfilterfw/parser.go @@ -8,7 +8,7 @@ import ( "strings" ) -var lineRegex = regexp.MustCompile("(state|block|signed|stealth|log mode|log option)(?:.*\\s)([0-9a-z]+)") +var lineRegex = regexp.MustCompile("(state|block|built-in|downloaded|stealth|log mode|log option)(?:.*\\s)([0-9a-z]+)") var disabledStateRegex = regexp.MustCompile("(0|off|disabled)") // socketfilterfw returns lines for each `get` argument supplied. @@ -37,8 +37,10 @@ func socketfilterfwParse(reader io.Reader) (any, error) { key = "global_state_enabled" case "block": key = "block_all_enabled" - case "signed": - key = "allow_signed_enabled" + case "built-in": + key = "allow_built-in_signed_enabled" + case "downloaded": + key = "allow_downloaded_signed_enabled" case "stealth": key = "stealth_enabled" case "log mode": @@ -57,7 +59,9 @@ func socketfilterfwParse(reader io.Reader) (any, error) { // There should only be one row of data for application firewall, // so this append is slightly awkward but should be fine. - results = append(results, row) + if len(row) > 0 { + results = append(results, row) + } return results, nil } From 145d0d4857d7a067ab8192770fc46f52501ff7d8 Mon Sep 17 00:00:00 2001 From: Micah-Kolide <109157253+Micah-Kolide@users.noreply.github.com> Date: Tue, 30 Jul 2024 15:18:11 -0700 Subject: [PATCH 3/9] Add tests --- .../execparsers/socketfilterfw/parser_test.go | 75 +++++++++++++++++++ .../socketfilterfw/test-data/data.txt | 7 ++ .../socketfilterfw/test-data/empty.txt | 0 .../socketfilterfw/test-data/malformed.txt | 11 +++ 4 files changed, 93 insertions(+) create mode 100644 ee/tables/execparsers/socketfilterfw/parser_test.go create mode 100644 ee/tables/execparsers/socketfilterfw/test-data/data.txt create mode 100644 ee/tables/execparsers/socketfilterfw/test-data/empty.txt create mode 100644 ee/tables/execparsers/socketfilterfw/test-data/malformed.txt diff --git a/ee/tables/execparsers/socketfilterfw/parser_test.go b/ee/tables/execparsers/socketfilterfw/parser_test.go new file mode 100644 index 000000000..8fbf07862 --- /dev/null +++ b/ee/tables/execparsers/socketfilterfw/parser_test.go @@ -0,0 +1,75 @@ +package socketfilterfw + +import ( + "bytes" + _ "embed" + "testing" + + "github.com/stretchr/testify/require" +) + +//go:embed test-data/data.txt +var data []byte + +//go:embed test-data/empty.txt +var empty []byte + +//go:embed test-data/malformed.txt +var malformed []byte + +func TestParse(t *testing.T) { + t.Parallel() + + var tests = []struct { + name string + input []byte + expected []map[string]string + }{ + { + name: "empty input", + input: empty, + }, + { + name: "data", + input: data, + expected: []map[string]string{ + { + "global_state_enabled": "true", + "block_all_enabled": "false", + "allow_built-in_signed_enabled": "true", + "allow_downloaded_signed_enabled": "true", + "stealth_enabled": "false", + "logging_enabled": "true", + "logging_option": "throttled", + }, + }, + }, + { + name: "malformed", + input: malformed, + expected: []map[string]string{ + { + "global_state_enabled": "false", + "block_all_enabled": "true", + "allow_built-in_signed_enabled": "false", + "allow_downloaded_signed_enabled": "true", + "stealth_enabled": "false", + "logging_enabled": "true", + "logging_option": "throttled", + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + p := New() + result, err := p.Parse(bytes.NewReader(tt.input)) + require.NoError(t, err, "unexpected error parsing input") + require.ElementsMatch(t, tt.expected, result) + }) + } +} diff --git a/ee/tables/execparsers/socketfilterfw/test-data/data.txt b/ee/tables/execparsers/socketfilterfw/test-data/data.txt new file mode 100644 index 000000000..82d71c469 --- /dev/null +++ b/ee/tables/execparsers/socketfilterfw/test-data/data.txt @@ -0,0 +1,7 @@ +Firewall is enabled. (State = 1) +Firewall has block all state set to disabled. +Automatically allow built-in signed software ENABLED. +Automatically allow downloaded signed software ENABLED. +Firewall stealth mode is off +Log mode is on +Log Option is throttled diff --git a/ee/tables/execparsers/socketfilterfw/test-data/empty.txt b/ee/tables/execparsers/socketfilterfw/test-data/empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/ee/tables/execparsers/socketfilterfw/test-data/malformed.txt b/ee/tables/execparsers/socketfilterfw/test-data/malformed.txt new file mode 100644 index 000000000..09f421110 --- /dev/null +++ b/ee/tables/execparsers/socketfilterfw/test-data/malformed.txt @@ -0,0 +1,11 @@ +Firewall is enabled. (State %#)*Q&^= 0) +Firewall is blocking all non-essential incoming connections.x^CFS. +%#UO +Automatically allow built-in signed software DISABLED. + + + +Automatically allow downloaded signed software ENABLED. +Firewall stealth mode is off +Log mode is on\r\n\r\n +Log Option is throttled From f64ffa06188c88940da01700762f3862bf8045cf Mon Sep 17 00:00:00 2001 From: Micah-Kolide <109157253+Micah-Kolide@users.noreply.github.com> Date: Tue, 30 Jul 2024 15:44:00 -0700 Subject: [PATCH 4/9] Clean up logic, remove extra regex, increase validation, and beef up malformed test --- .../execparsers/socketfilterfw/parser.go | 33 +++++++++++-------- .../execparsers/socketfilterfw/parser_test.go | 32 +++++++++--------- .../socketfilterfw/test-data/malformed.txt | 8 ++++- 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/ee/tables/execparsers/socketfilterfw/parser.go b/ee/tables/execparsers/socketfilterfw/parser.go index f31aa6ee6..eafe67a6c 100755 --- a/ee/tables/execparsers/socketfilterfw/parser.go +++ b/ee/tables/execparsers/socketfilterfw/parser.go @@ -4,12 +4,10 @@ import ( "bufio" "io" "regexp" - "strconv" "strings" ) var lineRegex = regexp.MustCompile("(state|block|built-in|downloaded|stealth|log mode|log option)(?:.*\\s)([0-9a-z]+)") -var disabledStateRegex = regexp.MustCompile("(0|off|disabled)") // socketfilterfw returns lines for each `get` argument supplied. // The output data is in the same order as the supplied arguments. @@ -30,8 +28,6 @@ func socketfilterfwParse(reader io.Reader) (any, error) { } var key string - value := sanitizeState(matches[2]) - switch matches[1] { case "state": key = "global_state_enabled" @@ -47,14 +43,15 @@ func socketfilterfwParse(reader io.Reader) (any, error) { key = "logging_enabled" case "log option": key = "logging_option" - // The logging option value differs from the booleans. - // Can be one of `throttled`, `brief`, or `detail`. - value = matches[2] default: continue } - row[key] = value + // Don't allow overwrites. + _, ok := row[key] + if !ok { + row[key] = sanitizeState(matches[2]) + } } // There should only be one row of data for application firewall, @@ -68,10 +65,20 @@ func socketfilterfwParse(reader io.Reader) (any, error) { // sanitizeState takes in a state like string and returns // the correct boolean to create a consistent state value. -// -// When the "block all" firewall option is enabled, it doesn't -// include a state like string, which is why we search for -// a disabled state, and return the reversed value of that match. func sanitizeState(state string) string { - return strconv.FormatBool(!disabledStateRegex.MatchString(state)) + switch state { + case "0", "off", "disabled": + return "0" + // When the "block all" firewall option is enabled, it doesn't + // include a state like string, which is why we match on + // the string value of "connections" for that mode. + case "1", "on", "enabled", "connections": + return "1" + case "throttled", "brief", "detail": + // The "logging option" value differs from the booleans. + // Can be one of `throttled`, `brief`, or `detail`. + return state + default: + return "" + } } diff --git a/ee/tables/execparsers/socketfilterfw/parser_test.go b/ee/tables/execparsers/socketfilterfw/parser_test.go index 8fbf07862..fa1e5aa59 100644 --- a/ee/tables/execparsers/socketfilterfw/parser_test.go +++ b/ee/tables/execparsers/socketfilterfw/parser_test.go @@ -30,17 +30,17 @@ func TestParse(t *testing.T) { input: empty, }, { - name: "data", - input: data, + name: "data", + input: data, expected: []map[string]string{ { - "global_state_enabled": "true", - "block_all_enabled": "false", - "allow_built-in_signed_enabled": "true", - "allow_downloaded_signed_enabled": "true", - "stealth_enabled": "false", - "logging_enabled": "true", - "logging_option": "throttled", + "global_state_enabled": "1", + "block_all_enabled": "0", + "allow_built-in_signed_enabled": "1", + "allow_downloaded_signed_enabled": "1", + "stealth_enabled": "0", + "logging_enabled": "1", + "logging_option": "throttled", }, }, }, @@ -49,13 +49,13 @@ func TestParse(t *testing.T) { input: malformed, expected: []map[string]string{ { - "global_state_enabled": "false", - "block_all_enabled": "true", - "allow_built-in_signed_enabled": "false", - "allow_downloaded_signed_enabled": "true", - "stealth_enabled": "false", - "logging_enabled": "true", - "logging_option": "throttled", + "global_state_enabled": "0", + "block_all_enabled": "1", + "allow_built-in_signed_enabled": "0", + "allow_downloaded_signed_enabled": "1", + "stealth_enabled": "0", + "logging_enabled": "1", + "logging_option": "throttled", }, }, }, diff --git a/ee/tables/execparsers/socketfilterfw/test-data/malformed.txt b/ee/tables/execparsers/socketfilterfw/test-data/malformed.txt index 09f421110..ed36818c9 100644 --- a/ee/tables/execparsers/socketfilterfw/test-data/malformed.txt +++ b/ee/tables/execparsers/socketfilterfw/test-data/malformed.txt @@ -3,7 +3,13 @@ Firewall is blocking all non-essential incoming connections.x^CFS. %#UO Automatically allow built-in signed software DISABLED. - +Total number of apps = 6 +replicatord (state: 1) +Pop Helper.app (state: 1) +Google Chrome (state: 1) +rtadvd (state: 1) +com.docker.backend (state: 1) +sshd-keygen-wrapper (state: 1) Automatically allow downloaded signed software ENABLED. Firewall stealth mode is off From 1b6eb3d77c85d059733aedeb1d7f3df945aab091 Mon Sep 17 00:00:00 2001 From: Micah-Kolide <109157253+Micah-Kolide@users.noreply.github.com> Date: Tue, 30 Jul 2024 15:53:06 -0700 Subject: [PATCH 5/9] Slightly adjust malformed test to include more states of the data --- ee/tables/execparsers/socketfilterfw/parser_test.go | 4 ++-- ee/tables/execparsers/socketfilterfw/test-data/malformed.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ee/tables/execparsers/socketfilterfw/parser_test.go b/ee/tables/execparsers/socketfilterfw/parser_test.go index fa1e5aa59..5253cb676 100644 --- a/ee/tables/execparsers/socketfilterfw/parser_test.go +++ b/ee/tables/execparsers/socketfilterfw/parser_test.go @@ -52,9 +52,9 @@ func TestParse(t *testing.T) { "global_state_enabled": "0", "block_all_enabled": "1", "allow_built-in_signed_enabled": "0", - "allow_downloaded_signed_enabled": "1", + "allow_downloaded_signed_enabled": "", "stealth_enabled": "0", - "logging_enabled": "1", + "logging_enabled": "", "logging_option": "throttled", }, }, diff --git a/ee/tables/execparsers/socketfilterfw/test-data/malformed.txt b/ee/tables/execparsers/socketfilterfw/test-data/malformed.txt index ed36818c9..ebdd4efa0 100644 --- a/ee/tables/execparsers/socketfilterfw/test-data/malformed.txt +++ b/ee/tables/execparsers/socketfilterfw/test-data/malformed.txt @@ -11,7 +11,7 @@ rtadvd (state: 1) com.docker.backend (state: 1) sshd-keygen-wrapper (state: 1) -Automatically allow downloaded signed software ENABLED. +Automatically allow downloaded signed software DISABLEDENABLED. Firewall stealth mode is off -Log mode is on\r\n\r\n +Log mode is onr\r\n\r\n Log Option is throttled From 50cc900ff0f497e42be846bb64d1b4f41fa2f970 Mon Sep 17 00:00:00 2001 From: Micah-Kolide <109157253+Micah-Kolide@users.noreply.github.com> Date: Wed, 31 Jul 2024 11:56:18 -0700 Subject: [PATCH 6/9] Add socketfilterfw apps list table, add apps list test data, update parsers handling of rows --- .../execparsers/socketfilterfw/parser.go | 49 ++++++++++++++----- .../execparsers/socketfilterfw/parser_test.go | 37 +++++++++++++- .../socketfilterfw/test-data/apps.txt | 7 +++ .../socketfilterfw/test-data/malformed.txt | 8 --- pkg/osquery/table/platform_tables_darwin.go | 1 + 5 files changed, 81 insertions(+), 21 deletions(-) create mode 100644 ee/tables/execparsers/socketfilterfw/test-data/apps.txt diff --git a/ee/tables/execparsers/socketfilterfw/parser.go b/ee/tables/execparsers/socketfilterfw/parser.go index eafe67a6c..516a306cb 100755 --- a/ee/tables/execparsers/socketfilterfw/parser.go +++ b/ee/tables/execparsers/socketfilterfw/parser.go @@ -7,22 +7,46 @@ import ( "strings" ) +var appRegex = regexp.MustCompile("(.*)(?:\\s\\(state:\\s)([0-9]+)") var lineRegex = regexp.MustCompile("(state|block|built-in|downloaded|stealth|log mode|log option)(?:.*\\s)([0-9a-z]+)") // socketfilterfw returns lines for each `get` argument supplied. // The output data is in the same order as the supplied arguments. // -// Each line describes a part of the feature and what state it's in. +// This supports parsing the list of apps and their allow state, or +// each line describes a part of the feature and what state it's in. +// // These are not very well-formed, so I'm doing some regex magic to // know which option the current line is, and then sanitize the state. func socketfilterfwParse(reader io.Reader) (any, error) { results := make([]map[string]string, 0) row := make(map[string]string) + parse_app_data := false scanner := bufio.NewScanner(reader) for scanner.Scan() { - line := strings.ToLower(scanner.Text()) - matches := lineRegex.FindStringSubmatch(line) + line := scanner.Text() + + // When parsing the app list, the first line of output is a total + // count of apps. We can break on this line to start parsing apps. + if strings.Contains(line, "Total number of apps") { + parse_app_data = true + + if len(row) > 0 { + results = append(results, row) + row = make(map[string]string) + } + + continue + } + + matches := make([]string, 0) + if parse_app_data { + matches = appRegex.FindStringSubmatch(line) + } else { + matches = lineRegex.FindStringSubmatch(strings.ToLower(line)) + } + if len(matches) != 3 { continue } @@ -44,18 +68,19 @@ func socketfilterfwParse(reader io.Reader) (any, error) { case "log option": key = "logging_option" default: + if parse_app_data { + row["name"] = matches[1] + row["allow_incoming_connections"] = sanitizeState(matches[2]) + results = append(results, row) + row = make(map[string]string) + } + continue } - // Don't allow overwrites. - _, ok := row[key] - if !ok { - row[key] = sanitizeState(matches[2]) - } + row[key] = sanitizeState(matches[2]) } - // There should only be one row of data for application firewall, - // so this append is slightly awkward but should be fine. if len(row) > 0 { results = append(results, row) } @@ -67,7 +92,9 @@ func socketfilterfwParse(reader io.Reader) (any, error) { // the correct boolean to create a consistent state value. func sanitizeState(state string) string { switch state { - case "0", "off", "disabled": + // The app list state for when an app is blocking incoming connections + // is output as `4`, while `1` is the state to allow those connections. + case "0", "off", "disabled", "4": return "0" // When the "block all" firewall option is enabled, it doesn't // include a state like string, which is why we match on diff --git a/ee/tables/execparsers/socketfilterfw/parser_test.go b/ee/tables/execparsers/socketfilterfw/parser_test.go index 5253cb676..fda3dc9b0 100644 --- a/ee/tables/execparsers/socketfilterfw/parser_test.go +++ b/ee/tables/execparsers/socketfilterfw/parser_test.go @@ -8,6 +8,9 @@ import ( "github.com/stretchr/testify/require" ) +//go:embed test-data/apps.txt +var apps []byte + //go:embed test-data/data.txt var data []byte @@ -26,8 +29,34 @@ func TestParse(t *testing.T) { expected []map[string]string }{ { - name: "empty input", - input: empty, + name: "apps", + input: apps, + expected: []map[string]string{ + { + "name": "replicatord", + "allow_incoming_connections": "1", + }, + { + "name": "Pop Helper.app", + "allow_incoming_connections": "0", + }, + { + "name": "Google Chrome", + "allow_incoming_connections": "0", + }, + { + "name": "rtadvd", + "allow_incoming_connections": "1", + }, + { + "name": "com.docker.backend", + "allow_incoming_connections": "1", + }, + { + "name": "sshd-keygen-wrapper", + "allow_incoming_connections": "1", + }, + }, }, { name: "data", @@ -44,6 +73,10 @@ func TestParse(t *testing.T) { }, }, }, + { + name: "empty input", + input: empty, + }, { name: "malformed", input: malformed, diff --git a/ee/tables/execparsers/socketfilterfw/test-data/apps.txt b/ee/tables/execparsers/socketfilterfw/test-data/apps.txt new file mode 100644 index 000000000..ad8c9a3c2 --- /dev/null +++ b/ee/tables/execparsers/socketfilterfw/test-data/apps.txt @@ -0,0 +1,7 @@ +Total number of apps = 6 +replicatord (state: 1) +Pop Helper.app (state: 4) +Google Chrome (state: 4) +rtadvd (state: 1) +com.docker.backend (state: 1) +sshd-keygen-wrapper (state: 1) diff --git a/ee/tables/execparsers/socketfilterfw/test-data/malformed.txt b/ee/tables/execparsers/socketfilterfw/test-data/malformed.txt index ebdd4efa0..9d9dac9ad 100644 --- a/ee/tables/execparsers/socketfilterfw/test-data/malformed.txt +++ b/ee/tables/execparsers/socketfilterfw/test-data/malformed.txt @@ -3,14 +3,6 @@ Firewall is blocking all non-essential incoming connections.x^CFS. %#UO Automatically allow built-in signed software DISABLED. -Total number of apps = 6 -replicatord (state: 1) -Pop Helper.app (state: 1) -Google Chrome (state: 1) -rtadvd (state: 1) -com.docker.backend (state: 1) -sshd-keygen-wrapper (state: 1) - Automatically allow downloaded signed software DISABLEDENABLED. Firewall stealth mode is off Log mode is onr\r\n\r\n diff --git a/pkg/osquery/table/platform_tables_darwin.go b/pkg/osquery/table/platform_tables_darwin.go index 0a9010a40..416464257 100644 --- a/pkg/osquery/table/platform_tables_darwin.go +++ b/pkg/osquery/table/platform_tables_darwin.go @@ -125,6 +125,7 @@ func platformSpecificTables(slogger *slog.Logger, currentOsquerydBinaryPath stri dataflattentable.TablePluginExec(slogger, "kolide_nix_upgradeable", dataflattentable.XmlType, allowedcmd.NixEnv, []string{"--query", "--installed", "-c", "--xml"}), dataflattentable.NewExecAndParseTable(slogger, "kolide_remotectl", remotectl.Parser, allowedcmd.Remotectl, []string{`dumpstate`}), dataflattentable.NewExecAndParseTable(slogger, "kolide_socketfilterfw", socketfilterfw.Parser, allowedcmd.Socketfilterfw, []string{"--getglobalstate", "--getblockall", "--getallowsigned", "--getstealthmode", "--getloggingmode", "--getloggingopt"}, dataflattentable.WithIncludeStderr()), + dataflattentable.NewExecAndParseTable(slogger, "kolide_socketfilterfw_apps", socketfilterfw.Parser, allowedcmd.Socketfilterfw, []string{"--listapps"}, dataflattentable.WithIncludeStderr()), dataflattentable.NewExecAndParseTable(slogger, "kolide_softwareupdate", softwareupdate.Parser, allowedcmd.Softwareupdate, []string{`--list`, `--no-scan`}, dataflattentable.WithIncludeStderr()), dataflattentable.NewExecAndParseTable(slogger, "kolide_softwareupdate_scan", softwareupdate.Parser, allowedcmd.Softwareupdate, []string{`--list`}, dataflattentable.WithIncludeStderr()), dataflattentable.NewExecAndParseTable(slogger, "kolide_carbonblack_repcli_status", repcli.Parser, allowedcmd.Repcli, []string{"status"}, dataflattentable.WithIncludeStderr()), From fe557c4a24878339118a73c0836f62e7bdcea84b Mon Sep 17 00:00:00 2001 From: Micah-Kolide <109157253+Micah-Kolide@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:00:21 -0700 Subject: [PATCH 7/9] Remove useless assignment --- ee/tables/execparsers/socketfilterfw/parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/tables/execparsers/socketfilterfw/parser.go b/ee/tables/execparsers/socketfilterfw/parser.go index 516a306cb..549d13b9e 100755 --- a/ee/tables/execparsers/socketfilterfw/parser.go +++ b/ee/tables/execparsers/socketfilterfw/parser.go @@ -40,7 +40,7 @@ func socketfilterfwParse(reader io.Reader) (any, error) { continue } - matches := make([]string, 0) + var matches []string if parse_app_data { matches = appRegex.FindStringSubmatch(line) } else { From 41d0f13b75019b674ce91b0f5615cb9c997878e7 Mon Sep 17 00:00:00 2001 From: Micah-Kolide <109157253+Micah-Kolide@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:03:49 -0700 Subject: [PATCH 8/9] Format code --- .../execparsers/socketfilterfw/parser_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ee/tables/execparsers/socketfilterfw/parser_test.go b/ee/tables/execparsers/socketfilterfw/parser_test.go index fda3dc9b0..a98093de8 100644 --- a/ee/tables/execparsers/socketfilterfw/parser_test.go +++ b/ee/tables/execparsers/socketfilterfw/parser_test.go @@ -29,31 +29,31 @@ func TestParse(t *testing.T) { expected []map[string]string }{ { - name: "apps", + name: "apps", input: apps, expected: []map[string]string{ { - "name": "replicatord", + "name": "replicatord", "allow_incoming_connections": "1", }, { - "name": "Pop Helper.app", + "name": "Pop Helper.app", "allow_incoming_connections": "0", }, { - "name": "Google Chrome", + "name": "Google Chrome", "allow_incoming_connections": "0", }, { - "name": "rtadvd", + "name": "rtadvd", "allow_incoming_connections": "1", }, { - "name": "com.docker.backend", + "name": "com.docker.backend", "allow_incoming_connections": "1", }, { - "name": "sshd-keygen-wrapper", + "name": "sshd-keygen-wrapper", "allow_incoming_connections": "1", }, }, From 2a7fcf9ebac9cb85df6c7f7d183deb8cd8dfb9e1 Mon Sep 17 00:00:00 2001 From: Micah-Kolide <109157253+Micah-Kolide@users.noreply.github.com> Date: Thu, 1 Aug 2024 10:23:58 -0700 Subject: [PATCH 9/9] Split out different parse methods --- .../execparsers/socketfilterfw/parser.go | 98 +++++++++++-------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/ee/tables/execparsers/socketfilterfw/parser.go b/ee/tables/execparsers/socketfilterfw/parser.go index 549d13b9e..fe721678c 100755 --- a/ee/tables/execparsers/socketfilterfw/parser.go +++ b/ee/tables/execparsers/socketfilterfw/parser.go @@ -21,7 +21,7 @@ var lineRegex = regexp.MustCompile("(state|block|built-in|downloaded|stealth|log func socketfilterfwParse(reader io.Reader) (any, error) { results := make([]map[string]string, 0) row := make(map[string]string) - parse_app_data := false + parseAppData := false scanner := bufio.NewScanner(reader) for scanner.Scan() { @@ -30,55 +30,23 @@ func socketfilterfwParse(reader io.Reader) (any, error) { // When parsing the app list, the first line of output is a total // count of apps. We can break on this line to start parsing apps. if strings.Contains(line, "Total number of apps") { - parse_app_data = true - - if len(row) > 0 { - results = append(results, row) - row = make(map[string]string) - } - + parseAppData = true continue } - var matches []string - if parse_app_data { - matches = appRegex.FindStringSubmatch(line) - } else { - matches = lineRegex.FindStringSubmatch(strings.ToLower(line)) - } - - if len(matches) != 3 { - continue - } - - var key string - switch matches[1] { - case "state": - key = "global_state_enabled" - case "block": - key = "block_all_enabled" - case "built-in": - key = "allow_built-in_signed_enabled" - case "downloaded": - key = "allow_downloaded_signed_enabled" - case "stealth": - key = "stealth_enabled" - case "log mode": - key = "logging_enabled" - case "log option": - key = "logging_option" - default: - if parse_app_data { - row["name"] = matches[1] - row["allow_incoming_connections"] = sanitizeState(matches[2]) - results = append(results, row) - row = make(map[string]string) + if parseAppData { + appRow := parseAppList(line) + if appRow != nil { + results = append(results, appRow) } continue } - row[key] = sanitizeState(matches[2]) + k, v := parseLine(line) + if k != "" { + row[k] = v + } } if len(row) > 0 { @@ -88,6 +56,52 @@ func socketfilterfwParse(reader io.Reader) (any, error) { return results, nil } +// parseAppList parses the current line and returns the app name and +// state matches as a row of data. +func parseAppList(line string) map[string]string { + matches := appRegex.FindStringSubmatch(line) + if len(matches) != 3 { + return nil + } + + return map[string]string{ + "name": matches[1], + "allow_incoming_connections": sanitizeState(matches[2]), + } +} + +// parseLine parse the current line and returns a feature key with the +// respective state/mode of said feature. We want all features to be a +// part of the same row of data, so we do not return this pair as a row. +func parseLine(line string) (string, string) { + matches := lineRegex.FindStringSubmatch(strings.ToLower(line)) + if len(matches) != 3 { + return "", "" + } + + var key string + switch matches[1] { + case "state": + key = "global_state_enabled" + case "block": + key = "block_all_enabled" + case "built-in": + key = "allow_built-in_signed_enabled" + case "downloaded": + key = "allow_downloaded_signed_enabled" + case "stealth": + key = "stealth_enabled" + case "log mode": + key = "logging_enabled" + case "log option": + key = "logging_option" + default: + return "", "" + } + + return key, sanitizeState(matches[2]) +} + // sanitizeState takes in a state like string and returns // the correct boolean to create a consistent state value. func sanitizeState(state string) string {