diff --git a/BUILD.bazel b/BUILD.bazel index bb87762..59bfd9b 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -41,6 +41,7 @@ go_library( "//tables/authdb", "//tables/chromeuserprofiles", "//tables/crowdstrike_falcon", + "//tables/dockutil", "//tables/energyimpact", "//tables/fileline", "//tables/filevaultusers", diff --git a/main.go b/main.go index b223982..7c8138a 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "github.com/macadmins/osquery-extension/tables/alt_system_info" "github.com/macadmins/osquery-extension/tables/chromeuserprofiles" "github.com/macadmins/osquery-extension/tables/crowdstrike_falcon" + "github.com/macadmins/osquery-extension/tables/dockutil" "github.com/macadmins/osquery-extension/tables/energyimpact" "github.com/macadmins/osquery-extension/tables/fileline" "github.com/macadmins/osquery-extension/tables/filevaultusers" @@ -93,6 +94,7 @@ func main() { if runtime.GOOS == "darwin" { darwinPlugins := []osquery.OsqueryPlugin{ + table.NewPlugin("dockutil", dockutil.DockutilColumns(), dockutil.DockutilGenerate), table.NewPlugin("energy_impact", energyimpact.EnergyImpactColumns(), energyimpact.EnergyImpactGenerate), table.NewPlugin("filevault_users", filevaultusers.FileVaultUsersColumns(), filevaultusers.FileVaultUsersGenerate), table.NewPlugin("local_network_permissions", localnetworkpermissions.LocalNetworkPermissionsColumns(), localnetworkpermissions.LocalNetworkPermissionsGenerate), diff --git a/tables/dockutil/BUILD.bazel b/tables/dockutil/BUILD.bazel new file mode 100644 index 0000000..f5a4dbb --- /dev/null +++ b/tables/dockutil/BUILD.bazel @@ -0,0 +1,26 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "dockutil", + srcs = ["dockutil.go"], + importpath = "github.com/macadmins/osquery-extension/tables/dockutil", + visibility = ["//visibility:public"], + deps = [ + "//pkg/utils", + "@com_github_osquery_osquery_go//plugin/table", + "@com_github_pkg_errors//:errors", + ], +) + +go_test( + name = "dockutil_test", + srcs = ["dockutil_test.go"], + embed = [":dockutil"], + deps = [ + "//pkg/utils", + "@com_github_osquery_osquery_go//plugin/table", + "@com_github_pkg_errors//:errors", + "@com_github_stretchr_testify//assert", + "@com_github_stretchr_testify//require", + ], +) diff --git a/tables/dockutil/dockutil.go b/tables/dockutil/dockutil.go new file mode 100644 index 0000000..3615370 --- /dev/null +++ b/tables/dockutil/dockutil.go @@ -0,0 +1,65 @@ +package dockutil + +import ( + "context" + "os" + "strings" + + "github.com/macadmins/osquery-extension/pkg/utils" + "github.com/osquery/osquery-go/plugin/table" + "github.com/pkg/errors" +) + +const dockutilPath = "/usr/local/bin/dockutil" + +func DockutilColumns() []table.ColumnDefinition { + return []table.ColumnDefinition{ + table.TextColumn("version"), + table.TextColumn("path"), + } +} + +func DockutilGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { + var results []map[string]string + r := utils.NewRunner() + fs := utils.OSFileSystem{} + + version, path, err := runDockutil(r, fs) + if err != nil { + return results, err + } + + // Only add a row if dockutil is installed + if version != "" { + results = append(results, map[string]string{ + "version": version, + "path": path, + }) + } + + return results, nil +} + +func runDockutil(r utils.Runner, fs utils.FileSystem) (string, string, error) { + // Check if dockutil exists + _, err := fs.Stat(dockutilPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + // Not an error, just not installed + return "", "", nil + } + return "", "", err + } + + // Run dockutil --version + out, err := r.Runner.RunCmd(dockutilPath, "--version") + if err != nil { + return "", "", errors.Wrap(err, "dockutil --version") + } + + // Parse the output - typically "x.x.x" or "dockutil-x.x.x" + version := strings.TrimSpace(string(out)) + version = strings.TrimPrefix(version, "dockutil-") + + return version, dockutilPath, nil +} diff --git a/tables/dockutil/dockutil_test.go b/tables/dockutil/dockutil_test.go new file mode 100644 index 0000000..f6472f6 --- /dev/null +++ b/tables/dockutil/dockutil_test.go @@ -0,0 +1,87 @@ +package dockutil + +import ( + "testing" + + "github.com/macadmins/osquery-extension/pkg/utils" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDockutilColumns(t *testing.T) { + columns := DockutilColumns() + require.Len(t, columns, 2) + assert.Equal(t, "version", columns[0].Name) + assert.Equal(t, "path", columns[1].Name) +} + +func TestRunDockutil(t *testing.T) { + tests := []struct { + name string + mockOutput string + mockErr error + fileExists bool + expectedVer string + expectedPath string + shouldError bool + }{ + { + name: "dockutil installed - simple version", + mockOutput: "3.0.2\n", + mockErr: nil, + fileExists: true, + expectedVer: "3.0.2", + expectedPath: dockutilPath, + shouldError: false, + }, + { + name: "dockutil installed - version with prefix", + mockOutput: "dockutil-3.0.2\n", + mockErr: nil, + fileExists: true, + expectedVer: "3.0.2", + expectedPath: dockutilPath, + shouldError: false, + }, + { + name: "dockutil not installed", + mockOutput: "", + mockErr: nil, + fileExists: false, + expectedVer: "", + expectedPath: "", + shouldError: false, + }, + { + name: "dockutil command fails", + mockOutput: "", + mockErr: errors.New("command failed"), + fileExists: true, + expectedVer: "", + expectedPath: "", + shouldError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockCmd := utils.MockCmdRunner{ + Output: tt.mockOutput, + Err: tt.mockErr, + } + runner := utils.Runner{Runner: mockCmd} + fs := utils.MockFileSystem{FileExists: tt.fileExists} + + version, path, err := runDockutil(runner, fs) + + if tt.shouldError { + require.Error(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, tt.expectedVer, version) + assert.Equal(t, tt.expectedPath, path) + } + }) + } +}