diff --git a/orbit/changes/orbit-extensions-user-login-settings b/orbit/changes/orbit-extensions-user-login-settings new file mode 100644 index 0000000000..6ba629cb7b --- /dev/null +++ b/orbit/changes/orbit-extensions-user-login-settings @@ -0,0 +1 @@ +- Implement table to hold user_login_settings options extension via Orbit diff --git a/orbit/pkg/table/common/common.go b/orbit/pkg/table/common/common.go new file mode 100644 index 0000000000..890cc307e1 --- /dev/null +++ b/orbit/pkg/table/common/common.go @@ -0,0 +1,26 @@ +//go:build darwin +// +build darwin + +package common + +import ( + "fmt" + "os" + "syscall" +) + +// GetActiveUserGroup gets the uid and gid of the current (or more accurately, most recently logged +// in) *console* user. In most scenarios this should be the currently logged in user on the system. +// Note that getting the current user of the Orbit process is typically going to return root and we +// need the underlying user. +func GetConsoleUidGid() (uid uint32, gid uint32, err error) { + info, err := os.Stat("/dev/console") + if err != nil { + return 0, 0, err + } + stat, ok := info.Sys().(*syscall.Stat_t) + if !ok { + return 0, 0, fmt.Errorf("unexpected type %T", info.Sys()) + } + return stat.Uid, stat.Gid, nil +} diff --git a/orbit/pkg/table/common/common_test.go b/orbit/pkg/table/common/common_test.go new file mode 100644 index 0000000000..6ba2b2ea02 --- /dev/null +++ b/orbit/pkg/table/common/common_test.go @@ -0,0 +1,15 @@ +//go:build darwin +// +build darwin + +package common + +import ( + "testing" +) + +func TestGetConsoleUidGid(t *testing.T) { + _, _, err := GetConsoleUidGid() + if err != nil { + t.Fatalf(`Err expected to be nil. got %s`, err) + } +} diff --git a/orbit/pkg/table/extension_darwin.go b/orbit/pkg/table/extension_darwin.go index 4fb0def52f..02a3cde416 100644 --- a/orbit/pkg/table/extension_darwin.go +++ b/orbit/pkg/table/extension_darwin.go @@ -4,6 +4,7 @@ package table import ( "github.com/fleetdm/fleet/v4/orbit/pkg/table/privaterelay" + "github.com/fleetdm/fleet/v4/orbit/pkg/table/user_login_settings" "github.com/osquery/osquery-go" "github.com/osquery/osquery-go/plugin/table" @@ -18,6 +19,7 @@ func platformTables() []osquery.OsqueryPlugin { return []osquery.OsqueryPlugin{ // Fleet tables table.NewPlugin("icloud_private_relay", privaterelay.Columns(), privaterelay.Generate), + table.NewPlugin("user_login_settings", user_login_settings.Columns(), user_login_settings.Generate), // Macadmins extension tables table.NewPlugin("filevault_users", filevaultusers.FileVaultUsersColumns(), filevaultusers.FileVaultUsersGenerate), diff --git a/orbit/pkg/table/privaterelay/privaterelay.go b/orbit/pkg/table/privaterelay/privaterelay.go index efc9ea3894..c53e50969e 100644 --- a/orbit/pkg/table/privaterelay/privaterelay.go +++ b/orbit/pkg/table/privaterelay/privaterelay.go @@ -6,7 +6,7 @@ package privaterelay import ( "context" "fmt" - "os" + tbl_common "github.com/fleetdm/fleet/v4/orbit/pkg/table/common" "os/exec" "strings" "syscall" @@ -26,7 +26,7 @@ func Columns() []table.ColumnDefinition { // // Constraints for generating can be retrieved from the queryContext. func Generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - uid, gid, err := getConsoleUidGid() + uid, gid, err := tbl_common.GetConsoleUidGid() if err != nil { return nil, fmt.Errorf("failed to get console user: %w", err) } @@ -62,19 +62,3 @@ func Generate(ctx context.Context, queryContext table.QueryContext) ([]map[strin return nil, fmt.Errorf("failed to parse: '%s'", s) } } - -// getActiveUserGroup gets the uid and gid of the current (or more accurately, most recently logged -// in) *console* user. In most scenarios this should be the currently logged in user on the system. -// Note that getting the current user of the Orbit process is typically going to return root and we -// need the underlying user. -func getConsoleUidGid() (uid uint32, gid uint32, err error) { - info, err := os.Stat("/dev/console") - if err != nil { - return 0, 0, err - } - stat, ok := info.Sys().(*syscall.Stat_t) - if !ok { - return 0, 0, fmt.Errorf("unexpected type %T", info.Sys()) - } - return stat.Uid, stat.Gid, nil -} diff --git a/orbit/pkg/table/user_login_settings/user_login_settings.go b/orbit/pkg/table/user_login_settings/user_login_settings.go new file mode 100644 index 0000000000..334912767d --- /dev/null +++ b/orbit/pkg/table/user_login_settings/user_login_settings.go @@ -0,0 +1,51 @@ +//go:build darwin +// +build darwin + +package user_login_settings + +import ( + "context" + "fmt" + tbl_common "github.com/fleetdm/fleet/v4/orbit/pkg/table/common" + "github.com/osquery/osquery-go/plugin/table" + "os/exec" + "strings" + "syscall" + "time" +) + +// Columns is the schema of the table. +func Columns() []table.ColumnDefinition { + return []table.ColumnDefinition{ + table.IntegerColumn("password_hint_enabled"), + } +} + +// Generate is called to return the results for the table at query time. +// Constraints for generating can be retrieved from the queryContext. +func Generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { + uid, gid, err := tbl_common.GetConsoleUidGid() + if err != nil { + return nil, fmt.Errorf("failed to get console user: %w", err) + } + + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + cmd := exec.CommandContext(ctx, "dscl", ".", "-list", "/Users", "hint") + + // Run as the current console user (otherwise we get empty results for the root user) + cmd.SysProcAttr = &syscall.SysProcAttr{ + Credential: &syscall.Credential{Uid: uid, Gid: gid}, + } + + out, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("generate failed: %w", err) + } + + res := "0" + if len(strings.TrimSpace(string(out))) > 0 { + res = "1" + } + return []map[string]string{{"password_hint_enabled": res}}, nil +} diff --git a/schema/tables/user_login_settings.yml b/schema/tables/user_login_settings.yml new file mode 100644 index 0000000000..d529e5401c --- /dev/null +++ b/schema/tables/user_login_settings.yml @@ -0,0 +1,12 @@ +name: user_login_settings +platforms: + - darwin +description: Options of login and password (e.g password hints enabled) for all users. +columns: + - name: password_hint_enabled + type: integer + required: false + description: whether password hint is enabled for any user. 1 means one or more users has a password hint set, 0 means no user has a password hint set +notes: >- + - This table is not a core osquery table. It is included as part of Fleetd, the osquery manager from Fleet. +evented: false